@kiberon-labs/behave-graph-flow 1.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.
- package/.fallowrc.json +16 -0
- package/.storybook/main.ts +32 -0
- package/.storybook/manager.ts +6 -0
- package/.storybook/preview.ts +64 -0
- package/.storybook/styles.css +16 -0
- package/.turbo/turbo-build.log +7 -0
- package/CHANGELOG.md +368 -0
- package/LICENSE +6 -0
- package/README.md +2 -2
- package/data/Polynomial.json +510 -0
- package/data/sequence.json +337 -0
- package/data/trigger-event.json +241 -0
- package/data/variable-change.json +210 -0
- package/dist/AnyControlImpl-Ds-CShIB.js +20 -0
- package/dist/AnyControlImpl-Ds-CShIB.js.map +1 -0
- package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js +166 -0
- package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js.map +1 -0
- package/dist/entry.css +4 -0
- package/dist/index.css +42 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +3597 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18009 -0
- package/dist/index.js.map +1 -0
- package/dist/noteImpl-KkrrWgJd.js +242 -0
- package/dist/noteImpl-KkrrWgJd.js.map +1 -0
- package/dist/styles.module-CvmpDkZj.css +3 -0
- package/dist/styles.module-CvmpDkZj.css.map +1 -0
- package/dist/styles.module-DZxg8aW9.js +271 -0
- package/dist/styles.module-DZxg8aW9.js.map +1 -0
- package/dist/useChangeNodeData-ChQGK7AI.js +23 -0
- package/dist/useChangeNodeData-ChQGK7AI.js.map +1 -0
- package/docs/notifications.md +246 -0
- package/docs/protocol.md +702 -0
- package/docs/specifics.md +191 -0
- package/package.json +82 -22
- package/postcss.config.ts +3 -4
- package/src/annotations/index.ts +32 -0
- package/src/components/FloatingToolbar/index.module.css +37 -0
- package/src/components/FloatingToolbar/index.tsx +256 -0
- package/src/components/Flow.tsx +287 -75
- package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
- package/src/components/contextMenus/NodePicker.module.css +274 -0
- package/src/components/contextMenus/NodePicker.tsx +481 -0
- package/src/components/contextMenus/edge.tsx +22 -0
- package/src/components/contextMenus/node.tsx +15 -0
- package/src/components/contextMenus/selection.tsx +11 -0
- package/src/components/controls/any/AnyControlImpl.tsx +14 -0
- package/src/components/controls/any/index.tsx +19 -0
- package/src/components/controls/boolean/index.tsx +13 -0
- package/src/components/controls/colorPicker/InputPopover.module.css +100 -0
- package/src/components/controls/colorPicker/InputPopover.tsx +31 -0
- package/src/components/controls/colorPicker/index.module.css +18 -0
- package/src/components/controls/colorPicker/index.tsx +61 -0
- package/src/components/controls/number/index.tsx +35 -0
- package/src/components/controls/string/index.tsx +16 -0
- package/src/components/edges/index.tsx +475 -0
- package/src/components/edges/offsetBezier.ts +134 -0
- package/src/components/hotKeys.tsx +20 -0
- package/src/components/layoutController/index.module.css +13 -0
- package/src/components/layoutController/index.tsx +140 -0
- package/src/components/layoutController/utils.ts +248 -0
- package/src/components/menubar/defaults.tsx +516 -0
- package/src/components/menubar/index.tsx +49 -0
- package/src/components/menubar/menuItem.module.css +31 -0
- package/src/components/menubar/menuItem.tsx +65 -0
- package/src/components/nodes/behave/Node.module.css +23 -0
- package/src/components/nodes/behave/Node.tsx +176 -0
- package/src/components/nodes/behave/NodeContainer.module.css +88 -0
- package/src/components/nodes/behave/NodeContainer.tsx +46 -0
- package/src/components/nodes/behave/index.tsx +14 -0
- package/src/components/nodes/group/index.tsx +109 -0
- package/src/components/nodes/wrapper/index.tsx +73 -0
- package/src/components/nodes/wrapper/styles.module.css +87 -0
- package/src/components/notifications/NotificationProvider.tsx +81 -0
- package/src/components/notifications/index.ts +2 -0
- package/src/components/notifications/utils.ts +71 -0
- package/src/components/panels/alignment/index.module.css +10 -0
- package/src/components/panels/alignment/index.tsx +244 -0
- package/src/components/panels/base/index.tsx +5 -0
- package/src/components/panels/base/styles.module.css +12 -0
- package/src/components/panels/common/PanelHeader.module.css +24 -0
- package/src/components/panels/common/PanelHeader.tsx +22 -0
- package/src/components/panels/common/SectionTitle.module.css +13 -0
- package/src/components/panels/common/SectionTitle.tsx +10 -0
- package/src/components/panels/events/EditEventPanel.tsx +324 -0
- package/src/components/panels/events/ManageEventsPanel.tsx +101 -0
- package/src/components/panels/events/index.tsx +23 -0
- package/src/components/panels/events/styles.module.css +178 -0
- package/src/components/panels/graphProperties/index.tsx +125 -0
- package/src/components/panels/history/index.tsx +92 -0
- package/src/components/panels/history/styles.module.css +97 -0
- package/src/components/panels/keymaps/index.module.css +68 -0
- package/src/components/panels/keymaps/index.tsx +166 -0
- package/src/components/panels/layers/index.tsx +245 -0
- package/src/components/panels/layers/styles.module.css +107 -0
- package/src/components/panels/legend/index.module.css +6 -0
- package/src/components/panels/legend/index.tsx +76 -0
- package/src/components/panels/logs/index.module.css +218 -0
- package/src/components/panels/logs/index.tsx +288 -0
- package/src/components/panels/nodeInputs/InputControl.tsx +63 -0
- package/src/components/panels/nodeInputs/InputsGroup.tsx +65 -0
- package/src/components/panels/nodeInputs/MultipleNodesView.tsx +37 -0
- package/src/components/panels/nodeInputs/NodeSettings.tsx +92 -0
- package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +125 -0
- package/src/components/panels/nodeInputs/OutputsGroup.tsx +55 -0
- package/src/components/panels/nodeInputs/SocketGenerators.tsx +32 -0
- package/src/components/panels/nodeInputs/index.module.css +308 -0
- package/src/components/panels/nodeInputs/index.tsx +349 -0
- package/src/components/panels/nodeInputs/useNodeHandlers.ts +76 -0
- package/src/components/panels/nodeInputs/useNodeInputsData.ts +153 -0
- package/src/components/panels/nodePicker/index.tsx +115 -0
- package/src/components/panels/panel/index.module.css +66 -0
- package/src/components/panels/panel/index.tsx +88 -0
- package/src/components/panels/search/index.module.css +16 -0
- package/src/components/panels/search/index.tsx +215 -0
- package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
- package/src/components/panels/systemSettings/index.tsx +251 -0
- package/src/components/panels/systemSettings/styles.module.css +138 -0
- package/src/components/panels/traces/GridLines.tsx +38 -0
- package/src/components/panels/traces/TimeGrid.tsx +48 -0
- package/src/components/panels/traces/TraceLane.tsx +62 -0
- package/src/components/panels/traces/TraceTooltip.tsx +22 -0
- package/src/components/panels/traces/TracesHeader.tsx +56 -0
- package/src/components/panels/traces/index.module.css +159 -0
- package/src/components/panels/traces/index.tsx +298 -0
- package/src/components/panels/traces/types.ts +48 -0
- package/src/components/panels/traces/useDerivedSpans.ts +307 -0
- package/src/components/panels/traces/utils.ts +33 -0
- package/src/components/panels/variables/CreateVariableScreen.tsx +162 -0
- package/src/components/panels/variables/ManageVariablesScreen.tsx +147 -0
- package/src/components/panels/variables/index.tsx +125 -0
- package/src/components/panels/variables/styles.module.css +149 -0
- package/src/components/primitives/icon.module.css +45 -0
- package/src/components/primitives/icon.tsx +38 -0
- package/src/components/sockets/input/index.tsx +83 -0
- package/src/components/sockets/input/styles.module.css +26 -0
- package/src/components/sockets/output/index.tsx +68 -0
- package/src/components/sockets/output/styles.module.css +22 -0
- package/src/css/notes.css +135 -0
- package/src/css/prosemirror.css +57 -0
- package/src/css/rc-dock.css +212 -0
- package/src/css/rc-menu.css +101 -0
- package/src/css/themes/kiberon.css +127 -0
- package/src/css/vars.css +198 -0
- package/src/css/vscode-elements.css +124 -0
- package/src/entry.css +4 -0
- package/src/generators/CallSubgraphGenerator.tsx +136 -0
- package/src/generators/CustomEventOnTriggeredGenerator.tsx +85 -0
- package/src/generators/GraphBoundaryGenerator.module.css +32 -0
- package/src/generators/GraphBoundaryGenerator.tsx +193 -0
- package/src/generators/SequenceGenerator.tsx +104 -0
- package/src/generators/SwitchOnIntegerGenerator.tsx +256 -0
- package/src/generators/SwitchOnStringGenerator.tsx +263 -0
- package/src/generators/callSubgraphSync.ts +126 -0
- package/src/generators/registerDefaultGenerators.ts +55 -0
- package/src/generators/registerDefaults.ts +26 -0
- package/src/hooks/useBehaveGraphFlow.ts +17 -16
- package/src/hooks/useFlowHandlers.ts +154 -30
- package/src/hooks/useWasdPan.ts +210 -0
- package/src/index.css +134 -0
- package/src/index.ts +53 -18
- package/src/manifest/contributionRegistry.ts +93 -0
- package/src/manifest/index.ts +4 -0
- package/src/manifest/loadManifest.ts +82 -0
- package/src/manifest/manifestPlugin.ts +29 -0
- package/src/manifest/passthroughValueType.ts +40 -0
- package/src/plugin/alignment/index.ts +91 -0
- package/src/plugin/autosave/controller.ts +366 -0
- package/src/plugin/autosave/index.tsx +114 -0
- package/src/plugin/autosave/panel/BackupPanel.tsx +141 -0
- package/src/plugin/autosave/panel/index.tsx +1 -0
- package/src/plugin/autosave/panel/styles.module.css +56 -0
- package/src/plugin/autosave/settings.ts +65 -0
- package/src/plugin/autosave/storage.ts +147 -0
- package/src/plugin/docs/index.tsx +297 -0
- package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
- package/src/plugin/docs/panel/index.tsx +21 -0
- package/src/plugin/docs/panel/styles.module.css +174 -0
- package/src/plugin/graphrunner/actions.ts +326 -0
- package/src/plugin/graphrunner/buttons.tsx +95 -0
- package/src/plugin/graphrunner/client.ts +707 -0
- package/src/plugin/graphrunner/index.tsx +184 -0
- package/src/plugin/graphrunner/panel.tsx +386 -0
- package/src/plugin/graphrunner/runController.ts +283 -0
- package/src/plugin/graphrunner/runner.ts +187 -0
- package/src/plugin/graphrunner/session.ts +243 -0
- package/src/plugin/graphrunner/store.ts +196 -0
- package/src/plugin/graphrunner/styles.module.css +171 -0
- package/src/plugin/graphrunner/transport.ts +250 -0
- package/src/plugin/graphrunner/types.ts +693 -0
- package/src/plugin/graphrunner-local/execution-utils.ts +637 -0
- package/src/plugin/graphrunner-local/index.tsx +172 -0
- package/src/plugin/graphrunner-local/panel.tsx +187 -0
- package/src/plugin/graphrunner-local/store.ts +41 -0
- package/src/plugin/graphrunner-local/styles.module.css +82 -0
- package/src/plugin/graphrunner-local/transport.ts +1339 -0
- package/src/plugin/graphrunner-local/types.ts +10 -0
- package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +635 -0
- package/src/plugin/graphrunner-webworker/index.tsx +140 -0
- package/src/plugin/graphrunner-webworker/panel.tsx +173 -0
- package/src/plugin/graphrunner-webworker/store.ts +98 -0
- package/src/plugin/graphrunner-webworker/worker-transport.ts +123 -0
- package/src/plugin/kitchen-sink/index.ts +38 -0
- package/src/plugin/layout/dagre.ts +131 -0
- package/src/plugin/layout/elk.ts +216 -0
- package/src/plugin/layout/index.ts +80 -0
- package/src/plugin/notes/FormatToolbar.tsx +200 -0
- package/src/plugin/notes/index.tsx +191 -0
- package/src/plugin/notes/nodeActions.ts +100 -0
- package/src/plugin/notes/note.tsx +20 -0
- package/src/plugin/notes/noteImpl.tsx +89 -0
- package/src/plugin/realtime/realtimeRunner.ts +624 -0
- package/src/specifics/CustomEventOnTriggeredSpecific.tsx +92 -0
- package/src/specifics/CustomEventTriggerSpecific.tsx +141 -0
- package/src/specifics/VariableGetSpecific.tsx +110 -0
- package/src/specifics/VariableSetSpecific.tsx +110 -0
- package/src/store/actions.tsx +698 -0
- package/src/store/commands.ts +278 -0
- package/src/store/contextMenu.ts +192 -0
- package/src/store/controls.tsx +62 -0
- package/src/store/conversions.ts +47 -0
- package/src/store/documentation.tsx +69 -0
- package/src/store/events.tsx +116 -0
- package/src/store/flow.tsx +230 -0
- package/src/store/graphMeta.ts +39 -0
- package/src/store/hotKeys.tsx +364 -0
- package/src/store/layers.ts +259 -0
- package/src/store/legend.tsx +76 -0
- package/src/store/logs.ts +28 -0
- package/src/store/menubar.ts +41 -0
- package/src/store/refs.ts +84 -0
- package/src/store/registry.ts +51 -0
- package/src/store/selection.ts +22 -0
- package/src/store/settings.ts +99 -0
- package/src/store/settingsSchema.ts +210 -0
- package/src/store/socketGenerator.tsx +54 -0
- package/src/store/specific.tsx +75 -0
- package/src/store/specs.tsx +35 -0
- package/src/store/tabs.ts +282 -0
- package/src/store/toolbar.tsx +45 -0
- package/src/store/traces.ts +240 -0
- package/src/store/variables.ts +37 -0
- package/src/system/graph.ts +131 -0
- package/src/system/graphSession.ts +172 -0
- package/src/system/index.ts +6 -0
- package/src/system/notifications.ts +111 -0
- package/src/system/persistence.ts +82 -0
- package/src/system/plugin.ts +55 -0
- package/src/system/provider.tsx +86 -0
- package/src/system/pubsub.ts +323 -0
- package/src/system/system.ts +653 -0
- package/src/system/tabLoader.tsx +303 -0
- package/src/system/undoRedo.ts +103 -0
- package/src/transformers/Uigraph.ts +61 -0
- package/src/transformers/behaveToFlow.ts +16 -4
- package/src/transformers/contract.ts +87 -0
- package/src/transformers/flowToBehave.ts +40 -12
- package/src/types/NodeMetadata.ts +27 -0
- package/src/types/graph.ts +49 -0
- package/src/types/nodes.ts +50 -0
- package/src/types.ts +18 -0
- package/src/util/autoConvert.ts +200 -0
- package/src/util/colors.ts +1 -29
- package/src/util/downloadJson.ts +18 -0
- package/src/util/extractNodeMetadata.ts +16 -0
- package/src/util/getPickerFilters.ts +1 -1
- package/src/util/isBehaveNode.ts +6 -0
- package/src/util/isValidConnection.ts +51 -17
- package/src/util/mergeSockets.ts +29 -0
- package/src/util/serializeVariables.ts +66 -0
- package/src/util/sockets.ts +43 -0
- package/stories/apex/layoutController/example-graph.worker.ts +39 -0
- package/stories/apex/layoutController/index.stories.tsx +48 -0
- package/stories/apex/layoutController/webworker.stories.tsx +103 -0
- package/stories/apex/menubar/menubar.stories.tsx +19 -0
- package/stories/components/colorpicker/index.stories.tsx +20 -0
- package/stories/components/contextMenus/edge.stories.tsx +32 -0
- package/stories/components/contextMenus/node.stories.tsx +26 -0
- package/stories/components/contextMenus/nodePicker.stories.tsx +115 -0
- package/stories/components/controls/any/index.stories.tsx +19 -0
- package/stories/components/controls/boolean/index.stories.tsx +19 -0
- package/stories/components/controls/colorPicker/index.stories.tsx +49 -0
- package/stories/components/controls/number/index.stories.tsx +19 -0
- package/stories/components/controls/string/index.stories.tsx +19 -0
- package/stories/components/nodes/behaveNode.stories.tsx +108 -0
- package/stories/components/panels/alignment.stories.tsx +24 -0
- package/stories/components/panels/events.stories.tsx +38 -0
- package/stories/components/panels/graphRunner.stories.tsx +317 -0
- package/stories/components/panels/history.stories.tsx +37 -0
- package/stories/components/panels/keymaps.stories.tsx +21 -0
- package/stories/components/panels/legend.stories.tsx +37 -0
- package/stories/components/panels/logs.stories.tsx +24 -0
- package/stories/components/panels/nodeInputs.stories.tsx +21 -0
- package/stories/components/panels/nodePicker.stories.tsx +37 -0
- package/stories/components/panels/panel.stories.tsx +39 -0
- package/stories/components/panels/search.stories.tsx +24 -0
- package/stories/components/panels/systemSettings.stories.tsx +26 -0
- package/stories/components/panels/traces.stories.tsx +225 -0
- package/stories/components/panels/variables.stories.tsx +24 -0
- package/stories/defaults/defaultStoryProvider.tsx +170 -0
- package/stories/defaults/systemGenerator.ts +43 -0
- package/stories/plugins/notes.stories.tsx +100 -0
- package/tests/autoConvert.test.ts +329 -0
- package/tests/autosavePlugin.test.ts +204 -0
- package/tests/callSubgraphSync.test.ts +148 -0
- package/tests/commandRegistry.test.ts +137 -0
- package/tests/components/edges/offsetBezier.test.ts +51 -0
- package/tests/components/layoutController/utils.test.ts +68 -0
- package/tests/components/panels/traces/utils.test.ts +52 -0
- package/tests/contract.test.ts +51 -0
- package/tests/contractSerialize.test.ts +62 -0
- package/tests/deriveSpans.test.ts +71 -0
- package/tests/flowToBehave.test.ts +27 -4
- package/tests/hotkeys.test.ts +79 -0
- package/tests/keepAliveLifecycle.test.ts +167 -0
- package/tests/loadManifest.test.ts +113 -0
- package/tests/noteMarkdown.test.ts +65 -0
- package/tests/notesPlugin.test.ts +162 -0
- package/tests/notifications.test.ts +87 -0
- package/tests/persistence.test.ts +51 -0
- package/tests/saveLoad.test.ts +373 -0
- package/tests/settings.test.ts +178 -0
- package/tests/traceStore.test.ts +46 -0
- package/tests/util/calculateNewEdge.test.ts +98 -0
- package/tests/util/getSocketsByNodeTypeAndHandleType.test.ts +31 -0
- package/tests/util/hasPositionMetaData.test.ts +33 -0
- package/tests/util/isBehaveNode.test.ts +22 -0
- package/tests/util/isHandleConnected.test.ts +37 -0
- package/tests/util/mergeSockets.test.ts +43 -0
- package/tests/visual/README.md +64 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-alignment-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-localGraphRunner-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-traces-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
- package/tests/visual/panels.visual.test.tsx +76 -0
- package/tests/wasdPan.test.ts +71 -0
- package/tsconfig.base.json +39 -0
- package/tsconfig.json +18 -59
- package/tsconfig.prod.json +23 -0
- package/tsdown.config.ts +15 -3
- package/typedoc.json +7 -7
- package/vite.config.js +7 -0
- package/vitest.config.ts +5 -2
- package/vitest.visual.config.ts +55 -0
- package/src/components/AutoSizeInput.tsx +0 -65
- package/src/components/Controls.tsx +0 -87
- package/src/components/InputSocket.tsx +0 -142
- package/src/components/Node.tsx +0 -68
- package/src/components/NodeContainer.tsx +0 -46
- package/src/components/NodePicker.tsx +0 -77
- package/src/components/OutputSocket.tsx +0 -58
- package/src/components/modals/ClearModal.tsx +0 -40
- package/src/components/modals/HelpModal.tsx +0 -36
- package/src/components/modals/LoadModal.tsx +0 -96
- package/src/components/modals/Modal.tsx +0 -64
- package/src/components/modals/SaveModal.tsx +0 -60
- package/src/hooks/useCustomNodeTypes.tsx +0 -31
- package/src/hooks/useGraphRunner.ts +0 -104
- package/src/hooks/useMergeMap.ts +0 -14
- package/src/hooks/useNodeSpecJson.ts +0 -20
- package/src/hooks/useQueriableDefinitions.ts +0 -22
- package/src/styles.css +0 -8
- package/tailwind.config.ts +0 -19
- package/tests/tsconfig.json +0 -10
- /package/src/{types.d.ts → types-declarations.d.ts} +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { System } from '@/system';
|
|
2
|
+
import type {
|
|
3
|
+
ElkExtendedEdge,
|
|
4
|
+
ElkNode,
|
|
5
|
+
ElkPort
|
|
6
|
+
} from 'elkjs/lib/elk.bundled.js';
|
|
7
|
+
import type { Edge, Node } from 'reactflow';
|
|
8
|
+
import { pinned } from '@/annotations';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* elkjs is ~1.4 MB — by far the largest dependency the editor can pull in, yet
|
|
12
|
+
* it is only used when the user explicitly runs an ELK layout. Load it lazily (a
|
|
13
|
+
* dynamic import the bundler code-splits into a separate chunk) so it stays out
|
|
14
|
+
* of the initial load even for consumers of this layout plugin. The instance is
|
|
15
|
+
* created once and reused.
|
|
16
|
+
*/
|
|
17
|
+
let elkPromise:
|
|
18
|
+
| Promise<{ layout: (graph: ElkNode) => Promise<ElkNode> }>
|
|
19
|
+
| undefined;
|
|
20
|
+
const getElk = () => {
|
|
21
|
+
elkPromise ??= import('elkjs/lib/elk.bundled.js').then(
|
|
22
|
+
(m) => new m.default()
|
|
23
|
+
);
|
|
24
|
+
return elkPromise;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const layoutOptions = {
|
|
28
|
+
// 'elk.algorithm': 'layered',
|
|
29
|
+
'elk.direction': 'RIGHT',
|
|
30
|
+
'elk.layered.spacing.edgeNodeBetweenLayers': '40',
|
|
31
|
+
'elk.spacing.nodeNode': '40',
|
|
32
|
+
'elk.layered.nodePlacement.strategy': 'SIMPLE'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type LayoutAlgorithm =
|
|
36
|
+
| 'org.eclipse.elk.layered'
|
|
37
|
+
| 'org.eclipse.elk.force'
|
|
38
|
+
| 'org.eclipse.elk.rectpacking';
|
|
39
|
+
|
|
40
|
+
const getLayoutedNodes = async (
|
|
41
|
+
nodes: Node[],
|
|
42
|
+
edges: Edge[],
|
|
43
|
+
algorithm: LayoutAlgorithm
|
|
44
|
+
) => {
|
|
45
|
+
// Filter out pinned nodes, group nodes, and nodes inside groups
|
|
46
|
+
const layoutNodes = nodes.filter((node) => {
|
|
47
|
+
const isPinned = 'data' in node && node.data.annotations?.[pinned];
|
|
48
|
+
const isInGroup = !!node.parentId;
|
|
49
|
+
const isGroup = node.type === 'group';
|
|
50
|
+
return !isPinned && !isInGroup && !isGroup;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
//Convert the edges to a lookup map
|
|
54
|
+
const edgeOut = new Map<string, Edge[]>();
|
|
55
|
+
const edgeIn = new Map<string, Edge[]>();
|
|
56
|
+
|
|
57
|
+
edges.forEach((edge) => {
|
|
58
|
+
if (!edgeOut.has(edge.source)) {
|
|
59
|
+
edgeOut.set(edge.source, []);
|
|
60
|
+
}
|
|
61
|
+
edgeOut.get(edge.source)!.push(edge);
|
|
62
|
+
|
|
63
|
+
if (!edgeIn.has(edge.target)) {
|
|
64
|
+
edgeIn.set(edge.target, []);
|
|
65
|
+
}
|
|
66
|
+
edgeIn.get(edge.target)!.push(edge);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const graph: ElkNode = {
|
|
70
|
+
id: 'root',
|
|
71
|
+
layoutOptions: { 'elk.algorithm': algorithm, ...layoutOptions },
|
|
72
|
+
children: layoutNodes.map((n) => {
|
|
73
|
+
//lookup the edges for this node
|
|
74
|
+
const outgoingEdges = edgeIn.get(n.id) || [];
|
|
75
|
+
const incomingEdges = edgeOut.get(n.id) || [];
|
|
76
|
+
|
|
77
|
+
// we need unique ids for the handles (called 'ports' in elkjs) for the layouting
|
|
78
|
+
// an id is structured like: nodeId-source/target-id
|
|
79
|
+
|
|
80
|
+
const targetPorts = outgoingEdges.map((e) => {
|
|
81
|
+
return {
|
|
82
|
+
id: e.sourceHandle as string,
|
|
83
|
+
width: 10,
|
|
84
|
+
height: 10,
|
|
85
|
+
properties: {
|
|
86
|
+
side: 'WEST'
|
|
87
|
+
}
|
|
88
|
+
} as ElkPort;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const sourcePorts = incomingEdges.map(
|
|
92
|
+
(e) =>
|
|
93
|
+
({
|
|
94
|
+
id: e.targetHandle as string,
|
|
95
|
+
width: 10,
|
|
96
|
+
height: 10,
|
|
97
|
+
properties: {
|
|
98
|
+
side: 'EAST'
|
|
99
|
+
}
|
|
100
|
+
}) as ElkPort
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
id: n.id,
|
|
105
|
+
width: n.width ?? 150,
|
|
106
|
+
height: n.height ?? 50,
|
|
107
|
+
// ⚠️ we need to tell elk that the ports are fixed, in order to reduce edge crossings
|
|
108
|
+
properties: {
|
|
109
|
+
'org.eclipse.elk.portConstraints': 'FIXED_ORDER'
|
|
110
|
+
},
|
|
111
|
+
// we are also passing the id, so we can also handle edges without a sourceHandle or targetHandle option
|
|
112
|
+
ports: [...targetPorts, ...sourcePorts]
|
|
113
|
+
};
|
|
114
|
+
}),
|
|
115
|
+
edges: edges.map(
|
|
116
|
+
(e) =>
|
|
117
|
+
({
|
|
118
|
+
id: e.id,
|
|
119
|
+
sources: [e.sourceHandle || e.source],
|
|
120
|
+
targets: [e.targetHandle || e.target]
|
|
121
|
+
}) as ElkExtendedEdge
|
|
122
|
+
)
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const elk = await getElk();
|
|
126
|
+
const layoutedGraph = await elk.layout(graph);
|
|
127
|
+
|
|
128
|
+
const layoutedNodes = nodes.map((node) => {
|
|
129
|
+
// Skip pinned nodes - keep their current position
|
|
130
|
+
const isPinned = 'data' in node && node.data.annotations?.[pinned];
|
|
131
|
+
if (isPinned) {
|
|
132
|
+
return node;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Skip group nodes for now - we'll position them based on children
|
|
136
|
+
if (node.type === 'group') {
|
|
137
|
+
return node;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Skip nodes inside groups - they maintain relative positions
|
|
141
|
+
if (node.parentId) {
|
|
142
|
+
return node;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const layoutedNode = layoutedGraph.children?.find(
|
|
146
|
+
(lgNode) => lgNode.id === node.id
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
...node,
|
|
151
|
+
position: {
|
|
152
|
+
x: layoutedNode?.x ?? 0,
|
|
153
|
+
y: layoutedNode?.y ?? 0
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Update group positions and sizes based on their children
|
|
159
|
+
const padding = 25;
|
|
160
|
+
return layoutedNodes.map((node) => {
|
|
161
|
+
if (node.type !== 'group') return node;
|
|
162
|
+
|
|
163
|
+
const children = layoutedNodes.filter((n) => n.parentId === node.id);
|
|
164
|
+
if (children.length === 0) return node;
|
|
165
|
+
|
|
166
|
+
// Calculate bounding box of children (in absolute coordinates)
|
|
167
|
+
let minX = Infinity;
|
|
168
|
+
let minY = Infinity;
|
|
169
|
+
let maxX = -Infinity;
|
|
170
|
+
let maxY = -Infinity;
|
|
171
|
+
|
|
172
|
+
children.forEach((child) => {
|
|
173
|
+
const childX = child.positionAbsolute?.x ?? child.position.x;
|
|
174
|
+
const childY = child.positionAbsolute?.y ?? child.position.y;
|
|
175
|
+
const childWidth = child.width ?? 150;
|
|
176
|
+
const childHeight = child.height ?? 50;
|
|
177
|
+
|
|
178
|
+
minX = Math.min(minX, childX);
|
|
179
|
+
minY = Math.min(minY, childY);
|
|
180
|
+
maxX = Math.max(maxX, childX + childWidth);
|
|
181
|
+
maxY = Math.max(maxY, childY + childHeight);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Position group with padding around children
|
|
185
|
+
return {
|
|
186
|
+
...node,
|
|
187
|
+
position: {
|
|
188
|
+
x: minX - padding,
|
|
189
|
+
y: minY - padding
|
|
190
|
+
},
|
|
191
|
+
style: {
|
|
192
|
+
...node.style,
|
|
193
|
+
width: maxX - minX + padding * 2,
|
|
194
|
+
height: maxY - minY + padding * 2
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const applyElkLayout = async (
|
|
201
|
+
sys: System,
|
|
202
|
+
algorithm: LayoutAlgorithm
|
|
203
|
+
) => {
|
|
204
|
+
const nodeStore = sys.nodeStore.getState();
|
|
205
|
+
const nodes = Object.values(nodeStore.nodes);
|
|
206
|
+
const edges = Object.values(sys.edgeStore.getState().edges);
|
|
207
|
+
const reactflow = sys.refStore.getState().getRef('reactflow');
|
|
208
|
+
|
|
209
|
+
if (!reactflow) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const layoutedNodes = await getLayoutedNodes(nodes, edges, algorithm);
|
|
214
|
+
|
|
215
|
+
nodeStore.setNodes(layoutedNodes);
|
|
216
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { plugin } from '@/system/plugin';
|
|
2
|
+
import type { System } from '@/system/system';
|
|
3
|
+
import { applyDagreLayout } from './dagre';
|
|
4
|
+
import { applyElkLayout } from './elk';
|
|
5
|
+
|
|
6
|
+
export * from './dagre';
|
|
7
|
+
export * from './elk';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Available auto-layout engines. `Dagre` is a small, synchronous layered layout;
|
|
11
|
+
* the `Elk - *` options use elkjs (~1.4 MB, loaded lazily on first use) for
|
|
12
|
+
* higher-quality layouts.
|
|
13
|
+
*/
|
|
14
|
+
export const LAYOUT_TYPE = {
|
|
15
|
+
dagre: 'Dagre',
|
|
16
|
+
elkForce: 'Elk - Force',
|
|
17
|
+
elkRect: 'Elk - Rect',
|
|
18
|
+
elkLayered: 'Elk - Layered'
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export type LayoutType = (typeof LAYOUT_TYPE)[keyof typeof LAYOUT_TYPE];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run the currently-selected auto-layout engine against the focused graph.
|
|
25
|
+
* Reads the `layoutType` setting (registered by this plugin) to pick the engine.
|
|
26
|
+
*/
|
|
27
|
+
export const applyAutoLayout = (system: System): void => {
|
|
28
|
+
switch (system.getSetting<LayoutType>('layoutType')) {
|
|
29
|
+
case LAYOUT_TYPE.dagre:
|
|
30
|
+
void applyDagreLayout(system);
|
|
31
|
+
break;
|
|
32
|
+
case LAYOUT_TYPE.elkLayered:
|
|
33
|
+
void applyElkLayout(system, 'org.eclipse.elk.layered');
|
|
34
|
+
break;
|
|
35
|
+
case LAYOUT_TYPE.elkForce:
|
|
36
|
+
void applyElkLayout(system, 'org.eclipse.elk.force');
|
|
37
|
+
break;
|
|
38
|
+
case LAYOUT_TYPE.elkRect:
|
|
39
|
+
void applyElkLayout(system, 'org.eclipse.elk.rectpacking');
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Adds graph auto-layout (Dagre + ELK) to the editor. elkjs and dagre are heavy
|
|
46
|
+
* dependencies that not every host needs, so they live here rather than in the
|
|
47
|
+
* core editor — register this plugin (directly or via the kitchen-sink plugin)
|
|
48
|
+
* to opt in.
|
|
49
|
+
*
|
|
50
|
+
* The plugin:
|
|
51
|
+
* - registers the `layoutType` setting (the engine picker in the Settings panel);
|
|
52
|
+
* - registers the `editor.autoLayout` command that the "Auto Layout" hotkey and
|
|
53
|
+
* menu dispatch to.
|
|
54
|
+
*
|
|
55
|
+
* Without it, `editor.autoLayout` is simply unregistered (the hotkey no-ops).
|
|
56
|
+
*/
|
|
57
|
+
export const layoutPlugin = plugin(
|
|
58
|
+
(system: System) => {
|
|
59
|
+
system.registerSetting({
|
|
60
|
+
key: 'layoutType',
|
|
61
|
+
section: 'Layout',
|
|
62
|
+
type: 'enum',
|
|
63
|
+
default: LAYOUT_TYPE.dagre,
|
|
64
|
+
options: Object.values(LAYOUT_TYPE).map((value) => ({
|
|
65
|
+
value,
|
|
66
|
+
label: value
|
|
67
|
+
})),
|
|
68
|
+
title: 'Layout Type',
|
|
69
|
+
description:
|
|
70
|
+
'Select the type of layout engine to use in the graph editor.'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
system.commandStore.getState().register({
|
|
74
|
+
id: 'editor.autoLayout',
|
|
75
|
+
title: 'Auto Layout',
|
|
76
|
+
run: (ctx) => applyAutoLayout(ctx.editor)
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
{ name: 'layout' }
|
|
80
|
+
);
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useEditorState, type Editor } from '@tiptap/react';
|
|
3
|
+
import { VscodeButton, VscodeTextfield } from '@vscode-elements/react-elements';
|
|
4
|
+
import {
|
|
5
|
+
Bold,
|
|
6
|
+
Code,
|
|
7
|
+
Italic,
|
|
8
|
+
List,
|
|
9
|
+
NumberedListLeft,
|
|
10
|
+
Quote,
|
|
11
|
+
Strikethrough,
|
|
12
|
+
Youtube
|
|
13
|
+
} from 'iconoir-react';
|
|
14
|
+
|
|
15
|
+
interface FormatToolbarProps {
|
|
16
|
+
editor: Editor;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Formatting actions for the note editor. Buttons are the same VscodeButton
|
|
21
|
+
* component the FloatingToolbar uses, so the note toolbar stays visually
|
|
22
|
+
* consistent with the rest of the editor chrome; the active format renders as
|
|
23
|
+
* a primary (accent) button.
|
|
24
|
+
*
|
|
25
|
+
* The button row swallows mousedown so clicking a formatting button keeps the
|
|
26
|
+
* editor's text selection instead of blurring it. The video row must NOT: its
|
|
27
|
+
* text field needs focus. Video embedding uses an inline URL field rather than
|
|
28
|
+
* `window.prompt`, which is blocked in VS Code webviews.
|
|
29
|
+
*/
|
|
30
|
+
export const FormatToolbar = ({ editor }: FormatToolbarProps) => {
|
|
31
|
+
// null = embed row closed; a string = the URL being typed.
|
|
32
|
+
const [videoUrl, setVideoUrl] = useState<string | null>(null);
|
|
33
|
+
|
|
34
|
+
const active = useEditorState({
|
|
35
|
+
editor,
|
|
36
|
+
selector: ({ editor }) => ({
|
|
37
|
+
bold: editor.isActive('bold'),
|
|
38
|
+
italic: editor.isActive('italic'),
|
|
39
|
+
strike: editor.isActive('strike'),
|
|
40
|
+
code: editor.isActive('code'),
|
|
41
|
+
h1: editor.isActive('heading', { level: 1 }),
|
|
42
|
+
h2: editor.isActive('heading', { level: 2 }),
|
|
43
|
+
h3: editor.isActive('heading', { level: 3 }),
|
|
44
|
+
bulletList: editor.isActive('bulletList'),
|
|
45
|
+
orderedList: editor.isActive('orderedList'),
|
|
46
|
+
codeBlock: editor.isActive('codeBlock'),
|
|
47
|
+
blockquote: editor.isActive('blockquote')
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const embedVideo = () => {
|
|
52
|
+
const src = videoUrl?.trim();
|
|
53
|
+
if (src) {
|
|
54
|
+
editor.chain().focus().setYoutubeVideo({ src }).run();
|
|
55
|
+
}
|
|
56
|
+
setVideoUrl(null);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="notes-toolbar nodrag nopan">
|
|
61
|
+
<div
|
|
62
|
+
className="notes-toolbar__row"
|
|
63
|
+
onMouseDown={(e) => e.preventDefault()}
|
|
64
|
+
>
|
|
65
|
+
<VscodeButton
|
|
66
|
+
secondary={!active.bold}
|
|
67
|
+
iconOnly
|
|
68
|
+
title="Bold"
|
|
69
|
+
onClick={() => editor.chain().focus().toggleBold().run()}
|
|
70
|
+
>
|
|
71
|
+
<Bold />
|
|
72
|
+
</VscodeButton>
|
|
73
|
+
<VscodeButton
|
|
74
|
+
secondary={!active.italic}
|
|
75
|
+
iconOnly
|
|
76
|
+
title="Italic"
|
|
77
|
+
onClick={() => editor.chain().focus().toggleItalic().run()}
|
|
78
|
+
>
|
|
79
|
+
<Italic />
|
|
80
|
+
</VscodeButton>
|
|
81
|
+
<VscodeButton
|
|
82
|
+
secondary={!active.strike}
|
|
83
|
+
iconOnly
|
|
84
|
+
title="Strikethrough"
|
|
85
|
+
onClick={() => editor.chain().focus().toggleStrike().run()}
|
|
86
|
+
>
|
|
87
|
+
<Strikethrough />
|
|
88
|
+
</VscodeButton>
|
|
89
|
+
<VscodeButton
|
|
90
|
+
secondary={!active.code}
|
|
91
|
+
iconOnly
|
|
92
|
+
title="Code"
|
|
93
|
+
onClick={() => editor.chain().focus().toggleCode().run()}
|
|
94
|
+
>
|
|
95
|
+
<Code />
|
|
96
|
+
</VscodeButton>
|
|
97
|
+
<div className="notes-toolbar__separator" />
|
|
98
|
+
<VscodeButton
|
|
99
|
+
secondary={!active.h1}
|
|
100
|
+
title="Heading 1"
|
|
101
|
+
onClick={() =>
|
|
102
|
+
editor.chain().focus().toggleHeading({ level: 1 }).run()
|
|
103
|
+
}
|
|
104
|
+
>
|
|
105
|
+
H1
|
|
106
|
+
</VscodeButton>
|
|
107
|
+
<VscodeButton
|
|
108
|
+
secondary={!active.h2}
|
|
109
|
+
title="Heading 2"
|
|
110
|
+
onClick={() =>
|
|
111
|
+
editor.chain().focus().toggleHeading({ level: 2 }).run()
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
H2
|
|
115
|
+
</VscodeButton>
|
|
116
|
+
<VscodeButton
|
|
117
|
+
secondary={!active.h3}
|
|
118
|
+
title="Heading 3"
|
|
119
|
+
onClick={() =>
|
|
120
|
+
editor.chain().focus().toggleHeading({ level: 3 }).run()
|
|
121
|
+
}
|
|
122
|
+
>
|
|
123
|
+
H3
|
|
124
|
+
</VscodeButton>
|
|
125
|
+
<div className="notes-toolbar__separator" />
|
|
126
|
+
<VscodeButton
|
|
127
|
+
secondary={!active.bulletList}
|
|
128
|
+
iconOnly
|
|
129
|
+
title="Bullet List"
|
|
130
|
+
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
|
131
|
+
>
|
|
132
|
+
<List />
|
|
133
|
+
</VscodeButton>
|
|
134
|
+
<VscodeButton
|
|
135
|
+
secondary={!active.orderedList}
|
|
136
|
+
iconOnly
|
|
137
|
+
title="Numbered List"
|
|
138
|
+
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
|
139
|
+
>
|
|
140
|
+
<NumberedListLeft />
|
|
141
|
+
</VscodeButton>
|
|
142
|
+
<VscodeButton
|
|
143
|
+
secondary={!active.codeBlock}
|
|
144
|
+
iconOnly
|
|
145
|
+
title="Code Block"
|
|
146
|
+
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
|
147
|
+
>
|
|
148
|
+
<Code />
|
|
149
|
+
</VscodeButton>
|
|
150
|
+
<VscodeButton
|
|
151
|
+
secondary={!active.blockquote}
|
|
152
|
+
iconOnly
|
|
153
|
+
title="Quote"
|
|
154
|
+
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
|
155
|
+
>
|
|
156
|
+
<Quote />
|
|
157
|
+
</VscodeButton>
|
|
158
|
+
<div className="notes-toolbar__separator" />
|
|
159
|
+
<VscodeButton
|
|
160
|
+
secondary={videoUrl === null}
|
|
161
|
+
iconOnly
|
|
162
|
+
title="Embed YouTube Video"
|
|
163
|
+
onClick={() => setVideoUrl(videoUrl === null ? '' : null)}
|
|
164
|
+
>
|
|
165
|
+
<Youtube />
|
|
166
|
+
</VscodeButton>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
{videoUrl !== null && (
|
|
170
|
+
<div
|
|
171
|
+
className="notes-toolbar__embed"
|
|
172
|
+
// Keep keystrokes (e.g. Backspace) from reaching React Flow, which
|
|
173
|
+
// would delete the selected note while the URL is being typed.
|
|
174
|
+
onKeyDown={(e) => {
|
|
175
|
+
e.stopPropagation();
|
|
176
|
+
if (e.key === 'Enter') embedVideo();
|
|
177
|
+
if (e.key === 'Escape') setVideoUrl(null);
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
<VscodeTextfield
|
|
181
|
+
type="text"
|
|
182
|
+
placeholder="YouTube video URL"
|
|
183
|
+
value={videoUrl}
|
|
184
|
+
onInput={(e: any) => setVideoUrl(e.target.value)}
|
|
185
|
+
/>
|
|
186
|
+
<VscodeButton title="Embed" onClick={embedVideo}>
|
|
187
|
+
Embed
|
|
188
|
+
</VscodeButton>
|
|
189
|
+
<VscodeButton
|
|
190
|
+
secondary
|
|
191
|
+
title="Cancel"
|
|
192
|
+
onClick={() => setVideoUrl(null)}
|
|
193
|
+
>
|
|
194
|
+
Cancel
|
|
195
|
+
</VscodeButton>
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Notes } from 'iconoir-react';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import { plugin } from '@/system/plugin';
|
|
4
|
+
import type { System } from '@/system/system';
|
|
5
|
+
import type { CommandContext } from '@/store/commands';
|
|
6
|
+
import type { INoteNode } from '@/types/nodes';
|
|
7
|
+
import { NoteNode } from './note';
|
|
8
|
+
import {
|
|
9
|
+
NOTE_NODE_TYPE,
|
|
10
|
+
LEGACY_COMMENT_NODE_TYPE,
|
|
11
|
+
noteAt,
|
|
12
|
+
duplicateNote,
|
|
13
|
+
deleteNote,
|
|
14
|
+
reorderNote
|
|
15
|
+
} from './nodeActions';
|
|
16
|
+
|
|
17
|
+
export { NoteNode } from './note';
|
|
18
|
+
export * from './nodeActions';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a note node on the graph the command targets. Placed at the given
|
|
22
|
+
* position (e.g. from a context menu) or, from surfaces without one (toolbar,
|
|
23
|
+
* hotkey), at the centre of the current viewport. Undo-aware.
|
|
24
|
+
*/
|
|
25
|
+
const addNote = (ctx: CommandContext): void => {
|
|
26
|
+
const reactflow = ctx.session.refStore.getState().getRef('reactflow');
|
|
27
|
+
const position = ctx.position ??
|
|
28
|
+
reactflow?.screenToFlowPosition({
|
|
29
|
+
x: window.innerWidth / 2,
|
|
30
|
+
y: window.innerHeight / 2
|
|
31
|
+
}) ?? { x: 0, y: 0 };
|
|
32
|
+
|
|
33
|
+
const note: INoteNode = {
|
|
34
|
+
id: uuidv4(),
|
|
35
|
+
type: NOTE_NODE_TYPE,
|
|
36
|
+
position,
|
|
37
|
+
selected: true,
|
|
38
|
+
// Only the header drags the node; the body is a text surface.
|
|
39
|
+
dragHandle: '.notes-node__header',
|
|
40
|
+
style: { width: 280, height: 180 },
|
|
41
|
+
data: { text: '' }
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
ctx.session.undoManager.execute({
|
|
45
|
+
name: 'Add note',
|
|
46
|
+
execute: () => {
|
|
47
|
+
ctx.session.nodeStore.getState().addNode(note);
|
|
48
|
+
},
|
|
49
|
+
undo: () => {
|
|
50
|
+
ctx.session.nodeStore
|
|
51
|
+
.getState()
|
|
52
|
+
.setNodes((existing) => existing.filter((n) => n.id !== note.id));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Adds markdown note nodes to the editor. Notes are purely presentational:
|
|
59
|
+
* they never appear in the compiled behave graph (only behave nodes do), but
|
|
60
|
+
* they persist with the UI graph JSON like any other canvas node.
|
|
61
|
+
*
|
|
62
|
+
* The note editor embeds tiptap/prosemirror, a heavy dependency most hosts do
|
|
63
|
+
* not need, so notes live here rather than in the core editor — register this
|
|
64
|
+
* plugin (directly or via the kitchen-sink plugin) to opt in.
|
|
65
|
+
*
|
|
66
|
+
* The plugin:
|
|
67
|
+
* - registers the `noteNode` component on every graph session (plus the legacy
|
|
68
|
+
* `commentNode` alias for graphs saved before notes moved here);
|
|
69
|
+
* - registers the `notes.addNote` command, an "Add Note" button on the
|
|
70
|
+
* floating toolbar, and a `Shift+N` hotkey that dispatch it;
|
|
71
|
+
* - registers note-specific node commands + context-menu items
|
|
72
|
+
* (duplicate / delete / bring to front / send to back).
|
|
73
|
+
*/
|
|
74
|
+
export const notesPlugin = plugin(
|
|
75
|
+
(system: System) => {
|
|
76
|
+
system.registerSessionExtension((session) => {
|
|
77
|
+
const { registerNodeType } = session.flowStore.getState();
|
|
78
|
+
registerNodeType(NOTE_NODE_TYPE, NoteNode);
|
|
79
|
+
registerNodeType(LEGACY_COMMENT_NODE_TYPE, NoteNode);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const commands = system.commandStore.getState();
|
|
83
|
+
commands.register({
|
|
84
|
+
id: 'notes.addNote',
|
|
85
|
+
title: 'Add Note',
|
|
86
|
+
run: addNote
|
|
87
|
+
});
|
|
88
|
+
commands.register({
|
|
89
|
+
id: 'note.duplicate',
|
|
90
|
+
title: 'Duplicate Note',
|
|
91
|
+
run: (ctx) => {
|
|
92
|
+
const note = noteAt(ctx);
|
|
93
|
+
if (note) duplicateNote(ctx.session, note);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
commands.register({
|
|
97
|
+
id: 'note.delete',
|
|
98
|
+
title: 'Delete Note',
|
|
99
|
+
run: (ctx) => {
|
|
100
|
+
const note = noteAt(ctx);
|
|
101
|
+
if (note) deleteNote(ctx.session, note);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
commands.register({
|
|
105
|
+
id: 'note.bringToFront',
|
|
106
|
+
title: 'Bring Note to Front',
|
|
107
|
+
run: (ctx) => {
|
|
108
|
+
const note = noteAt(ctx);
|
|
109
|
+
if (note) reorderNote(ctx.session, note, 'front');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
commands.register({
|
|
113
|
+
id: 'note.sendToBack',
|
|
114
|
+
title: 'Send Note to Back',
|
|
115
|
+
run: (ctx) => {
|
|
116
|
+
const note = noteAt(ctx);
|
|
117
|
+
if (note) reorderNote(ctx.session, note, 'back');
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Notes get their own node context menu; the behave items (trace, pin,
|
|
122
|
+
// ...) are guarded by `when` in the core defaults and stay hidden here.
|
|
123
|
+
const menu = system.contextMenuStore.getState();
|
|
124
|
+
const noteOnly = (ctx: CommandContext) => Boolean(noteAt(ctx));
|
|
125
|
+
menu.register({
|
|
126
|
+
id: 'note.duplicate',
|
|
127
|
+
target: 'node',
|
|
128
|
+
label: 'Duplicate',
|
|
129
|
+
order: 10,
|
|
130
|
+
group: 'note',
|
|
131
|
+
when: noteOnly,
|
|
132
|
+
commandId: 'note.duplicate'
|
|
133
|
+
});
|
|
134
|
+
menu.register({
|
|
135
|
+
id: 'note.bringToFront',
|
|
136
|
+
target: 'node',
|
|
137
|
+
label: 'Bring to Front',
|
|
138
|
+
order: 20,
|
|
139
|
+
group: 'note-order',
|
|
140
|
+
when: noteOnly,
|
|
141
|
+
commandId: 'note.bringToFront'
|
|
142
|
+
});
|
|
143
|
+
menu.register({
|
|
144
|
+
id: 'note.sendToBack',
|
|
145
|
+
target: 'node',
|
|
146
|
+
label: 'Send to Back',
|
|
147
|
+
order: 21,
|
|
148
|
+
group: 'note-order',
|
|
149
|
+
when: noteOnly,
|
|
150
|
+
commandId: 'note.sendToBack'
|
|
151
|
+
});
|
|
152
|
+
menu.register({
|
|
153
|
+
id: 'note.delete',
|
|
154
|
+
target: 'node',
|
|
155
|
+
label: 'Delete',
|
|
156
|
+
order: 30,
|
|
157
|
+
group: 'note-danger',
|
|
158
|
+
when: noteOnly,
|
|
159
|
+
commandId: 'note.delete'
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const dispatchAddNote = () => {
|
|
163
|
+
const session = system.session;
|
|
164
|
+
if (!session) return;
|
|
165
|
+
void system.commandStore
|
|
166
|
+
.getState()
|
|
167
|
+
.run('notes.addNote', { editor: system, session });
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
system.toolbarStore.getState().addGroup({
|
|
171
|
+
id: 'notes',
|
|
172
|
+
label: 'Notes',
|
|
173
|
+
buttons: [
|
|
174
|
+
{
|
|
175
|
+
id: 'notes.addNote',
|
|
176
|
+
icon: <Notes />,
|
|
177
|
+
label: 'Add Note',
|
|
178
|
+
onClick: dispatchAddNote
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
system.hotKeyStore.getState().register({
|
|
184
|
+
action: 'ADD_NOTE',
|
|
185
|
+
description: 'Add a markdown note to the graph',
|
|
186
|
+
trigger: 'shift+n',
|
|
187
|
+
handler: dispatchAddNote
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
{ name: 'notes' }
|
|
191
|
+
);
|