Add widget-control logic action and multi-column CSV / multi-field dialogs

Extend the panel logic editor with two capabilities:

- action.widget: drive a panel widget by id from a flow — enable/disable,
  show/hide, and (for plot widgets) clear, pause, or resume the live plot.
  Wired via a per-widget command store consumed by a Canvas WidgetView wrapper
  (disable overlay / hide) and PlotWidget (pause/clear), reset on panel unmount.
- action.export now emits multiple named arrays as CSV columns with per-column
  labels and an alignment mode (common/any/interpolate); dialogs support asking
  for and displaying multiple values per dialog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino Ferrari
2026-06-19 07:51:18 +02:00
parent afefba3184
commit ba836f00e6
9 changed files with 668 additions and 102 deletions
+17
View File
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from 'preact/hooks';
import uPlot from 'uplot';
import * as echarts from 'echarts';
import { getSignalStore } from '../lib/stores';
import { getWidgetCmdStore } from '../lib/widgetCommands';
import { wsClient } from '../lib/ws';
import { formatValue } from '../lib/format';
import { fftMag, resampleUniform } from '../lib/fft';
@@ -374,8 +375,24 @@ export default function PlotWidget({ widget, onContextMenu, timeRange }: Props)
// ── Live streaming mode ────────────────────────────────────────────────
setHistStatus('idle');
// Honour action.widget logic commands: `paused` suspends live ingestion and
// a bumped `clearNonce` empties the buffers + redraws once.
const cmdStore = getWidgetCmdStore(widget.id);
let paused = cmdStore.get().paused;
let lastClear = cmdStore.get().clearNonce;
unsubs.push(cmdStore.subscribe(cmd => {
paused = cmd.paused;
if (cmd.clearNonce !== lastClear) {
lastClear = cmd.clearNonce;
buffers.forEach(b => { b.timestamps.length = 0; b.values.length = 0; });
if (uplot) uplot.setData(uplotData(timeWindow));
if (echart) echart.setOption(echartsOption(), { notMerge: true });
}
}));
signals.forEach((sig: SignalRef & { color?: string }, i) => {
const unsub = getSignalStore(sig).subscribe(sv => {
if (paused) return;
if (sv.value === null || sv.value === undefined || sv.ts === null) return;
const ts = new Date(sv.ts).getTime() / 1000;