@kiberon-labs/behave-graph-flow 2.0.0 → 3.0.0

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.
Files changed (251) hide show
  1. package/.storybook/manager.ts +6 -0
  2. package/.storybook/preview.ts +49 -1
  3. package/.storybook/styles.css +9 -3
  4. package/.turbo/turbo-build.log +1 -1
  5. package/CHANGELOG.md +368 -0
  6. package/dist/AnyControlImpl-Ds-CShIB.js +20 -0
  7. package/dist/AnyControlImpl-Ds-CShIB.js.map +1 -0
  8. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js +166 -0
  9. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js.map +1 -0
  10. package/dist/index.css +36 -33
  11. package/dist/index.css.map +1 -1
  12. package/dist/index.d.ts +1865 -550
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +14357 -11221
  15. package/dist/index.js.map +1 -1
  16. package/dist/noteImpl-KkrrWgJd.js +242 -0
  17. package/dist/noteImpl-KkrrWgJd.js.map +1 -0
  18. package/dist/styles.module-CvmpDkZj.css +3 -0
  19. package/dist/styles.module-CvmpDkZj.css.map +1 -0
  20. package/dist/styles.module-DZxg8aW9.js +271 -0
  21. package/dist/styles.module-DZxg8aW9.js.map +1 -0
  22. package/dist/useChangeNodeData-ChQGK7AI.js +23 -0
  23. package/dist/useChangeNodeData-ChQGK7AI.js.map +1 -0
  24. package/docs/protocol.md +43 -20
  25. package/package.json +5 -9
  26. package/src/components/FloatingToolbar/index.module.css +5 -13
  27. package/src/components/FloatingToolbar/index.tsx +9 -9
  28. package/src/components/Flow.tsx +34 -23
  29. package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
  30. package/src/components/contextMenus/NodePicker.module.css +13 -13
  31. package/src/components/contextMenus/edge.tsx +9 -95
  32. package/src/components/contextMenus/node.tsx +9 -149
  33. package/src/components/contextMenus/selection.tsx +5 -71
  34. package/src/components/controls/any/AnyControlImpl.tsx +14 -0
  35. package/src/components/controls/any/index.tsx +13 -2
  36. package/src/components/edges/index.tsx +75 -69
  37. package/src/components/layoutController/index.module.css +3 -0
  38. package/src/components/layoutController/index.tsx +24 -1
  39. package/src/components/layoutController/utils.ts +46 -3
  40. package/src/components/menubar/defaults.tsx +55 -19
  41. package/src/components/menubar/menuItem.module.css +18 -3
  42. package/src/components/menubar/menuItem.tsx +34 -1
  43. package/src/components/nodes/behave/NodeContainer.module.css +26 -25
  44. package/src/components/nodes/group/index.tsx +3 -3
  45. package/src/components/nodes/wrapper/styles.module.css +6 -32
  46. package/src/components/panels/alignment/index.module.css +0 -10
  47. package/src/components/panels/alignment/index.tsx +4 -4
  48. package/src/components/panels/base/styles.module.css +2 -2
  49. package/src/components/panels/common/PanelHeader.module.css +24 -0
  50. package/src/components/panels/common/PanelHeader.tsx +22 -0
  51. package/src/components/panels/common/SectionTitle.module.css +13 -0
  52. package/src/components/panels/common/SectionTitle.tsx +10 -0
  53. package/src/components/panels/events/EditEventPanel.tsx +14 -5
  54. package/src/components/panels/events/ManageEventsPanel.tsx +11 -8
  55. package/src/components/panels/events/styles.module.css +6 -64
  56. package/src/components/panels/graphProperties/index.tsx +125 -0
  57. package/src/components/panels/history/index.tsx +2 -2
  58. package/src/components/panels/history/styles.module.css +0 -9
  59. package/src/components/panels/keymaps/index.module.css +3 -13
  60. package/src/components/panels/keymaps/index.tsx +1 -2
  61. package/src/components/panels/layers/index.tsx +20 -15
  62. package/src/components/panels/layers/styles.module.css +9 -12
  63. package/src/components/panels/legend/index.tsx +1 -1
  64. package/src/components/panels/logs/index.module.css +25 -19
  65. package/src/components/panels/logs/index.tsx +7 -7
  66. package/src/components/panels/nodeInputs/InputsGroup.tsx +1 -0
  67. package/src/components/panels/nodeInputs/NodeSettings.tsx +2 -2
  68. package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +1 -1
  69. package/src/components/panels/nodeInputs/OutputsGroup.tsx +2 -12
  70. package/src/components/panels/nodeInputs/index.module.css +99 -75
  71. package/src/components/panels/nodeInputs/index.tsx +21 -11
  72. package/src/components/panels/nodeInputs/useNodeHandlers.ts +2 -2
  73. package/src/components/panels/nodeInputs/useNodeInputsData.ts +23 -43
  74. package/src/components/panels/nodePicker/index.tsx +8 -8
  75. package/src/components/panels/panel/index.module.css +7 -7
  76. package/src/components/panels/search/index.module.css +0 -50
  77. package/src/components/panels/search/index.tsx +2 -2
  78. package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
  79. package/src/components/panels/systemSettings/index.tsx +221 -176
  80. package/src/components/panels/systemSettings/styles.module.css +135 -8
  81. package/src/components/panels/traces/GridLines.tsx +1 -1
  82. package/src/components/panels/traces/TimeGrid.tsx +3 -3
  83. package/src/components/panels/traces/TraceLane.tsx +1 -1
  84. package/src/components/panels/traces/index.module.css +1 -8
  85. package/src/components/panels/traces/index.tsx +8 -4
  86. package/src/components/panels/traces/useDerivedSpans.ts +241 -146
  87. package/src/components/panels/traces/utils.ts +8 -0
  88. package/src/components/panels/variables/CreateVariableScreen.tsx +3 -3
  89. package/src/components/panels/variables/ManageVariablesScreen.tsx +12 -9
  90. package/src/components/panels/variables/index.tsx +2 -2
  91. package/src/components/panels/variables/styles.module.css +4 -91
  92. package/src/components/primitives/icon.module.css +4 -4
  93. package/src/components/sockets/input/index.tsx +9 -2
  94. package/src/components/sockets/input/styles.module.css +2 -3
  95. package/src/components/sockets/output/index.tsx +10 -3
  96. package/src/components/sockets/output/styles.module.css +1 -6
  97. package/src/css/notes.css +135 -0
  98. package/src/css/prosemirror.css +3 -3
  99. package/src/css/rc-dock.css +143 -43
  100. package/src/css/rc-menu.css +56 -55
  101. package/src/css/themes/kiberon.css +127 -0
  102. package/src/css/vars.css +197 -13
  103. package/src/css/vscode-elements.css +124 -0
  104. package/src/generators/CallSubgraphGenerator.tsx +136 -0
  105. package/src/generators/CustomEventOnTriggeredGenerator.tsx +2 -2
  106. package/src/generators/GraphBoundaryGenerator.module.css +32 -0
  107. package/src/generators/GraphBoundaryGenerator.tsx +193 -0
  108. package/src/generators/SequenceGenerator.tsx +2 -2
  109. package/src/generators/SwitchOnIntegerGenerator.tsx +2 -2
  110. package/src/generators/SwitchOnStringGenerator.tsx +2 -2
  111. package/src/generators/callSubgraphSync.ts +126 -0
  112. package/src/generators/registerDefaultGenerators.ts +21 -0
  113. package/src/generators/registerDefaults.ts +26 -0
  114. package/src/hooks/useBehaveGraphFlow.ts +2 -2
  115. package/src/hooks/useFlowHandlers.ts +47 -9
  116. package/src/hooks/useWasdPan.ts +26 -4
  117. package/src/index.css +4 -16
  118. package/src/index.ts +17 -0
  119. package/src/manifest/contributionRegistry.ts +93 -0
  120. package/src/manifest/index.ts +4 -0
  121. package/src/manifest/loadManifest.ts +82 -0
  122. package/src/manifest/manifestPlugin.ts +29 -0
  123. package/src/manifest/passthroughValueType.ts +40 -0
  124. package/src/plugin/alignment/index.ts +22 -12
  125. package/src/plugin/autosave/controller.ts +366 -0
  126. package/src/plugin/autosave/index.tsx +114 -0
  127. package/src/plugin/autosave/panel/BackupPanel.tsx +141 -0
  128. package/src/plugin/autosave/panel/index.tsx +1 -0
  129. package/src/plugin/autosave/panel/styles.module.css +56 -0
  130. package/src/plugin/autosave/settings.ts +65 -0
  131. package/src/plugin/autosave/storage.ts +147 -0
  132. package/src/plugin/docs/index.tsx +2 -4
  133. package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
  134. package/src/plugin/docs/panel/index.tsx +15 -194
  135. package/src/plugin/docs/panel/styles.module.css +8 -8
  136. package/src/plugin/graphrunner/actions.ts +258 -185
  137. package/src/plugin/graphrunner/buttons.tsx +34 -26
  138. package/src/plugin/graphrunner/client.ts +4 -1
  139. package/src/plugin/graphrunner/index.tsx +29 -100
  140. package/src/plugin/graphrunner/panel.tsx +2 -2
  141. package/src/plugin/graphrunner/runController.ts +283 -0
  142. package/src/plugin/graphrunner/runner.ts +21 -192
  143. package/src/plugin/graphrunner/store.ts +14 -24
  144. package/src/plugin/graphrunner/styles.module.css +17 -57
  145. package/src/plugin/graphrunner/transport.ts +26 -0
  146. package/src/plugin/graphrunner/types.ts +21 -0
  147. package/src/plugin/graphrunner-local/execution-utils.ts +260 -80
  148. package/src/plugin/graphrunner-local/index.tsx +8 -2
  149. package/src/plugin/graphrunner-local/panel.tsx +131 -175
  150. package/src/plugin/graphrunner-local/styles.module.css +57 -76
  151. package/src/plugin/graphrunner-local/transport.ts +151 -184
  152. package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +2 -0
  153. package/src/plugin/graphrunner-webworker/index.tsx +4 -10
  154. package/src/plugin/graphrunner-webworker/store.ts +9 -0
  155. package/src/plugin/kitchen-sink/index.ts +38 -0
  156. package/src/{layout/dagre.tsx → plugin/layout/dagre.ts} +17 -5
  157. package/src/{layout → plugin/layout}/elk.ts +22 -6
  158. package/src/plugin/layout/index.ts +80 -0
  159. package/src/plugin/notes/FormatToolbar.tsx +200 -0
  160. package/src/plugin/notes/index.tsx +191 -0
  161. package/src/plugin/notes/nodeActions.ts +100 -0
  162. package/src/plugin/notes/note.tsx +20 -0
  163. package/src/plugin/notes/noteImpl.tsx +89 -0
  164. package/src/plugin/realtime/realtimeRunner.ts +58 -4
  165. package/src/specifics/CustomEventOnTriggeredSpecific.tsx +2 -2
  166. package/src/specifics/CustomEventTriggerSpecific.tsx +2 -2
  167. package/src/specifics/VariableGetSpecific.tsx +2 -2
  168. package/src/specifics/VariableSetSpecific.tsx +2 -2
  169. package/src/store/actions.tsx +5 -5
  170. package/src/store/commands.ts +278 -0
  171. package/src/store/contextMenu.ts +192 -0
  172. package/src/store/conversions.ts +47 -0
  173. package/src/store/flow.tsx +23 -38
  174. package/src/store/graphMeta.ts +39 -0
  175. package/src/store/hotKeys.tsx +301 -260
  176. package/src/store/layers.ts +3 -3
  177. package/src/store/registry.ts +12 -4
  178. package/src/store/selection.ts +3 -3
  179. package/src/store/settings.ts +82 -82
  180. package/src/store/settingsSchema.ts +210 -0
  181. package/src/store/tabs.ts +5 -1
  182. package/src/store/traces.ts +3 -3
  183. package/src/system/graph.ts +11 -14
  184. package/src/system/graphSession.ts +172 -0
  185. package/src/system/index.ts +3 -0
  186. package/src/system/notifications.ts +13 -0
  187. package/src/system/persistence.ts +82 -0
  188. package/src/system/plugin.ts +28 -0
  189. package/src/system/provider.tsx +64 -0
  190. package/src/system/system.ts +518 -88
  191. package/src/system/tabLoader.tsx +70 -32
  192. package/src/system/undoRedo.ts +1 -1
  193. package/src/transformers/Uigraph.ts +5 -4
  194. package/src/transformers/contract.ts +87 -0
  195. package/src/transformers/flowToBehave.ts +13 -5
  196. package/src/types/nodes.ts +8 -3
  197. package/src/types.ts +2 -0
  198. package/src/util/autoConvert.ts +200 -0
  199. package/src/util/isValidConnection.ts +23 -2
  200. package/stories/defaults/defaultStoryProvider.tsx +17 -14
  201. package/stories/defaults/systemGenerator.ts +6 -1
  202. package/stories/{components/nodes/comment.stories.tsx → plugins/notes.stories.tsx} +24 -30
  203. package/tests/autoConvert.test.ts +329 -0
  204. package/tests/autosavePlugin.test.ts +204 -0
  205. package/tests/callSubgraphSync.test.ts +148 -0
  206. package/tests/commandRegistry.test.ts +137 -0
  207. package/tests/contract.test.ts +51 -0
  208. package/tests/contractSerialize.test.ts +62 -0
  209. package/tests/deriveSpans.test.ts +71 -0
  210. package/tests/flowToBehave.test.ts +2 -1
  211. package/tests/hotkeys.test.ts +79 -0
  212. package/tests/keepAliveLifecycle.test.ts +167 -0
  213. package/tests/loadManifest.test.ts +113 -0
  214. package/tests/noteMarkdown.test.ts +65 -0
  215. package/tests/notesPlugin.test.ts +162 -0
  216. package/tests/persistence.test.ts +51 -0
  217. package/tests/saveLoad.test.ts +7 -6
  218. package/tests/settings.test.ts +178 -0
  219. package/tests/traceStore.test.ts +46 -0
  220. package/tests/visual/README.md +2 -2
  221. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
  222. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
  223. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
  224. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
  225. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
  226. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
  227. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-localGraphRunner-chromium-win32.png +0 -0
  228. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
  229. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
  230. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
  231. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
  232. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
  233. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
  234. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
  235. package/tests/visual/panels.visual.test.tsx +3 -3
  236. package/tests/wasdPan.test.ts +71 -0
  237. package/vitest.config.ts +1 -1
  238. package/vitest.visual.config.ts +7 -0
  239. package/.storybook/vscode.css +0 -814
  240. package/src/components/nodes/comment/FormatToolbar.tsx +0 -118
  241. package/src/components/nodes/comment/comment.tsx +0 -103
  242. package/src/components/nodes/comment/styles.module.css +0 -150
  243. package/src/components/panels/conversation/index.module.css +0 -151
  244. package/src/components/panels/conversation/index.tsx +0 -162
  245. package/src/components/panels/events/CustomEventsEditor.tsx +0 -384
  246. package/src/css/vscode.css +0 -13
  247. package/src/hooks/useDetachNodes.ts +0 -39
  248. package/src/plugin/graphrunner-webworker/types.ts +0 -17
  249. package/src/specifics/registerDefaultSpecifics.ts +0 -5
  250. package/src/store/chat.ts +0 -73
  251. package/src/store/graphRunnerClient.ts +0 -110
@@ -1,155 +1,15 @@
1
- import { useReactFlow } from 'reactflow';
2
1
  import type { CSSProperties } from 'react';
3
- import { useCallback } from 'react';
4
- import clsx from 'classnames';
5
- import {
6
- VscodeContextMenu,
7
- VscodeContextMenuItem,
8
- VscodeDivider
9
- } from '@vscode-elements/react-elements';
10
- import { useSystem } from '@/system';
11
- import { hidden, pinned } from '@/annotations';
2
+ import { DynamicContextMenu } from './DynamicContextMenu';
12
3
 
13
4
  export interface INodeContextMenuProps extends CSSProperties {
14
5
  nodeID: string;
15
6
  }
16
7
 
17
- export const NodeContextMenu = ({ nodeID, ...rest }: INodeContextMenuProps) => {
18
- const reactFlowInstance = useReactFlow();
19
- const sys = useSystem();
20
-
21
- // Get the current node to check if it's hidden
22
- const currentNode = sys.nodeStore
23
- .getState()
24
- .nodes.find((n) => n.id === nodeID);
25
- const isHidden =
26
- currentNode && 'data' in currentNode
27
- ? (currentNode.data.annotations?.[hidden] ?? false)
28
- : false;
29
- const isPinned =
30
- currentNode && 'data' in currentNode
31
- ? (currentNode.data.annotations?.[pinned] ?? false)
32
- : false;
33
-
34
- const focus = useCallback(() => {
35
- const nodeSearch = sys.nodeStore.getState().nodes;
36
- const reactFlowInstance = sys.refStore.getState().getRef('reactflow');
37
- const nodes = nodeID
38
- ? nodeSearch.filter((x) => x.id === nodeID)
39
- : nodeSearch.filter((x) => x.selected);
40
- if (nodes) {
41
- const focalCenter = nodes.reduce(
42
- (acc, node) => {
43
- return {
44
- x: acc.x + node.position.x + (node.width || 0) / 2,
45
- y: acc.y + node.position.y + (node.height || 0) / 2
46
- };
47
- },
48
- { x: 0, y: 0 }
49
- );
50
-
51
- reactFlowInstance?.setCenter(focalCenter.x, focalCenter.y, {
52
- duration: 200,
53
- zoom: 1
54
- });
55
- }
56
- }, [nodeID]);
57
-
58
- const onResetTrace = useCallback(() => {
59
- reactFlowInstance.setNodes((nodes) =>
60
- nodes.map((x) => {
61
- //Remove filtering
62
- return {
63
- ...x,
64
- className: clsx(x.className, {
65
- filtered: false
66
- })
67
- };
68
- })
69
- );
70
- }, [reactFlowInstance]);
71
-
72
- const onSelect = useCallback(
73
- (e: any) => {
74
- switch (e.detail.value) {
75
- case 'traceUpstream':
76
- sys.actionStore.getState().actions.traceUpstream(nodeID);
77
- break;
78
- case 'traceDownstream':
79
- sys.actionStore.getState().actions.traceDownstream(nodeID);
80
- break;
81
- case 'resetTrace':
82
- sys.actionStore.getState().actions.resetTrace();
83
- break;
84
-
85
- case 'focus':
86
- focus();
87
- break;
88
-
89
- case 'pin':
90
- sys.actionStore.getState().actions.toggleNodePinned(nodeID);
91
- break;
92
- case 'hide':
93
- sys.actionStore.getState().actions.toggleNodeHidden(nodeID);
94
- break;
95
- }
96
- },
97
- [focus, nodeID, sys]
98
- );
99
-
100
- return (
101
- <VscodeContextMenu
102
- show
103
- onVscContextMenuSelect={onSelect}
104
- style={{ zIndex: 2000, position: 'absolute', ...rest }}
105
- data={[
106
- {
107
- label: 'Focus',
108
- keybinding: 'Ctrl+Shift+F',
109
- value: 'focus'
110
- },
111
- {
112
- separator: true
113
- },
114
- {
115
- label: 'Trace Upstream',
116
- keybinding: 'Ctrl+Shift+R',
117
- value: 'traceUpstream'
118
- },
119
- {
120
- label: 'Trace Downstream',
121
- keybinding: 'Ctrl+Shift+T',
122
- value: 'traceDownstream'
123
- },
124
- {
125
- separator: true
126
- },
127
- {
128
- label: 'Reset Trace',
129
- keybinding: 'Ctrl+Shift+G',
130
- value: 'resetTrace'
131
- },
132
-
133
- {
134
- separator: true
135
- },
136
- {
137
- label: isPinned ? 'Unpin' : 'Pin',
138
- value: 'pin'
139
- },
140
- {
141
- label: isHidden ? 'Show' : 'Hide',
142
- value: 'hide'
143
- }
144
- ]}
145
- >
146
- <VscodeContextMenuItem onClick={focus}>Focus</VscodeContextMenuItem>
147
- <VscodeDivider />
148
-
149
- <VscodeContextMenuItem onClick={onResetTrace}>
150
- Reset Trace
151
- </VscodeContextMenuItem>
152
- <VscodeDivider />
153
- </VscodeContextMenu>
154
- );
155
- };
8
+ /**
9
+ * Node context menu. Items come from the editor's contextMenu registry (target
10
+ * 'node') and dispatch through the command registry , see store/contextMenu.ts
11
+ * and store/commands.ts to add or override entries.
12
+ */
13
+ export const NodeContextMenu = ({ nodeID, ...rest }: INodeContextMenuProps) => (
14
+ <DynamicContextMenu target="node" context={{ nodeId: nodeID }} style={rest} />
15
+ );
@@ -1,77 +1,11 @@
1
1
  import type { CSSProperties } from 'react';
2
- import { useCallback } from 'react';
3
- import {
4
- VscodeContextMenu,
5
- VscodeContextMenuItem,
6
- VscodeDivider
7
- } from '@vscode-elements/react-elements';
8
- import { useSystem } from '@/system';
2
+ import { DynamicContextMenu } from './DynamicContextMenu';
9
3
 
10
4
  export interface ISelectionContextMenuProps extends CSSProperties {}
11
5
 
6
+ /** Selection context menu , items from the contextMenu registry ('selection'). */
12
7
  export const SelectionContextMenu = ({
13
8
  ...rest
14
- }: ISelectionContextMenuProps) => {
15
- const sys = useSystem();
16
-
17
- const onCopy = useCallback(() => {
18
- sys.actionStore.getState().actions.copySelectionToClipboard();
19
- }, [sys]);
20
-
21
- const onPaste = useCallback(async () => {
22
- await sys.actionStore.getState().actions.pasteFromClipboard();
23
- }, [sys]);
24
-
25
- const onGroup = useCallback(() => {
26
- sys.actionStore.getState().actions.groupNodes();
27
- }, [sys]);
28
-
29
- const onSelect = useCallback(
30
- (e: any) => {
31
- switch (e.detail.value) {
32
- case 'copy':
33
- onCopy();
34
- break;
35
- case 'paste':
36
- void onPaste();
37
- break;
38
- case 'group':
39
- onGroup();
40
- break;
41
- }
42
- },
43
- [onCopy, onGroup, onPaste]
44
- );
45
-
46
- return (
47
- <VscodeContextMenu
48
- show
49
- onVscContextMenuSelect={onSelect}
50
- style={{ zIndex: 2000, position: 'absolute', ...rest }}
51
- data={[
52
- {
53
- label: 'Copy',
54
- keybinding: 'Ctrl+C',
55
- value: 'copy'
56
- },
57
- {
58
- label: 'Paste',
59
- keybinding: 'Ctrl+V',
60
- value: 'paste'
61
- },
62
- {
63
- separator: true
64
- },
65
- {
66
- label: 'Group',
67
- value: 'group'
68
- }
69
- ]}
70
- >
71
- <VscodeContextMenuItem onClick={onCopy}>Copy</VscodeContextMenuItem>
72
- <VscodeContextMenuItem onClick={onPaste}>Paste</VscodeContextMenuItem>
73
- <VscodeDivider />
74
- <VscodeContextMenuItem onClick={onGroup}>Group</VscodeContextMenuItem>
75
- </VscodeContextMenu>
76
- );
77
- };
9
+ }: ISelectionContextMenuProps) => (
10
+ <DynamicContextMenu target="selection" context={{}} style={rest} />
11
+ );
@@ -0,0 +1,14 @@
1
+ import type { ControlProps } from '@/store/controls';
2
+ import { JsonEditor } from 'json-edit-react';
3
+
4
+ /**
5
+ * The actual `any`-typed value editor. Split into its own module so the heavy
6
+ * `json-edit-react` dependency (~50 kB) is loaded lazily by [`AnyControl`](./index.tsx)
7
+ * only when an `any` control is first rendered, instead of sitting in the
8
+ * editor's first-paint bundle.
9
+ */
10
+ export const AnyControlImpl = (props: ControlProps) => {
11
+ return (
12
+ <JsonEditor data={props.value} setData={(data) => props.onChange(data)} />
13
+ );
14
+ };
@@ -1,8 +1,19 @@
1
+ import { lazy, Suspense } from 'react';
1
2
  import type { ControlProps } from '@/store/controls';
2
- import { JsonEditor } from 'json-edit-react';
3
+
4
+ // `json-edit-react` is a sizeable dependency and this is the *default* control
5
+ // (used whenever a socket has no specific control), so eager-importing it puts
6
+ // it on the editor's first-paint critical path. Load it lazily instead: the
7
+ // node still renders immediately, and the JSON editor swaps in once its chunk
8
+ // arrives.
9
+ const LazyAnyControl = lazy(() =>
10
+ import('./AnyControlImpl').then((m) => ({ default: m.AnyControlImpl }))
11
+ );
3
12
 
4
13
  export const AnyControl = (props: ControlProps) => {
5
14
  return (
6
- <JsonEditor data={props.value} setData={(data) => props.onChange(data)} />
15
+ <Suspense fallback={null}>
16
+ <LazyAnyControl {...props} />
17
+ </Suspense>
7
18
  );
8
19
  };
@@ -17,6 +17,74 @@ interface ControlPoint {
17
17
  y: number;
18
18
  }
19
19
 
20
+ /** The reactflow path builder that backs a given edge type when there are no
21
+ * control points. Unknown types fall back to the better-bezier builder. */
22
+ function edgeFnForType(edgeType: string) {
23
+ switch (edgeType) {
24
+ case EDGE_TYPE.simpleBezier:
25
+ return getSimpleBezierPath;
26
+ case EDGE_TYPE.smoothStep:
27
+ return getSmoothStepPath;
28
+ case EDGE_TYPE.straight:
29
+ return getStraightPath;
30
+ case EDGE_TYPE.bezier:
31
+ default:
32
+ return getBetterBezierPath;
33
+ }
34
+ }
35
+
36
+ /** Straight polyline through every point. */
37
+ function straightPathThrough(points: ControlPoint[]): string {
38
+ return points
39
+ .map((p, i) => (i === 0 ? `M ${p.x},${p.y}` : `L ${p.x},${p.y}`))
40
+ .join(' ');
41
+ }
42
+
43
+ /** Orthogonal "smooth step" path that jogs at the midpoint of each segment. */
44
+ function smoothStepPathThrough(points: ControlPoint[]): string {
45
+ let path = `M ${points[0]?.x},${points[0]?.y}`;
46
+ for (let i = 0; i < points.length - 1; i++) {
47
+ const current = points[i];
48
+ const next = points[i + 1];
49
+ if (!current || !next) continue;
50
+ const midX = (current.x + next.x) / 2;
51
+ path += ` L ${midX},${current.y} L ${midX},${next.y}`;
52
+ }
53
+ path += ` L ${points[points.length - 1]?.x},${points[points.length - 1]?.y}`;
54
+ return path;
55
+ }
56
+
57
+ /** Curved path through every point, degrading to line/quadratic for 2/3 points. */
58
+ function bezierPathThrough(points: ControlPoint[]): string {
59
+ if (points.length === 2) {
60
+ return `M ${points[0]?.x},${points[0]?.y} L ${points[1]?.x},${points[1]?.y}`;
61
+ }
62
+ if (points.length === 3) {
63
+ return `M ${points[0]?.x},${points[0]?.y} Q ${points[1]?.x},${points[1]?.y} ${points[2]?.x},${points[2]?.y}`;
64
+ }
65
+
66
+ // For multiple control points, chain quadratic segments and finish with a line.
67
+ let path = `M ${points[0]?.x},${points[0]?.y}`;
68
+ for (let i = 0; i < points.length - 1; i++) {
69
+ const p1 = points[i + 1];
70
+ if (!p1) continue;
71
+
72
+ if (i === points.length - 2) {
73
+ path += ` L ${p1.x},${p1.y}`;
74
+ continue;
75
+ }
76
+
77
+ path += ` Q ${p1.x},${p1.y}`;
78
+ const p2 = points[i + 2];
79
+ if (p2) {
80
+ const midX = (p1.x + p2.x) / 2;
81
+ const midY = (p1.y + p2.y) / 2;
82
+ path += ` ${midX},${midY}`;
83
+ }
84
+ }
85
+ return path;
86
+ }
87
+
20
88
  // Generate path for different edge types with control points
21
89
  function generatePathWithControlPoints(
22
90
  sourceX: number,
@@ -29,26 +97,8 @@ function generatePathWithControlPoints(
29
97
  _targetPosition?: Position
30
98
  ): string {
31
99
  if (controlPoints.length === 0) {
32
- // No control points, use default edge type
33
- let edgeFn;
34
- switch (edgeType) {
35
- case EDGE_TYPE.bezier:
36
- edgeFn = getBetterBezierPath;
37
- break;
38
- case EDGE_TYPE.simpleBezier:
39
- edgeFn = getSimpleBezierPath;
40
- break;
41
- case EDGE_TYPE.smoothStep:
42
- edgeFn = getSmoothStepPath;
43
- break;
44
- case EDGE_TYPE.straight:
45
- edgeFn = getStraightPath;
46
- break;
47
- default:
48
- edgeFn = getBetterBezierPath;
49
- }
50
-
51
- const [path] = edgeFn({
100
+ // No control points, use the default builder for the edge type.
101
+ const [path] = edgeFnForType(edgeType)({
52
102
  sourceX,
53
103
  sourceY,
54
104
  sourcePosition: _sourcePosition,
@@ -67,56 +117,12 @@ function generatePathWithControlPoints(
67
117
  ];
68
118
 
69
119
  if (edgeType === EDGE_TYPE.straight) {
70
- // Straight lines through all points
71
- const pathParts = allPoints.map((p, i) =>
72
- i === 0 ? `M ${p.x},${p.y}` : `L ${p.x},${p.y}`
73
- );
74
- return pathParts.join(' ');
75
- } else if (edgeType === EDGE_TYPE.smoothStep) {
76
- // Smooth step style through control points
77
- let path = `M ${allPoints[0]?.x},${allPoints[0]?.y}`;
78
- for (let i = 0; i < allPoints.length - 1; i++) {
79
- const current = allPoints[i];
80
- const next = allPoints[i + 1];
81
- if (!current || !next) continue;
82
- const midX = (current.x + next.x) / 2;
83
- // Create smooth step
84
- path += ` L ${midX},${current.y} L ${midX},${next.y}`;
85
- }
86
- path += ` L ${allPoints[allPoints.length - 1]?.x},${allPoints[allPoints.length - 1]?.y}`;
87
- return path;
88
- } else {
89
- // Bezier curve through all points
90
- if (allPoints.length === 2) {
91
- return `M ${allPoints[0]?.x},${allPoints[0]?.y} L ${allPoints[1]?.x},${allPoints[1]?.y}`;
92
- } else if (allPoints.length === 3) {
93
- return `M ${allPoints[0]?.x},${allPoints[0]?.y} Q ${allPoints[1]?.x},${allPoints[1]?.y} ${allPoints[2]?.x},${allPoints[2]?.y}`;
94
- } else {
95
- // For multiple control points, use cubic bezier segments
96
- let path = `M ${allPoints[0]?.x},${allPoints[0]?.y}`;
97
- for (let i = 0; i < allPoints.length - 1; i++) {
98
- const p1 = allPoints[i + 1];
99
- if (!p1) continue;
100
-
101
- if (i === allPoints.length - 2) {
102
- // Last segment
103
- path += ` L ${p1.x},${p1.y}`;
104
- } else {
105
- // Smooth curve using quadratic bezier
106
- path += ` Q ${p1.x},${p1.y}`;
107
- if (i < allPoints.length - 2) {
108
- const p2 = allPoints[i + 2];
109
- if (p2) {
110
- const midX = (p1.x + p2.x) / 2;
111
- const midY = (p1.y + p2.y) / 2;
112
- path += ` ${midX},${midY}`;
113
- }
114
- }
115
- }
116
- }
117
- return path;
118
- }
120
+ return straightPathThrough(allPoints);
121
+ }
122
+ if (edgeType === EDGE_TYPE.smoothStep) {
123
+ return smoothStepPathThrough(allPoints);
119
124
  }
125
+ return bezierPathThrough(allPoints);
120
126
  }
121
127
 
122
128
  type CustomEdgeProps = EdgeProps & {
@@ -1,5 +1,8 @@
1
1
  .panelExtra {
2
2
  display: flex;
3
+ align-items: center;
4
+ gap: 4px;
5
+ padding: 0 4px;
3
6
  }
4
7
 
5
8
  .root {
@@ -6,7 +6,12 @@ import { Reduce, Maximize, Xmark } from 'iconoir-react';
6
6
  import { useSystem } from '@/system/provider.js';
7
7
  import { MenuBar } from '../menubar';
8
8
  import { useStore } from 'zustand';
9
- import { findGraphPanel } from './utils';
9
+ import {
10
+ findGraphPanel,
11
+ collectGraphSessionIds,
12
+ isGraphTabId,
13
+ sessionIdFromTabId
14
+ } from './utils';
10
15
 
11
16
  import styles from './index.module.css';
12
17
  import { NotificationProvider } from '../notifications';
@@ -97,7 +102,25 @@ export const LayoutController = (props: {}) => {
97
102
  if (graphContainer?.activeId) {
98
103
  //Get the active Id to find the currently selected graph
99
104
  setCurrentPanel(graphContainer.activeId!);
105
+
106
+ // Keep the editor's active graph in sync with the focused graph tab so
107
+ // panels bound via useActiveGraph() rebind to it.
108
+ if (isGraphTabId(graphContainer.activeId)) {
109
+ system.activeGraph
110
+ .getState()
111
+ .setActiveGraph(sessionIdFromTabId(graphContainer.activeId));
112
+ }
113
+ }
114
+
115
+ // Dispose sessions whose tabs were closed in this layout change.
116
+ const before = collectGraphSessionIds(layout);
117
+ const after = collectGraphSessionIds(newLayout);
118
+ for (const id of before) {
119
+ if (!after.has(id)) {
120
+ system.disposeSession(id);
121
+ }
100
122
  }
123
+
101
124
  setLayout(newLayout);
102
125
  };
103
126
 
@@ -1,8 +1,51 @@
1
1
  import type { BoxBase, LayoutBase, PanelBase, TabData } from 'rc-dock';
2
2
 
3
- export function recurseFindGraphPanel(
4
- base: BoxBase | PanelBase
5
- ): PanelBase | null {
3
+ /**
4
+ * Tab-id convention for graph tabs. The default graph uses the bare id `graph`
5
+ * (session id `graph`); every other graph uses `graph:<sessionId>`. These three
6
+ * helpers are the single source of truth for the convention.
7
+ */
8
+ const GRAPH_TAB_PREFIX = 'graph:';
9
+ export const DEFAULT_GRAPH_ID = 'graph';
10
+
11
+ export function isGraphTabId(id: string | undefined): id is string {
12
+ return !!id && (id === DEFAULT_GRAPH_ID || id.startsWith(GRAPH_TAB_PREFIX));
13
+ }
14
+
15
+ export function sessionIdFromTabId(tabId: string): string {
16
+ return tabId === DEFAULT_GRAPH_ID
17
+ ? DEFAULT_GRAPH_ID
18
+ : tabId.slice(GRAPH_TAB_PREFIX.length);
19
+ }
20
+
21
+ export function tabIdForSession(sessionId: string): string {
22
+ return sessionId === DEFAULT_GRAPH_ID
23
+ ? DEFAULT_GRAPH_ID
24
+ : `${GRAPH_TAB_PREFIX}${sessionId}`;
25
+ }
26
+
27
+ /** Collect the session ids of every graph tab present in a layout. */
28
+ export function collectGraphSessionIds(layout: LayoutBase): Set<string> {
29
+ const ids = new Set<string>();
30
+ const visit = (base?: BoxBase | PanelBase) => {
31
+ if (!base) return;
32
+ const panel = base as PanelBase;
33
+ if (panel.tabs) {
34
+ for (const tab of panel.tabs) {
35
+ if (isGraphTabId(tab.id)) ids.add(sessionIdFromTabId(tab.id!));
36
+ }
37
+ }
38
+ const box = base as BoxBase;
39
+ if (box.children) box.children.forEach(visit);
40
+ };
41
+ visit(layout.dockbox);
42
+ visit(layout.floatbox);
43
+ visit(layout.maxbox);
44
+ visit(layout.windowbox);
45
+ return ids;
46
+ }
47
+
48
+ function recurseFindGraphPanel(base: BoxBase | PanelBase): PanelBase | null {
6
49
  if (base.id === 'graphs') {
7
50
  return base as PanelBase;
8
51
  }