working epics ioc and tested

This commit is contained in:
Martino Ferrari
2026-05-04 21:13:36 +02:00
parent 90669c5fd6
commit 0a5a85e4c4
25 changed files with 1550 additions and 136 deletions
+63 -4
View File
@@ -2,14 +2,17 @@ import { h } from 'preact';
import { useState, useMemo, useEffect } from 'preact/hooks';
import InterfaceList from './InterfaceList';
import Canvas from './Canvas';
import PlotPanel from './PlotPanel';
import type { PlotSignal } from './PlotPanel';
import { wsClient } from './lib/ws';
import { parseInterface } from './lib/xml';
import type { Interface } from './lib/types';
import type { Interface, SignalRef } from './lib/types';
import ContextualHelp from './ContextualHelp';
import HelpModal from './HelpModal';
interface Props {
onEdit?: (iface?: Interface) => void;
initialInterface?: Interface | null;
}
/** Format a Date as a datetime-local input value (YYYY-MM-DDTHH:MM) */
@@ -18,13 +21,19 @@ function toLocalInput(d: Date): string {
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
export default function ViewMode({ onEdit }: Props) {
const [currentInterface, setCurrentInterface] = useState<Interface | null>(null);
const PLOT_COLORS = ['#4a9eff', '#22c55e', '#f59e0b', '#ef4444', '#a78bfa', '#f472b6', '#34d399', '#fb923c'];
type ViewTab = 'hmi' | 'plot';
export default function ViewMode({ onEdit, initialInterface }: Props) {
const [currentInterface, setCurrentInterface] = useState<Interface | null>(initialInterface ?? null);
const [parseError, setParseError] = useState<string | null>(null);
const [wsStatus, setWsStatus] = useState('connecting');
const [showHelp, setShowHelp] = useState(false);
const [helpSection, setHelpSection] = useState('start');
const [showTimeNav, setShowTimeNav] = useState(false);
const [viewTab, setViewTab] = useState<ViewTab>('hmi');
const [plotSignals, setPlotSignals] = useState<PlotSignal[]>([]);
function openHelp(section = 'start') { setHelpSection(section); setShowHelp(true); }
@@ -39,6 +48,11 @@ export default function ViewMode({ onEdit }: Props) {
return unsub;
}, []);
// When returning from edit mode, show the edited interface
useEffect(() => {
if (initialInterface) setCurrentInterface(initialInterface);
}, [initialInterface]);
function handleLoad(xml: string) {
try {
setParseError(null);
@@ -81,6 +95,20 @@ export default function ViewMode({ onEdit }: Props) {
setTimeRange(null);
}
function addToPlot(ref: SignalRef) {
const key = `${ref.ds}\0${ref.name}`;
const already = plotSignals.some(s => `${s.ref.ds}\0${s.ref.name}` === key);
if (!already) {
const color = PLOT_COLORS[plotSignals.length % PLOT_COLORS.length];
setPlotSignals(prev => [...prev, { ref, color }]);
}
setViewTab('plot');
}
function removeFromPlot(ref: SignalRef) {
setPlotSignals(prev => prev.filter(s => !(s.ref.ds === ref.ds && s.ref.name === ref.name)));
}
const isLive = timeRange === null;
return (
@@ -172,12 +200,43 @@ export default function ViewMode({ onEdit }: Props) {
const res = await fetch(`/api/v1/interfaces/${encodeURIComponent(id)}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
handleLoad(await res.text());
setViewTab('hmi');
} catch (err) {
setParseError(`Failed to load interface "${id}": ${err instanceof Error ? err.message : err}`);
}
}}
/>
<Canvas iface={currentInterface} onNavigate={handleNavigate} timeRange={timeRange} />
<div class="view-content-area">
{/* Tab bar */}
<div class="view-tabs">
<button
class={`view-tab${viewTab === 'hmi' ? ' view-tab-active' : ''}`}
onClick={() => setViewTab('hmi')}
>
HMI
</button>
<button
class={`view-tab${viewTab === 'plot' ? ' view-tab-active' : ''}`}
onClick={() => setViewTab('plot')}
>
Plot{plotSignals.length > 0 ? ` (${plotSignals.length})` : ''}
</button>
</div>
{/* Keep both panels mounted so ring-buffer data is never lost on tab switch */}
<div style={`display:${viewTab === 'hmi' ? 'contents' : 'none'}`}>
<Canvas
iface={currentInterface}
onNavigate={handleNavigate}
timeRange={timeRange}
onPlot={addToPlot}
/>
</div>
<div style={`display:${viewTab === 'plot' ? 'contents' : 'none'}`}>
<PlotPanel signals={plotSignals} onRemove={removeFromPlot} />
</div>
</div>
</div>
{showHelp && (