import { NameTransitionCommand } from '../../core/commands/graphCommands'; import { findNode, type GraphEdge, isStateNode, resolveDef, stateName } from '../../core/graph'; import { graphLookup } from '../../core/model/graphLookup'; import { findGraphAsset, type Project } from '../../core/model/Project'; import { textOf } from '../../core/model/Properties'; import { Button, Label } from '../../ui'; import { hideLinks, linkNoun, removeGraphSelection } from '../actions'; import { createFieldRow, labelledRow } from './fieldFactory'; import { addGap, addNote, addTitle, type InspectorHost } from './host'; const NAME_KEY = 'transition-name'; /** What the panel says about one link: the two ends by the names drawn on them. */ export interface EdgeDescription { transition: boolean; from: string; output: string; to: string; input: string; /** What flows through it, as the output names it. */ type: string; name: string; } /** Null when the link is gone. An end whose node or port is gone reads `?`. */ export function describeEdge(project: Project, assetId: string, edgeId: string): EdgeDescription | null { const graph = findGraphAsset(project, assetId)?.graph; const edge = graph?.edges.find((e) => e.id === edgeId); if (!graph || !edge) return null; const lookup = graphLookup(project); const end = (side: GraphEdge['from'], ports: 'inputs' | 'outputs') => { const node = findNode(graph, side.node); const def = node && resolveDef(node, graph, lookup); const port = def?.[ports].find((p) => p.key === side.port); return { state: node !== undefined && isStateNode(node), node: node ? (isStateNode(node) ? stateName(node) : (def?.label ?? node.type)) : '?', port: port ? port.label || port.key : '?', type: port?.type ?? '?', }; }; const from = end(edge.from, 'outputs'); const to = end(edge.to, 'inputs'); return { transition: from.state && to.state, from: from.node, output: from.port, to: to.node, input: to.port, type: from.type, name: edge.label ?? '', }; } /** Structure of a link selection: how many, and for one of them what it joins. */ export function edgeSignature( project: Project, assetId: string, edgeId: string, edgeIds: readonly string[], ): string { if (edgeIds.length > 1) return `many:${edgeIds.length}`; const d = describeEdge(project, assetId, edgeId); return d ? [d.transition, d.from, d.output, d.to, d.input, d.type].join('|') : 'missing'; } /** One link: where it comes from and goes to. Several: how many, and the way to delete them. */ export function buildEdgeSection( host: InspectorHost, assetId: string, edgeId: string, edgeIds: readonly string[], ): void { const noun = linkNoun(host.state, assetId); const remove = (label: string) => host.body.add( new Button(label, { variant: 'danger', onClick: () => removeGraphSelection(host.state, assetId, [], [], edgeIds), }), ); if (edgeIds.length > 1) { addTitle(host, `${edgeIds.length} ${noun.toUpperCase()}S`); addNote(host, 'Delete removes all of them in one step.'); addGap(host); remove(`Delete ${edgeIds.length} ${noun}s`); return; } const d = describeEdge(host.state.project, assetId, edgeId); if (!d) return; addTitle(host, noun.toUpperCase()); const fact = (label: string, value: string) => { const { row } = labelledRow(label); row.add(new Label(value), 1); host.body.add(row); }; fact('From', d.from); if (!d.transition) fact('Output', d.output); fact('To', d.to); if (d.transition) { const name = createFieldRow(host, { key: NAME_KEY, label: 'Name', kind: 'text', default: '' }, d.name, { onInput: () => {}, onChange: (next) => void host.state.execute(new NameTransitionCommand(assetId, edgeId, textOf(next))), }); host.fields.set(NAME_KEY, name); host.body.add(name.row); addNote(host, 'A State transition node takes it by this name.', 'small'); } else { fact('Input', d.input); fact('Type', d.type); } addGap(host); if (!d.transition) host.body.add(new Button('Hide link', { onClick: () => hideLinks(host.state, assetId, [edgeId]) })); remove(`Delete ${noun}`); } /** Pushes the current name into its row. False when the link is gone (rebuild). */ export function refreshEdgeSection(host: InspectorHost, assetId: string, edgeId: string): boolean { const d = describeEdge(host.state.project, assetId, edgeId); if (!d) return false; host.fields.get(NAME_KEY)?.setValue(d.name); return true; }