@bian-womp/spark-workbench 0.2.53 → 0.2.55
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/lib/cjs/index.cjs +383 -292
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/core/AbstractWorkbench.d.ts +2 -0
- package/lib/cjs/src/core/AbstractWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts +9 -2
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/contracts.d.ts +3 -0
- package/lib/cjs/src/core/contracts.d.ts.map +1 -1
- package/lib/cjs/src/core/ui-extensions.d.ts +44 -47
- package/lib/cjs/src/core/ui-extensions.d.ts.map +1 -1
- package/lib/cjs/src/index.d.ts +1 -0
- package/lib/cjs/src/index.d.ts.map +1 -1
- package/lib/cjs/src/misc/DefaultContextMenu.d.ts +2 -12
- package/lib/cjs/src/misc/DefaultContextMenu.d.ts.map +1 -1
- package/lib/cjs/src/misc/NodeContextMenu.d.ts +2 -9
- package/lib/cjs/src/misc/NodeContextMenu.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/ContextMenuHandlers.d.ts +41 -0
- package/lib/cjs/src/misc/context/ContextMenuHandlers.d.ts.map +1 -0
- package/lib/cjs/src/misc/context/ContextMenuHelpers.d.ts +7 -0
- package/lib/cjs/src/misc/context/ContextMenuHelpers.d.ts.map +1 -0
- package/lib/cjs/src/misc/load.d.ts +5 -0
- package/lib/cjs/src/misc/load.d.ts.map +1 -0
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +3 -2
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +1 -0
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +0 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +384 -295
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/core/AbstractWorkbench.d.ts +2 -0
- package/lib/esm/src/core/AbstractWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts +9 -2
- package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/contracts.d.ts +3 -0
- package/lib/esm/src/core/contracts.d.ts.map +1 -1
- package/lib/esm/src/core/ui-extensions.d.ts +44 -47
- package/lib/esm/src/core/ui-extensions.d.ts.map +1 -1
- package/lib/esm/src/index.d.ts +1 -0
- package/lib/esm/src/index.d.ts.map +1 -1
- package/lib/esm/src/misc/DefaultContextMenu.d.ts +2 -12
- package/lib/esm/src/misc/DefaultContextMenu.d.ts.map +1 -1
- package/lib/esm/src/misc/NodeContextMenu.d.ts +2 -9
- package/lib/esm/src/misc/NodeContextMenu.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/context/ContextMenuHandlers.d.ts +41 -0
- package/lib/esm/src/misc/context/ContextMenuHandlers.d.ts.map +1 -0
- package/lib/esm/src/misc/context/ContextMenuHelpers.d.ts +7 -0
- package/lib/esm/src/misc/context/ContextMenuHelpers.d.ts.map +1 -0
- package/lib/esm/src/misc/load.d.ts +5 -0
- package/lib/esm/src/misc/load.d.ts.map +1 -0
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +3 -2
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +1 -0
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +0 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -12,71 +12,75 @@ var isEqual = require('lodash/isEqual');
|
|
|
12
12
|
class DefaultUIExtensionRegistry {
|
|
13
13
|
constructor() {
|
|
14
14
|
this.nodeRenderers = new Map();
|
|
15
|
-
this.portRenderers = new Map();
|
|
16
|
-
this.edgeRenderers = new Map();
|
|
17
15
|
}
|
|
18
16
|
registerNodeRenderer(nodeTypeId, renderer) {
|
|
19
|
-
|
|
17
|
+
if (renderer === undefined) {
|
|
18
|
+
this.nodeRenderers.delete(nodeTypeId);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
this.nodeRenderers.set(nodeTypeId, renderer);
|
|
22
|
+
}
|
|
20
23
|
return this;
|
|
21
24
|
}
|
|
22
25
|
getNodeRenderer(nodeTypeId) {
|
|
23
26
|
return this.nodeRenderers.get(nodeTypeId);
|
|
24
27
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
28
|
+
getAllNodeRenderers() {
|
|
29
|
+
const result = {};
|
|
30
|
+
for (const [nodeTypeId, renderer] of this.nodeRenderers.entries()) {
|
|
31
|
+
result[nodeTypeId] = renderer;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
31
34
|
}
|
|
32
|
-
|
|
33
|
-
this.
|
|
35
|
+
registerIconProvider(provider) {
|
|
36
|
+
this.iconProvider = provider;
|
|
34
37
|
return this;
|
|
35
38
|
}
|
|
36
|
-
|
|
37
|
-
return this.
|
|
39
|
+
getIconProvider() {
|
|
40
|
+
return this.iconProvider;
|
|
38
41
|
}
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
// React Flow renderers
|
|
43
|
+
registerConnectionLineRenderer(renderer) {
|
|
44
|
+
this.connectionLineRenderer = renderer;
|
|
41
45
|
return this;
|
|
42
46
|
}
|
|
43
|
-
|
|
44
|
-
return this.
|
|
47
|
+
getConnectionLineRenderer() {
|
|
48
|
+
return this.connectionLineRenderer;
|
|
45
49
|
}
|
|
46
|
-
|
|
47
|
-
this.
|
|
50
|
+
registerMinimapRenderer(renderer) {
|
|
51
|
+
this.minimapRenderer = renderer;
|
|
48
52
|
return this;
|
|
49
53
|
}
|
|
50
|
-
|
|
51
|
-
return this.
|
|
54
|
+
getMinimapRenderer() {
|
|
55
|
+
return this.minimapRenderer;
|
|
52
56
|
}
|
|
53
|
-
|
|
54
|
-
this.
|
|
57
|
+
registerControlsRenderer(renderer) {
|
|
58
|
+
this.controlsRenderer = renderer;
|
|
55
59
|
return this;
|
|
56
60
|
}
|
|
57
|
-
|
|
58
|
-
return this.
|
|
61
|
+
getControlsRenderer() {
|
|
62
|
+
return this.controlsRenderer;
|
|
59
63
|
}
|
|
60
|
-
|
|
61
|
-
this.
|
|
64
|
+
registerBackgroundRenderer(renderer) {
|
|
65
|
+
this.backgroundRenderer = renderer;
|
|
62
66
|
return this;
|
|
63
67
|
}
|
|
64
|
-
|
|
65
|
-
return this.
|
|
68
|
+
getBackgroundRenderer() {
|
|
69
|
+
return this.backgroundRenderer;
|
|
66
70
|
}
|
|
67
|
-
|
|
68
|
-
this.
|
|
71
|
+
registerDefaultContextMenuRenderer(renderer) {
|
|
72
|
+
this.defaultContextMenuRenderer = renderer;
|
|
69
73
|
return this;
|
|
70
74
|
}
|
|
71
|
-
|
|
72
|
-
return this.
|
|
75
|
+
getDefaultContextMenuRenderer() {
|
|
76
|
+
return this.defaultContextMenuRenderer;
|
|
73
77
|
}
|
|
74
|
-
|
|
75
|
-
this.
|
|
78
|
+
registerNodeContextMenuRenderer(renderer) {
|
|
79
|
+
this.nodeContextMenuRenderer = renderer;
|
|
76
80
|
return this;
|
|
77
81
|
}
|
|
78
|
-
|
|
79
|
-
return this.
|
|
82
|
+
getNodeContextMenuRenderer() {
|
|
83
|
+
return this.nodeContextMenuRenderer;
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
|
|
@@ -86,6 +90,7 @@ class AbstractWorkbench {
|
|
|
86
90
|
this.layout = args.layout;
|
|
87
91
|
this.storage = args.storage;
|
|
88
92
|
this.serializer = args.serializer;
|
|
93
|
+
this.genId = args.genId || sparkGraph.generateId;
|
|
89
94
|
}
|
|
90
95
|
// Expose UI registry to adapters (React Flow, CLI) to allow overrides
|
|
91
96
|
getUI() {
|
|
@@ -114,6 +119,13 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
114
119
|
const { positions } = await this.layout.layout(this.def);
|
|
115
120
|
this.positions = positions;
|
|
116
121
|
}
|
|
122
|
+
const defNodeIds = new Set(this.def.nodes.map((n) => n.nodeId));
|
|
123
|
+
const defEdgeIds = new Set(this.def.edges.map((e) => e.id));
|
|
124
|
+
const filteredPositions = Object.fromEntries(Object.entries(this.positions).filter(([id]) => defNodeIds.has(id)));
|
|
125
|
+
const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
|
|
126
|
+
const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
|
|
127
|
+
this.positions = filteredPositions;
|
|
128
|
+
this.selection = { nodes: filteredNodes, edges: filteredEdges };
|
|
117
129
|
this.emit("graphChanged", { def: this.def });
|
|
118
130
|
this.refreshValidation();
|
|
119
131
|
}
|
|
@@ -157,11 +169,14 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
157
169
|
return { ok: issues.every((i) => i.level !== "error"), issues };
|
|
158
170
|
}
|
|
159
171
|
addNode(node) {
|
|
160
|
-
const id = node.nodeId ??
|
|
172
|
+
const id = node.nodeId ??
|
|
173
|
+
this.genId("n", new Set(this.def.nodes.map((n) => n.nodeId)));
|
|
161
174
|
this.def.nodes.push({
|
|
162
175
|
nodeId: id,
|
|
163
176
|
typeId: node.typeId,
|
|
164
177
|
params: node.params,
|
|
178
|
+
initialInputs: node.initialInputs,
|
|
179
|
+
resolvedHandles: node.resolvedHandles,
|
|
165
180
|
});
|
|
166
181
|
if (node.position)
|
|
167
182
|
this.positions[id] = node.position;
|
|
@@ -183,7 +198,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
183
198
|
this.refreshValidation();
|
|
184
199
|
}
|
|
185
200
|
connect(edge) {
|
|
186
|
-
const id = edge.id ?? this.
|
|
201
|
+
const id = edge.id ?? this.genId("e", new Set(this.def.edges.map((e) => e.id)));
|
|
187
202
|
this.def.edges.push({
|
|
188
203
|
id,
|
|
189
204
|
source: { ...edge.source },
|
|
@@ -230,29 +245,32 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
230
245
|
});
|
|
231
246
|
}
|
|
232
247
|
// Position and selection APIs for React Flow bridge
|
|
233
|
-
setPosition(nodeId, pos) {
|
|
248
|
+
setPosition(nodeId, pos, opts) {
|
|
234
249
|
this.positions[nodeId] = pos;
|
|
235
250
|
this.emit("graphUiChanged", {
|
|
236
251
|
def: this.def,
|
|
237
252
|
change: { type: "moveNode", nodeId, pos },
|
|
253
|
+
commit: !!opts?.commit === true,
|
|
238
254
|
});
|
|
239
255
|
}
|
|
240
|
-
setPositions(map) {
|
|
256
|
+
setPositions(map, opts) {
|
|
241
257
|
this.positions = { ...map };
|
|
242
258
|
this.emit("graphUiChanged", {
|
|
243
259
|
def: this.def,
|
|
244
260
|
change: { type: "moveNodes" },
|
|
261
|
+
commit: opts?.commit,
|
|
245
262
|
});
|
|
246
263
|
}
|
|
247
264
|
getPositions() {
|
|
248
265
|
return { ...this.positions };
|
|
249
266
|
}
|
|
250
|
-
setSelection(sel) {
|
|
267
|
+
setSelection(sel, opts) {
|
|
251
268
|
this.selection = { nodes: [...sel.nodes], edges: [...sel.edges] };
|
|
252
269
|
this.emit("selectionChanged", this.selection);
|
|
253
270
|
this.emit("graphUiChanged", {
|
|
254
271
|
def: this.def,
|
|
255
272
|
change: { type: "selection" },
|
|
273
|
+
commit: opts?.commit,
|
|
256
274
|
});
|
|
257
275
|
}
|
|
258
276
|
getSelection() {
|
|
@@ -261,21 +279,31 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
261
279
|
edges: [...this.selection.edges],
|
|
262
280
|
};
|
|
263
281
|
}
|
|
264
|
-
setViewport(viewport) {
|
|
282
|
+
setViewport(viewport, opts) {
|
|
265
283
|
this.viewport = { ...viewport };
|
|
284
|
+
this.emit("graphUiChanged", {
|
|
285
|
+
def: this.def,
|
|
286
|
+
change: { type: "viewport" },
|
|
287
|
+
commit: opts?.commit,
|
|
288
|
+
});
|
|
266
289
|
}
|
|
267
290
|
getViewport() {
|
|
268
291
|
return this.viewport ? { ...this.viewport } : null;
|
|
269
292
|
}
|
|
270
293
|
getUIState() {
|
|
294
|
+
const defNodeIds = new Set(this.def.nodes.map((n) => n.nodeId));
|
|
295
|
+
const defEdgeIds = new Set(this.def.edges.map((e) => e.id));
|
|
296
|
+
const filteredPositions = Object.fromEntries(Object.entries(this.positions).filter(([id]) => defNodeIds.has(id)));
|
|
297
|
+
const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
|
|
298
|
+
const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
|
|
271
299
|
return {
|
|
272
|
-
positions: Object.keys(
|
|
273
|
-
?
|
|
300
|
+
positions: Object.keys(filteredPositions).length > 0
|
|
301
|
+
? filteredPositions
|
|
274
302
|
: undefined,
|
|
275
|
-
selection:
|
|
303
|
+
selection: filteredNodes.length > 0 || filteredEdges.length > 0
|
|
276
304
|
? {
|
|
277
|
-
nodes:
|
|
278
|
-
edges:
|
|
305
|
+
nodes: filteredNodes,
|
|
306
|
+
edges: filteredEdges,
|
|
279
307
|
}
|
|
280
308
|
: undefined,
|
|
281
309
|
viewport: this.viewport ? { ...this.viewport } : undefined,
|
|
@@ -311,9 +339,6 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
311
339
|
for (const h of Array.from(set))
|
|
312
340
|
h(payload);
|
|
313
341
|
}
|
|
314
|
-
generateId(prefix) {
|
|
315
|
-
return `${prefix}${Math.random().toString(36).slice(2, 8)}`;
|
|
316
|
-
}
|
|
317
342
|
}
|
|
318
343
|
|
|
319
344
|
class CLIWorkbench {
|
|
@@ -445,6 +470,18 @@ class AbstractGraphRunner {
|
|
|
445
470
|
}
|
|
446
471
|
}
|
|
447
472
|
}
|
|
473
|
+
getInputDefaults(def) {
|
|
474
|
+
const out = {};
|
|
475
|
+
for (const n of def.nodes) {
|
|
476
|
+
const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
|
|
477
|
+
const graphDefaults = n.initialInputs ?? {};
|
|
478
|
+
const merged = { ...dynDefaults, ...graphDefaults };
|
|
479
|
+
if (Object.keys(merged).length > 0) {
|
|
480
|
+
out[n.nodeId] = merged;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return out;
|
|
484
|
+
}
|
|
448
485
|
on(event, handler) {
|
|
449
486
|
if (!this.listeners.has(event))
|
|
450
487
|
this.listeners.set(event, new Set());
|
|
@@ -606,16 +643,6 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
606
643
|
}
|
|
607
644
|
return out;
|
|
608
645
|
}
|
|
609
|
-
getInputDefaults(def) {
|
|
610
|
-
const out = {};
|
|
611
|
-
for (const n of def.nodes) {
|
|
612
|
-
const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
|
|
613
|
-
if (Object.keys(dynDefaults).length > 0) {
|
|
614
|
-
out[n.nodeId] = dynDefaults;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
return out;
|
|
618
|
-
}
|
|
619
646
|
async snapshotFull() {
|
|
620
647
|
const def = undefined; // UI will supply def/positions on download for local
|
|
621
648
|
const inputs = this.getInputs(this.runtime
|
|
@@ -643,11 +670,25 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
643
670
|
if (payload.def)
|
|
644
671
|
this.build(payload.def);
|
|
645
672
|
this.setEnvironment?.(payload.environment || {}, { merge: false });
|
|
646
|
-
|
|
673
|
+
this.hydrateSnapshotFull(payload);
|
|
674
|
+
}
|
|
675
|
+
hydrateSnapshotFull(snapshot) {
|
|
676
|
+
// Hydrate via runtime for exact restore (this emits events on runtime emitter)
|
|
647
677
|
this.runtime?.hydrate({
|
|
648
|
-
inputs:
|
|
649
|
-
outputs:
|
|
678
|
+
inputs: snapshot.inputs || {},
|
|
679
|
+
outputs: snapshot.outputs || {},
|
|
650
680
|
});
|
|
681
|
+
// Also emit directly from runner to ensure UI gets events even if engine isn't running
|
|
682
|
+
for (const [nodeId, map] of Object.entries(snapshot.inputs || {})) {
|
|
683
|
+
for (const [handle, value] of Object.entries(map || {})) {
|
|
684
|
+
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
for (const [nodeId, map] of Object.entries(snapshot.outputs || {})) {
|
|
688
|
+
for (const [handle, value] of Object.entries(map || {})) {
|
|
689
|
+
this.emit("value", { nodeId, handle, value, io: "output" });
|
|
690
|
+
}
|
|
691
|
+
}
|
|
651
692
|
}
|
|
652
693
|
dispose() {
|
|
653
694
|
super.dispose();
|
|
@@ -842,10 +883,13 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
842
883
|
// Auto-fetch registry on first connection (only once)
|
|
843
884
|
if (!this.registryFetched && !this.registryFetching) {
|
|
844
885
|
// Log loading state (UI can listen to transport status for loading indication)
|
|
845
|
-
console.info("Loading registry from remote...");
|
|
846
|
-
this.fetchRegistry(client)
|
|
886
|
+
console.info("[RemoteGraphRunner] Loading registry from remote...");
|
|
887
|
+
this.fetchRegistry(client)
|
|
888
|
+
.then(() => {
|
|
889
|
+
console.info("[RemoteGraphRunner] Loaded registry from remote");
|
|
890
|
+
})
|
|
891
|
+
.catch((err) => {
|
|
847
892
|
console.error("[RemoteGraphRunner] Failed to fetch registry:", err);
|
|
848
|
-
// Error handling is done inside fetchRegistry, but we catch unhandled rejections
|
|
849
893
|
});
|
|
850
894
|
}
|
|
851
895
|
// Clear promise on success
|
|
@@ -1201,16 +1245,6 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1201
1245
|
}
|
|
1202
1246
|
return out;
|
|
1203
1247
|
}
|
|
1204
|
-
getInputDefaults(def) {
|
|
1205
|
-
const out = {};
|
|
1206
|
-
for (const n of def.nodes) {
|
|
1207
|
-
const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
|
|
1208
|
-
if (Object.keys(dynDefaults).length > 0) {
|
|
1209
|
-
out[n.nodeId] = dynDefaults;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
return out;
|
|
1213
|
-
}
|
|
1214
1248
|
dispose() {
|
|
1215
1249
|
// Idempotent: allow multiple calls safely
|
|
1216
1250
|
if (this.disposed)
|
|
@@ -1479,10 +1513,10 @@ function useWorkbenchBridge(wb) {
|
|
|
1479
1513
|
});
|
|
1480
1514
|
}, [wb]);
|
|
1481
1515
|
const onNodesChange = React.useCallback((changes) => {
|
|
1482
|
-
// Apply position updates
|
|
1516
|
+
// Apply position updates continuously, but mark commit only on drag end
|
|
1483
1517
|
changes.forEach((c) => {
|
|
1484
1518
|
if (c.type === "position" && c.position) {
|
|
1485
|
-
wb.setPosition(c.id, c.position);
|
|
1519
|
+
wb.setPosition(c.id, c.position, { commit: !c.dragging });
|
|
1486
1520
|
}
|
|
1487
1521
|
});
|
|
1488
1522
|
// Derive next node selection from change set
|
|
@@ -1956,6 +1990,99 @@ function getHandleClassName(args) {
|
|
|
1956
1990
|
return cx("!w-3 !h-3 !bg-white !dark:bg-stone-900", borderColor, kind === "output" && "!rounded-none");
|
|
1957
1991
|
}
|
|
1958
1992
|
|
|
1993
|
+
function generateTimestamp() {
|
|
1994
|
+
const d = new Date();
|
|
1995
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
1996
|
+
return `${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
|
|
1997
|
+
}
|
|
1998
|
+
function downloadJSON(payload, filename) {
|
|
1999
|
+
const pretty = JSON.stringify(payload, null, 2);
|
|
2000
|
+
const blob = new Blob([pretty], { type: "application/json" });
|
|
2001
|
+
const url = URL.createObjectURL(blob);
|
|
2002
|
+
const a = document.createElement("a");
|
|
2003
|
+
a.href = url;
|
|
2004
|
+
a.download = filename;
|
|
2005
|
+
document.body.appendChild(a);
|
|
2006
|
+
a.click();
|
|
2007
|
+
a.remove();
|
|
2008
|
+
URL.revokeObjectURL(url);
|
|
2009
|
+
}
|
|
2010
|
+
function isSnapshotPayload(parsed) {
|
|
2011
|
+
return (parsed !== null &&
|
|
2012
|
+
typeof parsed === "object" &&
|
|
2013
|
+
("def" in parsed ||
|
|
2014
|
+
"inputs" in parsed ||
|
|
2015
|
+
"outputs" in parsed ||
|
|
2016
|
+
"environment" in parsed));
|
|
2017
|
+
}
|
|
2018
|
+
async function download(wb, runner) {
|
|
2019
|
+
try {
|
|
2020
|
+
const def = wb.export();
|
|
2021
|
+
const uiState = wb.getUIState();
|
|
2022
|
+
let snapshot;
|
|
2023
|
+
if (runner.isRunning()) {
|
|
2024
|
+
const fullSnapshot = await runner.snapshotFull();
|
|
2025
|
+
snapshot = {
|
|
2026
|
+
...fullSnapshot,
|
|
2027
|
+
def,
|
|
2028
|
+
extData: {
|
|
2029
|
+
...(fullSnapshot.extData || {}),
|
|
2030
|
+
ui: uiState,
|
|
2031
|
+
},
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
else {
|
|
2035
|
+
const inputs = runner.getInputs(def);
|
|
2036
|
+
snapshot = {
|
|
2037
|
+
def,
|
|
2038
|
+
inputs,
|
|
2039
|
+
outputs: {},
|
|
2040
|
+
environment: {},
|
|
2041
|
+
extData: { ui: uiState },
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
downloadJSON(snapshot, `spark-snapshot-${generateTimestamp()}.json`);
|
|
2045
|
+
}
|
|
2046
|
+
catch (err) {
|
|
2047
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2048
|
+
throw new Error(`Failed to download snapshot: ${message}`);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
async function upload(parsed, wb, runner) {
|
|
2052
|
+
if (!isSnapshotPayload(parsed)) {
|
|
2053
|
+
throw new Error("Invalid snapshot format - expected RuntimeSnapshotFull");
|
|
2054
|
+
}
|
|
2055
|
+
const def = parsed.def;
|
|
2056
|
+
const environment = parsed.environment || {};
|
|
2057
|
+
const inputs = parsed.inputs || {};
|
|
2058
|
+
const outputs = parsed.outputs || {};
|
|
2059
|
+
const extData = parsed.extData || {};
|
|
2060
|
+
if (!def) {
|
|
2061
|
+
throw new Error("Graph definition is empty");
|
|
2062
|
+
}
|
|
2063
|
+
await wb.load(def);
|
|
2064
|
+
if (extData.ui && typeof extData.ui === "object") {
|
|
2065
|
+
wb.setUIState(extData.ui);
|
|
2066
|
+
}
|
|
2067
|
+
if (runner.isRunning()) {
|
|
2068
|
+
await runner.applySnapshotFull({
|
|
2069
|
+
def,
|
|
2070
|
+
environment,
|
|
2071
|
+
inputs,
|
|
2072
|
+
outputs,
|
|
2073
|
+
extData,
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
else {
|
|
2077
|
+
runner.build(wb.export());
|
|
2078
|
+
if (inputs && typeof inputs === "object") {
|
|
2079
|
+
for (const [nodeId, map] of Object.entries(inputs)) {
|
|
2080
|
+
runner.setInputs(nodeId, map);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
|
|
1959
2086
|
const WorkbenchContext = React.createContext(null);
|
|
1960
2087
|
function useWorkbenchContext() {
|
|
1961
2088
|
const ctx = React.useContext(WorkbenchContext);
|
|
@@ -2141,7 +2268,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2141
2268
|
}
|
|
2142
2269
|
curX += maxWidth + H_GAP;
|
|
2143
2270
|
}
|
|
2144
|
-
wb.setPositions(pos);
|
|
2271
|
+
wb.setPositions(pos, { commit: true });
|
|
2145
2272
|
}, [wb, registry, overrides?.getDefaultNodeSize]);
|
|
2146
2273
|
const updateEdgeType = React.useCallback((edgeId, typeId) => wb.updateEdgeType(edgeId, typeId), [wb]);
|
|
2147
2274
|
const triggerExternal = React.useCallback((nodeId, event) => runner.triggerExternal(nodeId, event), [runner]);
|
|
@@ -3098,15 +3225,13 @@ function DefaultNodeContent({ data, isConnectable, }) {
|
|
|
3098
3225
|
} })] }));
|
|
3099
3226
|
}
|
|
3100
3227
|
|
|
3101
|
-
function DefaultContextMenu({ open, clientPos,
|
|
3102
|
-
const { registry } = useWorkbenchContext();
|
|
3228
|
+
function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, }) {
|
|
3103
3229
|
const rf = react.useReactFlow();
|
|
3104
|
-
const ids = Array.from(registry.nodes.keys());
|
|
3105
3230
|
const [query, setQuery] = React.useState("");
|
|
3106
3231
|
const q = query.trim().toLowerCase();
|
|
3107
3232
|
const filteredIds = q
|
|
3108
|
-
?
|
|
3109
|
-
:
|
|
3233
|
+
? nodeIds.filter((id) => id.toLowerCase().includes(q))
|
|
3234
|
+
: nodeIds;
|
|
3110
3235
|
const root = { __children: {} };
|
|
3111
3236
|
for (const id of filteredIds) {
|
|
3112
3237
|
const parts = id.split(".");
|
|
@@ -3130,11 +3255,11 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3130
3255
|
if (!ref.current)
|
|
3131
3256
|
return;
|
|
3132
3257
|
if (!ref.current.contains(e.target))
|
|
3133
|
-
onClose();
|
|
3258
|
+
handlers.onClose();
|
|
3134
3259
|
};
|
|
3135
3260
|
const onKey = (e) => {
|
|
3136
3261
|
if (e.key === "Escape")
|
|
3137
|
-
onClose();
|
|
3262
|
+
handlers.onClose();
|
|
3138
3263
|
};
|
|
3139
3264
|
window.addEventListener("mousedown", onDown, true);
|
|
3140
3265
|
window.addEventListener("keydown", onKey);
|
|
@@ -3142,7 +3267,7 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3142
3267
|
window.removeEventListener("mousedown", onDown, true);
|
|
3143
3268
|
window.removeEventListener("keydown", onKey);
|
|
3144
3269
|
};
|
|
3145
|
-
}, [open,
|
|
3270
|
+
}, [open, handlers]);
|
|
3146
3271
|
// Focus search input when menu opens
|
|
3147
3272
|
const inputRef = React.useRef(null);
|
|
3148
3273
|
React.useEffect(() => {
|
|
@@ -3161,8 +3286,8 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3161
3286
|
const handleClick = (typeId) => {
|
|
3162
3287
|
// project() is deprecated; use screenToFlowPosition for screen coordinates
|
|
3163
3288
|
const p = rf.screenToFlowPosition({ x: clientPos.x, y: clientPos.y });
|
|
3164
|
-
|
|
3165
|
-
onClose();
|
|
3289
|
+
handlers.onAddNode(typeId, { position: p });
|
|
3290
|
+
handlers.onClose();
|
|
3166
3291
|
};
|
|
3167
3292
|
const renderTree = (tree, path = []) => {
|
|
3168
3293
|
const entries = Object.entries(tree?.__children ?? {}).sort((a, b) => a[0].localeCompare(b[0]));
|
|
@@ -3184,8 +3309,7 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3184
3309
|
}, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node", " ", jsxRuntime.jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsxRuntime.jsx("div", { className: "px-2 pb-1", children: jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Filter nodes...", className: "w-full border border-gray-300 px-2 py-1 text-sm outline-none focus:border-gray-400", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation() }) }), jsxRuntime.jsx("div", { className: "max-h-60 overflow-auto", children: totalCount > 0 ? (renderTree(root)) : (jsxRuntime.jsx("div", { className: "px-3 py-2 text-gray-400", children: "No matches" })) })] }));
|
|
3185
3310
|
}
|
|
3186
3311
|
|
|
3187
|
-
function NodeContextMenu({ open, clientPos, nodeId,
|
|
3188
|
-
const { wb, runner, engineKind, registry, outputsMap, outputTypesMap } = useWorkbenchContext();
|
|
3312
|
+
function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeableOutputs, }) {
|
|
3189
3313
|
const ref = React.useRef(null);
|
|
3190
3314
|
// outside click + ESC
|
|
3191
3315
|
React.useEffect(() => {
|
|
@@ -3195,11 +3319,11 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3195
3319
|
if (!ref.current)
|
|
3196
3320
|
return;
|
|
3197
3321
|
if (!ref.current.contains(e.target))
|
|
3198
|
-
onClose();
|
|
3322
|
+
handlers.onClose();
|
|
3199
3323
|
};
|
|
3200
3324
|
const onKey = (e) => {
|
|
3201
3325
|
if (e.key === "Escape")
|
|
3202
|
-
onClose();
|
|
3326
|
+
handlers.onClose();
|
|
3203
3327
|
};
|
|
3204
3328
|
window.addEventListener("mousedown", onDown, true);
|
|
3205
3329
|
window.addEventListener("keydown", onKey);
|
|
@@ -3207,48 +3331,26 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3207
3331
|
window.removeEventListener("mousedown", onDown, true);
|
|
3208
3332
|
window.removeEventListener("keydown", onKey);
|
|
3209
3333
|
};
|
|
3210
|
-
}, [open,
|
|
3334
|
+
}, [open, handlers]);
|
|
3211
3335
|
React.useEffect(() => {
|
|
3212
3336
|
if (open)
|
|
3213
3337
|
ref.current?.focus();
|
|
3214
3338
|
}, [open]);
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
const base = tId.slice(0, -2);
|
|
3231
|
-
const tArr = registry.types.get(tId);
|
|
3232
|
-
const tElem = registry.types.get(base);
|
|
3233
|
-
const arrT = tArr?.bakeTarget;
|
|
3234
|
-
const elemT = tElem?.bakeTarget;
|
|
3235
|
-
if ((arrT && registry.nodes.has(arrT.nodeTypeId)) ||
|
|
3236
|
-
(elemT && registry.nodes.has(elemT.nodeTypeId)))
|
|
3237
|
-
out.push(h);
|
|
3238
|
-
}
|
|
3239
|
-
else {
|
|
3240
|
-
const t = registry.types.get(tId);
|
|
3241
|
-
const bt = t?.bakeTarget;
|
|
3242
|
-
if (bt && registry.nodes.has(bt.nodeTypeId))
|
|
3243
|
-
out.push(h);
|
|
3244
|
-
}
|
|
3245
|
-
}
|
|
3246
|
-
return out;
|
|
3247
|
-
}
|
|
3248
|
-
catch {
|
|
3249
|
-
return [];
|
|
3250
|
-
}
|
|
3251
|
-
};
|
|
3339
|
+
if (!open || !clientPos || !nodeId)
|
|
3340
|
+
return null;
|
|
3341
|
+
// clamp
|
|
3342
|
+
const MENU_MIN_WIDTH = 180;
|
|
3343
|
+
const PADDING = 16;
|
|
3344
|
+
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
3345
|
+
(MENU_MIN_WIDTH + PADDING));
|
|
3346
|
+
const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
|
|
3347
|
+
return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
|
|
3348
|
+
e.preventDefault();
|
|
3349
|
+
e.stopPropagation();
|
|
3350
|
+
}, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDelete, children: "Delete" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDuplicate, children: "Duplicate" }), canRunPull && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunPull, children: "Run (pull)" })), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), bakeableOutputs.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxRuntime.jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h))), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" })] })), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopyId, children: "Copy Node ID" })] }));
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose) {
|
|
3252
3354
|
const doBake = async (handleId) => {
|
|
3253
3355
|
try {
|
|
3254
3356
|
const typeId = outputTypesMap?.[nodeId]?.[handleId];
|
|
@@ -3281,7 +3383,6 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3281
3383
|
const newId = wb.addNode({
|
|
3282
3384
|
typeId: singleTarget.nodeTypeId,
|
|
3283
3385
|
position: { x: pos.x + 180, y: pos.y },
|
|
3284
|
-
params: {},
|
|
3285
3386
|
});
|
|
3286
3387
|
runner.update(wb.export());
|
|
3287
3388
|
await runner.whenIdle();
|
|
@@ -3292,12 +3393,9 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3292
3393
|
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
3293
3394
|
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
3294
3395
|
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3295
|
-
const newId =
|
|
3296
|
-
wb.addNode({
|
|
3297
|
-
nodeId: newId,
|
|
3396
|
+
const newId = wb.addNode({
|
|
3298
3397
|
typeId: arrTarget.nodeTypeId,
|
|
3299
3398
|
position: { x: pos.x + 180, y: pos.y },
|
|
3300
|
-
params: {},
|
|
3301
3399
|
});
|
|
3302
3400
|
runner.update(wb.export());
|
|
3303
3401
|
await runner.whenIdle();
|
|
@@ -3315,14 +3413,11 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3315
3413
|
const DY = 160;
|
|
3316
3414
|
const nodeIds = [];
|
|
3317
3415
|
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3318
|
-
const cv = coercedItems[idx];
|
|
3319
3416
|
const col = idx % COLS;
|
|
3320
3417
|
const row = Math.floor(idx / COLS);
|
|
3321
3418
|
const newId = wb.addNode({
|
|
3322
3419
|
typeId: elemTarget.nodeTypeId,
|
|
3323
3420
|
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
3324
|
-
params: {},
|
|
3325
|
-
initialInputs: { [elemTarget.inputHandle]: structuredClone(cv) },
|
|
3326
3421
|
});
|
|
3327
3422
|
nodeIds.push(newId);
|
|
3328
3423
|
}
|
|
@@ -3340,63 +3435,88 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3340
3435
|
}
|
|
3341
3436
|
catch { }
|
|
3342
3437
|
};
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3438
|
+
return {
|
|
3439
|
+
onDelete: () => {
|
|
3440
|
+
wb.removeNode(nodeId);
|
|
3441
|
+
onClose();
|
|
3442
|
+
},
|
|
3443
|
+
onDuplicate: async () => {
|
|
3444
|
+
const def = wb.export();
|
|
3445
|
+
const n = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3446
|
+
if (!n)
|
|
3447
|
+
return onClose();
|
|
3448
|
+
const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
|
|
3449
|
+
const newId = wb.addNode({
|
|
3450
|
+
typeId: n.typeId,
|
|
3451
|
+
params: n.params,
|
|
3452
|
+
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3453
|
+
initialInputs: n.initialInputs,
|
|
3454
|
+
resolvedHandles: n.resolvedHandles,
|
|
3455
|
+
});
|
|
3456
|
+
await runner.whenIdle();
|
|
3457
|
+
runner.setInputs(newId, { ...runner.getInputs(def)[nodeId] });
|
|
3458
|
+
onClose();
|
|
3459
|
+
},
|
|
3460
|
+
onRunPull: async () => {
|
|
3461
|
+
try {
|
|
3462
|
+
await runner.computeNode(nodeId);
|
|
3463
|
+
}
|
|
3464
|
+
catch { }
|
|
3465
|
+
onClose();
|
|
3466
|
+
},
|
|
3467
|
+
onBake: async (handleId) => {
|
|
3468
|
+
await doBake(handleId);
|
|
3469
|
+
onClose();
|
|
3470
|
+
},
|
|
3471
|
+
onCopyId: async () => {
|
|
3472
|
+
try {
|
|
3473
|
+
await navigator.clipboard.writeText(nodeId);
|
|
3474
|
+
}
|
|
3475
|
+
catch { }
|
|
3476
|
+
onClose();
|
|
3477
|
+
},
|
|
3478
|
+
onClose,
|
|
3479
|
+
};
|
|
3480
|
+
}
|
|
3481
|
+
function getBakeableOutputs(nodeId, wb, registry, outputTypesMap) {
|
|
3482
|
+
try {
|
|
3349
3483
|
const def = wb.export();
|
|
3350
|
-
const
|
|
3351
|
-
if (!
|
|
3352
|
-
return
|
|
3353
|
-
const
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3484
|
+
const node = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3485
|
+
if (!node)
|
|
3486
|
+
return [];
|
|
3487
|
+
const desc = registry.nodes.get(node.typeId);
|
|
3488
|
+
const handles = Object.keys(desc?.outputs || {});
|
|
3489
|
+
const out = [];
|
|
3490
|
+
for (const h of handles) {
|
|
3491
|
+
const tId = outputTypesMap?.[nodeId]?.[h];
|
|
3492
|
+
if (!tId)
|
|
3493
|
+
continue;
|
|
3494
|
+
if (tId.endsWith("[]")) {
|
|
3495
|
+
const base = tId.slice(0, -2);
|
|
3496
|
+
const tArr = registry.types.get(tId);
|
|
3497
|
+
const tElem = registry.types.get(base);
|
|
3498
|
+
const arrT = tArr?.bakeTarget;
|
|
3499
|
+
const elemT = tElem?.bakeTarget;
|
|
3500
|
+
if ((arrT && registry.nodes.has(arrT.nodeTypeId)) ||
|
|
3501
|
+
(elemT && registry.nodes.has(elemT.nodeTypeId)))
|
|
3502
|
+
out.push(h);
|
|
3503
|
+
}
|
|
3504
|
+
else {
|
|
3505
|
+
const t = registry.types.get(tId);
|
|
3506
|
+
const bt = t?.bakeTarget;
|
|
3507
|
+
if (bt && registry.nodes.has(bt.nodeTypeId))
|
|
3508
|
+
out.push(h);
|
|
3509
|
+
}
|
|
3375
3510
|
}
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
// clamp
|
|
3382
|
-
const MENU_MIN_WIDTH = 180;
|
|
3383
|
-
const PADDING = 16;
|
|
3384
|
-
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
3385
|
-
(MENU_MIN_WIDTH + PADDING));
|
|
3386
|
-
const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
|
|
3387
|
-
const canRunPull = engineKind()?.toString() === "pull";
|
|
3388
|
-
const outs = getBakeableOutputs();
|
|
3389
|
-
return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
|
|
3390
|
-
e.preventDefault();
|
|
3391
|
-
e.stopPropagation();
|
|
3392
|
-
}, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleDelete, children: "Delete" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleDuplicate, children: "Duplicate" }), canRunPull && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleRunPull, children: "Run (pull)" })), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), outs.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), outs.map((h) => (jsxRuntime.jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: async () => {
|
|
3393
|
-
await doBake(h);
|
|
3394
|
-
onClose();
|
|
3395
|
-
}, children: ["Bake: ", h] }, h))), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" })] })), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleCopyId, children: "Copy Node ID" })] }));
|
|
3511
|
+
return out;
|
|
3512
|
+
}
|
|
3513
|
+
catch {
|
|
3514
|
+
return [];
|
|
3515
|
+
}
|
|
3396
3516
|
}
|
|
3397
3517
|
|
|
3398
3518
|
const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
|
|
3399
|
-
const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
|
|
3519
|
+
const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, runner, engineKind, } = useWorkbenchContext();
|
|
3400
3520
|
const nodeValidation = validationByNode;
|
|
3401
3521
|
const edgeValidation = validationByEdge.errors;
|
|
3402
3522
|
// Keep stable references for nodes/edges to avoid unnecessary updates
|
|
@@ -3463,9 +3583,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3463
3583
|
},
|
|
3464
3584
|
}));
|
|
3465
3585
|
const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete, } = useWorkbenchBridge(wb);
|
|
3586
|
+
const ui = wb.getUI();
|
|
3466
3587
|
const { nodeTypes, resolveNodeType } = React.useMemo(() => {
|
|
3467
3588
|
// Build nodeTypes map using UI extension registry
|
|
3468
|
-
const ui = wb.getUI();
|
|
3469
3589
|
const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
|
|
3470
3590
|
const def = wb.export();
|
|
3471
3591
|
const ids = new Set([
|
|
@@ -3487,7 +3607,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3487
3607
|
const resolver = (nodeTypeId) => custom.has(nodeTypeId) ? `spark-${nodeTypeId}` : "spark-default";
|
|
3488
3608
|
return { nodeTypes: types, resolveNodeType: resolver };
|
|
3489
3609
|
// Include uiVersion to recompute when custom renderers are registered
|
|
3490
|
-
}, [wb, registry, uiVersion]);
|
|
3610
|
+
}, [wb, registry, uiVersion, ui]);
|
|
3491
3611
|
const { nodes, edges } = React.useMemo(() => {
|
|
3492
3612
|
const def = wb.export();
|
|
3493
3613
|
const sel = wb.getSelection();
|
|
@@ -3651,19 +3771,63 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3651
3771
|
setNodeMenuOpen(false);
|
|
3652
3772
|
}
|
|
3653
3773
|
};
|
|
3654
|
-
const addNodeAt = React.useCallback((typeId,
|
|
3655
|
-
wb.addNode({
|
|
3656
|
-
|
|
3774
|
+
const addNodeAt = React.useCallback(async (typeId, opts) => {
|
|
3775
|
+
const nodeId = wb.addNode({
|
|
3776
|
+
typeId,
|
|
3777
|
+
initialInputs: opts.initialInputs,
|
|
3778
|
+
position: opts.position,
|
|
3779
|
+
});
|
|
3780
|
+
if (opts.inputs) {
|
|
3781
|
+
runner.update(wb.export());
|
|
3782
|
+
await runner.whenIdle();
|
|
3783
|
+
runner.setInputs(nodeId, opts.inputs);
|
|
3784
|
+
}
|
|
3785
|
+
}, [wb, runner]);
|
|
3657
3786
|
const onCloseMenu = React.useCallback(() => {
|
|
3658
3787
|
setMenuOpen(false);
|
|
3659
3788
|
}, []);
|
|
3660
3789
|
const onCloseNodeMenu = React.useCallback(() => {
|
|
3661
3790
|
setNodeMenuOpen(false);
|
|
3662
3791
|
}, []);
|
|
3792
|
+
const nodeIds = React.useMemo(() => Array.from(registry.nodes.keys()), [registry]);
|
|
3793
|
+
const defaultContextMenuHandlers = React.useMemo(() => ({
|
|
3794
|
+
onAddNode: addNodeAt,
|
|
3795
|
+
onClose: onCloseMenu,
|
|
3796
|
+
}), [addNodeAt, onCloseMenu]);
|
|
3797
|
+
const nodeContextMenuHandlers = React.useMemo(() => {
|
|
3798
|
+
if (!nodeAtMenu)
|
|
3799
|
+
return null;
|
|
3800
|
+
return createNodeContextMenuHandlers(nodeAtMenu, wb, runner, registry, outputsMap, outputTypesMap, onCloseNodeMenu);
|
|
3801
|
+
}, [
|
|
3802
|
+
nodeAtMenu,
|
|
3803
|
+
wb,
|
|
3804
|
+
runner,
|
|
3805
|
+
registry,
|
|
3806
|
+
outputsMap,
|
|
3807
|
+
outputTypesMap,
|
|
3808
|
+
onCloseNodeMenu,
|
|
3809
|
+
]);
|
|
3810
|
+
const canRunPull = React.useMemo(() => engineKind()?.toString() === "pull", [engineKind]);
|
|
3811
|
+
const bakeableOutputs = React.useMemo(() => {
|
|
3812
|
+
if (!nodeAtMenu)
|
|
3813
|
+
return [];
|
|
3814
|
+
return getBakeableOutputs(nodeAtMenu, wb, registry, outputTypesMap);
|
|
3815
|
+
}, [nodeAtMenu, wb, registry, outputTypesMap]);
|
|
3816
|
+
// Get custom renderers from UI extension registry (reactive to uiVersion changes)
|
|
3817
|
+
const { BackgroundRenderer, MinimapRenderer, ControlsRenderer, DefaultContextMenuRenderer, NodeContextMenuRenderer, connectionLineRenderer, } = React.useMemo(() => {
|
|
3818
|
+
return {
|
|
3819
|
+
BackgroundRenderer: ui.getBackgroundRenderer(),
|
|
3820
|
+
MinimapRenderer: ui.getMinimapRenderer(),
|
|
3821
|
+
ControlsRenderer: ui.getControlsRenderer(),
|
|
3822
|
+
DefaultContextMenuRenderer: ui.getDefaultContextMenuRenderer(),
|
|
3823
|
+
NodeContextMenuRenderer: ui.getNodeContextMenuRenderer(),
|
|
3824
|
+
connectionLineRenderer: ui.getConnectionLineRenderer(),
|
|
3825
|
+
};
|
|
3826
|
+
}, [ui, uiVersion]);
|
|
3663
3827
|
const onMoveEnd = React.useCallback(() => {
|
|
3664
3828
|
if (rfInstanceRef.current) {
|
|
3665
3829
|
const viewport = rfInstanceRef.current.getViewport();
|
|
3666
|
-
wb.setViewport({ x: viewport.x, y: viewport.y, zoom: viewport.zoom });
|
|
3830
|
+
wb.setViewport({ x: viewport.x, y: viewport.y, zoom: viewport.zoom }, { commit: true });
|
|
3667
3831
|
}
|
|
3668
3832
|
}, [wb]);
|
|
3669
3833
|
const viewportRef = React.useRef(null);
|
|
@@ -3684,7 +3848,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3684
3848
|
});
|
|
3685
3849
|
}
|
|
3686
3850
|
});
|
|
3687
|
-
return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsx(react.ReactFlowProvider, { children: jsxRuntime.jsxs(react.ReactFlow, { nodes: throttled.nodes, edges: throttled.edges, nodeTypes: nodeTypes, selectionOnDrag: true, onInit: (inst) => {
|
|
3851
|
+
return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsx(react.ReactFlowProvider, { children: jsxRuntime.jsxs(react.ReactFlow, { nodes: throttled.nodes, edges: throttled.edges, nodeTypes: nodeTypes, connectionLineComponent: connectionLineRenderer, selectionOnDrag: true, onInit: (inst) => {
|
|
3688
3852
|
rfInstanceRef.current = inst;
|
|
3689
3853
|
const savedViewport = wb.getViewport();
|
|
3690
3854
|
if (savedViewport) {
|
|
@@ -3695,7 +3859,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3695
3859
|
zoom: savedViewport.zoom,
|
|
3696
3860
|
});
|
|
3697
3861
|
}
|
|
3698
|
-
}, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 }), jsxRuntime.jsx(react.MiniMap, {}), jsxRuntime.jsx(react.Controls, {}), jsxRuntime.jsx(
|
|
3862
|
+
}, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [BackgroundRenderer ? (jsxRuntime.jsx(BackgroundRenderer, {})) : (jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 })), MinimapRenderer ? jsxRuntime.jsx(MinimapRenderer, {}) : jsxRuntime.jsx(react.MiniMap, {}), ControlsRenderer ? jsxRuntime.jsx(ControlsRenderer, {}) : jsxRuntime.jsx(react.Controls, {}), DefaultContextMenuRenderer ? (jsxRuntime.jsx(DefaultContextMenuRenderer, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds })) : (jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds })), !!nodeAtMenu &&
|
|
3863
|
+
nodeContextMenuHandlers &&
|
|
3864
|
+
(NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs })) : (jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs })))] }) }) }));
|
|
3699
3865
|
});
|
|
3700
3866
|
|
|
3701
3867
|
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
|
|
@@ -3830,9 +3996,20 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3830
3996
|
}
|
|
3831
3997
|
catch { }
|
|
3832
3998
|
});
|
|
3999
|
+
const off3 = wb.on("graphUiChanged", (evt) => {
|
|
4000
|
+
if (!evt.commit)
|
|
4001
|
+
return;
|
|
4002
|
+
try {
|
|
4003
|
+
const cur = wb.export();
|
|
4004
|
+
const inputs = runner.getInputs(cur);
|
|
4005
|
+
onChange({ def: cur, inputs });
|
|
4006
|
+
}
|
|
4007
|
+
catch { }
|
|
4008
|
+
});
|
|
3833
4009
|
return () => {
|
|
3834
4010
|
off1();
|
|
3835
4011
|
off2();
|
|
4012
|
+
off3();
|
|
3836
4013
|
};
|
|
3837
4014
|
}, [wb, runner, onChange]);
|
|
3838
4015
|
const applyExample = React.useCallback(async (key) => {
|
|
@@ -3868,24 +4045,9 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3868
4045
|
setRegistry,
|
|
3869
4046
|
backendKind,
|
|
3870
4047
|
]);
|
|
3871
|
-
const
|
|
4048
|
+
const download$1 = React.useCallback(async () => {
|
|
3872
4049
|
try {
|
|
3873
|
-
|
|
3874
|
-
const inputs = runner.getInputs(def);
|
|
3875
|
-
const payload = { def, inputs };
|
|
3876
|
-
const pretty = JSON.stringify(payload, null, 2);
|
|
3877
|
-
const blob = new Blob([pretty], { type: "application/json" });
|
|
3878
|
-
const url = URL.createObjectURL(blob);
|
|
3879
|
-
const a = document.createElement("a");
|
|
3880
|
-
const d = new Date();
|
|
3881
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
3882
|
-
const ts = `${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
|
|
3883
|
-
a.href = url;
|
|
3884
|
-
a.download = `spark-graph-${ts}.json`;
|
|
3885
|
-
document.body.appendChild(a);
|
|
3886
|
-
a.click();
|
|
3887
|
-
a.remove();
|
|
3888
|
-
URL.revokeObjectURL(url);
|
|
4050
|
+
await download(wb, runner);
|
|
3889
4051
|
}
|
|
3890
4052
|
catch (err) {
|
|
3891
4053
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -3899,51 +4061,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3899
4061
|
return;
|
|
3900
4062
|
const text = await file.text();
|
|
3901
4063
|
const parsed = JSON.parse(text);
|
|
3902
|
-
|
|
3903
|
-
const isSnapshot = parsed &&
|
|
3904
|
-
typeof parsed === "object" &&
|
|
3905
|
-
(parsed.def || parsed.inputs || parsed.outputs || parsed.environment);
|
|
3906
|
-
if (isSnapshot) {
|
|
3907
|
-
const def = parsed.def;
|
|
3908
|
-
const positions = parsed.positions || {};
|
|
3909
|
-
const environment = parsed.environment || {};
|
|
3910
|
-
const inputs = parsed.inputs || {};
|
|
3911
|
-
if (def && runner.isRunning()) {
|
|
3912
|
-
// Remote exact restore path
|
|
3913
|
-
await runner.applySnapshotFull({
|
|
3914
|
-
def,
|
|
3915
|
-
environment,
|
|
3916
|
-
inputs,
|
|
3917
|
-
outputs: parsed.outputs || {},
|
|
3918
|
-
});
|
|
3919
|
-
await wb.load(def);
|
|
3920
|
-
if (positions && typeof positions === "object")
|
|
3921
|
-
wb.setPositions(positions);
|
|
3922
|
-
}
|
|
3923
|
-
else if (!runner.isRunning()) {
|
|
3924
|
-
alert("Engine is not running");
|
|
3925
|
-
}
|
|
3926
|
-
else {
|
|
3927
|
-
alert("Graph definition is empty");
|
|
3928
|
-
}
|
|
3929
|
-
}
|
|
3930
|
-
else {
|
|
3931
|
-
const def = parsed?.def ?? parsed;
|
|
3932
|
-
const inputs = parsed?.inputs ?? {};
|
|
3933
|
-
await wb.load(def);
|
|
3934
|
-
try {
|
|
3935
|
-
runner.build(wb.export());
|
|
3936
|
-
}
|
|
3937
|
-
catch { }
|
|
3938
|
-
if (inputs && typeof inputs === "object") {
|
|
3939
|
-
for (const [nodeId, map] of Object.entries(inputs)) {
|
|
3940
|
-
try {
|
|
3941
|
-
runner.setInputs(nodeId, map);
|
|
3942
|
-
}
|
|
3943
|
-
catch { }
|
|
3944
|
-
}
|
|
3945
|
-
}
|
|
3946
|
-
}
|
|
4064
|
+
await upload(parsed, wb, runner);
|
|
3947
4065
|
}
|
|
3948
4066
|
catch (err) {
|
|
3949
4067
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -4199,36 +4317,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
4199
4317
|
// Normal change when not running
|
|
4200
4318
|
onEngineChange?.(kind);
|
|
4201
4319
|
}
|
|
4202
|
-
}, children: [jsxRuntime.jsx("option", { value: "", children: "Select Engine\u2026" }), jsxRuntime.jsx("option", { value: "push", children: "Push" }), jsxRuntime.jsx("option", { value: "batched", children: "Batched" }), jsxRuntime.jsx("option", { value: "pull", children: "Pull" }), jsxRuntime.jsx("option", { value: "hybrid", children: "Hybrid" }), jsxRuntime.jsx("option", { value: "step", children: "Step" })] }), engineKind === "step" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !isGraphRunning, title: "Step", children: jsxRuntime.jsx(react$1.PlayPauseIcon, { size: 24 }) })), engineKind === "batched" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !isGraphRunning, title: "Flush", children: jsxRuntime.jsx(react$1.LightningIcon, { size: 24 }) })), renderStartStopButton(), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsxRuntime.jsx(react$1.TreeStructureIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsxRuntime.jsx(react$1.CornersOutIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick:
|
|
4203
|
-
try {
|
|
4204
|
-
const def = wb.export();
|
|
4205
|
-
const positions = wb.getPositions();
|
|
4206
|
-
const snapshot = await runner.snapshotFull();
|
|
4207
|
-
const payload = {
|
|
4208
|
-
...snapshot,
|
|
4209
|
-
def,
|
|
4210
|
-
positions,
|
|
4211
|
-
schemaVersion: 1,
|
|
4212
|
-
};
|
|
4213
|
-
const pretty = JSON.stringify(payload, null, 2);
|
|
4214
|
-
const blob = new Blob([pretty], { type: "application/json" });
|
|
4215
|
-
const url = URL.createObjectURL(blob);
|
|
4216
|
-
const a = document.createElement("a");
|
|
4217
|
-
const d = new Date();
|
|
4218
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
4219
|
-
const ts = `${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
|
|
4220
|
-
a.href = url;
|
|
4221
|
-
a.download = `spark-snapshot-${ts}.json`;
|
|
4222
|
-
document.body.appendChild(a);
|
|
4223
|
-
a.click();
|
|
4224
|
-
a.remove();
|
|
4225
|
-
URL.revokeObjectURL(url);
|
|
4226
|
-
}
|
|
4227
|
-
catch (err) {
|
|
4228
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4229
|
-
alert(message);
|
|
4230
|
-
}
|
|
4231
|
-
}, children: jsxRuntime.jsx(react$1.DownloadIcon, { size: 24 }) }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsxRuntime.jsx(react$1.UploadIcon, { size: 24 }) }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx(react$1.BugBeetleIcon, { size: 24, weight: debug ? "fill" : undefined })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx(react$1.ListBulletsIcon, { size: 24, weight: showValues ? "fill" : undefined })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement, getDefaultNodeSize: overrides?.getDefaultNodeSize }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, contextPanel: overrides?.contextPanel })] })] }));
|
|
4320
|
+
}, children: [jsxRuntime.jsx("option", { value: "", children: "Select Engine\u2026" }), jsxRuntime.jsx("option", { value: "push", children: "Push" }), jsxRuntime.jsx("option", { value: "batched", children: "Batched" }), jsxRuntime.jsx("option", { value: "pull", children: "Pull" }), jsxRuntime.jsx("option", { value: "hybrid", children: "Hybrid" }), jsxRuntime.jsx("option", { value: "step", children: "Step" })] }), engineKind === "step" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !isGraphRunning, title: "Step", children: jsxRuntime.jsx(react$1.PlayPauseIcon, { size: 24 }) })), engineKind === "batched" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !isGraphRunning, title: "Flush", children: jsxRuntime.jsx(react$1.LightningIcon, { size: 24 }) })), renderStartStopButton(), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsxRuntime.jsx(react$1.TreeStructureIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsxRuntime.jsx(react$1.CornersOutIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsxRuntime.jsx(react$1.DownloadIcon, { size: 24 }) }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsxRuntime.jsx(react$1.UploadIcon, { size: 24 }) }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx(react$1.BugBeetleIcon, { size: 24, weight: debug ? "fill" : undefined })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx(react$1.ListBulletsIcon, { size: 24, weight: showValues ? "fill" : undefined })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement, getDefaultNodeSize: overrides?.getDefaultNodeSize }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, contextPanel: overrides?.contextPanel })] })] }));
|
|
4232
4321
|
}
|
|
4233
4322
|
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
|
|
4234
4323
|
const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
|
|
@@ -4320,6 +4409,7 @@ exports.WorkbenchProvider = WorkbenchProvider;
|
|
|
4320
4409
|
exports.WorkbenchStudio = WorkbenchStudio;
|
|
4321
4410
|
exports.computeEffectiveHandles = computeEffectiveHandles;
|
|
4322
4411
|
exports.countVisibleHandles = countVisibleHandles;
|
|
4412
|
+
exports.download = download;
|
|
4323
4413
|
exports.estimateNodeSize = estimateNodeSize;
|
|
4324
4414
|
exports.formatDataUrlAsLabel = formatDataUrlAsLabel;
|
|
4325
4415
|
exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
|
|
@@ -4331,6 +4421,7 @@ exports.prettyHandle = prettyHandle;
|
|
|
4331
4421
|
exports.resolveOutputDisplay = resolveOutputDisplay;
|
|
4332
4422
|
exports.summarizeDeep = summarizeDeep;
|
|
4333
4423
|
exports.toReactFlow = toReactFlow;
|
|
4424
|
+
exports.upload = upload;
|
|
4334
4425
|
exports.useQueryParamBoolean = useQueryParamBoolean;
|
|
4335
4426
|
exports.useQueryParamString = useQueryParamString;
|
|
4336
4427
|
exports.useThrottledValue = useThrottledValue;
|