@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.
- package/.storybook/manager.ts +6 -0
- package/.storybook/preview.ts +49 -1
- package/.storybook/styles.css +9 -3
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +368 -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/index.css +36 -33
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1865 -550
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14357 -11221
- package/dist/index.js.map +1 -1
- 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/protocol.md +43 -20
- package/package.json +5 -9
- package/src/components/FloatingToolbar/index.module.css +5 -13
- package/src/components/FloatingToolbar/index.tsx +9 -9
- package/src/components/Flow.tsx +34 -23
- package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
- package/src/components/contextMenus/NodePicker.module.css +13 -13
- package/src/components/contextMenus/edge.tsx +9 -95
- package/src/components/contextMenus/node.tsx +9 -149
- package/src/components/contextMenus/selection.tsx +5 -71
- package/src/components/controls/any/AnyControlImpl.tsx +14 -0
- package/src/components/controls/any/index.tsx +13 -2
- package/src/components/edges/index.tsx +75 -69
- package/src/components/layoutController/index.module.css +3 -0
- package/src/components/layoutController/index.tsx +24 -1
- package/src/components/layoutController/utils.ts +46 -3
- package/src/components/menubar/defaults.tsx +55 -19
- package/src/components/menubar/menuItem.module.css +18 -3
- package/src/components/menubar/menuItem.tsx +34 -1
- package/src/components/nodes/behave/NodeContainer.module.css +26 -25
- package/src/components/nodes/group/index.tsx +3 -3
- package/src/components/nodes/wrapper/styles.module.css +6 -32
- package/src/components/panels/alignment/index.module.css +0 -10
- package/src/components/panels/alignment/index.tsx +4 -4
- package/src/components/panels/base/styles.module.css +2 -2
- 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 +14 -5
- package/src/components/panels/events/ManageEventsPanel.tsx +11 -8
- package/src/components/panels/events/styles.module.css +6 -64
- package/src/components/panels/graphProperties/index.tsx +125 -0
- package/src/components/panels/history/index.tsx +2 -2
- package/src/components/panels/history/styles.module.css +0 -9
- package/src/components/panels/keymaps/index.module.css +3 -13
- package/src/components/panels/keymaps/index.tsx +1 -2
- package/src/components/panels/layers/index.tsx +20 -15
- package/src/components/panels/layers/styles.module.css +9 -12
- package/src/components/panels/legend/index.tsx +1 -1
- package/src/components/panels/logs/index.module.css +25 -19
- package/src/components/panels/logs/index.tsx +7 -7
- package/src/components/panels/nodeInputs/InputsGroup.tsx +1 -0
- package/src/components/panels/nodeInputs/NodeSettings.tsx +2 -2
- package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +1 -1
- package/src/components/panels/nodeInputs/OutputsGroup.tsx +2 -12
- package/src/components/panels/nodeInputs/index.module.css +99 -75
- package/src/components/panels/nodeInputs/index.tsx +21 -11
- package/src/components/panels/nodeInputs/useNodeHandlers.ts +2 -2
- package/src/components/panels/nodeInputs/useNodeInputsData.ts +23 -43
- package/src/components/panels/nodePicker/index.tsx +8 -8
- package/src/components/panels/panel/index.module.css +7 -7
- package/src/components/panels/search/index.module.css +0 -50
- package/src/components/panels/search/index.tsx +2 -2
- package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
- package/src/components/panels/systemSettings/index.tsx +221 -176
- package/src/components/panels/systemSettings/styles.module.css +135 -8
- package/src/components/panels/traces/GridLines.tsx +1 -1
- package/src/components/panels/traces/TimeGrid.tsx +3 -3
- package/src/components/panels/traces/TraceLane.tsx +1 -1
- package/src/components/panels/traces/index.module.css +1 -8
- package/src/components/panels/traces/index.tsx +8 -4
- package/src/components/panels/traces/useDerivedSpans.ts +241 -146
- package/src/components/panels/traces/utils.ts +8 -0
- package/src/components/panels/variables/CreateVariableScreen.tsx +3 -3
- package/src/components/panels/variables/ManageVariablesScreen.tsx +12 -9
- package/src/components/panels/variables/index.tsx +2 -2
- package/src/components/panels/variables/styles.module.css +4 -91
- package/src/components/primitives/icon.module.css +4 -4
- package/src/components/sockets/input/index.tsx +9 -2
- package/src/components/sockets/input/styles.module.css +2 -3
- package/src/components/sockets/output/index.tsx +10 -3
- package/src/components/sockets/output/styles.module.css +1 -6
- package/src/css/notes.css +135 -0
- package/src/css/prosemirror.css +3 -3
- package/src/css/rc-dock.css +143 -43
- package/src/css/rc-menu.css +56 -55
- package/src/css/themes/kiberon.css +127 -0
- package/src/css/vars.css +197 -13
- package/src/css/vscode-elements.css +124 -0
- package/src/generators/CallSubgraphGenerator.tsx +136 -0
- package/src/generators/CustomEventOnTriggeredGenerator.tsx +2 -2
- package/src/generators/GraphBoundaryGenerator.module.css +32 -0
- package/src/generators/GraphBoundaryGenerator.tsx +193 -0
- package/src/generators/SequenceGenerator.tsx +2 -2
- package/src/generators/SwitchOnIntegerGenerator.tsx +2 -2
- package/src/generators/SwitchOnStringGenerator.tsx +2 -2
- package/src/generators/callSubgraphSync.ts +126 -0
- package/src/generators/registerDefaultGenerators.ts +21 -0
- package/src/generators/registerDefaults.ts +26 -0
- package/src/hooks/useBehaveGraphFlow.ts +2 -2
- package/src/hooks/useFlowHandlers.ts +47 -9
- package/src/hooks/useWasdPan.ts +26 -4
- package/src/index.css +4 -16
- package/src/index.ts +17 -0
- 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 +22 -12
- 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 +2 -4
- package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
- package/src/plugin/docs/panel/index.tsx +15 -194
- package/src/plugin/docs/panel/styles.module.css +8 -8
- package/src/plugin/graphrunner/actions.ts +258 -185
- package/src/plugin/graphrunner/buttons.tsx +34 -26
- package/src/plugin/graphrunner/client.ts +4 -1
- package/src/plugin/graphrunner/index.tsx +29 -100
- package/src/plugin/graphrunner/panel.tsx +2 -2
- package/src/plugin/graphrunner/runController.ts +283 -0
- package/src/plugin/graphrunner/runner.ts +21 -192
- package/src/plugin/graphrunner/store.ts +14 -24
- package/src/plugin/graphrunner/styles.module.css +17 -57
- package/src/plugin/graphrunner/transport.ts +26 -0
- package/src/plugin/graphrunner/types.ts +21 -0
- package/src/plugin/graphrunner-local/execution-utils.ts +260 -80
- package/src/plugin/graphrunner-local/index.tsx +8 -2
- package/src/plugin/graphrunner-local/panel.tsx +131 -175
- package/src/plugin/graphrunner-local/styles.module.css +57 -76
- package/src/plugin/graphrunner-local/transport.ts +151 -184
- package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +2 -0
- package/src/plugin/graphrunner-webworker/index.tsx +4 -10
- package/src/plugin/graphrunner-webworker/store.ts +9 -0
- package/src/plugin/kitchen-sink/index.ts +38 -0
- package/src/{layout/dagre.tsx → plugin/layout/dagre.ts} +17 -5
- package/src/{layout → plugin/layout}/elk.ts +22 -6
- 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 +58 -4
- package/src/specifics/CustomEventOnTriggeredSpecific.tsx +2 -2
- package/src/specifics/CustomEventTriggerSpecific.tsx +2 -2
- package/src/specifics/VariableGetSpecific.tsx +2 -2
- package/src/specifics/VariableSetSpecific.tsx +2 -2
- package/src/store/actions.tsx +5 -5
- package/src/store/commands.ts +278 -0
- package/src/store/contextMenu.ts +192 -0
- package/src/store/conversions.ts +47 -0
- package/src/store/flow.tsx +23 -38
- package/src/store/graphMeta.ts +39 -0
- package/src/store/hotKeys.tsx +301 -260
- package/src/store/layers.ts +3 -3
- package/src/store/registry.ts +12 -4
- package/src/store/selection.ts +3 -3
- package/src/store/settings.ts +82 -82
- package/src/store/settingsSchema.ts +210 -0
- package/src/store/tabs.ts +5 -1
- package/src/store/traces.ts +3 -3
- package/src/system/graph.ts +11 -14
- package/src/system/graphSession.ts +172 -0
- package/src/system/index.ts +3 -0
- package/src/system/notifications.ts +13 -0
- package/src/system/persistence.ts +82 -0
- package/src/system/plugin.ts +28 -0
- package/src/system/provider.tsx +64 -0
- package/src/system/system.ts +518 -88
- package/src/system/tabLoader.tsx +70 -32
- package/src/system/undoRedo.ts +1 -1
- package/src/transformers/Uigraph.ts +5 -4
- package/src/transformers/contract.ts +87 -0
- package/src/transformers/flowToBehave.ts +13 -5
- package/src/types/nodes.ts +8 -3
- package/src/types.ts +2 -0
- package/src/util/autoConvert.ts +200 -0
- package/src/util/isValidConnection.ts +23 -2
- package/stories/defaults/defaultStoryProvider.tsx +17 -14
- package/stories/defaults/systemGenerator.ts +6 -1
- package/stories/{components/nodes/comment.stories.tsx → plugins/notes.stories.tsx} +24 -30
- 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/contract.test.ts +51 -0
- package/tests/contractSerialize.test.ts +62 -0
- package/tests/deriveSpans.test.ts +71 -0
- package/tests/flowToBehave.test.ts +2 -1
- 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/persistence.test.ts +51 -0
- package/tests/saveLoad.test.ts +7 -6
- package/tests/settings.test.ts +178 -0
- package/tests/traceStore.test.ts +46 -0
- package/tests/visual/README.md +2 -2
- 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-variables-chromium-win32.png +0 -0
- package/tests/visual/panels.visual.test.tsx +3 -3
- package/tests/wasdPan.test.ts +71 -0
- package/vitest.config.ts +1 -1
- package/vitest.visual.config.ts +7 -0
- package/.storybook/vscode.css +0 -814
- package/src/components/nodes/comment/FormatToolbar.tsx +0 -118
- package/src/components/nodes/comment/comment.tsx +0 -103
- package/src/components/nodes/comment/styles.module.css +0 -150
- package/src/components/panels/conversation/index.module.css +0 -151
- package/src/components/panels/conversation/index.tsx +0 -162
- package/src/components/panels/events/CustomEventsEditor.tsx +0 -384
- package/src/css/vscode.css +0 -13
- package/src/hooks/useDetachNodes.ts +0 -39
- package/src/plugin/graphrunner-webworker/types.ts +0 -17
- package/src/specifics/registerDefaultSpecifics.ts +0 -5
- package/src/store/chat.ts +0 -73
- package/src/store/graphRunnerClient.ts +0 -110
|
@@ -1,11 +1,138 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/* Compact, VSCode-settings-like layout */
|
|
2
|
+
|
|
3
|
+
.searchRow {
|
|
4
|
+
padding-bottom: var(--space-2, 0.5rem);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.list {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Label + reset affordance on one line (non-toggle rows) */
|
|
13
|
+
.labelRow {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
gap: 0.5rem;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Small dot next to a label whose value differs from its default */
|
|
21
|
+
.modified {
|
|
22
|
+
display: inline-block;
|
|
23
|
+
width: 6px;
|
|
24
|
+
height: 6px;
|
|
25
|
+
margin-left: 0.4rem;
|
|
26
|
+
border-radius: 50%;
|
|
27
|
+
background: var(--ds-focus-border, #0078d4);
|
|
28
|
+
vertical-align: middle;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Ghost icon button to restore a setting's default */
|
|
32
|
+
.reset {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
flex: none;
|
|
37
|
+
padding: 0.15rem;
|
|
38
|
+
background: transparent;
|
|
39
|
+
border: none;
|
|
40
|
+
border-radius: var(--component-radii-sm, 4px);
|
|
41
|
+
color: var(--ds-fg-muted);
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.reset:hover {
|
|
46
|
+
background: var(--ds-toolbar-hover-bg, rgba(90, 93, 94, 0.31));
|
|
47
|
+
color: var(--ds-fg);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* A single setting row */
|
|
51
|
+
.setting {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
gap: 0.15rem;
|
|
55
|
+
padding: var(--space-1, 0.25rem) 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Toggle settings put the control inline with the label */
|
|
59
|
+
.toggle {
|
|
60
|
+
flex-direction: row;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 0.5rem;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.toggle .body {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
gap: 0.1rem;
|
|
69
|
+
min-width: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.label {
|
|
73
|
+
font-size: var(--fs-label, 0.8rem);
|
|
74
|
+
color: var(--ds-fg);
|
|
75
|
+
line-height: 1.3;
|
|
5
76
|
}
|
|
6
77
|
|
|
7
78
|
.description {
|
|
8
|
-
font-size: 0.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
79
|
+
font-size: var(--fs-desc, 0.72rem);
|
|
80
|
+
line-height: 1.35;
|
|
81
|
+
color: var(--ds-fg-muted);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.divider {
|
|
85
|
+
margin: 0.25rem 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Conversions editor */
|
|
89
|
+
.conversions {
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
gap: 0.4rem;
|
|
93
|
+
margin-top: 0.35rem;
|
|
94
|
+
padding-left: 0.5rem;
|
|
95
|
+
border-left: 1px solid var(--ds-sash-border, var(--ds-border, #2b2b2b));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.conversionsLabel {
|
|
99
|
+
font-size: var(--fs-desc, 0.72rem);
|
|
100
|
+
color: var(--ds-fg-muted);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.empty {
|
|
104
|
+
font-size: var(--fs-desc, 0.72rem);
|
|
105
|
+
color: var(--ds-fg-muted);
|
|
106
|
+
opacity: 0.8;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.rule {
|
|
110
|
+
display: flex;
|
|
111
|
+
gap: 0.4rem;
|
|
112
|
+
align-items: center;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.ruleText {
|
|
116
|
+
flex: 1;
|
|
117
|
+
min-width: 0;
|
|
118
|
+
font-size: 0.75rem;
|
|
119
|
+
white-space: nowrap;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
text-overflow: ellipsis;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.ruleNode {
|
|
125
|
+
opacity: 0.65;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Stack the add-rule inputs vertically so nothing overflows the panel */
|
|
129
|
+
.addForm {
|
|
130
|
+
display: flex;
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
gap: 0.3rem;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.addForm vscode-single-select,
|
|
136
|
+
.addForm vscode-button {
|
|
137
|
+
width: 100%;
|
|
138
|
+
}
|
|
@@ -12,7 +12,7 @@ const TICK_STYLE: React.CSSProperties = {
|
|
|
12
12
|
|
|
13
13
|
const LABEL_STYLE: React.CSSProperties = {
|
|
14
14
|
fontSize: '10px',
|
|
15
|
-
color: 'var(--
|
|
15
|
+
color: 'var(--ds-fg-muted)',
|
|
16
16
|
padding: '2px 4px',
|
|
17
17
|
whiteSpace: 'nowrap'
|
|
18
18
|
};
|
|
@@ -32,8 +32,8 @@ export const TimeGrid = memo(function TimeGrid({
|
|
|
32
32
|
position: 'sticky',
|
|
33
33
|
top: 0,
|
|
34
34
|
height: 24,
|
|
35
|
-
background: 'var(--
|
|
36
|
-
borderBottom: '1px solid var(--
|
|
35
|
+
background: 'var(--ds-editor-bg)',
|
|
36
|
+
borderBottom: '1px solid var(--ds-panel-border)',
|
|
37
37
|
zIndex: 10,
|
|
38
38
|
padding: `0 ${padding}px`
|
|
39
39
|
}}
|
|
@@ -13,7 +13,7 @@ type TraceLaneProps = {
|
|
|
13
13
|
* Renders a single lane's spans.
|
|
14
14
|
*
|
|
15
15
|
* Event handling for hover/click is delegated to the parent scroll
|
|
16
|
-
* container via `data-*` attributes
|
|
16
|
+
* container via `data-*` attributes , no per-span closures needed.
|
|
17
17
|
*/
|
|
18
18
|
export const TraceLane = memo(function TraceLane({
|
|
19
19
|
laneData,
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
.root {
|
|
2
|
-
height: 100%;
|
|
3
|
-
width: 100%;
|
|
4
|
-
display: flex;
|
|
5
|
-
flex-direction: column;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
1
|
.header {
|
|
9
2
|
display: flex;
|
|
10
3
|
flex-direction: column;
|
|
@@ -109,7 +102,7 @@
|
|
|
109
102
|
align-items: center;
|
|
110
103
|
overflow: hidden;
|
|
111
104
|
font-size: 11px;
|
|
112
|
-
font-family: var(--
|
|
105
|
+
font-family: var(--ds-font-ui);
|
|
113
106
|
color: rgba(255, 255, 255, 0.95);
|
|
114
107
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
|
115
108
|
white-space: nowrap;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useStore } from 'zustand';
|
|
3
|
-
import {
|
|
3
|
+
import { useActiveGraph } from '@/system';
|
|
4
4
|
import type { TraceSpan } from '@/store/traces';
|
|
5
5
|
import { VscodeButton, VscodeDivider } from '@vscode-elements/react-elements';
|
|
6
6
|
import styles from './index.module.css';
|
|
7
7
|
import { BasePanel } from '../base';
|
|
8
8
|
import type { HoverInfo, ViewState } from './types';
|
|
9
|
-
import { clamp } from './utils';
|
|
9
|
+
import { clamp, MAX_ZOOM_OUT_FACTOR } from './utils';
|
|
10
10
|
import { useDerivedSpans } from './useDerivedSpans';
|
|
11
11
|
import { TracesHeader } from './TracesHeader';
|
|
12
12
|
import { TimeGrid } from './TimeGrid';
|
|
@@ -17,7 +17,7 @@ import { TraceTooltip } from './TraceTooltip';
|
|
|
17
17
|
const PADDING = 8;
|
|
18
18
|
|
|
19
19
|
export function TracesPanel() {
|
|
20
|
-
const system =
|
|
20
|
+
const system = useActiveGraph()!;
|
|
21
21
|
const version = useStore(system.traceStore, (s) => s.version);
|
|
22
22
|
const clear = useStore(system.traceStore, (s) => s.clear);
|
|
23
23
|
const collector = system.traceStore.getState().collector;
|
|
@@ -161,7 +161,11 @@ export function TracesPanel() {
|
|
|
161
161
|
|
|
162
162
|
const zoomFactor = Math.exp(e.deltaY * 0.0015);
|
|
163
163
|
setView((v) => {
|
|
164
|
-
const nextRange = clamp(
|
|
164
|
+
const nextRange = clamp(
|
|
165
|
+
v.range * zoomFactor,
|
|
166
|
+
1,
|
|
167
|
+
derived.range * MAX_ZOOM_OUT_FACTOR
|
|
168
|
+
);
|
|
165
169
|
const k = x / Math.max(1, rect.width);
|
|
166
170
|
const nextStart = clampViewStart(t - k * nextRange, nextRange);
|
|
167
171
|
return {
|
|
@@ -7,11 +7,27 @@ import type {
|
|
|
7
7
|
ViewState,
|
|
8
8
|
VisualSpan
|
|
9
9
|
} from './types';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
calculateTimeInterval,
|
|
12
|
+
clamp,
|
|
13
|
+
hashToHue,
|
|
14
|
+
MAX_ZOOM_OUT_FACTOR
|
|
15
|
+
} from './utils';
|
|
11
16
|
|
|
12
17
|
const MIN_WIDTH_PCT = 0.5;
|
|
13
18
|
const OVERLAP_EPSILON_PCT = 0.5;
|
|
14
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Minimum *visual* span length (ms). Instant nodes record start === end (0ms),
|
|
22
|
+
* which otherwise collapses the range (maxEnd <= minStart) and renders nothing.
|
|
23
|
+
* The true duration is still reported to the tooltip.
|
|
24
|
+
*/
|
|
25
|
+
const MIN_SPAN_MS = 1;
|
|
26
|
+
|
|
27
|
+
/** Effective end used for layout: clamps instant/zero-length spans to MIN_SPAN_MS. */
|
|
28
|
+
const visualEnd = (start: number, rawEnd: number): number =>
|
|
29
|
+
Math.max(rawEnd, start + MIN_SPAN_MS);
|
|
30
|
+
|
|
15
31
|
const EMPTY: DerivedData = {
|
|
16
32
|
now: 0,
|
|
17
33
|
size: 0,
|
|
@@ -30,7 +46,8 @@ const EMPTY: DerivedData = {
|
|
|
30
46
|
function computeTicks(
|
|
31
47
|
viewStart: number,
|
|
32
48
|
viewEnd: number,
|
|
33
|
-
viewRange: number
|
|
49
|
+
viewRange: number,
|
|
50
|
+
origin: number
|
|
34
51
|
): Tick[] {
|
|
35
52
|
const interval = calculateTimeInterval(viewRange);
|
|
36
53
|
const firstTick = Math.ceil(viewStart / interval) * interval;
|
|
@@ -38,7 +55,9 @@ function computeTicks(
|
|
|
38
55
|
for (let t = firstTick; t <= viewEnd; t += interval) {
|
|
39
56
|
const leftPct = ((t - viewStart) / viewRange) * 100;
|
|
40
57
|
if (leftPct >= 0 && leftPct <= 100) {
|
|
41
|
-
|
|
58
|
+
// Position uses absolute time; the label is relative to the trace origin
|
|
59
|
+
// (first span) so it reads 0ms… instead of the raw clock value.
|
|
60
|
+
ticks.push({ time: t - origin, leftPct });
|
|
42
61
|
}
|
|
43
62
|
}
|
|
44
63
|
return ticks;
|
|
@@ -50,163 +69,239 @@ export function useDerivedSpans(
|
|
|
50
69
|
windowMs: number,
|
|
51
70
|
view: ViewState
|
|
52
71
|
): DerivedData {
|
|
53
|
-
return useMemo(
|
|
54
|
-
|
|
55
|
-
|
|
72
|
+
return useMemo(
|
|
73
|
+
() => computeDerivedSpans(collector, windowMs, view),
|
|
74
|
+
[collector, version, windowMs, view.follow, view.range, view.start]
|
|
75
|
+
);
|
|
76
|
+
}
|
|
56
77
|
|
|
57
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Pure span → layout derivation used by {@link useDerivedSpans}. Exported so the
|
|
80
|
+
* layout math (lane assignment, min-span visibility, ticks, zoom clamping) can be
|
|
81
|
+
* unit-tested without a React render.
|
|
82
|
+
*/
|
|
83
|
+
type TraceSpan = import('@/store/traces').TraceSpan;
|
|
58
84
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
85
|
+
interface SpanScan {
|
|
86
|
+
minStart: number;
|
|
87
|
+
maxEnd: number;
|
|
88
|
+
maxLane: number;
|
|
89
|
+
buckets: TraceSpan[][];
|
|
90
|
+
}
|
|
62
91
|
|
|
63
|
-
|
|
92
|
+
/** Walk the ring buffer newest-window-first, bucketing spans by lane and
|
|
93
|
+
* tracking the overall time/lane bounds. */
|
|
94
|
+
function scanSpans(collector: SpanCollector, now: number): SpanScan {
|
|
95
|
+
const { capacity, size, writeIndex, spans } = collector;
|
|
96
|
+
let minStart = Number.POSITIVE_INFINITY;
|
|
97
|
+
let maxEnd = Number.NEGATIVE_INFINITY;
|
|
98
|
+
let maxLane = -1;
|
|
99
|
+
const buckets: TraceSpan[][] = [];
|
|
64
100
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
101
|
+
const startIndex = (writeIndex - size + capacity) % capacity;
|
|
102
|
+
for (let i = 0; i < size; i++) {
|
|
103
|
+
const idx = (startIndex + i) % capacity;
|
|
104
|
+
const s = spans[idx];
|
|
105
|
+
if (!s) continue;
|
|
70
106
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
107
|
+
const rawEnd = Number.isNaN(s.end) ? now : s.end;
|
|
108
|
+
const end = visualEnd(s.start, rawEnd);
|
|
109
|
+
minStart = Math.min(minStart, s.start);
|
|
110
|
+
maxEnd = Math.max(maxEnd, end);
|
|
111
|
+
maxLane = Math.max(maxLane, s.lane);
|
|
75
112
|
|
|
76
|
-
|
|
77
|
-
|
|
113
|
+
(buckets[s.lane] ??= []).push(s);
|
|
114
|
+
}
|
|
78
115
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
!Number.isFinite(maxEnd) ||
|
|
82
|
-
maxEnd <= minStart
|
|
83
|
-
) {
|
|
84
|
-
return {
|
|
85
|
-
...EMPTY,
|
|
86
|
-
now,
|
|
87
|
-
size,
|
|
88
|
-
lanes: maxLane + 1,
|
|
89
|
-
buckets,
|
|
90
|
-
laneData: []
|
|
91
|
-
};
|
|
92
|
-
}
|
|
116
|
+
return { minStart, maxEnd, maxLane, buckets };
|
|
117
|
+
}
|
|
93
118
|
|
|
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
|
-
const end = Number.isNaN(s.end) ? now : s.end;
|
|
136
|
-
if (end < viewStart || s.start > viewEnd) continue;
|
|
137
|
-
|
|
138
|
-
const visibleStart = Math.max(s.start, viewStart);
|
|
139
|
-
const visibleEnd = Math.min(end, viewEnd);
|
|
140
|
-
if (visibleEnd <= visibleStart) continue;
|
|
141
|
-
|
|
142
|
-
const rawLeft = ((visibleStart - viewStart) / viewRange) * 100;
|
|
143
|
-
const rawWidth = ((visibleEnd - visibleStart) / viewRange) * 100;
|
|
144
|
-
const leftPct = clamp(rawLeft, 0, 100);
|
|
145
|
-
const widthPct = clamp(
|
|
146
|
-
Math.max(MIN_WIDTH_PCT, rawWidth),
|
|
147
|
-
0,
|
|
148
|
-
100 - leftPct
|
|
149
|
-
);
|
|
150
|
-
const rightPct = leftPct + widthPct;
|
|
151
|
-
const durationMs = Math.max(0, end - s.start);
|
|
152
|
-
|
|
153
|
-
tmp.push({ span: s, leftPct, widthPct, rightPct, durationMs });
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
tmp.sort((a, b) => a.leftPct - b.leftPct);
|
|
157
|
-
|
|
158
|
-
const stackRight: number[] = [];
|
|
159
|
-
const visualSpans: VisualSpan[] = [];
|
|
160
|
-
|
|
161
|
-
for (const v of tmp) {
|
|
162
|
-
let stack = 0;
|
|
163
|
-
for (; stack < stackRight.length; stack++) {
|
|
164
|
-
if (v.leftPct >= stackRight[stack]! + OVERLAP_EPSILON_PCT) break;
|
|
165
|
-
}
|
|
166
|
-
if (stack === stackRight.length) stackRight.push(v.rightPct);
|
|
167
|
-
else stackRight[stack] = Math.max(stackRight[stack]!, v.rightPct);
|
|
168
|
-
|
|
169
|
-
const hue = hashToHue(v.span.nodeId);
|
|
170
|
-
const isOpen = Number.isNaN(v.span.end);
|
|
171
|
-
const lightness = clamp(56 - stack * 7, 30, 60);
|
|
172
|
-
const bg = isOpen
|
|
173
|
-
? `hsla(${hue}, 80%, ${clamp(lightness + 6, 30, 65)}%, 0.35)`
|
|
174
|
-
: `hsla(${hue}, 80%, ${lightness}%, 0.6)`;
|
|
175
|
-
const border = `hsla(${hue}, 90%, ${clamp(lightness + 22, 45, 80)}%, 0.95)`;
|
|
176
|
-
|
|
177
|
-
visualSpans.push({
|
|
178
|
-
span: v.span,
|
|
179
|
-
leftPct: v.leftPct,
|
|
180
|
-
widthPct: v.widthPct,
|
|
181
|
-
rightPct: v.rightPct,
|
|
182
|
-
durationMs: v.durationMs,
|
|
183
|
-
stack,
|
|
184
|
-
bg,
|
|
185
|
-
border
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
laneData[lane] = {
|
|
190
|
-
stackCount: Math.max(1, stackRight.length),
|
|
191
|
-
visualSpans
|
|
192
|
-
};
|
|
193
|
-
}
|
|
119
|
+
interface ViewWindow {
|
|
120
|
+
viewStart: number;
|
|
121
|
+
viewEnd: number;
|
|
122
|
+
viewRange: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Resolve the visible time window from the data bounds, the follow/zoom mode,
|
|
126
|
+
* and the requested window size. */
|
|
127
|
+
function resolveViewWindow(
|
|
128
|
+
scan: SpanScan,
|
|
129
|
+
fullRange: number,
|
|
130
|
+
windowMs: number,
|
|
131
|
+
view: ViewState
|
|
132
|
+
): ViewWindow {
|
|
133
|
+
const { minStart, maxEnd } = scan;
|
|
134
|
+
const desiredRange = windowMs <= 0 ? fullRange : Math.max(1, windowMs);
|
|
135
|
+
const desiredStart = Math.max(minStart, maxEnd - desiredRange);
|
|
136
|
+
|
|
137
|
+
// Following snaps to the data range; manual zoom may pull back past the data
|
|
138
|
+
// extent (up to MAX_ZOOM_OUT_FACTOR×) so short traces aren't un-zoom-out-able.
|
|
139
|
+
const viewRange = clamp(
|
|
140
|
+
view.follow ? desiredRange : view.range,
|
|
141
|
+
1,
|
|
142
|
+
view.follow ? fullRange : fullRange * MAX_ZOOM_OUT_FACTOR
|
|
143
|
+
);
|
|
144
|
+
const viewStart = clamp(
|
|
145
|
+
view.follow ? desiredStart : view.start,
|
|
146
|
+
minStart,
|
|
147
|
+
maxEnd - viewRange
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return { viewStart, viewEnd: viewStart + viewRange, viewRange };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
type PlacedSpan = {
|
|
154
|
+
span: TraceSpan;
|
|
155
|
+
leftPct: number;
|
|
156
|
+
widthPct: number;
|
|
157
|
+
rightPct: number;
|
|
158
|
+
durationMs: number;
|
|
159
|
+
};
|
|
194
160
|
|
|
195
|
-
|
|
161
|
+
/** Project a single span onto the view window, or null if it falls outside. */
|
|
162
|
+
function placeSpan(
|
|
163
|
+
s: TraceSpan,
|
|
164
|
+
now: number,
|
|
165
|
+
win: ViewWindow
|
|
166
|
+
): PlacedSpan | null {
|
|
167
|
+
const { viewStart, viewEnd, viewRange } = win;
|
|
168
|
+
const rawEnd = Number.isNaN(s.end) ? now : s.end;
|
|
169
|
+
const end = visualEnd(s.start, rawEnd);
|
|
170
|
+
if (end < viewStart || s.start > viewEnd) return null;
|
|
196
171
|
|
|
172
|
+
const visibleStart = Math.max(s.start, viewStart);
|
|
173
|
+
const visibleEnd = Math.min(end, viewEnd);
|
|
174
|
+
if (visibleEnd <= visibleStart) return null;
|
|
175
|
+
|
|
176
|
+
const rawLeft = ((visibleStart - viewStart) / viewRange) * 100;
|
|
177
|
+
const rawWidth = ((visibleEnd - visibleStart) / viewRange) * 100;
|
|
178
|
+
const leftPct = clamp(rawLeft, 0, 100);
|
|
179
|
+
const widthPct = clamp(Math.max(MIN_WIDTH_PCT, rawWidth), 0, 100 - leftPct);
|
|
180
|
+
const rightPct = leftPct + widthPct;
|
|
181
|
+
// Report the true duration (0ms for instant nodes), not the padded one.
|
|
182
|
+
const durationMs = Math.max(0, rawEnd - s.start);
|
|
183
|
+
|
|
184
|
+
return { span: s, leftPct, widthPct, rightPct, durationMs };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Pack a placed span into the lowest stack row that has cleared its left edge,
|
|
188
|
+
* returning the chosen stack index and updating `stackRight` in place. */
|
|
189
|
+
function assignStack(placed: PlacedSpan, stackRight: number[]): number {
|
|
190
|
+
let stack = 0;
|
|
191
|
+
for (; stack < stackRight.length; stack++) {
|
|
192
|
+
if (placed.leftPct >= stackRight[stack]! + OVERLAP_EPSILON_PCT) break;
|
|
193
|
+
}
|
|
194
|
+
if (stack === stackRight.length) stackRight.push(placed.rightPct);
|
|
195
|
+
else stackRight[stack] = Math.max(stackRight[stack]!, placed.rightPct);
|
|
196
|
+
return stack;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Build the fill/border colours for a span at a given stack depth. */
|
|
200
|
+
function spanColors(
|
|
201
|
+
span: TraceSpan,
|
|
202
|
+
stack: number
|
|
203
|
+
): { bg: string; border: string } {
|
|
204
|
+
const hue = hashToHue(span.nodeId);
|
|
205
|
+
const isOpen = Number.isNaN(span.end);
|
|
206
|
+
const lightness = clamp(56 - stack * 7, 30, 60);
|
|
207
|
+
const bg = isOpen
|
|
208
|
+
? `hsla(${hue}, 80%, ${clamp(lightness + 6, 30, 65)}%, 0.35)`
|
|
209
|
+
: `hsla(${hue}, 80%, ${lightness}%, 0.6)`;
|
|
210
|
+
const border = `hsla(${hue}, 90%, ${clamp(lightness + 22, 45, 80)}%, 0.95)`;
|
|
211
|
+
return { bg, border };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Lay out one lane's spans into stacked, coloured visual spans. */
|
|
215
|
+
function buildLaneData(
|
|
216
|
+
laneSpans: TraceSpan[] | undefined,
|
|
217
|
+
now: number,
|
|
218
|
+
win: ViewWindow
|
|
219
|
+
): LaneData {
|
|
220
|
+
if (!laneSpans || laneSpans.length === 0) {
|
|
221
|
+
return { stackCount: 1, visualSpans: [] };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const placed: PlacedSpan[] = [];
|
|
225
|
+
for (const s of laneSpans) {
|
|
226
|
+
const p = placeSpan(s, now, win);
|
|
227
|
+
if (p) placed.push(p);
|
|
228
|
+
}
|
|
229
|
+
placed.sort((a, b) => a.leftPct - b.leftPct);
|
|
230
|
+
|
|
231
|
+
const stackRight: number[] = [];
|
|
232
|
+
const visualSpans: VisualSpan[] = [];
|
|
233
|
+
for (const v of placed) {
|
|
234
|
+
const stack = assignStack(v, stackRight);
|
|
235
|
+
const { bg, border } = spanColors(v.span, stack);
|
|
236
|
+
visualSpans.push({
|
|
237
|
+
span: v.span,
|
|
238
|
+
leftPct: v.leftPct,
|
|
239
|
+
widthPct: v.widthPct,
|
|
240
|
+
rightPct: v.rightPct,
|
|
241
|
+
durationMs: v.durationMs,
|
|
242
|
+
stack,
|
|
243
|
+
bg,
|
|
244
|
+
border
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return { stackCount: Math.max(1, stackRight.length), visualSpans };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function computeDerivedSpans(
|
|
252
|
+
collector: SpanCollector,
|
|
253
|
+
windowMs: number,
|
|
254
|
+
view: ViewState
|
|
255
|
+
): DerivedData {
|
|
256
|
+
const now = performance.now();
|
|
257
|
+
|
|
258
|
+
if (collector.size <= 0) return { ...EMPTY, now };
|
|
259
|
+
|
|
260
|
+
const scan = scanSpans(collector, now);
|
|
261
|
+
const { minStart, maxEnd, maxLane, buckets } = scan;
|
|
262
|
+
const { size } = collector;
|
|
263
|
+
|
|
264
|
+
if (
|
|
265
|
+
!Number.isFinite(minStart) ||
|
|
266
|
+
!Number.isFinite(maxEnd) ||
|
|
267
|
+
maxEnd <= minStart
|
|
268
|
+
) {
|
|
197
269
|
return {
|
|
270
|
+
...EMPTY,
|
|
198
271
|
now,
|
|
199
272
|
size,
|
|
200
273
|
lanes: maxLane + 1,
|
|
201
|
-
minStart,
|
|
202
|
-
maxEnd,
|
|
203
|
-
range: fullRange,
|
|
204
|
-
viewStart,
|
|
205
|
-
viewEnd,
|
|
206
|
-
viewRange,
|
|
207
274
|
buckets,
|
|
208
|
-
laneData
|
|
209
|
-
ticks
|
|
275
|
+
laneData: []
|
|
210
276
|
};
|
|
211
|
-
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const fullRange = Math.max(1, maxEnd - minStart);
|
|
280
|
+
const win = resolveViewWindow(scan, fullRange, windowMs, view);
|
|
281
|
+
|
|
282
|
+
const laneData: LaneData[] = Array.from({ length: maxLane + 1 }, (_, lane) =>
|
|
283
|
+
buildLaneData(buckets[lane], now, win)
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const ticks = computeTicks(
|
|
287
|
+
win.viewStart,
|
|
288
|
+
win.viewEnd,
|
|
289
|
+
win.viewRange,
|
|
290
|
+
minStart
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
now,
|
|
295
|
+
size,
|
|
296
|
+
lanes: maxLane + 1,
|
|
297
|
+
minStart,
|
|
298
|
+
maxEnd,
|
|
299
|
+
range: fullRange,
|
|
300
|
+
viewStart: win.viewStart,
|
|
301
|
+
viewEnd: win.viewEnd,
|
|
302
|
+
viewRange: win.viewRange,
|
|
303
|
+
buckets,
|
|
304
|
+
laneData,
|
|
305
|
+
ticks
|
|
306
|
+
};
|
|
212
307
|
}
|
|
@@ -8,6 +8,14 @@ export const hashToHue = (str: string): number => {
|
|
|
8
8
|
export const clamp = (v: number, min: number, max: number): number =>
|
|
9
9
|
Math.max(min, Math.min(max, v));
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* How far past the captured data extent the user may zoom out. Without headroom
|
|
13
|
+
* the view range is clamped to the data range, so you hit a wall and "can't zoom
|
|
14
|
+
* out" , especially for very short traces. This lets the trace be pulled back
|
|
15
|
+
* into a smaller cluster with surrounding space.
|
|
16
|
+
*/
|
|
17
|
+
export const MAX_ZOOM_OUT_FACTOR = 8;
|
|
18
|
+
|
|
11
19
|
const NICE_INTERVALS = [
|
|
12
20
|
1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000
|
|
13
21
|
] as const;
|