This commit is contained in:
Martino Ferrari
2026-04-25 22:56:09 +02:00
parent 8b548ba1c2
commit 986f6cd6d8
85 changed files with 11479 additions and 5050 deletions
+199
View File
@@ -0,0 +1,199 @@
import { h } from 'preact';
import { useState } from 'preact/hooks';
import type { Widget, Interface } from './lib/types';
interface Props {
selected: Widget | null;
iface: Interface;
onChange: (updated: Widget) => void;
onIfaceChange: (updated: Interface) => void;
}
function Field({ label, children }: { label: string; children: any }) {
return (
<div class="prop-field">
<label class="prop-label">{label}</label>
{children}
</div>
);
}
function TextInput({ value, onCommit }: { value: string; onCommit: (v: string) => void }) {
const [local, setLocal] = useState(value);
return (
<input
class="prop-input"
value={local}
onInput={(e) => setLocal((e.target as HTMLInputElement).value)}
onChange={(e) => onCommit((e.target as HTMLInputElement).value)}
/>
);
}
export default function PropertiesPane({ selected, iface, onChange, onIfaceChange }: Props) {
const [collapsed, setCollapsed] = useState(false);
function setOpt(key: string, value: string) {
if (!selected) return;
onChange({ ...selected, options: { ...selected.options, [key]: value } });
}
function setProp(key: keyof Widget, value: any) {
if (!selected) return;
onChange({ ...selected, [key]: value } as Widget);
}
return (
<aside class={`panel props-panel${collapsed ? ' collapsed' : ''}`} style="border-left:1px solid #2d3748;border-right:none;">
<div class="panel-header">
{!collapsed && <span class="panel-title">Properties</span>}
<button class="icon-btn" onClick={() => setCollapsed(c => !c)} title={collapsed ? 'Expand' : 'Collapse'}>
{collapsed ? '◀' : '▶'}
</button>
</div>
{!collapsed && (
<div class="props-body">
{/* Interface-level properties (always shown) */}
<div class="props-section">
<div class="props-section-title">Canvas</div>
<Field label="Name">
<TextInput
value={iface.name}
onCommit={(v) => onIfaceChange({ ...iface, name: v })}
/>
</Field>
<Field label="Width">
<input
class="prop-input prop-input-num"
type="number"
value={iface.w}
min={100}
onChange={(e) => onIfaceChange({ ...iface, w: parseInt((e.target as HTMLInputElement).value, 10) || iface.w })}
/>
</Field>
<Field label="Height">
<input
class="prop-input prop-input-num"
type="number"
value={iface.h}
min={100}
onChange={(e) => onIfaceChange({ ...iface, h: parseInt((e.target as HTMLInputElement).value, 10) || iface.h })}
/>
</Field>
</div>
{selected && (
<div class="props-section">
<div class="props-section-title">{selected.type}</div>
<Field label="X">
<input class="prop-input prop-input-num" type="number" value={selected.x}
onChange={(e) => setProp('x', parseFloat((e.target as HTMLInputElement).value) || 0)} />
</Field>
<Field label="Y">
<input class="prop-input prop-input-num" type="number" value={selected.y}
onChange={(e) => setProp('y', parseFloat((e.target as HTMLInputElement).value) || 0)} />
</Field>
<Field label="Width">
<input class="prop-input prop-input-num" type="number" value={selected.w} min={10}
onChange={(e) => setProp('w', parseFloat((e.target as HTMLInputElement).value) || 10)} />
</Field>
<Field label="Height">
<input class="prop-input prop-input-num" type="number" value={selected.h} min={10}
onChange={(e) => setProp('h', parseFloat((e.target as HTMLInputElement).value) || 10)} />
</Field>
{/* Signal */}
{selected.signals.length > 0 && (
<Field label="Signal">
<span class="prop-sig">{selected.signals[0].ds}:{selected.signals[0].name}</span>
</Field>
)}
{/* Common options */}
<Field label="Label">
<TextInput
value={selected.options['label'] ?? ''}
onCommit={(v) => setOpt('label', v)}
/>
</Field>
{/* Type-specific options */}
{(selected.type === 'gauge' || selected.type === 'barh' || selected.type === 'barv') && (
<div>
<Field label="Min">
<TextInput value={selected.options['min'] ?? '0'} onCommit={(v) => setOpt('min', v)} />
</Field>
<Field label="Max">
<TextInput value={selected.options['max'] ?? '100'} onCommit={(v) => setOpt('max', v)} />
</Field>
</div>
)}
{selected.type === 'led' && (
<Field label="Condition">
<TextInput value={selected.options['condition'] ?? '>0'} onCommit={(v) => setOpt('condition', v)} />
</Field>
)}
{selected.type === 'setvalue' && (
<Field label="Min">
<TextInput value={selected.options['min'] ?? ''} onCommit={(v) => setOpt('min', v)} />
</Field>
)}
{selected.type === 'plot' && (
<Field label="Plot type">
<select
class="prop-select"
value={selected.options['plotType'] ?? 'timeseries'}
onChange={(e) => setOpt('plotType', (e.target as HTMLSelectElement).value)}
>
<option value="timeseries">Time-series</option>
<option value="histogram">Histogram</option>
<option value="bar">Bar chart</option>
<option value="fft">FFT</option>
<option value="waterfall">Waterfall</option>
<option value="logic">Logic analyser</option>
</select>
</Field>
)}
{selected.type === 'textlabel' && (
<div>
<Field label="Font size">
<TextInput value={selected.options['fontSize'] ?? '14'} onCommit={(v) => setOpt('fontSize', v)} />
</Field>
<Field label="Color">
<TextInput value={selected.options['color'] ?? '#e2e8f0'} onCommit={(v) => setOpt('color', v)} />
</Field>
</div>
)}
{selected.type === 'image' && (
<Field label="URL">
<TextInput value={selected.options['url'] ?? ''} onCommit={(v) => setOpt('url', v)} />
</Field>
)}
{selected.type === 'link' && (
<div>
<Field label="Target">
<TextInput value={selected.options['target'] ?? ''} onCommit={(v) => setOpt('target', v)} />
</Field>
<Field label="Label">
<TextInput value={selected.options['label'] ?? ''} onCommit={(v) => setOpt('label', v)} />
</Field>
</div>
)}
</div>
)}
{!selected && (
<p class="hint" style="padding:0.75rem;">Select a widget to edit its properties.</p>
)}
</div>
)}
</aside>
);
}