ControlLogicEditor: local-var declarations + array action nodes

This commit is contained in:
Martino Ferrari
2026-06-24 19:56:28 +02:00
parent 9c1f2685a7
commit 550ec06bc8
2 changed files with 72 additions and 1 deletions
+71
View File
@@ -10,6 +10,8 @@ import { formatBadge, badgeTitle, nodeDebugClass, type DebugSnapshot } from './l
import { wsClient } from './lib/ws'; import { wsClient } from './lib/ws';
import { useAuth } from './lib/auth'; import { useAuth } from './lib/auth';
import { ScopeFilter, ScopePicker, filterByScope, normalizeScope, type Bucket } from './lib/scope'; import { ScopeFilter, ScopePicker, filterByScope, normalizeScope, type Bucket } from './lib/scope';
import { LocalVars } from './LogicEditor';
import type { StateVar } from './lib/types';
// ── Types (mirror internal/controllogic/model.go) ──────────────────────────── // ── Types (mirror internal/controllogic/model.go) ────────────────────────────
@@ -27,6 +29,11 @@ type CLNodeKind =
| 'action.log' | 'action.log'
| 'action.lua' | 'action.lua'
| 'action.dialog' | 'action.dialog'
| 'action.array.push'
| 'action.array.set'
| 'action.array.remove'
| 'action.array.pop'
| 'action.array.clear'
| 'action.config.apply' | 'action.config.apply'
| 'action.config.read' | 'action.config.read'
| 'action.config.write' | 'action.config.write'
@@ -51,6 +58,7 @@ interface CLGraph {
owner?: string; owner?: string;
scope?: string; scope?: string;
scopeGroups?: string[]; scopeGroups?: string[];
statevars?: StateVar[];
} }
interface DataSource { name: string; } interface DataSource { name: string; }
@@ -101,6 +109,11 @@ const PALETTE: PaletteEntry[] = [
{ kind: 'action.log', label: 'Log', params: { expr: '', label: '' } }, { kind: 'action.log', label: 'Log', params: { expr: '', label: '' } },
{ kind: 'action.lua', label: 'Lua script', params: { script: '-- get("ds:name") reads, set("ds:name", v) writes,\n-- log("msg") logs to the server.\n' } }, { kind: 'action.lua', label: 'Lua script', params: { script: '-- get("ds:name") reads, set("ds:name", v) writes,\n-- log("msg") logs to the server.\n' } },
{ kind: 'action.dialog', label: 'Dialog', params: { kind: 'info', title: '', message: '', users: '', groups: '', target: '' } }, { kind: 'action.dialog', label: 'Dialog', params: { kind: 'info', title: '', message: '', users: '', groups: '', target: '' } },
{ kind: 'action.array.push', label: 'Array push', params: { array: '', expr: '' } },
{ kind: 'action.array.set', label: 'Array set', params: { array: '', index: '0', expr: '' } },
{ kind: 'action.array.remove', label: 'Array remove', params: { array: '', index: '0' } },
{ kind: 'action.array.pop', label: 'Array pop', params: { array: '' } },
{ kind: 'action.array.clear', label: 'Array clear', params: { array: '' } },
{ kind: 'action.config.apply', label: 'Apply config', params: { instance: '' } }, { kind: 'action.config.apply', label: 'Apply config', params: { instance: '' } },
{ kind: 'action.config.read', label: 'Read config', params: { instance: '', key: '', target: '' } }, { kind: 'action.config.read', label: 'Read config', params: { instance: '', key: '', target: '' } },
{ kind: 'action.config.write', label: 'Write config', params: { instance: '', key: '', expr: '' } }, { kind: 'action.config.write', label: 'Write config', params: { instance: '', key: '', expr: '' } },
@@ -122,6 +135,11 @@ const KIND_LABEL: Record<CLNodeKind, string> = {
'action.log': 'Log', 'action.log': 'Log',
'action.lua': 'Lua script', 'action.lua': 'Lua script',
'action.dialog': 'Dialog', 'action.dialog': 'Dialog',
'action.array.push': 'Array push',
'action.array.set': 'Array set',
'action.array.remove': 'Array remove',
'action.array.pop': 'Array pop',
'action.array.clear': 'Array clear',
'action.config.apply': 'Apply config', 'action.config.apply': 'Apply config',
'action.config.read': 'Read config', 'action.config.read': 'Read config',
'action.config.write': 'Write config', 'action.config.write': 'Write config',
@@ -377,6 +395,9 @@ export default function ControlLogicEditor({ onClose }: Props) {
<ScopePicker me={me} scope={normalizeScope(graph.scope)} groups={graph.scopeGroups ?? []} <ScopePicker me={me} scope={normalizeScope(graph.scope)} groups={graph.scopeGroups ?? []}
onChange={(scope, scopeGroups) => patchGraph({ scope, scopeGroups })} /> onChange={(scope, scopeGroups) => patchGraph({ scope, scopeGroups })} />
<span class="hint">Saving a disabled graph stops it; enabling + saving starts it.</span> <span class="hint">Saving a disabled graph stops it; enabling + saving starts it.</span>
<LocalVars
statevars={graph.statevars ?? []}
onChange={(vars) => patchGraph({ statevars: vars })} />
</div> </div>
<div class="cl-editor-main"> <div class="cl-editor-main">
<FlowEditor graph={graph} <FlowEditor graph={graph}
@@ -1158,6 +1179,51 @@ function FlowEditor({ graph, onChange }: {
</Fragment> </Fragment>
)} )}
{(selected.kind === 'action.array.push' || selected.kind === 'action.array.set' ||
selected.kind === 'action.array.remove' || selected.kind === 'action.array.pop' ||
selected.kind === 'action.array.clear') && (() => {
const arrayLocals = (graph.statevars ?? []).filter(v => v.type === 'array');
const cur = selected.params.array ?? '';
return (
<Fragment>
<div class="wizard-field">
<label>Array (local variable)</label>
<select class="prop-select" value={cur}
onChange={(e) => patchParams(selected.id, { array: (e.target as HTMLSelectElement).value })}>
<option value="">— select array —</option>
{arrayLocals.map(v => <option key={v.name} value={v.name}>{v.name}</option>)}
{cur && !arrayLocals.some(v => v.name === cur) && <option value={cur}>{cur} (unknown)</option>}
</select>
{arrayLocals.length === 0 && (
<p class="hint">No array locals declared yet — add one in the Local vars panel.</p>
)}
</div>
{(selected.kind === 'action.array.set' || selected.kind === 'action.array.remove') && (
<div class="wizard-field">
<label>Index{selected.kind === 'action.array.set' ? ' (comma-separated for nested)' : ''}</label>
<input class="prop-input" value={selected.params.index ?? ''}
placeholder="e.g. 0 or 2,1"
onInput={(e) => patchParams(selected.id, { index: (e.target as HTMLInputElement).value })} />
</div>
)}
{(selected.kind === 'action.array.push' || selected.kind === 'action.array.set') && (
<ExprField label="Value (expression)" value={selected.params.expr ?? ''}
onChange={(v) => patchParams(selected.id, { expr: v })}
onInsert={(ds2, sig) => insertRef(selected.id, 'expr', ds2, sig)}
allSignals={allSignalOptions()} onOpenSignals={openAllSignals}
hint={selected.kind === 'action.array.push'
? 'Value to append to the end of the array.'
: 'Value to store at the given index / path.'} />
)}
{(selected.kind === 'action.array.pop' || selected.kind === 'action.array.clear') && (
<p class="hint">{selected.kind === 'action.array.pop'
? 'Removes and returns the last element of the array.'
: 'Removes all elements from the array.'}</p>
)}
</Fragment>
);
})()}
{selected.kind === 'action.delay' && ( {selected.kind === 'action.delay' && (
<div class="wizard-field"> <div class="wizard-field">
<label>Delay (ms)</label> <label>Delay (ms)</label>
@@ -1426,6 +1492,11 @@ function nodeSummary(n: CLNode): string {
case 'action.log': return `log ${n.params.label ? n.params.label + ': ' : ''}${n.params.expr || ''}`; case 'action.log': return `log ${n.params.label ? n.params.label + ': ' : ''}${n.params.expr || ''}`;
case 'action.lua': return 'Lua script'; case 'action.lua': return 'Lua script';
case 'action.dialog': return `${n.params.kind || 'info'} dialog${n.params.title ? ': ' + n.params.title : ''}`; case 'action.dialog': return `${n.params.kind || 'info'} dialog${n.params.title ? ': ' + n.params.title : ''}`;
case 'action.array.push': return `push ${n.params.expr || ''}${n.params.array || '?'}`;
case 'action.array.set': return `${n.params.array || '?'}[${n.params.index || '?'}] = ${n.params.expr || ''}`;
case 'action.array.remove': return `remove ${n.params.array || '?'}[${n.params.index || '?'}]`;
case 'action.array.pop': return `pop ${n.params.array || '?'}`;
case 'action.array.clear': return `clear ${n.params.array || '?'}`;
default: return ''; default: return '';
} }
} }
+1 -1
View File
@@ -1448,7 +1448,7 @@ function nodeSummary(n: LogicNode): string {
// Inline editor for the panel's local state variables, shown in the logic // Inline editor for the panel's local state variables, shown in the logic
// palette so they can be created without leaving the (full-width) logic tab. // palette so they can be created without leaving the (full-width) logic tab.
function LocalVars({ statevars, onChange }: { export function LocalVars({ statevars, onChange }: {
statevars: StateVar[]; statevars: StateVar[];
onChange: (vars: StateVar[]) => void; onChange: (vars: StateVar[]) => void;
}) { }) {