Phase 6
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { h } from 'preact';
|
||||
import { useState, useEffect } from 'preact/hooks';
|
||||
import { getSignalStore, getMetaStore } from '../lib/stores';
|
||||
import type { Widget, SignalValue, SignalMeta } from '../lib/types';
|
||||
|
||||
const DEFAULT_VALUE: SignalValue = { value: null, quality: 'unknown', ts: null };
|
||||
|
||||
function qualityColor(q: string): string {
|
||||
switch (q) {
|
||||
case 'good': return '#4ade80';
|
||||
case 'uncertain': return '#fbbf24';
|
||||
case 'bad': return '#f87171';
|
||||
default: return '#6b7280';
|
||||
}
|
||||
}
|
||||
|
||||
interface Props { widget: Widget; onContextMenu?: (e: MouseEvent) => void; }
|
||||
|
||||
export default function BarV({ widget, onContextMenu }: Props) {
|
||||
const sigRef = widget.signals[0];
|
||||
const [sv, setSv] = useState<SignalValue>(DEFAULT_VALUE);
|
||||
const [meta, setMeta] = useState<SignalMeta | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sigRef) return;
|
||||
const unsubV = getSignalStore(sigRef).subscribe(setSv);
|
||||
const unsubM = getMetaStore(sigRef).subscribe(setMeta);
|
||||
return () => { unsubV(); unsubM(); };
|
||||
}, [sigRef?.ds, sigRef?.name]);
|
||||
|
||||
const label = widget.options['label'] ?? sigRef?.name ?? '';
|
||||
const unit = widget.options['unit'] ?? meta?.unit ?? '';
|
||||
const minVal = parseFloat(widget.options['min'] ?? String(meta?.displayLow ?? 0));
|
||||
const maxVal = parseFloat(widget.options['max'] ?? String(meta?.displayHigh ?? 100));
|
||||
const quality = sv.quality;
|
||||
|
||||
const rawV = sv.value;
|
||||
const rawValue: number | null = rawV === null || rawV === undefined ? null
|
||||
: typeof rawV === 'number' ? rawV : parseFloat(String(rawV));
|
||||
|
||||
function displayValue(): string {
|
||||
if (rawValue === null || isNaN(rawValue)) return '---';
|
||||
return Number.isFinite(rawValue) ? rawValue.toPrecision(4).replace(/\.?0+$/, '') : String(rawValue);
|
||||
}
|
||||
|
||||
function fillPercent(): number {
|
||||
if (rawValue === null || isNaN(rawValue)) return 0;
|
||||
const frac = maxVal === minVal ? 0 : (rawValue - minVal) / (maxVal - minVal);
|
||||
return Math.max(0, Math.min(100, frac * 100));
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class="barv"
|
||||
style={`left:${widget.x}px;top:${widget.y}px;width:${widget.w}px;height:${widget.h}px;`}
|
||||
onContextMenu={onContextMenu}
|
||||
>
|
||||
<span class="quality-dot" style={`background:${qualityColor(quality)};`} title={`Quality: ${quality}`} />
|
||||
{label && <div class="bar-label">{label}</div>}
|
||||
<div class="bar-value">
|
||||
<span class="value">{displayValue()}</span>
|
||||
{unit && <span class="unit">{unit}</span>}
|
||||
</div>
|
||||
<div class="bar-track">
|
||||
<div class="bar-fill" style={`height:${fillPercent()}%;`} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user