Files
feuerwerk/src/editor/graph/paletteEntries.ts
T
root 3c76106fd2 Let a Sequence grow an output as you connect the last one
The Outputs setting is gone. A Sequence now keeps one free output below the
last connected one: nothing plugged in is one output, connecting the spare
grows a new one, unplugging the bottom edge shrinks it back.

It counts the last *connected* port rather than the number of edges, so
unplugging a middle one leaves a gap instead of silently dropping an edge.

Removing the setting also removes a bug it caused. Typing in the Outputs field
did nothing and the arrow keys did nothing, while dragging the value worked:
onInput fires per keystroke, the preview writes into node.params, that emits
'project', and Inspector.refreshValues() sees the node's signature change --
the port list is part of it -- so it rebuilds and destroys the row being typed
in. A drag survives because it keeps receiving globalpointermove on the old
widget. refreshValues() has no focus guard where refreshLive() does; the trap
is still there for the next typed field that changes a node's shape.

Ports that depend on the edges around a node mean derivePorts needs the graph,
not just the params, so resolveDef and portSignature take it too. And a node
whose ports are derived cannot know how many it has from its type, so run is
handed the definition resolved for it.
2026-09-10 08:32:14 +02:00

49 lines
1.8 KiB
TypeScript

import {
allNodeDefs,
BEHAVIOR_PARAM,
createEmptyGraph,
isTerminalType,
type NodeDef,
resolveDef,
RUN_BEHAVIOR,
terminalNode,
} from '../../core/graph';
import type { BehaviorAsset } from '../../core/model/Asset';
import { behaviorLookup } from '../../core/model/behaviorPrograms';
import type { PropertyBag } from '../../core/model/Properties';
import type { Project } from '../../core/model/Project';
/**
* One row of the palette. `def` is the definition the node would actually have once placed, so the
* drop filter and the port it lands on are right for a behavior entry too.
*/
export interface PaletteEntry {
id: string;
hint: string;
def: NodeDef;
params: PropertyBag;
}
/**
* The node types offerable in `asset`'s graph. Every other behavior appears under its own name
* instead of behind a generic "Run behavior"; the two terminals drop out once the graph has them.
*/
export function paletteEntries(project: Project, asset: BehaviorAsset): PaletteEntry[] {
const entries: PaletteEntry[] = [];
for (const def of allNodeDefs()) {
if (def.type === RUN_BEHAVIOR) continue;
if (isTerminalType(def.type) && terminalNode(asset.graph, def.type)) continue;
entries.push({ id: def.type, hint: def.category, def, params: {} });
}
const lookup = behaviorLookup(project);
for (const other of project.assets) {
// A behavior running itself is an endless loop, so it is not on offer.
if (other.type !== 'behavior' || other.id === asset.id) continue;
const params = { [BEHAVIOR_PARAM]: other.id };
// The entry is a preview of a node that sits in no graph yet, hence the empty one.
const def = resolveDef({ id: '', type: RUN_BEHAVIOR, x: 0, y: 0, params }, createEmptyGraph(), lookup);
if (def) entries.push({ id: `${RUN_BEHAVIOR}:${other.id}`, hint: 'behavior', def, params });
}
return entries;
}