@signalsafe/tree-spec-editor-react 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -28
- package/README.standalone.md +14 -0
- package/dist/GraphEditorCanvasContext.d.ts +26 -0
- package/dist/GraphEditorCanvasContext.d.ts.map +1 -0
- package/dist/GraphEditorCanvasContext.js +20 -0
- package/dist/TreeSpecGraphEditor.d.ts +22 -1
- package/dist/TreeSpecGraphEditor.d.ts.map +1 -1
- package/dist/TreeSpecGraphEditor.js +133 -260
- package/dist/canvas/constants.d.ts +41 -0
- package/dist/canvas/constants.d.ts.map +1 -0
- package/dist/canvas/constants.js +40 -0
- package/dist/canvas/edgeBuilders.d.ts +6 -0
- package/dist/canvas/edgeBuilders.d.ts.map +1 -0
- package/dist/canvas/edgeBuilders.js +57 -0
- package/dist/canvas/edgeStyle.d.ts +16 -0
- package/dist/canvas/edgeStyle.d.ts.map +1 -0
- package/dist/canvas/edgeStyle.js +44 -0
- package/dist/canvas/focusChoice.d.ts +3 -0
- package/dist/canvas/focusChoice.d.ts.map +1 -0
- package/dist/canvas/focusChoice.js +12 -0
- package/dist/canvas/typeGuards.d.ts +3 -0
- package/dist/canvas/typeGuards.d.ts.map +1 -0
- package/dist/canvas/typeGuards.js +16 -0
- package/dist/contextMenu/GraphCanvasContextMenu.d.ts +10 -0
- package/dist/contextMenu/GraphCanvasContextMenu.d.ts.map +1 -0
- package/dist/contextMenu/GraphCanvasContextMenu.js +39 -0
- package/dist/contextMenu/types.d.ts +11 -0
- package/dist/contextMenu/types.d.ts.map +1 -0
- package/dist/contextMenu/types.js +1 -0
- package/dist/hooks/keyboardShortcutDispatch.d.ts +30 -0
- package/dist/hooks/keyboardShortcutDispatch.d.ts.map +1 -0
- package/dist/hooks/keyboardShortcutDispatch.js +88 -0
- package/dist/hooks/types.d.ts +32 -2
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/hooks/useCanvasContextMenu.d.ts +15 -0
- package/dist/hooks/useCanvasContextMenu.d.ts.map +1 -0
- package/dist/hooks/useCanvasContextMenu.js +50 -0
- package/dist/hooks/useCanvasGraphState.d.ts +29 -0
- package/dist/hooks/useCanvasGraphState.d.ts.map +1 -0
- package/dist/hooks/useCanvasGraphState.js +150 -0
- package/dist/hooks/useCanvasIssueIndex.d.ts +12 -0
- package/dist/hooks/useCanvasIssueIndex.d.ts.map +1 -0
- package/dist/hooks/useCanvasIssueIndex.js +32 -0
- package/dist/hooks/useCanvasNodeResize.d.ts +17 -0
- package/dist/hooks/useCanvasNodeResize.d.ts.map +1 -0
- package/dist/hooks/useCanvasNodeResize.js +53 -0
- package/dist/hooks/useCanvasViewport.d.ts +12 -0
- package/dist/hooks/useCanvasViewport.d.ts.map +1 -0
- package/dist/hooks/useCanvasViewport.js +84 -0
- package/dist/hooks/useChoiceDragDrop.d.ts +25 -0
- package/dist/hooks/useChoiceDragDrop.d.ts.map +1 -0
- package/dist/hooks/useChoiceDragDrop.js +40 -0
- package/dist/hooks/useEditorAdapter.d.ts +46 -0
- package/dist/hooks/useEditorAdapter.d.ts.map +1 -0
- package/dist/hooks/useEditorAdapter.js +281 -0
- package/dist/hooks/useEditorAutosave.d.ts +18 -0
- package/dist/hooks/useEditorAutosave.d.ts.map +1 -0
- package/dist/hooks/useEditorAutosave.js +37 -0
- package/dist/hooks/useEditorHistory.d.ts +16 -0
- package/dist/hooks/useEditorHistory.d.ts.map +1 -0
- package/dist/hooks/useEditorHistory.js +103 -0
- package/dist/hooks/useEditorSelection.d.ts +22 -0
- package/dist/hooks/useEditorSelection.d.ts.map +1 -0
- package/dist/hooks/useEditorSelection.js +75 -0
- package/dist/hooks/useGraphConnect.d.ts +22 -0
- package/dist/hooks/useGraphConnect.d.ts.map +1 -0
- package/dist/hooks/useGraphConnect.js +75 -0
- package/dist/hooks/useTreeSpecEditor.d.ts +1 -9
- package/dist/hooks/useTreeSpecEditor.d.ts.map +1 -1
- package/dist/hooks/useTreeSpecEditor.js +231 -462
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/nodes/ChoiceCanvasRow.d.ts +10 -0
- package/dist/nodes/ChoiceCanvasRow.d.ts.map +1 -0
- package/dist/nodes/ChoiceCanvasRow.js +55 -0
- package/dist/nodes/EndNode.d.ts +3 -0
- package/dist/nodes/EndNode.d.ts.map +1 -0
- package/dist/nodes/EndNode.js +7 -0
- package/dist/nodes/PromptNode.d.ts +6 -0
- package/dist/nodes/PromptNode.d.ts.map +1 -0
- package/dist/nodes/PromptNode.js +51 -0
- package/dist/nodes/PromptNodeChoicesList.d.ts +14 -0
- package/dist/nodes/PromptNodeChoicesList.d.ts.map +1 -0
- package/dist/nodes/PromptNodeChoicesList.js +24 -0
- package/dist/nodes/PromptNodeHeader.d.ts +10 -0
- package/dist/nodes/PromptNodeHeader.d.ts.map +1 -0
- package/dist/nodes/PromptNodeHeader.js +6 -0
- package/dist/nodes/PromptNodeIssueBadges.d.ts +7 -0
- package/dist/nodes/PromptNodeIssueBadges.d.ts.map +1 -0
- package/dist/nodes/PromptNodeIssueBadges.js +6 -0
- package/dist/nodes/PromptNodeToolbar.d.ts +6 -0
- package/dist/nodes/PromptNodeToolbar.d.ts.map +1 -0
- package/dist/nodes/PromptNodeToolbar.js +5 -0
- package/dist/nodes/types.d.ts +13 -0
- package/dist/nodes/types.d.ts.map +1 -0
- package/dist/nodes/types.js +1 -0
- package/dist/utils/joinClasses.d.ts +2 -0
- package/dist/utils/joinClasses.d.ts.map +1 -0
- package/dist/utils/joinClasses.js +3 -0
- package/package.json +36 -13
|
@@ -1,276 +1,149 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback,
|
|
3
|
-
import ReactFlow, { Background,
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import ReactFlow, { Background, ConnectionMode, Controls, MiniMap, ReactFlowProvider, useReactFlow, } from 'reactflow';
|
|
4
4
|
import 'reactflow/dist/style.css';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return style;
|
|
24
|
-
}
|
|
25
|
-
return style ? { ...style, strokeWidth: 2, strokeDasharray: '6 4' } : { strokeWidth: 2, strokeDasharray: '6 4' };
|
|
26
|
-
}
|
|
27
|
-
function PromptNode({ data, selected }) {
|
|
28
|
-
const n = data.node;
|
|
29
|
-
const choices = n.choices ?? [];
|
|
30
|
-
const hasErrors = data.issuesErrors > 0;
|
|
31
|
-
const borderClass = getPromptNodeBorderClass(hasErrors, data.issuesWarnings);
|
|
32
|
-
return (_jsxs("div", { className: joinClasses('card', 'card-min-width-280', borderClass, selected && CANVAS_NODE_SELECTED_CLASS), children: [_jsx(Handle, { type: "target", position: Position.Left, id: "in", className: "handle-bg-default" }), _jsxs("div", { className: "card-body p-2", children: [_jsxs("div", { className: "d-flex justify-content-between align-items-center", children: [_jsxs("div", { children: [_jsxs("div", { className: "fw-bold font-size-13", children: [data.isStart ? '▶ ' : '', n.type, data.issuesTotal > 0 ? (_jsxs("span", { className: "ms-2", title: `${data.issuesErrors} errors, ${data.issuesWarnings} warnings, ${data.issuesInfo} info`, children: [data.issuesErrors > 0 ? _jsx("span", { className: "badge bg-danger me-1", children: data.issuesErrors }) : null, data.issuesWarnings > 0 ? (_jsx("span", { className: "badge bg-warning text-dark me-1", children: data.issuesWarnings })) : null, data.issuesInfo > 0 ? _jsx("span", { className: "badge bg-info text-dark", children: data.issuesInfo }) : null] })) : null] }), _jsx("div", { className: "font-size-12 opacity-90 text-truncate", children: n.prompt || _jsx("em", { className: "text-muted", children: "(empty prompt)" }) })] }), _jsx("div", { className: "text-muted font-size-11", children: n.id.slice(0, 8) })] }), _jsx("hr", { className: "my-2" }), _jsx("div", { className: "font-size-12", children: choices.length === 0 ? (_jsx("div", { className: "text-muted", children: _jsx("em", { children: "No choices" }) })) : (_jsx("ul", { className: "list-unstyled mb-0", children: choices.map((c) => (_jsxs("li", { className: "d-flex justify-content-between align-items-center position-relative pr-18", children: [_jsx("span", { className: "text-truncate max-w-210", children: c.label }), _jsx("span", { className: "badge bg-light text-dark", children: c.id }), _jsx(Handle, { type: "source", position: Position.Right, id: `choice:${c.id}`, className: "graph-editor-handle", style: {
|
|
33
|
-
position: 'absolute',
|
|
34
|
-
right: -6,
|
|
35
|
-
top: '50%',
|
|
36
|
-
transform: 'translateY(-50%)',
|
|
37
|
-
} })] }, c.id))) })) })] })] }));
|
|
38
|
-
}
|
|
39
|
-
function EndNode({ selected }) {
|
|
40
|
-
return (_jsxs("div", { className: joinClasses('card', 'border-danger', 'w-180', selected && CANVAS_NODE_SELECTED_CLASS), children: [_jsx(Handle, { type: "target", position: Position.Left, id: "in", className: "handle-bg-danger" }), _jsxs("div", { className: "card-body p-2 text-center", children: [_jsx("div", { className: "fw-bold text-danger", children: "END" }), _jsx("div", { className: "text-muted font-size-12", children: "Outcome required" })] })] }));
|
|
41
|
-
}
|
|
42
|
-
function choiceIdFromHandle(h) {
|
|
43
|
-
if (!h)
|
|
44
|
-
return '';
|
|
45
|
-
if (h.startsWith('choice:'))
|
|
46
|
-
return h.slice('choice:'.length);
|
|
47
|
-
return h;
|
|
48
|
-
}
|
|
49
|
-
function edgeLabelForTransition(tree, t) {
|
|
50
|
-
const node = tree.nodes[t.fromNodeId];
|
|
51
|
-
const choice = node?.choices?.find((c) => c.id === t.fromChoiceId);
|
|
52
|
-
const base = choice?.label || t.fromChoiceId;
|
|
53
|
-
if (t.toNodeId === END_NODE_ID) {
|
|
54
|
-
const oc = t.outcome ?? TERMINAL_OUTCOME.AT_RISK;
|
|
55
|
-
return `${base} → END (${oc})`;
|
|
56
|
-
}
|
|
57
|
-
return base;
|
|
58
|
-
}
|
|
59
|
-
function buildEdgesFromTransitions(tree) {
|
|
60
|
-
return tree.transitions.map((t) => ({
|
|
61
|
-
id: t.id,
|
|
62
|
-
source: t.fromNodeId,
|
|
63
|
-
target: t.toNodeId,
|
|
64
|
-
sourceHandle: `choice:${t.fromChoiceId}`,
|
|
65
|
-
label: edgeLabelForTransition(tree, t),
|
|
66
|
-
}));
|
|
67
|
-
}
|
|
68
|
-
function buildTransitionsFromEdges(edges, existing) {
|
|
69
|
-
const existingById = new Map(existing.map((t) => [t.id, t]));
|
|
70
|
-
return edges
|
|
71
|
-
.filter((e) => e.source && e.target && e.source !== END_NODE_ID)
|
|
72
|
-
.map((e) => {
|
|
73
|
-
const fromChoiceId = choiceIdFromHandle(e.sourceHandle);
|
|
74
|
-
const prior = existingById.get(String(e.id));
|
|
75
|
-
return {
|
|
76
|
-
id: String(e.id),
|
|
77
|
-
fromNodeId: String(e.source),
|
|
78
|
-
fromChoiceId: fromChoiceId,
|
|
79
|
-
toNodeId: String(e.target),
|
|
80
|
-
outcome: String(e.target) === END_NODE_ID ? (prior?.outcome ?? TERMINAL_OUTCOME.AT_RISK) : undefined,
|
|
81
|
-
};
|
|
82
|
-
})
|
|
83
|
-
.filter((t) => t.fromChoiceId.length > 0);
|
|
84
|
-
}
|
|
85
|
-
function TreeSpecGraphInner({ tree, onChange, issues = [], selected, onSelect, focusNodeId, showMiniMap = true, fitViewNonce, className = 'h-70vh border rounded', }) {
|
|
5
|
+
import { GRAPH_SELECTION_KIND, choiceIdFromHandle, resolveGraphViewport, LAYOUT_SNAP_GRID, } from '@signalsafe/tree-spec-editor-core';
|
|
6
|
+
import { GraphEditorCanvasContext, } from './GraphEditorCanvasContext.js';
|
|
7
|
+
import { buildEdgeMarker, getIssueEdgeStyle, resolveSelectedEdgeStroke } from './canvas/edgeStyle.js';
|
|
8
|
+
import { CANVAS_CLASS } from './canvas/constants.js';
|
|
9
|
+
import { resolveCanvasFocusChoiceId } from './canvas/focusChoice.js';
|
|
10
|
+
import { isChoiceRowClickTarget } from './canvas/typeGuards.js';
|
|
11
|
+
import { GraphCanvasContextMenu } from './contextMenu/GraphCanvasContextMenu.js';
|
|
12
|
+
import { EndNode } from './nodes/EndNode.js';
|
|
13
|
+
import { PromptNode } from './nodes/PromptNode.js';
|
|
14
|
+
import { joinClasses } from './utils/joinClasses.js';
|
|
15
|
+
import { useCanvasContextMenu } from './hooks/useCanvasContextMenu.js';
|
|
16
|
+
import { useCanvasGraphState } from './hooks/useCanvasGraphState.js';
|
|
17
|
+
import { useCanvasIssueIndex } from './hooks/useCanvasIssueIndex.js';
|
|
18
|
+
import { useCanvasNodeResize } from './hooks/useCanvasNodeResize.js';
|
|
19
|
+
import { useCanvasViewport } from './hooks/useCanvasViewport.js';
|
|
20
|
+
import { useChoiceDragDrop } from './hooks/useChoiceDragDrop.js';
|
|
21
|
+
import { useGraphConnect } from './hooks/useGraphConnect.js';
|
|
22
|
+
function TreeSpecGraphInner({ tree, onChange, issues = [], selected, onSelect, onChoiceSelect, onRepositionChoice, focusNodeId, focusChoiceId = null, showMiniMap = true, fitViewNonce, className = 'h-70vh border rounded', readOnly = false, onDuplicateNode, onDeleteNode, onAutoLayout, contextualZoom = true, colorMode = 'light', }) {
|
|
86
23
|
const rf = useReactFlow();
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
data: {},
|
|
139
|
-
selectable: true,
|
|
140
|
-
draggable: true,
|
|
141
|
-
});
|
|
142
|
-
return arr;
|
|
143
|
-
}, [tree.nodes, tree.start_node, issuesByNode]);
|
|
144
|
-
const initialEdges = useMemo(() => buildEdgesFromTransitions(tree), [tree]);
|
|
145
|
-
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
|
146
|
-
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
|
147
|
-
useEffect(() => {
|
|
148
|
-
nodesRef.current = nodes;
|
|
149
|
-
}, [nodes]);
|
|
150
|
-
useEffect(() => {
|
|
151
|
-
edgesRef.current = edges;
|
|
152
|
-
}, [edges]);
|
|
153
|
-
const lastTreeRef = useRef('');
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
const nextKey = JSON.stringify({ tree, issues: issues.length });
|
|
156
|
-
if (nextKey === lastTreeRef.current)
|
|
157
|
-
return;
|
|
158
|
-
lastTreeRef.current = nextKey;
|
|
159
|
-
setNodes(initialNodes);
|
|
160
|
-
setEdges(initialEdges);
|
|
161
|
-
}, [tree, issues.length, initialNodes, initialEdges, setNodes, setEdges]);
|
|
162
|
-
useEffect(() => {
|
|
163
|
-
if (!focusNodeId)
|
|
164
|
-
return;
|
|
165
|
-
try {
|
|
166
|
-
const n = rf.getNode(focusNodeId);
|
|
167
|
-
if (!n)
|
|
168
|
-
return;
|
|
169
|
-
rf.setCenter(n.position.x + 150, n.position.y + 60, { zoom: 1, duration: 300 });
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
// ignore
|
|
173
|
-
}
|
|
174
|
-
}, [focusNodeId, rf]);
|
|
175
|
-
useEffect(() => {
|
|
176
|
-
if (!fitViewNonce)
|
|
177
|
-
return;
|
|
178
|
-
const id = setTimeout(() => {
|
|
179
|
-
try {
|
|
180
|
-
rf.fitView({ padding: 0.2, duration: 300 });
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
// ignore
|
|
184
|
-
}
|
|
185
|
-
}, 100);
|
|
186
|
-
return () => clearTimeout(id);
|
|
187
|
-
}, [fitViewNonce, rf]);
|
|
24
|
+
const treeRef = useRef(tree);
|
|
25
|
+
treeRef.current = tree;
|
|
26
|
+
const suppressViewportSaveRef = useRef(true);
|
|
27
|
+
const isResizingRef = useRef(false);
|
|
28
|
+
const { issuesByNode, issueKeySet } = useCanvasIssueIndex(issues);
|
|
29
|
+
const { choiceDrag, choiceDropTarget, handleChoiceDragStart, handleChoiceDragEnd, handleChoiceDragOver, handleChoiceDrop, } = useChoiceDragDrop({
|
|
30
|
+
readOnly,
|
|
31
|
+
onChoiceSelect,
|
|
32
|
+
onSelect,
|
|
33
|
+
onRepositionChoice,
|
|
34
|
+
});
|
|
35
|
+
const { contextMenu, closeContextMenu, onNodeContextMenu, onPaneContextMenu } = useCanvasContextMenu({
|
|
36
|
+
readOnly,
|
|
37
|
+
onAutoLayout,
|
|
38
|
+
});
|
|
39
|
+
const [resizeHeightByNodeId, setResizeHeightByNodeId] = useState({});
|
|
40
|
+
const graphState = useCanvasGraphState({
|
|
41
|
+
tree,
|
|
42
|
+
onChange,
|
|
43
|
+
issues,
|
|
44
|
+
issuesByNode,
|
|
45
|
+
readOnly,
|
|
46
|
+
resizeHeightByNodeId,
|
|
47
|
+
isResizingRef,
|
|
48
|
+
suppressViewportSaveRef,
|
|
49
|
+
});
|
|
50
|
+
const { nodes, edges, onNodesChangeWrapped, onEdgesChangeWrapped, onNodeDragStart, onNodeDragStop, onMoveEnd, setNodes, } = graphState;
|
|
51
|
+
const resizeHandlers = useCanvasNodeResize({
|
|
52
|
+
readOnly,
|
|
53
|
+
treeRef,
|
|
54
|
+
onChange,
|
|
55
|
+
setNodes,
|
|
56
|
+
isResizingRef,
|
|
57
|
+
resizeHeightByNodeId,
|
|
58
|
+
setResizeHeightByNodeId,
|
|
59
|
+
});
|
|
60
|
+
useCanvasViewport({
|
|
61
|
+
rf,
|
|
62
|
+
focusNodeId,
|
|
63
|
+
fitViewNonce,
|
|
64
|
+
contextualZoom,
|
|
65
|
+
selected,
|
|
66
|
+
suppressViewportSaveRef,
|
|
67
|
+
});
|
|
68
|
+
const { onConnect, onConnectStart, onConnectEnd, onReconnect, onEdgesDelete, isValidConnection, } = useGraphConnect({
|
|
69
|
+
treeRef,
|
|
70
|
+
onChange,
|
|
71
|
+
onSelect,
|
|
72
|
+
readOnly,
|
|
73
|
+
});
|
|
74
|
+
const savedViewport = useMemo(() => resolveGraphViewport(tree), [tree._meta]);
|
|
188
75
|
const nodeTypes = useMemo(() => ({
|
|
189
76
|
promptNode: PromptNode,
|
|
190
77
|
endNode: EndNode,
|
|
191
78
|
}), []);
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
79
|
+
const onNodeClick = useCallback((evt, node) => {
|
|
80
|
+
if (isChoiceRowClickTarget(evt.target))
|
|
194
81
|
return;
|
|
195
|
-
const updatedNodes = { ...tree.nodes };
|
|
196
|
-
for (const n of nextNodes) {
|
|
197
|
-
if (n.id === END_NODE_ID)
|
|
198
|
-
continue;
|
|
199
|
-
const existing = updatedNodes[n.id];
|
|
200
|
-
if (!existing)
|
|
201
|
-
continue;
|
|
202
|
-
updatedNodes[n.id] = {
|
|
203
|
-
...existing,
|
|
204
|
-
position: { x: n.position.x, y: n.position.y },
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
const transitions = buildTransitionsFromEdges(nextEdges, tree.transitions);
|
|
208
|
-
onChange({
|
|
209
|
-
...tree,
|
|
210
|
-
nodes: updatedNodes,
|
|
211
|
-
transitions,
|
|
212
|
-
});
|
|
213
|
-
}, [tree, onChange]);
|
|
214
|
-
const onConnect = useCallback((conn) => {
|
|
215
|
-
const choiceId = choiceIdFromHandle(conn.sourceHandle);
|
|
216
|
-
if (!conn.source || !conn.target || !choiceId)
|
|
217
|
-
return;
|
|
218
|
-
const newEdge = {
|
|
219
|
-
id: safeUUID(),
|
|
220
|
-
source: conn.source,
|
|
221
|
-
target: conn.target,
|
|
222
|
-
sourceHandle: conn.sourceHandle,
|
|
223
|
-
label: choiceId,
|
|
224
|
-
};
|
|
225
|
-
setEdges((eds) => {
|
|
226
|
-
const next = addEdge(newEdge, eds);
|
|
227
|
-
commit(nodesRef.current, next);
|
|
228
|
-
return next;
|
|
229
|
-
});
|
|
230
|
-
}, [setEdges, commit]);
|
|
231
|
-
const onNodesChangeWrapped = useCallback((changes) => {
|
|
232
|
-
onNodesChange(changes);
|
|
233
|
-
const shouldCommit = changes.some((c) => {
|
|
234
|
-
if (c?.type === 'select' || c?.type === 'dimensions')
|
|
235
|
-
return false;
|
|
236
|
-
if (c?.type === 'position' && c?.dragging)
|
|
237
|
-
return false;
|
|
238
|
-
return true;
|
|
239
|
-
});
|
|
240
|
-
if (shouldCommit)
|
|
241
|
-
queueMicrotask(() => commit(nodesRef.current, edgesRef.current));
|
|
242
|
-
}, [onNodesChange, commit]);
|
|
243
|
-
const onEdgesChangeWrapped = useCallback((changes) => {
|
|
244
|
-
onEdgesChange(changes);
|
|
245
|
-
const shouldCommit = changes.some((c) => c?.type !== 'select');
|
|
246
|
-
if (shouldCommit)
|
|
247
|
-
queueMicrotask(() => commit(nodesRef.current, edgesRef.current));
|
|
248
|
-
}, [onEdgesChange, commit]);
|
|
249
|
-
const onNodeDragStart = useCallback(() => {
|
|
250
|
-
isDraggingRef.current = true;
|
|
251
|
-
}, []);
|
|
252
|
-
const onNodeDragStop = useCallback(() => {
|
|
253
|
-
isDraggingRef.current = false;
|
|
254
|
-
commit(nodesRef.current, edgesRef.current);
|
|
255
|
-
}, [commit]);
|
|
256
|
-
const onNodeClick = useCallback((_evt, node) => {
|
|
257
82
|
onSelect?.({ kind: GRAPH_SELECTION_KIND.NODE, id: node.id });
|
|
258
83
|
}, [onSelect]);
|
|
259
84
|
const onEdgeClick = useCallback((_evt, edge) => {
|
|
260
85
|
onSelect?.({ kind: GRAPH_SELECTION_KIND.EDGE, id: edge.id });
|
|
261
86
|
}, [onSelect]);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
87
|
+
const onPaneClick = useCallback(() => {
|
|
88
|
+
onSelect?.({ kind: null, id: null });
|
|
89
|
+
}, [onSelect]);
|
|
90
|
+
const canvasContextValue = useMemo(() => ({
|
|
91
|
+
readOnly,
|
|
92
|
+
onDuplicateNode: (nodeId) => onDuplicateNode?.(nodeId),
|
|
93
|
+
onDeleteNode: (nodeId) => onDeleteNode?.(nodeId),
|
|
94
|
+
onResizeNode: resizeHandlers.handleResizeNode,
|
|
95
|
+
onResizeNodeStart: resizeHandlers.handleResizeNodeStart,
|
|
96
|
+
onSelectChoice: (nodeId, choiceId) => {
|
|
97
|
+
if (onChoiceSelect) {
|
|
98
|
+
onChoiceSelect(nodeId, choiceId);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
onSelect?.({ kind: GRAPH_SELECTION_KIND.NODE, id: nodeId });
|
|
102
|
+
},
|
|
103
|
+
choiceDrag,
|
|
104
|
+
choiceDropTarget,
|
|
105
|
+
onChoiceDragStart: handleChoiceDragStart,
|
|
106
|
+
onChoiceDragEnd: handleChoiceDragEnd,
|
|
107
|
+
onChoiceDragOver: handleChoiceDragOver,
|
|
108
|
+
onChoiceDrop: handleChoiceDrop,
|
|
109
|
+
}), [
|
|
110
|
+
readOnly,
|
|
111
|
+
onDuplicateNode,
|
|
112
|
+
onDeleteNode,
|
|
113
|
+
resizeHandlers.handleResizeNode,
|
|
114
|
+
resizeHandlers.handleResizeNodeStart,
|
|
115
|
+
onSelect,
|
|
116
|
+
onChoiceSelect,
|
|
117
|
+
choiceDrag,
|
|
118
|
+
choiceDropTarget,
|
|
119
|
+
handleChoiceDragStart,
|
|
120
|
+
handleChoiceDragEnd,
|
|
121
|
+
handleChoiceDragOver,
|
|
122
|
+
handleChoiceDrop,
|
|
123
|
+
]);
|
|
124
|
+
return (_jsx(GraphEditorCanvasContext.Provider, { value: canvasContextValue, children: _jsxs("div", { className: joinClasses(className, CANVAS_CLASS), "data-color-mode": colorMode, children: [_jsx(GraphCanvasContextMenu, { menu: contextMenu, readOnly: readOnly, onClose: closeContextMenu, onDuplicateNode: onDuplicateNode, onDeleteNode: onDeleteNode, onAutoLayout: onAutoLayout }), _jsxs(ReactFlow, { defaultViewport: savedViewport, elementsSelectable: false, selectNodesOnDrag: false, nodes: nodes.map((n) => ({
|
|
125
|
+
...n,
|
|
126
|
+
selected: selected?.kind === GRAPH_SELECTION_KIND.NODE && selected.id === n.id,
|
|
127
|
+
data: n.type === 'promptNode'
|
|
128
|
+
? {
|
|
129
|
+
...n.data,
|
|
130
|
+
focusChoiceId: resolveCanvasFocusChoiceId(n.id, selected, focusNodeId, focusChoiceId),
|
|
131
|
+
}
|
|
132
|
+
: n.data,
|
|
133
|
+
})), edges: edges.map((e) => {
|
|
134
|
+
const fromChoiceId = choiceIdFromHandle(e.sourceHandle);
|
|
135
|
+
const hasIssue = fromChoiceId ? issueKeySet.has(`${e.source}::${fromChoiceId}`) : false;
|
|
136
|
+
const isEdgeSelected = selected?.kind === GRAPH_SELECTION_KIND.EDGE && selected.id === e.id;
|
|
137
|
+
let style = getIssueEdgeStyle(e.style, hasIssue);
|
|
138
|
+
const { style: resolvedStyle, stroke } = resolveSelectedEdgeStroke(style, isEdgeSelected);
|
|
139
|
+
style = resolvedStyle;
|
|
140
|
+
return {
|
|
141
|
+
...e,
|
|
142
|
+
selected: isEdgeSelected,
|
|
143
|
+
style,
|
|
144
|
+
markerEnd: buildEdgeMarker(stroke),
|
|
145
|
+
};
|
|
146
|
+
}), onNodesChange: onNodesChangeWrapped, onEdgesChange: onEdgesChangeWrapped, onConnect: onConnect, onConnectStart: onConnectStart, onConnectEnd: onConnectEnd, onReconnect: onReconnect, onEdgesDelete: onEdgesDelete, isValidConnection: isValidConnection, connectionMode: ConnectionMode.Strict, snapToGrid: true, snapGrid: [LAYOUT_SNAP_GRID, LAYOUT_SNAP_GRID], onNodeDragStart: onNodeDragStart, onNodeDragStop: onNodeDragStop, onNodeClick: onNodeClick, onEdgeClick: onEdgeClick, onPaneClick: onPaneClick, onNodeContextMenu: onNodeContextMenu, onPaneContextMenu: onPaneContextMenu, onMoveEnd: onMoveEnd, nodeTypes: nodeTypes, children: [showMiniMap ? _jsx(MiniMap, { pannable: true, ariaLabel: "Canvas overview \u2014 drag to pan" }) : null, _jsx(Controls, {}), _jsx(Background, {})] })] }) }));
|
|
274
147
|
}
|
|
275
148
|
export default function TreeSpecGraphEditor(props) {
|
|
276
149
|
return (_jsx(ReactFlowProvider, { children: _jsx(TreeSpecGraphInner, { ...props }) }));
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MarkerType } from 'reactflow';
|
|
2
|
+
/** Background highlight when the node matches editor selection (sidebar / issues / canvas). */
|
|
3
|
+
export declare const CANVAS_NODE_SELECTED_CLASS = "bg-primary-subtle";
|
|
4
|
+
/** Dark readable text on selected canvas node cards. */
|
|
5
|
+
export declare const CANVAS_NODE_SELECTED_TEXT_CLASS = "graph-editor-canvas-selected";
|
|
6
|
+
export declare const CANVAS_CLASS = "graph-editor-canvas";
|
|
7
|
+
export declare const CANVAS_NODE_CLASS = "graph-editor-canvas-node";
|
|
8
|
+
export declare const CANVAS_NODE_BODY_CLASS = "graph-editor-canvas-node-body";
|
|
9
|
+
export declare const CANVAS_CHOICE_SELECTED_CLASS = "graph-editor-canvas-choice-selected";
|
|
10
|
+
export declare const CHOICE_ROW_CLASS = "graph-editor-choice-row";
|
|
11
|
+
export declare const CHOICE_ROW_SELECTOR = ".graph-editor-choice-row";
|
|
12
|
+
export declare const CHOICE_DRAG_HANDLE_CLASS = "graph-editor-choice-drag-handle";
|
|
13
|
+
export declare const CHOICE_DRAG_HANDLE_SELECTOR = ".graph-editor-choice-drag-handle";
|
|
14
|
+
export declare const CHOICE_DROP_TARGET_CLASS = "graph-editor-choice-drop-target";
|
|
15
|
+
export declare const CHOICE_DROP_APPEND_CLASS = "graph-editor-choice-drop-append";
|
|
16
|
+
export declare const CHOICE_ROW_SELECT_CLASS = "graph-editor-choice-row-select";
|
|
17
|
+
export declare const CHOICE_ROW_SELECTABLE_CLASS = "graph-editor-choice-row-selectable";
|
|
18
|
+
export declare const CHOICE_HANDLE_CLASS = "graph-editor-handle graph-editor-choice-handle";
|
|
19
|
+
export declare const NODE_DRAG_HANDLE_CLASS = "graph-editor-drag-handle";
|
|
20
|
+
export declare const NODE_DRAG_HANDLE_SELECTOR = ".graph-editor-drag-handle";
|
|
21
|
+
export declare const TARGET_HANDLE_CLASS_DEFAULT = "handle-bg-default graph-editor-target-handle";
|
|
22
|
+
export declare const TARGET_HANDLE_CLASS_DANGER = "handle-bg-danger graph-editor-target-handle";
|
|
23
|
+
export declare const CONTEXT_MENU_CLASS = "graph-editor-context-menu dropdown-menu show position-fixed shadow-sm";
|
|
24
|
+
export declare const REACT_FLOW_PANE_CLASS = "react-flow__pane";
|
|
25
|
+
export declare const BORDER_DANGER_CLASS = "border-danger";
|
|
26
|
+
export declare const BORDER_WARNING_CLASS = "border-warning";
|
|
27
|
+
export declare const END_NODE_WIDTH_CLASS = "w-180";
|
|
28
|
+
export declare const MIN_NODE_WIDTH = 180;
|
|
29
|
+
export declare const MAX_NODE_WIDTH = 560;
|
|
30
|
+
export declare const MIN_NODE_HEIGHT = 80;
|
|
31
|
+
export declare const MAX_NODE_HEIGHT = 480;
|
|
32
|
+
/** Matches React Flow selected edge stroke (`.react-flow__edge.selected .react-flow__edge-path`). */
|
|
33
|
+
export declare const SELECTED_EDGE_STROKE = "#555";
|
|
34
|
+
export declare const EDGE_ARROW_MARKER: {
|
|
35
|
+
type: MarkerType;
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
};
|
|
39
|
+
export declare const TARGET_HANDLE_ID = "in";
|
|
40
|
+
export declare const CHOICE_HANDLE_PREFIX = "choice:";
|
|
41
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/canvas/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,+FAA+F;AAC/F,eAAO,MAAM,0BAA0B,sBAAsB,CAAC;AAE9D,wDAAwD;AACxD,eAAO,MAAM,+BAA+B,iCAAiC,CAAC;AAE9E,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAClD,eAAO,MAAM,iBAAiB,6BAA6B,CAAC;AAC5D,eAAO,MAAM,sBAAsB,kCAAkC,CAAC;AACtE,eAAO,MAAM,4BAA4B,wCAAwC,CAAC;AAElF,eAAO,MAAM,gBAAgB,4BAA4B,CAAC;AAC1D,eAAO,MAAM,mBAAmB,6BAAyB,CAAC;AAC1D,eAAO,MAAM,wBAAwB,oCAAoC,CAAC;AAC1E,eAAO,MAAM,2BAA2B,qCAAiC,CAAC;AAC1E,eAAO,MAAM,wBAAwB,oCAAoC,CAAC;AAC1E,eAAO,MAAM,wBAAwB,oCAAoC,CAAC;AAC1E,eAAO,MAAM,uBAAuB,mCAAmC,CAAC;AACxE,eAAO,MAAM,2BAA2B,uCAAuC,CAAC;AAChF,eAAO,MAAM,mBAAmB,mDAAmD,CAAC;AAEpF,eAAO,MAAM,sBAAsB,6BAA6B,CAAC;AACjE,eAAO,MAAM,yBAAyB,8BAA+B,CAAC;AAEtE,eAAO,MAAM,2BAA2B,iDAAiD,CAAC;AAC1F,eAAO,MAAM,0BAA0B,gDAAgD,CAAC;AAExF,eAAO,MAAM,kBAAkB,0EAA0E,CAAC;AAE1G,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AACnD,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAErD,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,eAAO,MAAM,cAAc,MAAM,CAAC;AAClC,eAAO,MAAM,cAAc,MAAM,CAAC;AAClC,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC,qGAAqG;AACrG,eAAO,MAAM,oBAAoB,SAAS,CAAC;AAE3C,eAAO,MAAM,iBAAiB;;;;CAI7B,CAAC;AAEF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AACrC,eAAO,MAAM,oBAAoB,YAAY,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { MarkerType } from 'reactflow';
|
|
2
|
+
/** Background highlight when the node matches editor selection (sidebar / issues / canvas). */
|
|
3
|
+
export const CANVAS_NODE_SELECTED_CLASS = 'bg-primary-subtle';
|
|
4
|
+
/** Dark readable text on selected canvas node cards. */
|
|
5
|
+
export const CANVAS_NODE_SELECTED_TEXT_CLASS = 'graph-editor-canvas-selected';
|
|
6
|
+
export const CANVAS_CLASS = 'graph-editor-canvas';
|
|
7
|
+
export const CANVAS_NODE_CLASS = 'graph-editor-canvas-node';
|
|
8
|
+
export const CANVAS_NODE_BODY_CLASS = 'graph-editor-canvas-node-body';
|
|
9
|
+
export const CANVAS_CHOICE_SELECTED_CLASS = 'graph-editor-canvas-choice-selected';
|
|
10
|
+
export const CHOICE_ROW_CLASS = 'graph-editor-choice-row';
|
|
11
|
+
export const CHOICE_ROW_SELECTOR = `.${CHOICE_ROW_CLASS}`;
|
|
12
|
+
export const CHOICE_DRAG_HANDLE_CLASS = 'graph-editor-choice-drag-handle';
|
|
13
|
+
export const CHOICE_DRAG_HANDLE_SELECTOR = `.${CHOICE_DRAG_HANDLE_CLASS}`;
|
|
14
|
+
export const CHOICE_DROP_TARGET_CLASS = 'graph-editor-choice-drop-target';
|
|
15
|
+
export const CHOICE_DROP_APPEND_CLASS = 'graph-editor-choice-drop-append';
|
|
16
|
+
export const CHOICE_ROW_SELECT_CLASS = 'graph-editor-choice-row-select';
|
|
17
|
+
export const CHOICE_ROW_SELECTABLE_CLASS = 'graph-editor-choice-row-selectable';
|
|
18
|
+
export const CHOICE_HANDLE_CLASS = 'graph-editor-handle graph-editor-choice-handle';
|
|
19
|
+
export const NODE_DRAG_HANDLE_CLASS = 'graph-editor-drag-handle';
|
|
20
|
+
export const NODE_DRAG_HANDLE_SELECTOR = `.${NODE_DRAG_HANDLE_CLASS}`;
|
|
21
|
+
export const TARGET_HANDLE_CLASS_DEFAULT = 'handle-bg-default graph-editor-target-handle';
|
|
22
|
+
export const TARGET_HANDLE_CLASS_DANGER = 'handle-bg-danger graph-editor-target-handle';
|
|
23
|
+
export const CONTEXT_MENU_CLASS = 'graph-editor-context-menu dropdown-menu show position-fixed shadow-sm';
|
|
24
|
+
export const REACT_FLOW_PANE_CLASS = 'react-flow__pane';
|
|
25
|
+
export const BORDER_DANGER_CLASS = 'border-danger';
|
|
26
|
+
export const BORDER_WARNING_CLASS = 'border-warning';
|
|
27
|
+
export const END_NODE_WIDTH_CLASS = 'w-180';
|
|
28
|
+
export const MIN_NODE_WIDTH = 180;
|
|
29
|
+
export const MAX_NODE_WIDTH = 560;
|
|
30
|
+
export const MIN_NODE_HEIGHT = 80;
|
|
31
|
+
export const MAX_NODE_HEIGHT = 480;
|
|
32
|
+
/** Matches React Flow selected edge stroke (`.react-flow__edge.selected .react-flow__edge-path`). */
|
|
33
|
+
export const SELECTED_EDGE_STROKE = '#555';
|
|
34
|
+
export const EDGE_ARROW_MARKER = {
|
|
35
|
+
type: MarkerType.ArrowClosed,
|
|
36
|
+
width: 16,
|
|
37
|
+
height: 16,
|
|
38
|
+
};
|
|
39
|
+
export const TARGET_HANDLE_ID = 'in';
|
|
40
|
+
export const CHOICE_HANDLE_PREFIX = 'choice:';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type EditorTree, type EditorTransition } from '@signalsafe/tree-spec-editor-core';
|
|
2
|
+
import type { Edge } from 'reactflow';
|
|
3
|
+
export declare function edgeLabelForTransition(tree: EditorTree, t: EditorTransition): string;
|
|
4
|
+
export declare function buildEdgesFromTransitions(tree: EditorTree): Edge[];
|
|
5
|
+
export declare function buildTransitionsFromEdges(edges: Edge[], existing: EditorTransition[]): EditorTransition[];
|
|
6
|
+
//# sourceMappingURL=edgeBuilders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edgeBuilders.d.ts","sourceRoot":"","sources":["../../src/canvas/edgeBuilders.ts"],"names":[],"mappings":"AACA,OAAO,EAOH,KAAK,UAAU,EACf,KAAK,gBAAgB,EACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQtC,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,gBAAgB,GAAG,MAAM,CASpF;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,EAAE,CA0BlE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CAgBzG"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { TERMINAL_OUTCOME } from '@signalsafe/tree-spec';
|
|
2
|
+
import { END_NODE_ID, choiceIdFromHandle, resolveDefaultEdgeType, resolveEffectiveEdgeType, resolveEdgeStrokeColor, shouldShowEdgeLabel, } from '@signalsafe/tree-spec-editor-core';
|
|
3
|
+
import { CHOICE_HANDLE_PREFIX, TARGET_HANDLE_ID, } from './constants.js';
|
|
4
|
+
import { buildEdgeMarker, buildEdgeStyle, resolveEdgePathStroke } from './edgeStyle.js';
|
|
5
|
+
export function edgeLabelForTransition(tree, t) {
|
|
6
|
+
const node = tree.nodes[t.fromNodeId];
|
|
7
|
+
const choice = node?.choices?.find((c) => c.id === t.fromChoiceId);
|
|
8
|
+
const base = choice?.label || t.fromChoiceId;
|
|
9
|
+
if (t.toNodeId === END_NODE_ID) {
|
|
10
|
+
const oc = t.outcome ?? TERMINAL_OUTCOME.AT_RISK;
|
|
11
|
+
return `${base} → END (${oc})`;
|
|
12
|
+
}
|
|
13
|
+
return base;
|
|
14
|
+
}
|
|
15
|
+
export function buildEdgesFromTransitions(tree) {
|
|
16
|
+
return tree.transitions.map((t) => {
|
|
17
|
+
const node = tree.nodes[t.fromNodeId];
|
|
18
|
+
const choice = node?.choices?.find((c) => c.id === t.fromChoiceId);
|
|
19
|
+
const showLabel = choice ? shouldShowEdgeLabel(choice) : true;
|
|
20
|
+
const strokeColor = choice ? resolveEdgeStrokeColor(choice) : undefined;
|
|
21
|
+
const edgeType = choice
|
|
22
|
+
? resolveEffectiveEdgeType(choice, tree._meta)
|
|
23
|
+
: resolveDefaultEdgeType(tree._meta);
|
|
24
|
+
const style = buildEdgeStyle(strokeColor);
|
|
25
|
+
const markerStroke = resolveEdgePathStroke(style);
|
|
26
|
+
return {
|
|
27
|
+
id: t.id,
|
|
28
|
+
source: t.fromNodeId,
|
|
29
|
+
target: t.toNodeId,
|
|
30
|
+
sourceHandle: `${CHOICE_HANDLE_PREFIX}${t.fromChoiceId}`,
|
|
31
|
+
targetHandle: TARGET_HANDLE_ID,
|
|
32
|
+
reconnectable: 'target',
|
|
33
|
+
type: edgeType,
|
|
34
|
+
label: showLabel ? edgeLabelForTransition(tree, t) : undefined,
|
|
35
|
+
labelShowBg: true,
|
|
36
|
+
markerEnd: buildEdgeMarker(markerStroke),
|
|
37
|
+
style,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function buildTransitionsFromEdges(edges, existing) {
|
|
42
|
+
const existingById = new Map(existing.map((t) => [t.id, t]));
|
|
43
|
+
return edges
|
|
44
|
+
.filter((e) => e.source && e.target && e.source !== END_NODE_ID)
|
|
45
|
+
.map((e) => {
|
|
46
|
+
const fromChoiceId = choiceIdFromHandle(e.sourceHandle);
|
|
47
|
+
const prior = existingById.get(String(e.id));
|
|
48
|
+
return {
|
|
49
|
+
id: String(e.id),
|
|
50
|
+
fromNodeId: String(e.source),
|
|
51
|
+
fromChoiceId: fromChoiceId,
|
|
52
|
+
toNodeId: String(e.target),
|
|
53
|
+
outcome: String(e.target) === END_NODE_ID ? (prior?.outcome ?? TERMINAL_OUTCOME.AT_RISK) : undefined,
|
|
54
|
+
};
|
|
55
|
+
})
|
|
56
|
+
.filter((t) => t.fromChoiceId.length > 0);
|
|
57
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Edge } from 'reactflow';
|
|
2
|
+
export declare function getPromptNodeBorderClass(hasErrors: boolean, warningCount: number): string;
|
|
3
|
+
export declare function getIssueEdgeStyle(style: Edge['style'], hasIssue: boolean): Edge['style'];
|
|
4
|
+
export declare function resolveEdgePathStroke(style: Edge['style'] | undefined): string;
|
|
5
|
+
export declare function buildEdgeMarker(stroke: string): {
|
|
6
|
+
color: string;
|
|
7
|
+
type: import("reactflow").MarkerType;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
};
|
|
11
|
+
export declare function buildEdgeStyle(strokeColor: string | undefined): Edge['style'];
|
|
12
|
+
export declare function resolveSelectedEdgeStroke(style: Edge['style'], isEdgeSelected: boolean): {
|
|
13
|
+
style: Edge['style'];
|
|
14
|
+
stroke: string;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=edgeStyle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edgeStyle.d.ts","sourceRoot":"","sources":["../../src/canvas/edgeStyle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAItC,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAQzF;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CASxF;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,MAAM,CAK9E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM;;;;;EAK7C;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,CAE7E;AAED,wBAAgB,yBAAyB,CACrC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,EACpB,cAAc,EAAE,OAAO,GACxB;IAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAO1C"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DEFAULT_CANVAS_EDGE_STROKE } from '@signalsafe/tree-spec-editor-core';
|
|
2
|
+
import { BORDER_DANGER_CLASS, BORDER_WARNING_CLASS, EDGE_ARROW_MARKER, SELECTED_EDGE_STROKE } from './constants.js';
|
|
3
|
+
export function getPromptNodeBorderClass(hasErrors, warningCount) {
|
|
4
|
+
if (hasErrors) {
|
|
5
|
+
return BORDER_DANGER_CLASS;
|
|
6
|
+
}
|
|
7
|
+
if (warningCount > 0) {
|
|
8
|
+
return BORDER_WARNING_CLASS;
|
|
9
|
+
}
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
export function getIssueEdgeStyle(style, hasIssue) {
|
|
13
|
+
if (!hasIssue) {
|
|
14
|
+
return style;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
...style,
|
|
18
|
+
strokeWidth: 2,
|
|
19
|
+
strokeDasharray: '6 4',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function resolveEdgePathStroke(style) {
|
|
23
|
+
if (style?.stroke && typeof style.stroke === 'string') {
|
|
24
|
+
return style.stroke;
|
|
25
|
+
}
|
|
26
|
+
return DEFAULT_CANVAS_EDGE_STROKE;
|
|
27
|
+
}
|
|
28
|
+
export function buildEdgeMarker(stroke) {
|
|
29
|
+
return {
|
|
30
|
+
...EDGE_ARROW_MARKER,
|
|
31
|
+
color: stroke,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function buildEdgeStyle(strokeColor) {
|
|
35
|
+
return strokeColor ? { stroke: strokeColor, strokeWidth: 2 } : { strokeWidth: 2 };
|
|
36
|
+
}
|
|
37
|
+
export function resolveSelectedEdgeStroke(style, isEdgeSelected) {
|
|
38
|
+
let nextStyle = style;
|
|
39
|
+
const stroke = isEdgeSelected ? SELECTED_EDGE_STROKE : resolveEdgePathStroke(nextStyle);
|
|
40
|
+
if (isEdgeSelected) {
|
|
41
|
+
nextStyle = { ...nextStyle, stroke: SELECTED_EDGE_STROKE };
|
|
42
|
+
}
|
|
43
|
+
return { style: nextStyle, stroke };
|
|
44
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type GraphSelection } from '@signalsafe/tree-spec-editor-core';
|
|
2
|
+
export declare function resolveCanvasFocusChoiceId(nodeId: string, selected: GraphSelection | undefined, focusNodeId: string | null | undefined, focusChoiceId: string | null | undefined): string | null;
|
|
3
|
+
//# sourceMappingURL=focusChoice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focusChoice.d.ts","sourceRoot":"","sources":["../../src/canvas/focusChoice.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAE9F,wBAAgB,0BAA0B,CACtC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACtC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACzC,MAAM,GAAG,IAAI,CASf"}
|