@bian-womp/spark-workbench 0.2.54 → 0.2.56
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 +340 -232
- 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 +0 -1
- package/lib/cjs/src/core/InMemoryWorkbench.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/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/NodeHandles.d.ts +1 -3
- package/lib/cjs/src/misc/NodeHandles.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/constants.d.ts +2 -1
- package/lib/cjs/src/misc/constants.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/layout.d.ts +46 -0
- package/lib/cjs/src/misc/layout.d.ts.map +1 -1
- package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
- 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 +337 -234
- 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 +0 -1
- package/lib/esm/src/core/InMemoryWorkbench.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/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/NodeHandles.d.ts +1 -3
- package/lib/esm/src/misc/NodeHandles.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/constants.d.ts +2 -1
- package/lib/esm/src/misc/constants.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/layout.d.ts +46 -0
- package/lib/esm/src/misc/layout.d.ts.map +1 -1
- package/lib/esm/src/misc/mapping.d.ts.map +1 -1
- 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() {
|
|
@@ -164,11 +169,14 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
164
169
|
return { ok: issues.every((i) => i.level !== "error"), issues };
|
|
165
170
|
}
|
|
166
171
|
addNode(node) {
|
|
167
|
-
const id = node.nodeId ??
|
|
172
|
+
const id = node.nodeId ??
|
|
173
|
+
this.genId("n", new Set(this.def.nodes.map((n) => n.nodeId)));
|
|
168
174
|
this.def.nodes.push({
|
|
169
175
|
nodeId: id,
|
|
170
176
|
typeId: node.typeId,
|
|
171
177
|
params: node.params,
|
|
178
|
+
initialInputs: node.initialInputs,
|
|
179
|
+
resolvedHandles: node.resolvedHandles,
|
|
172
180
|
});
|
|
173
181
|
if (node.position)
|
|
174
182
|
this.positions[id] = node.position;
|
|
@@ -190,7 +198,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
190
198
|
this.refreshValidation();
|
|
191
199
|
}
|
|
192
200
|
connect(edge) {
|
|
193
|
-
const id = edge.id ?? this.
|
|
201
|
+
const id = edge.id ?? this.genId("e", new Set(this.def.edges.map((e) => e.id)));
|
|
194
202
|
this.def.edges.push({
|
|
195
203
|
id,
|
|
196
204
|
source: { ...edge.source },
|
|
@@ -331,9 +339,6 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
331
339
|
for (const h of Array.from(set))
|
|
332
340
|
h(payload);
|
|
333
341
|
}
|
|
334
|
-
generateId(prefix) {
|
|
335
|
-
return `${prefix}${Math.random().toString(36).slice(2, 8)}`;
|
|
336
|
-
}
|
|
337
342
|
}
|
|
338
343
|
|
|
339
344
|
class CLIWorkbench {
|
|
@@ -465,6 +470,18 @@ class AbstractGraphRunner {
|
|
|
465
470
|
}
|
|
466
471
|
}
|
|
467
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
|
+
}
|
|
468
485
|
on(event, handler) {
|
|
469
486
|
if (!this.listeners.has(event))
|
|
470
487
|
this.listeners.set(event, new Set());
|
|
@@ -626,16 +643,6 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
626
643
|
}
|
|
627
644
|
return out;
|
|
628
645
|
}
|
|
629
|
-
getInputDefaults(def) {
|
|
630
|
-
const out = {};
|
|
631
|
-
for (const n of def.nodes) {
|
|
632
|
-
const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
|
|
633
|
-
if (Object.keys(dynDefaults).length > 0) {
|
|
634
|
-
out[n.nodeId] = dynDefaults;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
return out;
|
|
638
|
-
}
|
|
639
646
|
async snapshotFull() {
|
|
640
647
|
const def = undefined; // UI will supply def/positions on download for local
|
|
641
648
|
const inputs = this.getInputs(this.runtime
|
|
@@ -663,11 +670,25 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
663
670
|
if (payload.def)
|
|
664
671
|
this.build(payload.def);
|
|
665
672
|
this.setEnvironment?.(payload.environment || {}, { merge: false });
|
|
666
|
-
|
|
673
|
+
this.hydrateSnapshotFull(payload);
|
|
674
|
+
}
|
|
675
|
+
hydrateSnapshotFull(snapshot) {
|
|
676
|
+
// Hydrate via runtime for exact restore (this emits events on runtime emitter)
|
|
667
677
|
this.runtime?.hydrate({
|
|
668
|
-
inputs:
|
|
669
|
-
outputs:
|
|
678
|
+
inputs: snapshot.inputs || {},
|
|
679
|
+
outputs: snapshot.outputs || {},
|
|
670
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
|
+
}
|
|
671
692
|
}
|
|
672
693
|
dispose() {
|
|
673
694
|
super.dispose();
|
|
@@ -1224,16 +1245,6 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1224
1245
|
}
|
|
1225
1246
|
return out;
|
|
1226
1247
|
}
|
|
1227
|
-
getInputDefaults(def) {
|
|
1228
|
-
const out = {};
|
|
1229
|
-
for (const n of def.nodes) {
|
|
1230
|
-
const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
|
|
1231
|
-
if (Object.keys(dynDefaults).length > 0) {
|
|
1232
|
-
out[n.nodeId] = dynDefaults;
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
return out;
|
|
1236
|
-
}
|
|
1237
1248
|
dispose() {
|
|
1238
1249
|
// Idempotent: allow multiple calls safely
|
|
1239
1250
|
if (this.disposed)
|
|
@@ -1403,7 +1414,8 @@ function summarizeDeep(value) {
|
|
|
1403
1414
|
|
|
1404
1415
|
// Shared UI constants for node layout to keep mapping and rendering in sync
|
|
1405
1416
|
const NODE_HEADER_HEIGHT_PX = 24;
|
|
1406
|
-
const NODE_ROW_HEIGHT_PX =
|
|
1417
|
+
const NODE_ROW_HEIGHT_PX = 18;
|
|
1418
|
+
const HANDLE_SIZE_PX = 12;
|
|
1407
1419
|
|
|
1408
1420
|
function computeEffectiveHandles(node, registry) {
|
|
1409
1421
|
const desc = registry.nodes.get(node.typeId);
|
|
@@ -1432,6 +1444,61 @@ function estimateNodeSize(args) {
|
|
|
1432
1444
|
const height = overrides?.height ?? NODE_HEADER_HEIGHT_PX + rows * NODE_ROW_HEIGHT_PX;
|
|
1433
1445
|
return { width, height, inputsCount, outputsCount, rowCount: rows };
|
|
1434
1446
|
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Calculate the Y position for handle layout (center of row).
|
|
1449
|
+
* Used for positioning handles in React Flow.
|
|
1450
|
+
*/
|
|
1451
|
+
function getHandleLayoutY(rowIndex) {
|
|
1452
|
+
return (NODE_HEADER_HEIGHT_PX +
|
|
1453
|
+
rowIndex * NODE_ROW_HEIGHT_PX +
|
|
1454
|
+
NODE_ROW_HEIGHT_PX / 2);
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Calculate the Y position for handle bounds (top + centering offset).
|
|
1458
|
+
* Used for hit-testing and edge routing.
|
|
1459
|
+
*/
|
|
1460
|
+
function getHandleBoundsY(rowIndex) {
|
|
1461
|
+
return (NODE_HEADER_HEIGHT_PX +
|
|
1462
|
+
rowIndex * NODE_ROW_HEIGHT_PX +
|
|
1463
|
+
(NODE_ROW_HEIGHT_PX - HANDLE_SIZE_PX) / 2 +
|
|
1464
|
+
1);
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Calculate the X position for handle bounds based on position and node width.
|
|
1468
|
+
*/
|
|
1469
|
+
function getHandleBoundsX(position, nodeWidth) {
|
|
1470
|
+
if (position === react.Position.Left) {
|
|
1471
|
+
return -HANDLE_SIZE_PX / 2 + 1;
|
|
1472
|
+
}
|
|
1473
|
+
else {
|
|
1474
|
+
return nodeWidth - HANDLE_SIZE_PX / 2 - 1;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Create handle bounds object for hit-testing/edge routing.
|
|
1479
|
+
*/
|
|
1480
|
+
function createHandleBounds(args) {
|
|
1481
|
+
return {
|
|
1482
|
+
id: args.id,
|
|
1483
|
+
type: args.type,
|
|
1484
|
+
position: args.position,
|
|
1485
|
+
x: getHandleBoundsX(args.position, args.nodeWidth),
|
|
1486
|
+
y: getHandleBoundsY(args.rowIndex),
|
|
1487
|
+
width: HANDLE_SIZE_PX,
|
|
1488
|
+
height: HANDLE_SIZE_PX,
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Create handle layout object for React Flow rendering.
|
|
1493
|
+
*/
|
|
1494
|
+
function createHandleLayout(args) {
|
|
1495
|
+
return {
|
|
1496
|
+
id: args.id,
|
|
1497
|
+
type: args.type,
|
|
1498
|
+
position: args.position,
|
|
1499
|
+
y: getHandleLayoutY(args.rowIndex),
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1435
1502
|
function layoutNode(args) {
|
|
1436
1503
|
const { node, registry, showValues, overrides } = args;
|
|
1437
1504
|
const { inputs, outputs } = computeEffectiveHandles(node, registry);
|
|
@@ -1443,40 +1510,34 @@ function layoutNode(args) {
|
|
|
1443
1510
|
showValues,
|
|
1444
1511
|
overrides,
|
|
1445
1512
|
});
|
|
1446
|
-
const HEADER = NODE_HEADER_HEIGHT_PX;
|
|
1447
|
-
const ROW = NODE_ROW_HEIGHT_PX;
|
|
1448
1513
|
const handles = [
|
|
1449
|
-
...inputOrder.map((id, i) => ({
|
|
1514
|
+
...inputOrder.map((id, i) => createHandleBounds({
|
|
1450
1515
|
id,
|
|
1451
1516
|
type: "target",
|
|
1452
1517
|
position: react.Position.Left,
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
width: 1,
|
|
1456
|
-
height: ROW + 2,
|
|
1518
|
+
rowIndex: i,
|
|
1519
|
+
nodeWidth: width,
|
|
1457
1520
|
})),
|
|
1458
|
-
...outputOrder.map((id, i) => ({
|
|
1521
|
+
...outputOrder.map((id, i) => createHandleBounds({
|
|
1459
1522
|
id,
|
|
1460
1523
|
type: "source",
|
|
1461
1524
|
position: react.Position.Right,
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
width: 1,
|
|
1465
|
-
height: ROW + 2,
|
|
1525
|
+
rowIndex: i,
|
|
1526
|
+
nodeWidth: width,
|
|
1466
1527
|
})),
|
|
1467
1528
|
];
|
|
1468
1529
|
const handleLayout = [
|
|
1469
|
-
...inputOrder.map((id, i) => ({
|
|
1530
|
+
...inputOrder.map((id, i) => createHandleLayout({
|
|
1470
1531
|
id,
|
|
1471
1532
|
type: "target",
|
|
1472
1533
|
position: react.Position.Left,
|
|
1473
|
-
|
|
1534
|
+
rowIndex: i,
|
|
1474
1535
|
})),
|
|
1475
|
-
...outputOrder.map((id, i) => ({
|
|
1536
|
+
...outputOrder.map((id, i) => createHandleLayout({
|
|
1476
1537
|
id,
|
|
1477
1538
|
type: "source",
|
|
1478
1539
|
position: react.Position.Right,
|
|
1479
|
-
|
|
1540
|
+
rowIndex: i,
|
|
1480
1541
|
})),
|
|
1481
1542
|
];
|
|
1482
1543
|
return { width, height, inputOrder, outputOrder, handles, handleLayout };
|
|
@@ -1791,20 +1852,22 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1791
1852
|
const baseRightCount = geom.outputOrder.length;
|
|
1792
1853
|
const extraInputs = Array.from(missingInputsByNode[n.nodeId] || []);
|
|
1793
1854
|
const extraOutputs = Array.from(missingOutputsByNode[n.nodeId] || []);
|
|
1794
|
-
const HEADER = NODE_HEADER_HEIGHT_PX;
|
|
1795
|
-
const ROW = NODE_ROW_HEIGHT_PX;
|
|
1796
1855
|
const extraHandleLayoutLeft = extraInputs.map((id, i) => ({
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1856
|
+
...createHandleLayout({
|
|
1857
|
+
id,
|
|
1858
|
+
type: "target",
|
|
1859
|
+
position: react.Position.Left,
|
|
1860
|
+
rowIndex: baseLeftCount + i,
|
|
1861
|
+
}),
|
|
1801
1862
|
missing: true,
|
|
1802
1863
|
}));
|
|
1803
1864
|
const extraHandleLayoutRight = extraOutputs.map((id, i) => ({
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1865
|
+
...createHandleLayout({
|
|
1866
|
+
id,
|
|
1867
|
+
type: "source",
|
|
1868
|
+
position: react.Position.Right,
|
|
1869
|
+
rowIndex: baseRightCount + i,
|
|
1870
|
+
}),
|
|
1808
1871
|
missing: true,
|
|
1809
1872
|
}));
|
|
1810
1873
|
const handleLayout = [
|
|
@@ -1813,23 +1876,19 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1813
1876
|
...extraHandleLayoutRight,
|
|
1814
1877
|
];
|
|
1815
1878
|
// Precompute handle bounds (including missing) so edges can render immediately
|
|
1816
|
-
const missingBoundsLeft = extraInputs.map((id, i) => ({
|
|
1879
|
+
const missingBoundsLeft = extraInputs.map((id, i) => createHandleBounds({
|
|
1817
1880
|
id,
|
|
1818
1881
|
type: "target",
|
|
1819
1882
|
position: react.Position.Left,
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
width: 1,
|
|
1823
|
-
height: ROW + 2,
|
|
1883
|
+
rowIndex: baseLeftCount + i,
|
|
1884
|
+
nodeWidth: geom.width,
|
|
1824
1885
|
}));
|
|
1825
|
-
const missingBoundsRight = extraOutputs.map((id, i) => ({
|
|
1886
|
+
const missingBoundsRight = extraOutputs.map((id, i) => createHandleBounds({
|
|
1826
1887
|
id,
|
|
1827
1888
|
type: "source",
|
|
1828
1889
|
position: react.Position.Right,
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
width: 1,
|
|
1832
|
-
height: ROW + 2,
|
|
1890
|
+
rowIndex: baseRightCount + i,
|
|
1891
|
+
nodeWidth: geom.width,
|
|
1833
1892
|
}));
|
|
1834
1893
|
const handles = [
|
|
1835
1894
|
...geom.handles,
|
|
@@ -1840,7 +1899,7 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1840
1899
|
const baseRows = Math.max(baseLeftCount, baseRightCount);
|
|
1841
1900
|
const newRows = Math.max(baseLeftCount + extraInputs.length, baseRightCount + extraOutputs.length);
|
|
1842
1901
|
const initialWidth = geom.width;
|
|
1843
|
-
const initialHeight = geom.height + Math.max(0, newRows - baseRows) *
|
|
1902
|
+
const initialHeight = geom.height + Math.max(0, newRows - baseRows) * NODE_ROW_HEIGHT_PX;
|
|
1844
1903
|
return {
|
|
1845
1904
|
id: n.nodeId,
|
|
1846
1905
|
data: {
|
|
@@ -1976,7 +2035,7 @@ function getHandleClassName(args) {
|
|
|
1976
2035
|
else {
|
|
1977
2036
|
borderColor = "!border-gray-500 dark:!border-gray-400";
|
|
1978
2037
|
}
|
|
1979
|
-
return cx("!w-3 !h-3 !bg-white !dark:bg-stone-900", borderColor, kind === "output" && "!rounded-none");
|
|
2038
|
+
return cx("!w-3 !h-3 !bg-white/50 !dark:bg-stone-900", borderColor, kind === "output" && "!rounded-none");
|
|
1980
2039
|
}
|
|
1981
2040
|
|
|
1982
2041
|
function generateTimestamp() {
|
|
@@ -3048,7 +3107,7 @@ function NodeHandleItem({ kind, id, type, position, y, isConnectable, className,
|
|
|
3048
3107
|
textOverflow: "ellipsis",
|
|
3049
3108
|
}, children: renderLabel({ kind, id }) }))] }));
|
|
3050
3109
|
}
|
|
3051
|
-
function NodeHandles({ data, isConnectable,
|
|
3110
|
+
function NodeHandles({ data, isConnectable, getClassName, renderLabel, labelClassName = "absolute text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", }) {
|
|
3052
3111
|
const layout = data.handleLayout ?? [];
|
|
3053
3112
|
const byId = React.useMemo(() => {
|
|
3054
3113
|
const m = new Map();
|
|
@@ -3079,28 +3138,26 @@ function NodeHandles({ data, isConnectable, inputClassName = "!w-2 !h-2 !bg-gray
|
|
|
3079
3138
|
const placed = byId.get(`target:${h.id}`) ?? byId.get(h.id);
|
|
3080
3139
|
const position = placed?.position ?? react.Position.Left;
|
|
3081
3140
|
const y = placed?.y;
|
|
3082
|
-
const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ??
|
|
3083
|
-
inputClassName;
|
|
3141
|
+
const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ?? "";
|
|
3084
3142
|
return (jsxRuntime.jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: isConnectable, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
|
|
3085
3143
|
}), missingInputs.map((h) => {
|
|
3086
3144
|
const key = `missing-input:${h.id}`;
|
|
3087
3145
|
const position = h.position ?? react.Position.Left;
|
|
3088
3146
|
const y = h.y;
|
|
3089
3147
|
const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500";
|
|
3090
|
-
return (jsxRuntime.jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: false, className: cls
|
|
3148
|
+
return (jsxRuntime.jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: false, className: `${cls} wb-nodrag wb-nowheel`, labelClassName: labelClassName, renderLabel: renderLabel }, key));
|
|
3091
3149
|
}), (data.outputHandles ?? []).map((h) => {
|
|
3092
3150
|
const placed = byId.get(`source:${h.id}`) ?? byId.get(h.id);
|
|
3093
3151
|
const position = placed?.position ?? react.Position.Right;
|
|
3094
3152
|
const y = placed?.y;
|
|
3095
|
-
const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ??
|
|
3096
|
-
|
|
3097
|
-
return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: isConnectable, className: `${cls} wb-nodrag wb-nowheel`, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
|
|
3153
|
+
const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ?? "";
|
|
3154
|
+
return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: isConnectable, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
|
|
3098
3155
|
}), missingOutputs.map((h) => {
|
|
3099
3156
|
const key = `missing-output:${h.id}`;
|
|
3100
3157
|
const position = h.position ?? react.Position.Right;
|
|
3101
3158
|
const y = h.y;
|
|
3102
3159
|
const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500 !rounded-none wb-nodrag wb-nowheel";
|
|
3103
|
-
return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: false, className: cls
|
|
3160
|
+
return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: false, className: `${cls} wb-nodrag wb-nowheel`, labelClassName: labelClassName, renderLabel: renderLabel }, key));
|
|
3104
3161
|
})] }));
|
|
3105
3162
|
}
|
|
3106
3163
|
|
|
@@ -3129,7 +3186,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
3129
3186
|
status,
|
|
3130
3187
|
validation,
|
|
3131
3188
|
});
|
|
3132
|
-
return (jsxRuntime.jsxs("div", { className: cx("rounded-lg bg-white/
|
|
3189
|
+
return (jsxRuntime.jsxs("div", { className: cx("rounded-lg bg-white/50 !dark:bg-stone-900", containerBorder), style: {
|
|
3133
3190
|
position: "relative",
|
|
3134
3191
|
minWidth: typeof data.renderWidth === "number" ? data.renderWidth : undefined,
|
|
3135
3192
|
minHeight: typeof data.renderHeight === "number" ? data.renderHeight : undefined,
|
|
@@ -3214,15 +3271,13 @@ function DefaultNodeContent({ data, isConnectable, }) {
|
|
|
3214
3271
|
} })] }));
|
|
3215
3272
|
}
|
|
3216
3273
|
|
|
3217
|
-
function DefaultContextMenu({ open, clientPos,
|
|
3218
|
-
const { registry } = useWorkbenchContext();
|
|
3274
|
+
function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, }) {
|
|
3219
3275
|
const rf = react.useReactFlow();
|
|
3220
|
-
const ids = Array.from(registry.nodes.keys());
|
|
3221
3276
|
const [query, setQuery] = React.useState("");
|
|
3222
3277
|
const q = query.trim().toLowerCase();
|
|
3223
3278
|
const filteredIds = q
|
|
3224
|
-
?
|
|
3225
|
-
:
|
|
3279
|
+
? nodeIds.filter((id) => id.toLowerCase().includes(q))
|
|
3280
|
+
: nodeIds;
|
|
3226
3281
|
const root = { __children: {} };
|
|
3227
3282
|
for (const id of filteredIds) {
|
|
3228
3283
|
const parts = id.split(".");
|
|
@@ -3246,11 +3301,11 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3246
3301
|
if (!ref.current)
|
|
3247
3302
|
return;
|
|
3248
3303
|
if (!ref.current.contains(e.target))
|
|
3249
|
-
onClose();
|
|
3304
|
+
handlers.onClose();
|
|
3250
3305
|
};
|
|
3251
3306
|
const onKey = (e) => {
|
|
3252
3307
|
if (e.key === "Escape")
|
|
3253
|
-
onClose();
|
|
3308
|
+
handlers.onClose();
|
|
3254
3309
|
};
|
|
3255
3310
|
window.addEventListener("mousedown", onDown, true);
|
|
3256
3311
|
window.addEventListener("keydown", onKey);
|
|
@@ -3258,7 +3313,7 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3258
3313
|
window.removeEventListener("mousedown", onDown, true);
|
|
3259
3314
|
window.removeEventListener("keydown", onKey);
|
|
3260
3315
|
};
|
|
3261
|
-
}, [open,
|
|
3316
|
+
}, [open, handlers]);
|
|
3262
3317
|
// Focus search input when menu opens
|
|
3263
3318
|
const inputRef = React.useRef(null);
|
|
3264
3319
|
React.useEffect(() => {
|
|
@@ -3277,8 +3332,8 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3277
3332
|
const handleClick = (typeId) => {
|
|
3278
3333
|
// project() is deprecated; use screenToFlowPosition for screen coordinates
|
|
3279
3334
|
const p = rf.screenToFlowPosition({ x: clientPos.x, y: clientPos.y });
|
|
3280
|
-
|
|
3281
|
-
onClose();
|
|
3335
|
+
handlers.onAddNode(typeId, { position: p });
|
|
3336
|
+
handlers.onClose();
|
|
3282
3337
|
};
|
|
3283
3338
|
const renderTree = (tree, path = []) => {
|
|
3284
3339
|
const entries = Object.entries(tree?.__children ?? {}).sort((a, b) => a[0].localeCompare(b[0]));
|
|
@@ -3300,8 +3355,7 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3300
3355
|
}, 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" })) })] }));
|
|
3301
3356
|
}
|
|
3302
3357
|
|
|
3303
|
-
function NodeContextMenu({ open, clientPos, nodeId,
|
|
3304
|
-
const { wb, runner, engineKind, registry, outputsMap, outputTypesMap } = useWorkbenchContext();
|
|
3358
|
+
function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeableOutputs, }) {
|
|
3305
3359
|
const ref = React.useRef(null);
|
|
3306
3360
|
// outside click + ESC
|
|
3307
3361
|
React.useEffect(() => {
|
|
@@ -3311,11 +3365,11 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3311
3365
|
if (!ref.current)
|
|
3312
3366
|
return;
|
|
3313
3367
|
if (!ref.current.contains(e.target))
|
|
3314
|
-
onClose();
|
|
3368
|
+
handlers.onClose();
|
|
3315
3369
|
};
|
|
3316
3370
|
const onKey = (e) => {
|
|
3317
3371
|
if (e.key === "Escape")
|
|
3318
|
-
onClose();
|
|
3372
|
+
handlers.onClose();
|
|
3319
3373
|
};
|
|
3320
3374
|
window.addEventListener("mousedown", onDown, true);
|
|
3321
3375
|
window.addEventListener("keydown", onKey);
|
|
@@ -3323,48 +3377,26 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3323
3377
|
window.removeEventListener("mousedown", onDown, true);
|
|
3324
3378
|
window.removeEventListener("keydown", onKey);
|
|
3325
3379
|
};
|
|
3326
|
-
}, [open,
|
|
3380
|
+
}, [open, handlers]);
|
|
3327
3381
|
React.useEffect(() => {
|
|
3328
3382
|
if (open)
|
|
3329
3383
|
ref.current?.focus();
|
|
3330
3384
|
}, [open]);
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
const base = tId.slice(0, -2);
|
|
3347
|
-
const tArr = registry.types.get(tId);
|
|
3348
|
-
const tElem = registry.types.get(base);
|
|
3349
|
-
const arrT = tArr?.bakeTarget;
|
|
3350
|
-
const elemT = tElem?.bakeTarget;
|
|
3351
|
-
if ((arrT && registry.nodes.has(arrT.nodeTypeId)) ||
|
|
3352
|
-
(elemT && registry.nodes.has(elemT.nodeTypeId)))
|
|
3353
|
-
out.push(h);
|
|
3354
|
-
}
|
|
3355
|
-
else {
|
|
3356
|
-
const t = registry.types.get(tId);
|
|
3357
|
-
const bt = t?.bakeTarget;
|
|
3358
|
-
if (bt && registry.nodes.has(bt.nodeTypeId))
|
|
3359
|
-
out.push(h);
|
|
3360
|
-
}
|
|
3361
|
-
}
|
|
3362
|
-
return out;
|
|
3363
|
-
}
|
|
3364
|
-
catch {
|
|
3365
|
-
return [];
|
|
3366
|
-
}
|
|
3367
|
-
};
|
|
3385
|
+
if (!open || !clientPos || !nodeId)
|
|
3386
|
+
return null;
|
|
3387
|
+
// clamp
|
|
3388
|
+
const MENU_MIN_WIDTH = 180;
|
|
3389
|
+
const PADDING = 16;
|
|
3390
|
+
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
3391
|
+
(MENU_MIN_WIDTH + PADDING));
|
|
3392
|
+
const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
|
|
3393
|
+
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) => {
|
|
3394
|
+
e.preventDefault();
|
|
3395
|
+
e.stopPropagation();
|
|
3396
|
+
}, 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" })] }));
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3399
|
+
function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose) {
|
|
3368
3400
|
const doBake = async (handleId) => {
|
|
3369
3401
|
try {
|
|
3370
3402
|
const typeId = outputTypesMap?.[nodeId]?.[handleId];
|
|
@@ -3397,7 +3429,6 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3397
3429
|
const newId = wb.addNode({
|
|
3398
3430
|
typeId: singleTarget.nodeTypeId,
|
|
3399
3431
|
position: { x: pos.x + 180, y: pos.y },
|
|
3400
|
-
params: {},
|
|
3401
3432
|
});
|
|
3402
3433
|
runner.update(wb.export());
|
|
3403
3434
|
await runner.whenIdle();
|
|
@@ -3408,12 +3439,9 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3408
3439
|
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
3409
3440
|
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
3410
3441
|
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3411
|
-
const newId =
|
|
3412
|
-
wb.addNode({
|
|
3413
|
-
nodeId: newId,
|
|
3442
|
+
const newId = wb.addNode({
|
|
3414
3443
|
typeId: arrTarget.nodeTypeId,
|
|
3415
3444
|
position: { x: pos.x + 180, y: pos.y },
|
|
3416
|
-
params: {},
|
|
3417
3445
|
});
|
|
3418
3446
|
runner.update(wb.export());
|
|
3419
3447
|
await runner.whenIdle();
|
|
@@ -3431,14 +3459,11 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3431
3459
|
const DY = 160;
|
|
3432
3460
|
const nodeIds = [];
|
|
3433
3461
|
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3434
|
-
const cv = coercedItems[idx];
|
|
3435
3462
|
const col = idx % COLS;
|
|
3436
3463
|
const row = Math.floor(idx / COLS);
|
|
3437
3464
|
const newId = wb.addNode({
|
|
3438
3465
|
typeId: elemTarget.nodeTypeId,
|
|
3439
3466
|
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
3440
|
-
params: {},
|
|
3441
|
-
initialInputs: { [elemTarget.inputHandle]: structuredClone(cv) },
|
|
3442
3467
|
});
|
|
3443
3468
|
nodeIds.push(newId);
|
|
3444
3469
|
}
|
|
@@ -3456,65 +3481,91 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3456
3481
|
}
|
|
3457
3482
|
catch { }
|
|
3458
3483
|
};
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3484
|
+
return {
|
|
3485
|
+
onDelete: () => {
|
|
3486
|
+
wb.removeNode(nodeId);
|
|
3487
|
+
onClose();
|
|
3488
|
+
},
|
|
3489
|
+
onDuplicate: async () => {
|
|
3490
|
+
const def = wb.export();
|
|
3491
|
+
const n = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3492
|
+
if (!n)
|
|
3493
|
+
return onClose();
|
|
3494
|
+
const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
|
|
3495
|
+
const newId = wb.addNode({
|
|
3496
|
+
typeId: n.typeId,
|
|
3497
|
+
params: n.params,
|
|
3498
|
+
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3499
|
+
initialInputs: n.initialInputs,
|
|
3500
|
+
resolvedHandles: n.resolvedHandles,
|
|
3501
|
+
});
|
|
3502
|
+
await runner.whenIdle();
|
|
3503
|
+
runner.setInputs(newId, { ...runner.getInputs(def)[nodeId] });
|
|
3504
|
+
onClose();
|
|
3505
|
+
},
|
|
3506
|
+
onRunPull: async () => {
|
|
3507
|
+
try {
|
|
3508
|
+
await runner.computeNode(nodeId);
|
|
3509
|
+
}
|
|
3510
|
+
catch { }
|
|
3511
|
+
onClose();
|
|
3512
|
+
},
|
|
3513
|
+
onBake: async (handleId) => {
|
|
3514
|
+
await doBake(handleId);
|
|
3515
|
+
onClose();
|
|
3516
|
+
},
|
|
3517
|
+
onCopyId: async () => {
|
|
3518
|
+
try {
|
|
3519
|
+
await navigator.clipboard.writeText(nodeId);
|
|
3520
|
+
}
|
|
3521
|
+
catch { }
|
|
3522
|
+
onClose();
|
|
3523
|
+
},
|
|
3524
|
+
onClose,
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
function getBakeableOutputs(nodeId, wb, registry, outputTypesMap) {
|
|
3528
|
+
try {
|
|
3465
3529
|
const def = wb.export();
|
|
3466
|
-
const
|
|
3467
|
-
if (!
|
|
3468
|
-
return
|
|
3469
|
-
const
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3530
|
+
const node = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3531
|
+
if (!node)
|
|
3532
|
+
return [];
|
|
3533
|
+
const desc = registry.nodes.get(node.typeId);
|
|
3534
|
+
const handles = Object.keys(desc?.outputs || {});
|
|
3535
|
+
const out = [];
|
|
3536
|
+
for (const h of handles) {
|
|
3537
|
+
const tId = outputTypesMap?.[nodeId]?.[h];
|
|
3538
|
+
if (!tId)
|
|
3539
|
+
continue;
|
|
3540
|
+
if (tId.endsWith("[]")) {
|
|
3541
|
+
const base = tId.slice(0, -2);
|
|
3542
|
+
const tArr = registry.types.get(tId);
|
|
3543
|
+
const tElem = registry.types.get(base);
|
|
3544
|
+
const arrT = tArr?.bakeTarget;
|
|
3545
|
+
const elemT = tElem?.bakeTarget;
|
|
3546
|
+
if ((arrT && registry.nodes.has(arrT.nodeTypeId)) ||
|
|
3547
|
+
(elemT && registry.nodes.has(elemT.nodeTypeId)))
|
|
3548
|
+
out.push(h);
|
|
3549
|
+
}
|
|
3550
|
+
else {
|
|
3551
|
+
const t = registry.types.get(tId);
|
|
3552
|
+
const bt = t?.bakeTarget;
|
|
3553
|
+
if (bt && registry.nodes.has(bt.nodeTypeId))
|
|
3554
|
+
out.push(h);
|
|
3555
|
+
}
|
|
3491
3556
|
}
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
// clamp
|
|
3498
|
-
const MENU_MIN_WIDTH = 180;
|
|
3499
|
-
const PADDING = 16;
|
|
3500
|
-
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
3501
|
-
(MENU_MIN_WIDTH + PADDING));
|
|
3502
|
-
const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
|
|
3503
|
-
const canRunPull = engineKind()?.toString() === "pull";
|
|
3504
|
-
const outs = getBakeableOutputs();
|
|
3505
|
-
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) => {
|
|
3506
|
-
e.preventDefault();
|
|
3507
|
-
e.stopPropagation();
|
|
3508
|
-
}, 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 () => {
|
|
3509
|
-
await doBake(h);
|
|
3510
|
-
onClose();
|
|
3511
|
-
}, 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" })] }));
|
|
3557
|
+
return out;
|
|
3558
|
+
}
|
|
3559
|
+
catch {
|
|
3560
|
+
return [];
|
|
3561
|
+
}
|
|
3512
3562
|
}
|
|
3513
3563
|
|
|
3514
3564
|
const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
|
|
3515
|
-
const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
|
|
3565
|
+
const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, runner, engineKind, } = useWorkbenchContext();
|
|
3516
3566
|
const nodeValidation = validationByNode;
|
|
3517
3567
|
const edgeValidation = validationByEdge.errors;
|
|
3568
|
+
const [registryVersion, setRegistryVersion] = React.useState(0);
|
|
3518
3569
|
// Keep stable references for nodes/edges to avoid unnecessary updates
|
|
3519
3570
|
const prevNodesRef = React.useRef([]);
|
|
3520
3571
|
const prevEdgesRef = React.useRef([]);
|
|
@@ -3579,9 +3630,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3579
3630
|
},
|
|
3580
3631
|
}));
|
|
3581
3632
|
const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete, } = useWorkbenchBridge(wb);
|
|
3633
|
+
const ui = wb.getUI();
|
|
3582
3634
|
const { nodeTypes, resolveNodeType } = React.useMemo(() => {
|
|
3583
3635
|
// Build nodeTypes map using UI extension registry
|
|
3584
|
-
const ui = wb.getUI();
|
|
3585
3636
|
const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
|
|
3586
3637
|
const def = wb.export();
|
|
3587
3638
|
const ids = new Set([
|
|
@@ -3603,7 +3654,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3603
3654
|
const resolver = (nodeTypeId) => custom.has(nodeTypeId) ? `spark-${nodeTypeId}` : "spark-default";
|
|
3604
3655
|
return { nodeTypes: types, resolveNodeType: resolver };
|
|
3605
3656
|
// Include uiVersion to recompute when custom renderers are registered
|
|
3606
|
-
}, [wb, registry, uiVersion]);
|
|
3657
|
+
}, [wb, registry, uiVersion, ui]);
|
|
3607
3658
|
const { nodes, edges } = React.useMemo(() => {
|
|
3608
3659
|
const def = wb.export();
|
|
3609
3660
|
const sel = wb.getSelection();
|
|
@@ -3767,15 +3818,65 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3767
3818
|
setNodeMenuOpen(false);
|
|
3768
3819
|
}
|
|
3769
3820
|
};
|
|
3770
|
-
const addNodeAt = React.useCallback((typeId,
|
|
3771
|
-
wb.addNode({
|
|
3772
|
-
|
|
3821
|
+
const addNodeAt = React.useCallback(async (typeId, opts) => {
|
|
3822
|
+
const nodeId = wb.addNode({
|
|
3823
|
+
typeId,
|
|
3824
|
+
initialInputs: opts.initialInputs,
|
|
3825
|
+
position: opts.position,
|
|
3826
|
+
});
|
|
3827
|
+
if (opts.inputs) {
|
|
3828
|
+
runner.update(wb.export());
|
|
3829
|
+
await runner.whenIdle();
|
|
3830
|
+
runner.setInputs(nodeId, opts.inputs);
|
|
3831
|
+
}
|
|
3832
|
+
}, [wb, runner]);
|
|
3773
3833
|
const onCloseMenu = React.useCallback(() => {
|
|
3774
3834
|
setMenuOpen(false);
|
|
3775
3835
|
}, []);
|
|
3776
3836
|
const onCloseNodeMenu = React.useCallback(() => {
|
|
3777
3837
|
setNodeMenuOpen(false);
|
|
3778
3838
|
}, []);
|
|
3839
|
+
React.useEffect(() => {
|
|
3840
|
+
const off = runner.on("registry", () => {
|
|
3841
|
+
setRegistryVersion((v) => v + 1);
|
|
3842
|
+
});
|
|
3843
|
+
return () => off();
|
|
3844
|
+
}, [runner]);
|
|
3845
|
+
const nodeIds = React.useMemo(() => Array.from(registry.nodes.keys()), [registry, registryVersion]);
|
|
3846
|
+
const defaultContextMenuHandlers = React.useMemo(() => ({
|
|
3847
|
+
onAddNode: addNodeAt,
|
|
3848
|
+
onClose: onCloseMenu,
|
|
3849
|
+
}), [addNodeAt, onCloseMenu]);
|
|
3850
|
+
const nodeContextMenuHandlers = React.useMemo(() => {
|
|
3851
|
+
if (!nodeAtMenu)
|
|
3852
|
+
return null;
|
|
3853
|
+
return createNodeContextMenuHandlers(nodeAtMenu, wb, runner, registry, outputsMap, outputTypesMap, onCloseNodeMenu);
|
|
3854
|
+
}, [
|
|
3855
|
+
nodeAtMenu,
|
|
3856
|
+
wb,
|
|
3857
|
+
runner,
|
|
3858
|
+
registry,
|
|
3859
|
+
outputsMap,
|
|
3860
|
+
outputTypesMap,
|
|
3861
|
+
onCloseNodeMenu,
|
|
3862
|
+
]);
|
|
3863
|
+
const canRunPull = React.useMemo(() => engineKind()?.toString() === "pull", [engineKind]);
|
|
3864
|
+
const bakeableOutputs = React.useMemo(() => {
|
|
3865
|
+
if (!nodeAtMenu)
|
|
3866
|
+
return [];
|
|
3867
|
+
return getBakeableOutputs(nodeAtMenu, wb, registry, outputTypesMap);
|
|
3868
|
+
}, [nodeAtMenu, wb, registry, outputTypesMap]);
|
|
3869
|
+
// Get custom renderers from UI extension registry (reactive to uiVersion changes)
|
|
3870
|
+
const { BackgroundRenderer, MinimapRenderer, ControlsRenderer, DefaultContextMenuRenderer, NodeContextMenuRenderer, connectionLineRenderer, } = React.useMemo(() => {
|
|
3871
|
+
return {
|
|
3872
|
+
BackgroundRenderer: ui.getBackgroundRenderer(),
|
|
3873
|
+
MinimapRenderer: ui.getMinimapRenderer(),
|
|
3874
|
+
ControlsRenderer: ui.getControlsRenderer(),
|
|
3875
|
+
DefaultContextMenuRenderer: ui.getDefaultContextMenuRenderer(),
|
|
3876
|
+
NodeContextMenuRenderer: ui.getNodeContextMenuRenderer(),
|
|
3877
|
+
connectionLineRenderer: ui.getConnectionLineRenderer(),
|
|
3878
|
+
};
|
|
3879
|
+
}, [ui, uiVersion]);
|
|
3779
3880
|
const onMoveEnd = React.useCallback(() => {
|
|
3780
3881
|
if (rfInstanceRef.current) {
|
|
3781
3882
|
const viewport = rfInstanceRef.current.getViewport();
|
|
@@ -3800,7 +3901,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3800
3901
|
});
|
|
3801
3902
|
}
|
|
3802
3903
|
});
|
|
3803
|
-
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) => {
|
|
3904
|
+
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) => {
|
|
3804
3905
|
rfInstanceRef.current = inst;
|
|
3805
3906
|
const savedViewport = wb.getViewport();
|
|
3806
3907
|
if (savedViewport) {
|
|
@@ -3811,7 +3912,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3811
3912
|
zoom: savedViewport.zoom,
|
|
3812
3913
|
});
|
|
3813
3914
|
}
|
|
3814
|
-
}, 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(
|
|
3915
|
+
}, 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 &&
|
|
3916
|
+
nodeContextMenuHandlers &&
|
|
3917
|
+
(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 })))] }) }) }));
|
|
3815
3918
|
});
|
|
3816
3919
|
|
|
3817
3920
|
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
|
|
@@ -4359,11 +4462,16 @@ exports.WorkbenchProvider = WorkbenchProvider;
|
|
|
4359
4462
|
exports.WorkbenchStudio = WorkbenchStudio;
|
|
4360
4463
|
exports.computeEffectiveHandles = computeEffectiveHandles;
|
|
4361
4464
|
exports.countVisibleHandles = countVisibleHandles;
|
|
4465
|
+
exports.createHandleBounds = createHandleBounds;
|
|
4466
|
+
exports.createHandleLayout = createHandleLayout;
|
|
4362
4467
|
exports.download = download;
|
|
4363
4468
|
exports.estimateNodeSize = estimateNodeSize;
|
|
4364
4469
|
exports.formatDataUrlAsLabel = formatDataUrlAsLabel;
|
|
4365
4470
|
exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
|
|
4471
|
+
exports.getHandleBoundsX = getHandleBoundsX;
|
|
4472
|
+
exports.getHandleBoundsY = getHandleBoundsY;
|
|
4366
4473
|
exports.getHandleClassName = getHandleClassName;
|
|
4474
|
+
exports.getHandleLayoutY = getHandleLayoutY;
|
|
4367
4475
|
exports.getNodeBorderClassNames = getNodeBorderClassNames;
|
|
4368
4476
|
exports.layoutNode = layoutNode;
|
|
4369
4477
|
exports.preformatValueForDisplay = preformatValueForDisplay;
|