81 lines
3.1 KiB
TypeScript
81 lines
3.1 KiB
TypeScript
import { h } from 'preact';
|
|
import { useState, useEffect } from 'preact/hooks';
|
|
import { wsClient } from '../lib/ws';
|
|
import { getSignalStore } from '../lib/stores';
|
|
import type { Widget, SignalValue } from '../lib/types';
|
|
|
|
interface InstanceMeta { id: string; name: string; setId?: string; }
|
|
|
|
interface Props { widget: Widget; onContextMenu?: (e: MouseEvent) => void; }
|
|
|
|
// A combo that lists the config instances of a chosen set and writes the
|
|
// selected instance id (a string) to a panel-local variable. The output var can
|
|
// then drive the apply/read/write config logic nodes (set to "From variable"),
|
|
// letting an operator pick which configuration the flow operates on at run time.
|
|
export default function ConfigSelect({ widget, onContextMenu }: Props) {
|
|
const setId = widget.options['set'] || '';
|
|
const output = widget.options['output'] || '';
|
|
const label = widget.options['label'] || 'Config';
|
|
// Optional comma-separated allow-list of instance ids; empty shows them all.
|
|
const subset = (widget.options['instances'] || '')
|
|
.split(',').map(s => s.trim()).filter(Boolean);
|
|
|
|
const [instances, setInstances] = useState<InstanceMeta[]>([]);
|
|
const [selected, setSelected] = useState('');
|
|
|
|
// Mirror the output var so external writers (e.g. a create-config node) keep
|
|
// the combo in sync.
|
|
useEffect(() => {
|
|
if (!output) return;
|
|
const unsub = getSignalStore({ ds: 'local', name: output }).subscribe((v: SignalValue) => {
|
|
const id = v.value == null ? '' : String(v.value);
|
|
setSelected(prev => (id && id !== prev ? id : prev));
|
|
});
|
|
return unsub;
|
|
}, [output]);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
fetch('/api/v1/config/instances')
|
|
.then(r => r.ok ? r.json() : [])
|
|
.then((list: InstanceMeta[]) => {
|
|
if (cancelled) return;
|
|
let filtered = (list ?? []).filter(ci => !setId || ci.setId === setId);
|
|
if (subset.length > 0) filtered = filtered.filter(ci => subset.includes(ci.id));
|
|
setInstances(filtered);
|
|
// Default the output to the first instance when nothing is chosen yet.
|
|
setSelected(prev => {
|
|
if (prev && filtered.some(ci => ci.id === prev)) return prev;
|
|
const first = filtered[0]?.id ?? '';
|
|
if (first && output) wsClient.write({ ds: 'local', name: output }, first);
|
|
return first;
|
|
});
|
|
})
|
|
.catch(() => {});
|
|
return () => { cancelled = true; };
|
|
}, [setId, widget.options['instances']]);
|
|
|
|
function onChange(id: string) {
|
|
setSelected(id);
|
|
if (output) wsClient.write({ ds: 'local', name: output }, id);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
class="configselect"
|
|
style={`left:${widget.x}px;top:${widget.y}px;width:${widget.w}px;height:${widget.h}px;`}
|
|
onContextMenu={onContextMenu}
|
|
>
|
|
<span class="cs-label">{label}:</span>
|
|
<select
|
|
class="cs-select"
|
|
value={selected}
|
|
onChange={(e: Event) => onChange((e.target as HTMLSelectElement).value)}
|
|
>
|
|
{instances.length === 0 && <option value="">no instances</option>}
|
|
{instances.map(ci => <option key={ci.id} value={ci.id}>{ci.name}</option>)}
|
|
</select>
|
|
</div>
|
|
);
|
|
}
|