062bb44dba
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
// Panel-local state variables (ds === 'local').
|
|
//
|
|
// Definitions live in the interface XML and travel with the panel, but the live
|
|
// value is instantiated per browser/panel instance starting from the variable's
|
|
// initial value — nothing is persisted server-side and no WebSocket traffic is
|
|
// involved. The panel logic (a future feature) and write-capable widgets can
|
|
// update these values locally.
|
|
//
|
|
// This module deliberately depends only on the store primitives and types so it
|
|
// can be imported by both stores.ts and ws.ts without creating an import cycle.
|
|
|
|
import { writable, type Readable, type Writable } from './store';
|
|
import type { SignalValue, SignalMeta, StateVar } from './types';
|
|
import { parseInitialArray, applySizing, type ArrVal } from './arraypolicy';
|
|
|
|
const valueStores = new Map<string, Writable<SignalValue>>();
|
|
const metaStores = new Map<string, Writable<SignalMeta | null>>();
|
|
const decls = new Map<string, StateVar>();
|
|
|
|
export function declaredVar(name: string): StateVar | undefined {
|
|
return decls.get(name);
|
|
}
|
|
|
|
const DEFAULT_VALUE: SignalValue = { value: null, quality: 'unknown', ts: null };
|
|
|
|
function valueW(name: string): Writable<SignalValue> {
|
|
let s = valueStores.get(name);
|
|
if (!s) {
|
|
s = writable<SignalValue>(DEFAULT_VALUE);
|
|
valueStores.set(name, s);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
function metaW(name: string): Writable<SignalMeta | null> {
|
|
let s = metaStores.get(name);
|
|
if (!s) {
|
|
s = writable<SignalMeta | null>(null);
|
|
metaStores.set(name, s);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// coerce parses a state variable's stored string into a live value.
|
|
function coerce(v: StateVar): any {
|
|
switch (v.type) {
|
|
case 'bool':
|
|
return v.initial === 'true' || v.initial === '1';
|
|
case 'string':
|
|
return v.initial;
|
|
case 'array':
|
|
return parseInitialArray(v);
|
|
default: {
|
|
const n = parseFloat(v.initial);
|
|
return isNaN(n) ? 0 : n;
|
|
}
|
|
}
|
|
}
|
|
|
|
// initLocalState (re)instantiates every state variable to its initial value and
|
|
// publishes metadata. Call this whenever a panel is loaded.
|
|
export function initLocalState(vars: StateVar[] | undefined): void {
|
|
for (const v of vars ?? []) {
|
|
decls.set(v.name, v);
|
|
valueW(v.name).set({
|
|
value: coerce(v),
|
|
quality: 'good',
|
|
ts: new Date().toISOString(),
|
|
});
|
|
metaW(v.name).set({
|
|
type: v.type ?? 'number',
|
|
unit: v.unit,
|
|
displayLow: v.low ?? 0,
|
|
displayHigh: v.high ?? 100,
|
|
writable: true,
|
|
elem: v.elem,
|
|
sizing: v.sizing,
|
|
capacity: v.capacity,
|
|
});
|
|
}
|
|
}
|
|
|
|
export function getLocalValueStore(name: string): Readable<SignalValue> {
|
|
return valueW(name);
|
|
}
|
|
|
|
export function getLocalMetaStore(name: string): Readable<SignalMeta | null> {
|
|
return metaW(name);
|
|
}
|
|
|
|
// writeLocalState updates a local variable's live value in place.
|
|
// When the declared variable is an array type, the written value is passed
|
|
// through applySizing to enforce the declared sizing policy.
|
|
export function writeLocalState(name: string, value: any): void {
|
|
const sv = decls.get(name);
|
|
let v = value;
|
|
if (sv?.type === 'array' && Array.isArray(value)) v = applySizing(value as ArrVal[], sv);
|
|
valueW(name).set({ value: v, quality: 'good', ts: new Date().toISOString() });
|
|
}
|