From 550ec06bc874d7790d85faa23f6b24fb48d046c4 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Wed, 24 Jun 2026 19:56:28 +0200 Subject: [PATCH] ControlLogicEditor: local-var declarations + array action nodes --- web/src/ControlLogicEditor.tsx | 71 ++++++++++++++++++++++++++++++++++ web/src/LogicEditor.tsx | 2 +- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/web/src/ControlLogicEditor.tsx b/web/src/ControlLogicEditor.tsx index e979d85..27bca26 100644 --- a/web/src/ControlLogicEditor.tsx +++ b/web/src/ControlLogicEditor.tsx @@ -10,6 +10,8 @@ import { formatBadge, badgeTitle, nodeDebugClass, type DebugSnapshot } from './l import { wsClient } from './lib/ws'; import { useAuth } from './lib/auth'; 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) ──────────────────────────── @@ -27,6 +29,11 @@ type CLNodeKind = | 'action.log' | 'action.lua' | 'action.dialog' + | 'action.array.push' + | 'action.array.set' + | 'action.array.remove' + | 'action.array.pop' + | 'action.array.clear' | 'action.config.apply' | 'action.config.read' | 'action.config.write' @@ -51,6 +58,7 @@ interface CLGraph { owner?: string; scope?: string; scopeGroups?: string[]; + statevars?: StateVar[]; } interface DataSource { name: string; } @@ -101,6 +109,11 @@ const PALETTE: PaletteEntry[] = [ { 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.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.read', label: 'Read config', params: { instance: '', key: '', target: '' } }, { kind: 'action.config.write', label: 'Write config', params: { instance: '', key: '', expr: '' } }, @@ -122,6 +135,11 @@ const KIND_LABEL: Record = { 'action.log': 'Log', 'action.lua': 'Lua script', '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.read': 'Read config', 'action.config.write': 'Write config', @@ -377,6 +395,9 @@ export default function ControlLogicEditor({ onClose }: Props) { patchGraph({ scope, scopeGroups })} /> Saving a disabled graph stops it; enabling + saving starts it. + patchGraph({ statevars: vars })} />
)} + {(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 ( + +
+ + + {arrayLocals.length === 0 && ( +

No array locals declared yet — add one in the Local vars panel.

+ )} +
+ {(selected.kind === 'action.array.set' || selected.kind === 'action.array.remove') && ( +
+ + patchParams(selected.id, { index: (e.target as HTMLInputElement).value })} /> +
+ )} + {(selected.kind === 'action.array.push' || selected.kind === 'action.array.set') && ( + 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') && ( +

{selected.kind === 'action.array.pop' + ? 'Removes and returns the last element of the array.' + : 'Removes all elements from the array.'}

+ )} +
+ ); + })()} + {selected.kind === 'action.delay' && (
@@ -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.lua': return 'Lua script'; 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 ''; } } diff --git a/web/src/LogicEditor.tsx b/web/src/LogicEditor.tsx index 19c3573..7678581 100644 --- a/web/src/LogicEditor.tsx +++ b/web/src/LogicEditor.tsx @@ -1448,7 +1448,7 @@ function nodeSummary(n: LogicNode): string { // 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. -function LocalVars({ statevars, onChange }: { +export function LocalVars({ statevars, onChange }: { statevars: StateVar[]; onChange: (vars: StateVar[]) => void; }) {