ControlLogicEditor: local-var declarations + array action nodes
This commit is contained in:
@@ -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<CLNodeKind, string> = {
|
||||
'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) {
|
||||
<ScopePicker me={me} scope={normalizeScope(graph.scope)} groups={graph.scopeGroups ?? []}
|
||||
onChange={(scope, scopeGroups) => patchGraph({ scope, scopeGroups })} />
|
||||
<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 class="cl-editor-main">
|
||||
<FlowEditor graph={graph}
|
||||
@@ -1158,6 +1179,51 @@ function FlowEditor({ graph, onChange }: {
|
||||
</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' && (
|
||||
<div class="wizard-field">
|
||||
<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.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 '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}) {
|
||||
|
||||
Reference in New Issue
Block a user