Improved plot view

This commit is contained in:
Martino Ferrari
2026-06-22 00:30:15 +02:00
parent 113e5a0fe8
commit 73fcbe7b28
14 changed files with 744 additions and 61 deletions
+25 -3
View File
@@ -3,7 +3,7 @@ import { useState, useEffect, useRef } from 'preact/hooks';
import SignalPicker, { SignalOption } from './SignalPicker';
import LuaEditor from './LuaEditor';
import type { SignalDef, SynthGraph, SynthGraphNode } from './lib/types';
import { inferNodeTypes, SynthType } from './lib/synthTypes';
import { inferNodeTypes, portAccept, typesCompatible, SynthType } from './lib/synthTypes';
import { VersionTree, DiffViewer } from './VersionHistory';
interface DataSource { name: string; }
@@ -542,6 +542,14 @@ export default function SyntheticGraphEditor({ name, create, panelId, onClose, o
const fn = graph.nodes.find(n => n.id === from);
const tn = graph.nodes.find(n => n.id === to);
if (!fn || !tn || !hasOutput(fn.kind) || tn.kind === 'source') return;
// Reject a definitely type-incompatible link (e.g. a scalar into an array
// reduction). 'unknown'/'any' ports never block — runtime typing is final.
const { types } = inferNodeTypes(compile(graph), sourceSynthType);
const accept = tn.kind === 'op' ? portAccept(tn.op ?? '') : 'any';
if (!typesCompatible(types.get(from) ?? 'unknown', accept)) {
setError(`Incompatible connection: ${accept} port can't take a ${types.get(from)} value`);
return;
}
// One wire per input port: replace any existing wire on that port.
const wires = graph.wires.filter(w => !(w.to === to && w.toPort === toPort));
if (wires.some(w => w.from === from && w.to === to && w.toPort === toPort)) return;
@@ -754,6 +762,20 @@ export default function SyntheticGraphEditor({ name, create, panelId, onClose, o
}
}
// Port colour class. Output ports take the node's own inferred output type;
// input ports take what the op accepts (gray when it accepts either type).
function portTypeClass(t: SynthType | 'any'): string {
if (t === 'array') return ' flow-port-type-array';
if (t === 'scalar') return ' flow-port-type-scalar';
return ' flow-port-type-any';
}
function inPortTypeClass(n: GNode): string {
return portTypeClass(n.kind === 'op' ? portAccept(n.op ?? '') : 'any');
}
function outPortTypeClass(n: GNode): string {
return portTypeClass(nodeTypes.get(n.id) ?? 'unknown');
}
function nodeClass(n: GNode): string {
const cat = n.kind === 'source' ? 'trigger' : n.kind === 'output' ? 'action' : 'flow';
const err = nodeErrors.has(n.id) ? ' flow-node-error' : '';
@@ -846,7 +868,7 @@ export default function SyntheticGraphEditor({ name, create, panelId, onClose, o
const label = portLabel(node, port);
return (
<Fragment key={port}>
<div class="flow-port flow-port-in" title={label || `Input ${port + 1}`}
<div class={`flow-port flow-port-in${inPortTypeClass(node)}`} title={label || `Input ${port + 1}`}
style={`top:${PORT_TOP + port * PORT_GAP - PORT_R}px; left:${-PORT_R}px;`}
onMouseDown={(e) => e.stopPropagation()}
onMouseUp={(e) => { e.stopPropagation(); finishWire(node, port); }} />
@@ -858,7 +880,7 @@ export default function SyntheticGraphEditor({ name, create, panelId, onClose, o
);
})}
{hasOutput(node.kind) && (
<div class="flow-port flow-port-out" title="Output"
<div class={`flow-port flow-port-out${outPortTypeClass(node)}`} title="Output"
style={`top:${PORT_TOP - PORT_R}px; right:${-PORT_R}px;`}
onMouseDown={(e) => startWire(e, node)} />
)}