@bian-womp/spark-workbench 0.2.84 → 0.2.86
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 +665 -36
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts +11 -3
- package/lib/cjs/src/core/InMemoryWorkbench.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/DefaultNodeContent.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/thumbnail-utils.d.ts +17 -0
- package/lib/cjs/src/misc/thumbnail-utils.d.ts.map +1 -0
- package/lib/esm/index.js +665 -38
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts +11 -3
- package/lib/esm/src/core/InMemoryWorkbench.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/DefaultNodeContent.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/thumbnail-utils.d.ts +17 -0
- package/lib/esm/src/misc/thumbnail-utils.d.ts.map +1 -0
- package/package.json +4 -4
package/lib/esm/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Position, Handle, NodeResizer, useReactFlow, ReactFlowProvider, ReactFl
|
|
|
5
5
|
import React, { useCallback, useState, useRef, useEffect, useMemo, createContext, useContext, useImperativeHandle } from 'react';
|
|
6
6
|
import cx from 'classnames';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
-
import { XCircleIcon, WarningCircleIcon, CopyIcon, TrashIcon, XIcon, ArrowClockwiseIcon, ClockClockwiseIcon, StopIcon, PlayIcon, PlugsConnectedIcon, WifiHighIcon, WifiSlashIcon, PlayPauseIcon, LightningIcon, TreeStructureIcon, CornersOutIcon, DownloadIcon, UploadIcon, BugBeetleIcon, ListBulletsIcon } from '@phosphor-icons/react';
|
|
8
|
+
import { XCircleIcon, WarningCircleIcon, CopyIcon, TrashIcon, XIcon, ArrowClockwiseIcon, ClockClockwiseIcon, StopIcon, PlayIcon, PlugsConnectedIcon, WifiHighIcon, WifiSlashIcon, PlayPauseIcon, LightningIcon, TreeStructureIcon, CornersOutIcon, DownloadIcon, UploadIcon, ImageIcon, BugBeetleIcon, ListBulletsIcon } from '@phosphor-icons/react';
|
|
9
9
|
|
|
10
10
|
class DefaultUIExtensionRegistry {
|
|
11
11
|
constructor() {
|
|
@@ -157,9 +157,11 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
157
157
|
const defNodeIds = new Set(this._def.nodes.map((n) => n.nodeId));
|
|
158
158
|
const defEdgeIds = new Set(this._def.edges.map((e) => e.id));
|
|
159
159
|
const filteredPositions = Object.fromEntries(Object.entries(this.positions).filter(([id]) => defNodeIds.has(id)));
|
|
160
|
+
const filteredSizes = Object.fromEntries(Object.entries(this.sizes).filter(([id]) => defNodeIds.has(id)));
|
|
160
161
|
const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
|
|
161
162
|
const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
|
|
162
163
|
this.positions = filteredPositions;
|
|
164
|
+
this.sizes = filteredSizes;
|
|
163
165
|
this.selection = { nodes: filteredNodes, edges: filteredEdges };
|
|
164
166
|
this.emit("graphChanged", { def: this._def });
|
|
165
167
|
this.refreshValidation();
|
|
@@ -216,8 +218,10 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
216
218
|
params: node.params,
|
|
217
219
|
resolvedHandles: node.resolvedHandles,
|
|
218
220
|
});
|
|
219
|
-
if (
|
|
220
|
-
this.positions[id] =
|
|
221
|
+
if (options?.position)
|
|
222
|
+
this.positions[id] = options.position;
|
|
223
|
+
if (options?.size)
|
|
224
|
+
this.sizes[id] = options.size;
|
|
221
225
|
this.emit("graphChanged", {
|
|
222
226
|
def: this._def,
|
|
223
227
|
change: {
|
|
@@ -235,6 +239,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
235
239
|
this._def.nodes = this._def.nodes.filter((n) => n.nodeId !== nodeId);
|
|
236
240
|
this._def.edges = this._def.edges.filter((e) => e.source.nodeId !== nodeId && e.target.nodeId !== nodeId);
|
|
237
241
|
delete this.positions[nodeId];
|
|
242
|
+
delete this.sizes[nodeId];
|
|
238
243
|
delete this.nodeNames[nodeId];
|
|
239
244
|
this.emit("graphChanged", {
|
|
240
245
|
def: this._def,
|
|
@@ -301,14 +306,16 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
301
306
|
return { ...this.positions };
|
|
302
307
|
}
|
|
303
308
|
setSizes(sizes, options) {
|
|
309
|
+
const updatedSizes = { ...this.sizes };
|
|
304
310
|
for (const [nodeId, size] of Object.entries(sizes)) {
|
|
305
311
|
if (size) {
|
|
306
|
-
|
|
312
|
+
updatedSizes[nodeId] = size;
|
|
307
313
|
}
|
|
308
314
|
else {
|
|
309
|
-
|
|
315
|
+
delete updatedSizes[nodeId];
|
|
310
316
|
}
|
|
311
317
|
}
|
|
318
|
+
this.sizes = updatedSizes;
|
|
312
319
|
this.emit("graphUiChanged", {
|
|
313
320
|
change: { type: "resizeNodes" },
|
|
314
321
|
...options,
|
|
@@ -466,6 +473,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
466
473
|
if (selection.nodes.length === 0)
|
|
467
474
|
return null;
|
|
468
475
|
const positions = this.getPositions();
|
|
476
|
+
const sizes = this.getSizes();
|
|
469
477
|
const selectedNodeSet = new Set(selection.nodes);
|
|
470
478
|
// Collect nodes to copy
|
|
471
479
|
const nodesToCopy = this.def.nodes.filter((n) => selectedNodeSet.has(n.nodeId));
|
|
@@ -477,15 +485,16 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
477
485
|
let maxX = -Infinity;
|
|
478
486
|
let maxY = -Infinity;
|
|
479
487
|
nodesToCopy.forEach((node) => {
|
|
480
|
-
const pos = positions[node.nodeId]
|
|
481
|
-
const size = getNodeSize?.(node.nodeId, node.typeId)
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
488
|
+
const pos = positions[node.nodeId];
|
|
489
|
+
const size = sizes[node.nodeId] || getNodeSize?.(node.nodeId, node.typeId);
|
|
490
|
+
if (pos) {
|
|
491
|
+
minX = Math.min(minX, pos.x);
|
|
492
|
+
minY = Math.min(minY, pos.y);
|
|
493
|
+
if (size) {
|
|
494
|
+
maxX = Math.max(maxX, pos.x + size.width);
|
|
495
|
+
maxY = Math.max(maxY, pos.y + size.height);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
489
498
|
});
|
|
490
499
|
const bounds = { minX, minY, maxX, maxY };
|
|
491
500
|
const centerX = (bounds.minX + bounds.maxX) / 2;
|
|
@@ -495,7 +504,8 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
495
504
|
const allInputs = runner.getInputs(this.def);
|
|
496
505
|
const selectedEdgeSet = new Set(selection.edges);
|
|
497
506
|
const copiedNodes = nodesToCopy.map((node) => {
|
|
498
|
-
const pos = positions[node.nodeId]
|
|
507
|
+
const pos = positions[node.nodeId];
|
|
508
|
+
const size = sizes[node.nodeId];
|
|
499
509
|
// Get all inbound edges for this node
|
|
500
510
|
const inboundEdges = this.def.edges.filter((e) => e.target.nodeId === node.nodeId);
|
|
501
511
|
// Build set of handles that have inbound edges
|
|
@@ -512,10 +522,13 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
512
522
|
typeId: node.typeId,
|
|
513
523
|
params: node.params,
|
|
514
524
|
resolvedHandles: node.resolvedHandles,
|
|
515
|
-
position:
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
525
|
+
position: pos
|
|
526
|
+
? {
|
|
527
|
+
x: pos.x - centerX,
|
|
528
|
+
y: pos.y - centerY,
|
|
529
|
+
}
|
|
530
|
+
: undefined,
|
|
531
|
+
size,
|
|
519
532
|
inputs: inputsToCopy,
|
|
520
533
|
originalNodeId: node.nodeId,
|
|
521
534
|
};
|
|
@@ -550,6 +563,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
550
563
|
if (selection.nodes.length === 0)
|
|
551
564
|
return [];
|
|
552
565
|
const positions = this.getPositions();
|
|
566
|
+
const sizes = this.getSizes();
|
|
553
567
|
const newNodes = [];
|
|
554
568
|
// Get inputs without bindings (literal values only)
|
|
555
569
|
const allInputs = runner.getInputs(this.def) || {};
|
|
@@ -558,7 +572,8 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
558
572
|
const n = this.def.nodes.find((n) => n.nodeId === nodeId);
|
|
559
573
|
if (!n)
|
|
560
574
|
continue;
|
|
561
|
-
const pos = positions[nodeId]
|
|
575
|
+
const pos = positions[nodeId];
|
|
576
|
+
const size = sizes[nodeId];
|
|
562
577
|
const inboundHandles = new Set(this.def.edges
|
|
563
578
|
.filter((e) => e.target.nodeId === nodeId)
|
|
564
579
|
.map((e) => e.target.handle));
|
|
@@ -566,10 +581,11 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
566
581
|
const newNodeId = this.addNode({
|
|
567
582
|
typeId: n.typeId,
|
|
568
583
|
params: n.params,
|
|
569
|
-
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
570
584
|
resolvedHandles: n.resolvedHandles,
|
|
571
585
|
}, {
|
|
572
586
|
inputs: inputsWithoutBindings,
|
|
587
|
+
position: pos ? { x: pos.x + 24, y: pos.y + 24 } : undefined,
|
|
588
|
+
size,
|
|
573
589
|
copyOutputsFrom: nodeId,
|
|
574
590
|
dry: true,
|
|
575
591
|
});
|
|
@@ -616,8 +632,10 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
616
632
|
const coerced = await coerceIfNeeded(outputTypeId, inType, unwrap(outputValue));
|
|
617
633
|
newNodeId = this.addNode({
|
|
618
634
|
typeId: singleTarget.nodeTypeId,
|
|
635
|
+
}, {
|
|
636
|
+
inputs: { [singleTarget.inputHandle]: coerced },
|
|
619
637
|
position: { x: pos.x + 180, y: pos.y },
|
|
620
|
-
}
|
|
638
|
+
});
|
|
621
639
|
}
|
|
622
640
|
else if (isArray && arrTarget) {
|
|
623
641
|
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
@@ -625,8 +643,10 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
625
643
|
const coerced = await coerceIfNeeded(outputTypeId, inType, unwrap(outputValue));
|
|
626
644
|
newNodeId = this.addNode({
|
|
627
645
|
typeId: arrTarget.nodeTypeId,
|
|
646
|
+
}, {
|
|
628
647
|
position: { x: pos.x + 180, y: pos.y },
|
|
629
|
-
|
|
648
|
+
inputs: { [arrTarget.inputHandle]: coerced },
|
|
649
|
+
});
|
|
630
650
|
}
|
|
631
651
|
else if (isArray && elemTarget) {
|
|
632
652
|
const nodeDesc = registry.nodes.get(elemTarget.nodeTypeId);
|
|
@@ -642,8 +662,10 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
642
662
|
const row = Math.floor(idx / COLS);
|
|
643
663
|
newNodeId = this.addNode({
|
|
644
664
|
typeId: elemTarget.nodeTypeId,
|
|
665
|
+
}, {
|
|
645
666
|
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
646
|
-
|
|
667
|
+
inputs: { [elemTarget.inputHandle]: coercedItems[idx] },
|
|
668
|
+
});
|
|
647
669
|
}
|
|
648
670
|
}
|
|
649
671
|
if (newNodeId) {
|
|
@@ -665,7 +687,8 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
665
687
|
const n = this.def.nodes.find((n) => n.nodeId === nodeId);
|
|
666
688
|
if (!n)
|
|
667
689
|
return undefined;
|
|
668
|
-
const pos = this.getPositions()[nodeId]
|
|
690
|
+
const pos = this.getPositions()[nodeId];
|
|
691
|
+
const size = this.getSizes()[nodeId];
|
|
669
692
|
// Get inputs without bindings (literal values only)
|
|
670
693
|
const allInputs = runner.getInputs(this.def)[nodeId] || {};
|
|
671
694
|
const inboundHandles = new Set(this.def.edges
|
|
@@ -675,10 +698,11 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
675
698
|
const newNodeId = this.addNode({
|
|
676
699
|
typeId: n.typeId,
|
|
677
700
|
params: n.params,
|
|
678
|
-
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
679
701
|
resolvedHandles: n.resolvedHandles,
|
|
680
702
|
}, {
|
|
681
703
|
inputs: inputsWithoutBindings,
|
|
704
|
+
position: pos ? { x: pos.x + 24, y: pos.y + 24 } : undefined,
|
|
705
|
+
size,
|
|
682
706
|
copyOutputsFrom: nodeId,
|
|
683
707
|
dry: true,
|
|
684
708
|
});
|
|
@@ -696,17 +720,19 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
696
720
|
const n = this.def.nodes.find((n) => n.nodeId === nodeId);
|
|
697
721
|
if (!n)
|
|
698
722
|
return undefined;
|
|
699
|
-
const pos = this.getPositions()[nodeId]
|
|
723
|
+
const pos = this.getPositions()[nodeId];
|
|
724
|
+
const size = this.getSizes()[nodeId];
|
|
700
725
|
// Get all inputs (including those with bindings, since edges will be duplicated)
|
|
701
726
|
const inputs = runner.getInputs(this.def)[nodeId] || {};
|
|
702
727
|
// Add the duplicated node
|
|
703
728
|
const newNodeId = this.addNode({
|
|
704
729
|
typeId: n.typeId,
|
|
705
730
|
params: n.params,
|
|
706
|
-
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
707
731
|
resolvedHandles: n.resolvedHandles,
|
|
708
732
|
}, {
|
|
709
733
|
inputs,
|
|
734
|
+
position: pos ? { x: pos.x + 24, y: pos.y + 24 } : undefined,
|
|
735
|
+
size,
|
|
710
736
|
copyOutputsFrom: nodeId,
|
|
711
737
|
dry: true,
|
|
712
738
|
});
|
|
@@ -740,12 +766,15 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
740
766
|
typeId: nodeData.typeId,
|
|
741
767
|
params: nodeData.params,
|
|
742
768
|
resolvedHandles: nodeData.resolvedHandles,
|
|
743
|
-
position: {
|
|
744
|
-
x: nodeData.position.x + center.x,
|
|
745
|
-
y: nodeData.position.y + center.y,
|
|
746
|
-
},
|
|
747
769
|
}, {
|
|
748
770
|
inputs: nodeData.inputs,
|
|
771
|
+
position: nodeData.position
|
|
772
|
+
? {
|
|
773
|
+
x: nodeData.position.x + center.x,
|
|
774
|
+
y: nodeData.position.y + center.y,
|
|
775
|
+
}
|
|
776
|
+
: undefined,
|
|
777
|
+
size: nodeData.size,
|
|
749
778
|
copyOutputsFrom: nodeData.originalNodeId,
|
|
750
779
|
dry: true,
|
|
751
780
|
});
|
|
@@ -4422,7 +4451,7 @@ function DefaultNodeHeader({ id, typeId, title, validation, right, showId, onInv
|
|
|
4422
4451
|
return (jsxs("div", { className: "flex items-center justify-center px-2 border-b border-solid border-gray-500 dark:border-gray-400 text-gray-600 dark:text-gray-300", style: {
|
|
4423
4452
|
maxHeight: NODE_HEADER_HEIGHT_PX,
|
|
4424
4453
|
minHeight: NODE_HEADER_HEIGHT_PX,
|
|
4425
|
-
}, children: [isEditing ? (jsx("input", { ref: inputRef, type: "text", value: editValue, onChange: (e) => setEditValue(e.target.value), onBlur: handleSave, onKeyDown: handleKeyDown, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), className: "flex-1 h-full text-sm bg-transparent border border-blue-500 rounded px-1 outline-none wb-nodrag", style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` } })) : (jsx("strong", { className: `flex-1 h-full text-sm select-none truncate ${typeId ? "cursor-text" : ""}`, style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` }, onDoubleClick: handleDoubleClick, title: typeId ? "Double-click to rename" : undefined, children: displayName })), jsxs("div", { className: "flex items-center gap-1", children: [jsx("button", { className: "w-4 h-4 border border-gray-400 rounded text-[10px] leading-3 flex items-center justify-center", title: "Invalidate and re-run", onClick: (e) => {
|
|
4454
|
+
}, children: [isEditing ? (jsx("input", { ref: inputRef, type: "text", value: editValue, onChange: (e) => setEditValue(e.target.value), onBlur: handleSave, onKeyDown: handleKeyDown, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), className: "flex-1 h-full text-sm bg-transparent border border-blue-500 rounded px-1 outline-none wb-nodrag", style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` } })) : (jsx("strong", { className: `react-flow__node-title flex-1 h-full text-sm select-none truncate ${typeId ? "cursor-text" : ""}`, style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` }, onDoubleClick: handleDoubleClick, title: typeId ? "Double-click to rename" : undefined, children: displayName })), jsxs("div", { className: "flex items-center gap-1", children: [jsx("button", { className: "w-4 h-4 border border-gray-400 rounded text-[10px] leading-3 flex items-center justify-center", title: "Invalidate and re-run", onClick: (e) => {
|
|
4426
4455
|
e.stopPropagation();
|
|
4427
4456
|
handleInvalidate();
|
|
4428
4457
|
}, children: jsx(ArrowClockwiseIcon, { size: 10 }) }), right, validation.issues && validation.issues.length > 0 && (jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
|
|
@@ -4433,7 +4462,7 @@ function DefaultNodeHeader({ id, typeId, title, validation, right, showId, onInv
|
|
|
4433
4462
|
}
|
|
4434
4463
|
|
|
4435
4464
|
function NodeHandleItem({ kind, id, type, position, y, isConnectable, className, labelClassName, renderLabel, }) {
|
|
4436
|
-
return (jsxs(Fragment, { children: [jsx(Handle, { id: id, type: type, position: position, isConnectable: isConnectable, className: className, style: y !== undefined ? { top: y } : undefined }), renderLabel && (jsx("div", { className: labelClassName
|
|
4465
|
+
return (jsxs(Fragment, { children: [jsx(Handle, { id: id, type: type, position: position, isConnectable: isConnectable, className: className, style: y !== undefined ? { top: y } : undefined }), renderLabel && (jsx("div", { className: `${labelClassName} ${kind === "input" ? " left-2" : " right-2"}`, style: {
|
|
4437
4466
|
top: (y ?? 0) - 8,
|
|
4438
4467
|
...(kind === "input"
|
|
4439
4468
|
? { right: "50%" }
|
|
@@ -4545,7 +4574,7 @@ function DefaultNodeContent({ data, isConnectable, }) {
|
|
|
4545
4574
|
isDefault: false,
|
|
4546
4575
|
};
|
|
4547
4576
|
})();
|
|
4548
|
-
return (jsxs("span", { className: `flex items-center gap-1 w-full ${valueText?.isDefault ? "text-gray-400" : ""}`, children: [kind === "output" ? (jsxs(Fragment, { children: [valueText !== undefined ? (jsx("span", { className: "opacity-60 truncate pl-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText.text })) : (jsx("span", { style: { flex: 1, minWidth: 0, maxWidth: "100%" } })), jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) })] })) : (jsxs(Fragment, { children: [jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) }), valueText !== undefined && (jsx("span", { className: `truncate pr-1 ${valueText.isDefault ? "text-gray-400" : "opacity-60"}`, style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText.text }))] })), hasAny && (jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "shrink-0", title: title }))] }));
|
|
4577
|
+
return (jsxs("span", { className: `flex items-center gap-1 w-full ${valueText?.isDefault ? "text-gray-400" : ""}`, children: [kind === "output" ? (jsxs(Fragment, { children: [valueText !== undefined ? (jsx("span", { className: "opacity-60 truncate pl-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText.text })) : (jsx("span", { style: { flex: 1, minWidth: 0, maxWidth: "100%" } })), jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) })] })) : (jsxs(Fragment, { children: [jsx("span", { className: "truncate shrink-0", "data-handle-id": handleId, style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) }), valueText !== undefined && (jsx("span", { className: `truncate pr-1 ${valueText.isDefault ? "text-gray-400" : "opacity-60"}`, style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText.text }))] })), hasAny && (jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "shrink-0", title: title }))] }));
|
|
4549
4578
|
} })] }));
|
|
4550
4579
|
}
|
|
4551
4580
|
|
|
@@ -5158,7 +5187,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5158
5187
|
setNodeMenuOpen(false);
|
|
5159
5188
|
setSelectionMenuOpen(false);
|
|
5160
5189
|
};
|
|
5161
|
-
const addNodeAt = useCallback(async (typeId, opts) => wb.addNode({ typeId
|
|
5190
|
+
const addNodeAt = useCallback(async (typeId, opts) => wb.addNode({ typeId }, { inputs: opts.inputs, position: opts.position, commit: true }), [wb]);
|
|
5162
5191
|
const onCloseMenu = useCallback(() => {
|
|
5163
5192
|
setMenuOpen(false);
|
|
5164
5193
|
}, []);
|
|
@@ -5459,6 +5488,601 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5459
5488
|
(SelectionContextMenuRenderer ? (jsx(SelectionContextMenuRenderer, { open: selectionMenuOpen, clientPos: selectionMenuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })) : (jsx(SelectionContextMenu, { open: selectionMenuOpen, clientPos: selectionMenuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })))] }) }), toast && (jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id))] }));
|
|
5460
5489
|
});
|
|
5461
5490
|
|
|
5491
|
+
/**
|
|
5492
|
+
* Flow thumbnail capture utility
|
|
5493
|
+
* Captures React Flow canvas as SVG image
|
|
5494
|
+
*/
|
|
5495
|
+
// ============================================================================
|
|
5496
|
+
// Utility Functions
|
|
5497
|
+
// ============================================================================
|
|
5498
|
+
/**
|
|
5499
|
+
* Parses CSS transform string to extract translate and scale values
|
|
5500
|
+
*/
|
|
5501
|
+
function parseViewportTransform(transform) {
|
|
5502
|
+
let translateX = 0;
|
|
5503
|
+
let translateY = 0;
|
|
5504
|
+
let scale = 1;
|
|
5505
|
+
if (transform && transform !== "none") {
|
|
5506
|
+
// Try translate() scale() format first
|
|
5507
|
+
const translateMatch = transform.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
|
|
5508
|
+
const scaleMatch = transform.match(/scale\(([^)]+)\)/);
|
|
5509
|
+
if (translateMatch) {
|
|
5510
|
+
translateX = parseFloat(translateMatch[1]);
|
|
5511
|
+
translateY = parseFloat(translateMatch[2]);
|
|
5512
|
+
}
|
|
5513
|
+
if (scaleMatch) {
|
|
5514
|
+
scale = parseFloat(scaleMatch[1]);
|
|
5515
|
+
}
|
|
5516
|
+
// Fallback to matrix format
|
|
5517
|
+
if (!translateMatch) {
|
|
5518
|
+
const matrixMatch = transform.match(/matrix\(([^)]+)\)/);
|
|
5519
|
+
if (matrixMatch) {
|
|
5520
|
+
const values = matrixMatch[1]
|
|
5521
|
+
.split(",")
|
|
5522
|
+
.map((v) => parseFloat(v.trim()));
|
|
5523
|
+
if (values.length >= 6) {
|
|
5524
|
+
scale = Math.sqrt(values[0] * values[0] + values[1] * values[1]);
|
|
5525
|
+
translateX = values[4];
|
|
5526
|
+
translateY = values[5];
|
|
5527
|
+
}
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
return { translateX, translateY, scale };
|
|
5532
|
+
}
|
|
5533
|
+
/**
|
|
5534
|
+
* Calculates visible viewport bounds in flow coordinates
|
|
5535
|
+
*/
|
|
5536
|
+
function calculateVisibleBounds(viewportRect, transform) {
|
|
5537
|
+
const { translateX, translateY, scale } = transform;
|
|
5538
|
+
// Guard against division by zero
|
|
5539
|
+
if (scale === 0) {
|
|
5540
|
+
console.warn("[flowThumbnail] Viewport scale is 0, using default bounds");
|
|
5541
|
+
return {
|
|
5542
|
+
minX: -translateX,
|
|
5543
|
+
minY: -translateY,
|
|
5544
|
+
maxX: viewportRect.width - translateX,
|
|
5545
|
+
maxY: viewportRect.height - translateY,
|
|
5546
|
+
};
|
|
5547
|
+
}
|
|
5548
|
+
// Screen to flow: (screenX - translateX) / scale
|
|
5549
|
+
return {
|
|
5550
|
+
minX: (0 - translateX) / scale,
|
|
5551
|
+
minY: (0 - translateY) / scale,
|
|
5552
|
+
maxX: (viewportRect.width - translateX) / scale,
|
|
5553
|
+
maxY: (viewportRect.height - translateY) / scale,
|
|
5554
|
+
};
|
|
5555
|
+
}
|
|
5556
|
+
/**
|
|
5557
|
+
* Parses border radius string (px or rem) to pixels
|
|
5558
|
+
*/
|
|
5559
|
+
function parseBorderRadius(borderRadiusStr) {
|
|
5560
|
+
if (borderRadiusStr === "0px") {
|
|
5561
|
+
return 8; // default
|
|
5562
|
+
}
|
|
5563
|
+
const match = borderRadiusStr.match(/([\d.]+)(px|rem)/);
|
|
5564
|
+
if (match) {
|
|
5565
|
+
const value = parseFloat(match[1]);
|
|
5566
|
+
const unit = match[2];
|
|
5567
|
+
// Convert rem to px (assuming 16px base) or use px directly
|
|
5568
|
+
return unit === "rem" ? value * 16 : value;
|
|
5569
|
+
}
|
|
5570
|
+
// Try direct parseFloat as fallback
|
|
5571
|
+
const parsed = parseFloat(borderRadiusStr);
|
|
5572
|
+
return isNaN(parsed) ? 8 : parsed;
|
|
5573
|
+
}
|
|
5574
|
+
/**
|
|
5575
|
+
* Extracts stroke color from element, with fallback
|
|
5576
|
+
*/
|
|
5577
|
+
function extractStrokeColor(element) {
|
|
5578
|
+
if (element instanceof SVGPathElement) {
|
|
5579
|
+
return (element.getAttribute("stroke") ||
|
|
5580
|
+
window.getComputedStyle(element).stroke ||
|
|
5581
|
+
"#b1b1b7");
|
|
5582
|
+
}
|
|
5583
|
+
const style = window.getComputedStyle(element);
|
|
5584
|
+
return (style.borderColor || style.borderTopColor || "#6b7280" // gray-500 default
|
|
5585
|
+
);
|
|
5586
|
+
}
|
|
5587
|
+
/**
|
|
5588
|
+
* Extracts stroke/border width from element, ensuring minimum value
|
|
5589
|
+
*/
|
|
5590
|
+
function extractStrokeWidth(element, minWidth = 1) {
|
|
5591
|
+
if (element instanceof SVGPathElement) {
|
|
5592
|
+
const width = parseFloat(element.getAttribute("stroke-width") || "0") ||
|
|
5593
|
+
parseFloat(window.getComputedStyle(element).strokeWidth || "2");
|
|
5594
|
+
return width > 0 ? width : minWidth;
|
|
5595
|
+
}
|
|
5596
|
+
const style = window.getComputedStyle(element);
|
|
5597
|
+
const width = parseFloat(style.borderWidth || style.borderTopWidth || "0");
|
|
5598
|
+
return width > 0 ? width : minWidth;
|
|
5599
|
+
}
|
|
5600
|
+
/**
|
|
5601
|
+
* Checks if a rectangle intersects with visible bounds
|
|
5602
|
+
*/
|
|
5603
|
+
function isRectVisible(x, y, width, height, bounds) {
|
|
5604
|
+
return (x + width >= bounds.minX &&
|
|
5605
|
+
x <= bounds.maxX &&
|
|
5606
|
+
y + height >= bounds.minY &&
|
|
5607
|
+
y <= bounds.maxY);
|
|
5608
|
+
}
|
|
5609
|
+
/**
|
|
5610
|
+
* Parses path data to get bounding box
|
|
5611
|
+
* Handles M (moveTo), L (lineTo), C (cubic Bezier), Q (quadratic Bezier), and H/V (horizontal/vertical) commands
|
|
5612
|
+
*/
|
|
5613
|
+
function getPathBounds(pathData) {
|
|
5614
|
+
let minX = Infinity;
|
|
5615
|
+
let minY = Infinity;
|
|
5616
|
+
let maxX = -Infinity;
|
|
5617
|
+
let maxY = -Infinity;
|
|
5618
|
+
// Match coordinates from various path commands: M, L, C, Q, T, S, H, V
|
|
5619
|
+
// Pattern matches: command letter followed by coordinate pairs
|
|
5620
|
+
const coordPattern = /[MLCQTSHV](-?\d+\.?\d*),(-?\d+\.?\d*)/g;
|
|
5621
|
+
const coords = pathData.match(coordPattern);
|
|
5622
|
+
if (coords) {
|
|
5623
|
+
coords.forEach((coord) => {
|
|
5624
|
+
const match = coord.match(/(-?\d+\.?\d*),(-?\d+\.?\d*)/);
|
|
5625
|
+
if (match) {
|
|
5626
|
+
const x = parseFloat(match[1]);
|
|
5627
|
+
const y = parseFloat(match[2]);
|
|
5628
|
+
if (!isNaN(x) && !isNaN(y)) {
|
|
5629
|
+
minX = Math.min(minX, x);
|
|
5630
|
+
minY = Math.min(minY, y);
|
|
5631
|
+
maxX = Math.max(maxX, x);
|
|
5632
|
+
maxY = Math.max(maxY, y);
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
});
|
|
5636
|
+
}
|
|
5637
|
+
return { minX, minY, maxX, maxY };
|
|
5638
|
+
}
|
|
5639
|
+
// ============================================================================
|
|
5640
|
+
// Edge Extraction
|
|
5641
|
+
// ============================================================================
|
|
5642
|
+
/**
|
|
5643
|
+
* Extracts visible edge paths from React Flow viewport
|
|
5644
|
+
*/
|
|
5645
|
+
function extractEdgePaths(viewport, visibleBounds) {
|
|
5646
|
+
const edges = [];
|
|
5647
|
+
let minX = Infinity;
|
|
5648
|
+
let minY = Infinity;
|
|
5649
|
+
let maxX = -Infinity;
|
|
5650
|
+
let maxY = -Infinity;
|
|
5651
|
+
const edgePathElements = viewport.querySelectorAll(".react-flow__edge-path");
|
|
5652
|
+
edgePathElements.forEach((pathEl) => {
|
|
5653
|
+
const pathData = pathEl.getAttribute("d");
|
|
5654
|
+
if (!pathData)
|
|
5655
|
+
return;
|
|
5656
|
+
const pathBounds = getPathBounds(pathData);
|
|
5657
|
+
// Only include edge if it intersects with visible viewport
|
|
5658
|
+
if (pathBounds.maxX >= visibleBounds.minX &&
|
|
5659
|
+
pathBounds.minX <= visibleBounds.maxX &&
|
|
5660
|
+
pathBounds.maxY >= visibleBounds.minY &&
|
|
5661
|
+
pathBounds.minY <= visibleBounds.maxY) {
|
|
5662
|
+
edges.push({
|
|
5663
|
+
d: pathData,
|
|
5664
|
+
stroke: extractStrokeColor(pathEl),
|
|
5665
|
+
strokeWidth: extractStrokeWidth(pathEl, 2),
|
|
5666
|
+
});
|
|
5667
|
+
// Update bounding box
|
|
5668
|
+
minX = Math.min(minX, pathBounds.minX);
|
|
5669
|
+
minY = Math.min(minY, pathBounds.minY);
|
|
5670
|
+
maxX = Math.max(maxX, pathBounds.maxX);
|
|
5671
|
+
maxY = Math.max(maxY, pathBounds.maxY);
|
|
5672
|
+
}
|
|
5673
|
+
});
|
|
5674
|
+
return { edges, bounds: { minX, minY, maxX, maxY } };
|
|
5675
|
+
}
|
|
5676
|
+
// ============================================================================
|
|
5677
|
+
// Node Extraction
|
|
5678
|
+
// ============================================================================
|
|
5679
|
+
/**
|
|
5680
|
+
* Extracts node position from transform style
|
|
5681
|
+
*/
|
|
5682
|
+
function extractNodePosition(nodeEl) {
|
|
5683
|
+
const transformStyle = nodeEl.style.transform || "";
|
|
5684
|
+
const translateMatch = transformStyle.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
|
|
5685
|
+
if (translateMatch) {
|
|
5686
|
+
return {
|
|
5687
|
+
x: parseFloat(translateMatch[1]),
|
|
5688
|
+
y: parseFloat(translateMatch[2]),
|
|
5689
|
+
};
|
|
5690
|
+
}
|
|
5691
|
+
return { x: 0, y: 0 };
|
|
5692
|
+
}
|
|
5693
|
+
/**
|
|
5694
|
+
* Extracts node dimensions from inline styles
|
|
5695
|
+
*/
|
|
5696
|
+
function extractNodeDimensions(nodeEl) {
|
|
5697
|
+
const widthMatch = nodeEl.style.width?.match(/(\d+)px/);
|
|
5698
|
+
const heightMatch = nodeEl.style.height?.match(/(\d+)px/);
|
|
5699
|
+
return {
|
|
5700
|
+
width: widthMatch ? parseFloat(widthMatch[1]) : 150,
|
|
5701
|
+
height: heightMatch ? parseFloat(heightMatch[1]) : 40,
|
|
5702
|
+
};
|
|
5703
|
+
}
|
|
5704
|
+
/**
|
|
5705
|
+
* Extracts node styles (colors, border, radius) from computed styles
|
|
5706
|
+
*/
|
|
5707
|
+
function extractNodeStyles(nodeContent) {
|
|
5708
|
+
const computedStyle = window.getComputedStyle(nodeContent);
|
|
5709
|
+
// Use gray background for nodes in thumbnail
|
|
5710
|
+
const fill = "#f3f4f6"; // gray-100 equivalent
|
|
5711
|
+
const stroke = extractStrokeColor(nodeContent);
|
|
5712
|
+
const strokeWidth = extractStrokeWidth(nodeContent, 1);
|
|
5713
|
+
const borderRadiusStr = computedStyle.borderRadius || "8px";
|
|
5714
|
+
const rx = parseBorderRadius(borderRadiusStr);
|
|
5715
|
+
const ry = rx; // Use same radius for both x and y
|
|
5716
|
+
return { fill, stroke, strokeWidth, rx, ry };
|
|
5717
|
+
}
|
|
5718
|
+
/**
|
|
5719
|
+
* Determines if a handle is a source (output) or target (input)
|
|
5720
|
+
*/
|
|
5721
|
+
function isHandleSource(handleEl) {
|
|
5722
|
+
return (handleEl.classList.contains("react-flow__handle-right") ||
|
|
5723
|
+
handleEl.classList.contains("react-flow__handle-source"));
|
|
5724
|
+
}
|
|
5725
|
+
/**
|
|
5726
|
+
* Extracts handle position and calculates absolute coordinates
|
|
5727
|
+
*/
|
|
5728
|
+
function extractHandlePosition(handleEl, nodeX, nodeY, nodeWidth, isSource) {
|
|
5729
|
+
const handleStyle = window.getComputedStyle(handleEl);
|
|
5730
|
+
const handleTop = parseFloat(handleStyle.top || "0");
|
|
5731
|
+
const handleLeft = handleStyle.left;
|
|
5732
|
+
const handleRight = handleStyle.right;
|
|
5733
|
+
const handleY = nodeY + handleTop;
|
|
5734
|
+
let handleX;
|
|
5735
|
+
if (isSource) {
|
|
5736
|
+
// Source handles are on the right edge
|
|
5737
|
+
if (handleRight !== "auto" && handleRight !== "") {
|
|
5738
|
+
const rightValue = parseFloat(handleRight) || 0;
|
|
5739
|
+
handleX = nodeX + nodeWidth + rightValue;
|
|
5740
|
+
}
|
|
5741
|
+
else {
|
|
5742
|
+
handleX = nodeX + nodeWidth;
|
|
5743
|
+
}
|
|
5744
|
+
}
|
|
5745
|
+
else {
|
|
5746
|
+
// Target handles are on the left edge
|
|
5747
|
+
if (handleLeft !== "auto" && handleLeft !== "") {
|
|
5748
|
+
const leftValue = parseFloat(handleLeft) || 0;
|
|
5749
|
+
handleX = nodeX + leftValue;
|
|
5750
|
+
}
|
|
5751
|
+
else {
|
|
5752
|
+
handleX = nodeX;
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
return { x: handleX, y: handleY };
|
|
5756
|
+
}
|
|
5757
|
+
/**
|
|
5758
|
+
* Extracts handles from a node element
|
|
5759
|
+
*/
|
|
5760
|
+
function extractNodeHandles(nodeEl, nodeX, nodeY, nodeWidth) {
|
|
5761
|
+
const handles = [];
|
|
5762
|
+
const handleElements = nodeEl.querySelectorAll(".react-flow__handle");
|
|
5763
|
+
handleElements.forEach((handleEl) => {
|
|
5764
|
+
const handleStyle = window.getComputedStyle(handleEl);
|
|
5765
|
+
const handleWidth = parseFloat(handleStyle.width || "12");
|
|
5766
|
+
const handleHeight = parseFloat(handleStyle.height || "12");
|
|
5767
|
+
const isSource = isHandleSource(handleEl);
|
|
5768
|
+
const position = extractHandlePosition(handleEl, nodeX, nodeY, nodeWidth, isSource);
|
|
5769
|
+
handles.push({
|
|
5770
|
+
x: position.x,
|
|
5771
|
+
y: position.y,
|
|
5772
|
+
width: handleWidth,
|
|
5773
|
+
height: handleHeight,
|
|
5774
|
+
fill: handleStyle.backgroundColor || "rgba(255, 255, 255, 0.5)",
|
|
5775
|
+
stroke: extractStrokeColor(handleEl),
|
|
5776
|
+
strokeWidth: extractStrokeWidth(handleEl, 1),
|
|
5777
|
+
type: isSource ? "source" : "target",
|
|
5778
|
+
});
|
|
5779
|
+
});
|
|
5780
|
+
return handles;
|
|
5781
|
+
}
|
|
5782
|
+
/**
|
|
5783
|
+
* Extracts node title text and position
|
|
5784
|
+
*/
|
|
5785
|
+
function extractNodeTitle(nodeEl, nodeX, nodeY) {
|
|
5786
|
+
const titleElement = nodeEl.querySelector(".react-flow__node-title");
|
|
5787
|
+
if (!titleElement)
|
|
5788
|
+
return undefined;
|
|
5789
|
+
const titleText = titleElement.textContent || titleElement.innerText || "";
|
|
5790
|
+
if (!titleText.trim())
|
|
5791
|
+
return undefined;
|
|
5792
|
+
const titleStyle = window.getComputedStyle(titleElement);
|
|
5793
|
+
const titleRect = titleElement.getBoundingClientRect();
|
|
5794
|
+
const nodeRect = nodeEl.getBoundingClientRect();
|
|
5795
|
+
// Calculate title position relative to node (in flow coordinates)
|
|
5796
|
+
const titleRelativeX = titleRect.left - nodeRect.left;
|
|
5797
|
+
const titleRelativeY = titleRect.top - nodeRect.top;
|
|
5798
|
+
// Get font properties
|
|
5799
|
+
const fontSize = parseFloat(titleStyle.fontSize || "14");
|
|
5800
|
+
const fill = titleStyle.color || "#374151"; // gray-700 default
|
|
5801
|
+
const fontWeight = titleStyle.fontWeight || "600"; // bold default
|
|
5802
|
+
// Calculate text position (SVG text uses baseline)
|
|
5803
|
+
const lineHeight = parseFloat(titleStyle.lineHeight || String(fontSize * 1.2));
|
|
5804
|
+
const textBaselineOffset = lineHeight * 0.8; // Approximate baseline offset
|
|
5805
|
+
return {
|
|
5806
|
+
text: titleText.trim(),
|
|
5807
|
+
x: nodeX + titleRelativeX,
|
|
5808
|
+
y: nodeY + titleRelativeY + textBaselineOffset,
|
|
5809
|
+
fontSize,
|
|
5810
|
+
fill,
|
|
5811
|
+
fontWeight,
|
|
5812
|
+
};
|
|
5813
|
+
}
|
|
5814
|
+
/**
|
|
5815
|
+
* Extracts visible nodes from React Flow viewport
|
|
5816
|
+
*/
|
|
5817
|
+
function extractNodes(viewport, visibleBounds) {
|
|
5818
|
+
const nodes = [];
|
|
5819
|
+
let minX = Infinity;
|
|
5820
|
+
let minY = Infinity;
|
|
5821
|
+
let maxX = -Infinity;
|
|
5822
|
+
let maxY = -Infinity;
|
|
5823
|
+
const nodeElements = viewport.querySelectorAll(".react-flow__node");
|
|
5824
|
+
nodeElements.forEach((nodeEl) => {
|
|
5825
|
+
const position = extractNodePosition(nodeEl);
|
|
5826
|
+
const dimensions = extractNodeDimensions(nodeEl);
|
|
5827
|
+
// Get the actual node content div (first child)
|
|
5828
|
+
const nodeContent = nodeEl.firstElementChild;
|
|
5829
|
+
if (!nodeContent)
|
|
5830
|
+
return;
|
|
5831
|
+
const styles = extractNodeStyles(nodeContent);
|
|
5832
|
+
const handles = extractNodeHandles(nodeEl, position.x, position.y, dimensions.width);
|
|
5833
|
+
const title = extractNodeTitle(nodeEl, position.x, position.y);
|
|
5834
|
+
// Only include node if it's within visible viewport
|
|
5835
|
+
if (isRectVisible(position.x, position.y, dimensions.width, dimensions.height, visibleBounds)) {
|
|
5836
|
+
nodes.push({
|
|
5837
|
+
x: position.x,
|
|
5838
|
+
y: position.y,
|
|
5839
|
+
width: dimensions.width,
|
|
5840
|
+
height: dimensions.height,
|
|
5841
|
+
...styles,
|
|
5842
|
+
handles,
|
|
5843
|
+
title,
|
|
5844
|
+
});
|
|
5845
|
+
// Update bounding box
|
|
5846
|
+
minX = Math.min(minX, position.x);
|
|
5847
|
+
minY = Math.min(minY, position.y);
|
|
5848
|
+
maxX = Math.max(maxX, position.x + dimensions.width);
|
|
5849
|
+
maxY = Math.max(maxY, position.y + dimensions.height);
|
|
5850
|
+
// Update bounding box to include handles
|
|
5851
|
+
handles.forEach((handle) => {
|
|
5852
|
+
minX = Math.min(minX, handle.x);
|
|
5853
|
+
minY = Math.min(minY, handle.y);
|
|
5854
|
+
maxX = Math.max(maxX, handle.x + handle.width);
|
|
5855
|
+
maxY = Math.max(maxY, handle.y + handle.height);
|
|
5856
|
+
});
|
|
5857
|
+
}
|
|
5858
|
+
});
|
|
5859
|
+
return { nodes, bounds: { minX, minY, maxX, maxY } };
|
|
5860
|
+
}
|
|
5861
|
+
// ============================================================================
|
|
5862
|
+
// SVG Rendering
|
|
5863
|
+
// ============================================================================
|
|
5864
|
+
/**
|
|
5865
|
+
* Creates SVG element with dot pattern background (matching React Flow)
|
|
5866
|
+
*/
|
|
5867
|
+
function createSVGElement(width, height) {
|
|
5868
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
5869
|
+
svg.setAttribute("width", String(width));
|
|
5870
|
+
svg.setAttribute("height", String(height));
|
|
5871
|
+
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
5872
|
+
// Create defs section for patterns
|
|
5873
|
+
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
5874
|
+
// Create dot pattern (matching React Flow's BackgroundVariant.Dots)
|
|
5875
|
+
// React Flow uses gap={12} and size={1} by default
|
|
5876
|
+
const pattern = document.createElementNS("http://www.w3.org/2000/svg", "pattern");
|
|
5877
|
+
pattern.setAttribute("id", "dot-pattern");
|
|
5878
|
+
pattern.setAttribute("x", "0");
|
|
5879
|
+
pattern.setAttribute("y", "0");
|
|
5880
|
+
pattern.setAttribute("width", "24"); // gap between dots (matching React Flow default)
|
|
5881
|
+
pattern.setAttribute("height", "24");
|
|
5882
|
+
pattern.setAttribute("patternUnits", "userSpaceOnUse");
|
|
5883
|
+
// Create a circle for the dot (centered in the pattern cell)
|
|
5884
|
+
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
5885
|
+
circle.setAttribute("cx", "12"); // Center of 24x24 pattern cell
|
|
5886
|
+
circle.setAttribute("cy", "12");
|
|
5887
|
+
circle.setAttribute("r", "1"); // dot radius = 1 (matching React Flow size={1})
|
|
5888
|
+
circle.setAttribute("fill", "#f1f1f1"); // gray color matching React Flow default
|
|
5889
|
+
pattern.appendChild(circle);
|
|
5890
|
+
defs.appendChild(pattern);
|
|
5891
|
+
svg.appendChild(defs);
|
|
5892
|
+
// Create background rectangle with white base
|
|
5893
|
+
const bgRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
5894
|
+
bgRect.setAttribute("width", String(width));
|
|
5895
|
+
bgRect.setAttribute("height", String(height));
|
|
5896
|
+
bgRect.setAttribute("fill", "#ffffff"); // Base background color
|
|
5897
|
+
svg.appendChild(bgRect);
|
|
5898
|
+
// Create pattern overlay rectangle
|
|
5899
|
+
const patternRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
5900
|
+
patternRect.setAttribute("width", String(width));
|
|
5901
|
+
patternRect.setAttribute("height", String(height));
|
|
5902
|
+
patternRect.setAttribute("fill", "url(#dot-pattern)");
|
|
5903
|
+
svg.appendChild(patternRect);
|
|
5904
|
+
// Create group with transform
|
|
5905
|
+
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
5906
|
+
svg.appendChild(group);
|
|
5907
|
+
return { svg, group };
|
|
5908
|
+
}
|
|
5909
|
+
/**
|
|
5910
|
+
* Renders a node rectangle to SVG group
|
|
5911
|
+
*/
|
|
5912
|
+
function renderNodeRect(group, node) {
|
|
5913
|
+
const rectEl = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
5914
|
+
rectEl.setAttribute("x", String(node.x));
|
|
5915
|
+
rectEl.setAttribute("y", String(node.y));
|
|
5916
|
+
rectEl.setAttribute("width", String(node.width));
|
|
5917
|
+
rectEl.setAttribute("height", String(node.height));
|
|
5918
|
+
rectEl.setAttribute("rx", String(node.rx));
|
|
5919
|
+
rectEl.setAttribute("ry", String(node.ry));
|
|
5920
|
+
rectEl.setAttribute("fill", node.fill);
|
|
5921
|
+
rectEl.setAttribute("stroke", node.stroke);
|
|
5922
|
+
rectEl.setAttribute("stroke-width", String(node.strokeWidth));
|
|
5923
|
+
group.appendChild(rectEl);
|
|
5924
|
+
}
|
|
5925
|
+
/**
|
|
5926
|
+
* Renders a handle to SVG group
|
|
5927
|
+
*/
|
|
5928
|
+
function renderHandle(group, handle) {
|
|
5929
|
+
const handleEl = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
5930
|
+
// Handles are centered on edges
|
|
5931
|
+
handleEl.setAttribute("x", String(handle.x - handle.width / 2));
|
|
5932
|
+
handleEl.setAttribute("y", String(handle.y - handle.height / 2));
|
|
5933
|
+
handleEl.setAttribute("width", String(handle.width));
|
|
5934
|
+
handleEl.setAttribute("height", String(handle.height));
|
|
5935
|
+
handleEl.setAttribute("rx", String(handle.width / 2)); // Make handles circular/rounded
|
|
5936
|
+
handleEl.setAttribute("ry", String(handle.height / 2));
|
|
5937
|
+
handleEl.setAttribute("fill", handle.fill);
|
|
5938
|
+
handleEl.setAttribute("stroke", handle.stroke);
|
|
5939
|
+
handleEl.setAttribute("stroke-width", String(handle.strokeWidth));
|
|
5940
|
+
group.appendChild(handleEl);
|
|
5941
|
+
}
|
|
5942
|
+
/**
|
|
5943
|
+
* Renders node title text to SVG group
|
|
5944
|
+
*/
|
|
5945
|
+
function renderNodeTitle(group, title) {
|
|
5946
|
+
const textEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
5947
|
+
textEl.setAttribute("x", String(title.x));
|
|
5948
|
+
textEl.setAttribute("y", String(title.y));
|
|
5949
|
+
textEl.setAttribute("font-size", String(title.fontSize));
|
|
5950
|
+
textEl.setAttribute("fill", title.fill);
|
|
5951
|
+
textEl.setAttribute("font-weight", title.fontWeight);
|
|
5952
|
+
textEl.setAttribute("font-family", "system-ui, -apple-system, sans-serif");
|
|
5953
|
+
textEl.textContent = title.text;
|
|
5954
|
+
group.appendChild(textEl);
|
|
5955
|
+
}
|
|
5956
|
+
/**
|
|
5957
|
+
* Renders an edge path to SVG group
|
|
5958
|
+
*/
|
|
5959
|
+
function renderEdgePath(group, edge) {
|
|
5960
|
+
const pathEl = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
5961
|
+
pathEl.setAttribute("d", edge.d);
|
|
5962
|
+
pathEl.setAttribute("fill", "none");
|
|
5963
|
+
pathEl.setAttribute("stroke", edge.stroke);
|
|
5964
|
+
pathEl.setAttribute("stroke-width", String(edge.strokeWidth));
|
|
5965
|
+
group.appendChild(pathEl);
|
|
5966
|
+
}
|
|
5967
|
+
/**
|
|
5968
|
+
* Renders all nodes, handles, titles, and edges to SVG group
|
|
5969
|
+
*/
|
|
5970
|
+
function renderContentToSVG(group, nodes, edges, transformX, transformY) {
|
|
5971
|
+
group.setAttribute("transform", `translate(${transformX}, ${transformY})`);
|
|
5972
|
+
// Render nodes
|
|
5973
|
+
nodes.forEach((node) => {
|
|
5974
|
+
renderNodeRect(group, node);
|
|
5975
|
+
node.handles.forEach((handle) => renderHandle(group, handle));
|
|
5976
|
+
if (node.title) {
|
|
5977
|
+
renderNodeTitle(group, node.title);
|
|
5978
|
+
}
|
|
5979
|
+
});
|
|
5980
|
+
// Render edges
|
|
5981
|
+
edges.forEach((edge) => renderEdgePath(group, edge));
|
|
5982
|
+
}
|
|
5983
|
+
// ============================================================================
|
|
5984
|
+
// Main Capture Function
|
|
5985
|
+
// ============================================================================
|
|
5986
|
+
/**
|
|
5987
|
+
* Captures a React Flow container element as an SVG image and returns data URL
|
|
5988
|
+
* @param containerElement - The React Flow container DOM element
|
|
5989
|
+
* @returns Promise resolving to SVG data URL string, or null if capture fails
|
|
5990
|
+
*/
|
|
5991
|
+
async function captureCanvasThumbnail(containerElement) {
|
|
5992
|
+
if (!containerElement) {
|
|
5993
|
+
console.warn("[flowThumbnail] Container element is null");
|
|
5994
|
+
return null;
|
|
5995
|
+
}
|
|
5996
|
+
try {
|
|
5997
|
+
// Find the React Flow viewport element
|
|
5998
|
+
const reactFlowViewport = containerElement.querySelector(".react-flow__viewport");
|
|
5999
|
+
if (!reactFlowViewport) {
|
|
6000
|
+
console.warn("[flowThumbnail] React Flow viewport not found");
|
|
6001
|
+
return null;
|
|
6002
|
+
}
|
|
6003
|
+
// Parse viewport transform
|
|
6004
|
+
const viewportStyle = window.getComputedStyle(reactFlowViewport);
|
|
6005
|
+
const viewportTransform = viewportStyle.transform || viewportStyle.getPropertyValue("transform");
|
|
6006
|
+
const transform = parseViewportTransform(viewportTransform);
|
|
6007
|
+
// Calculate visible bounds
|
|
6008
|
+
const viewportRect = reactFlowViewport.getBoundingClientRect();
|
|
6009
|
+
const visibleBounds = calculateVisibleBounds(viewportRect, transform);
|
|
6010
|
+
// Extract edges and nodes
|
|
6011
|
+
const { edges, bounds: edgeBounds } = extractEdgePaths(reactFlowViewport, visibleBounds);
|
|
6012
|
+
const { nodes, bounds: nodeBounds } = extractNodes(reactFlowViewport, visibleBounds);
|
|
6013
|
+
// Calculate overall bounding box
|
|
6014
|
+
// Handle case where one or both bounds might be Infinity (no content)
|
|
6015
|
+
let minX = Infinity;
|
|
6016
|
+
let minY = Infinity;
|
|
6017
|
+
let maxX = -Infinity;
|
|
6018
|
+
let maxY = -Infinity;
|
|
6019
|
+
if (edgeBounds.minX !== Infinity) {
|
|
6020
|
+
minX = Math.min(minX, edgeBounds.minX);
|
|
6021
|
+
minY = Math.min(minY, edgeBounds.minY);
|
|
6022
|
+
maxX = Math.max(maxX, edgeBounds.maxX);
|
|
6023
|
+
maxY = Math.max(maxY, edgeBounds.maxY);
|
|
6024
|
+
}
|
|
6025
|
+
if (nodeBounds.minX !== Infinity) {
|
|
6026
|
+
minX = Math.min(minX, nodeBounds.minX);
|
|
6027
|
+
minY = Math.min(minY, nodeBounds.minY);
|
|
6028
|
+
maxX = Math.max(maxX, nodeBounds.maxX);
|
|
6029
|
+
maxY = Math.max(maxY, nodeBounds.maxY);
|
|
6030
|
+
}
|
|
6031
|
+
// If no visible content, use the visible viewport bounds
|
|
6032
|
+
if (minX === Infinity || (nodes.length === 0 && edges.length === 0)) {
|
|
6033
|
+
minX = visibleBounds.minX;
|
|
6034
|
+
minY = visibleBounds.minY;
|
|
6035
|
+
maxX = visibleBounds.maxX;
|
|
6036
|
+
maxY = visibleBounds.maxY;
|
|
6037
|
+
}
|
|
6038
|
+
// Use the visible viewport bounds exactly (what the user sees)
|
|
6039
|
+
const contentMinX = visibleBounds.minX;
|
|
6040
|
+
const contentMinY = visibleBounds.minY;
|
|
6041
|
+
const contentMaxX = visibleBounds.maxX;
|
|
6042
|
+
const contentMaxY = visibleBounds.maxY;
|
|
6043
|
+
const contentWidth = contentMaxX - contentMinX;
|
|
6044
|
+
const contentHeight = contentMaxY - contentMinY;
|
|
6045
|
+
// Create SVG
|
|
6046
|
+
const { svg, group } = createSVGElement(contentWidth, contentHeight);
|
|
6047
|
+
// Render content
|
|
6048
|
+
renderContentToSVG(group, nodes, edges, -contentMinX, -contentMinY);
|
|
6049
|
+
// Serialize SVG to string
|
|
6050
|
+
const serializer = new XMLSerializer();
|
|
6051
|
+
const svgString = serializer.serializeToString(svg);
|
|
6052
|
+
// Return SVG data URL
|
|
6053
|
+
const svgDataUrl = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgString)))}`;
|
|
6054
|
+
return svgDataUrl;
|
|
6055
|
+
}
|
|
6056
|
+
catch (error) {
|
|
6057
|
+
console.error("[flowThumbnail] Failed to capture thumbnail:", error);
|
|
6058
|
+
return null;
|
|
6059
|
+
}
|
|
6060
|
+
}
|
|
6061
|
+
/**
|
|
6062
|
+
* Captures a React Flow container element as an SVG image and downloads it
|
|
6063
|
+
* @param containerElement - The React Flow container DOM element
|
|
6064
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
6065
|
+
*/
|
|
6066
|
+
async function downloadCanvasThumbnail(containerElement) {
|
|
6067
|
+
const svgDataUrl = await captureCanvasThumbnail(containerElement);
|
|
6068
|
+
if (!svgDataUrl) {
|
|
6069
|
+
return false;
|
|
6070
|
+
}
|
|
6071
|
+
// Create blob and download
|
|
6072
|
+
const base64Data = svgDataUrl.split(",")[1];
|
|
6073
|
+
const svgString = atob(base64Data);
|
|
6074
|
+
const blob = new Blob([svgString], { type: "image/svg+xml" });
|
|
6075
|
+
const url = URL.createObjectURL(blob);
|
|
6076
|
+
const link = document.createElement("a");
|
|
6077
|
+
link.href = url;
|
|
6078
|
+
link.download = `flow-thumbnail-${Date.now()}.svg`;
|
|
6079
|
+
document.body.appendChild(link);
|
|
6080
|
+
link.click();
|
|
6081
|
+
document.body.removeChild(link);
|
|
6082
|
+
URL.revokeObjectURL(url);
|
|
6083
|
+
return true;
|
|
6084
|
+
}
|
|
6085
|
+
|
|
5462
6086
|
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
|
|
5463
6087
|
const { wb, runner, registry, selectedNodeId, runAutoLayout } = useWorkbenchContext();
|
|
5464
6088
|
const [transportStatus, setTransportStatus] = useState({
|
|
@@ -5547,6 +6171,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5547
6171
|
return defaultExamples;
|
|
5548
6172
|
}, [overrides, defaultExamples]);
|
|
5549
6173
|
const canvasRef = useRef(null);
|
|
6174
|
+
const canvasContainerRef = useRef(null);
|
|
5550
6175
|
const uploadInputRef = useRef(null);
|
|
5551
6176
|
const [registryReady, setRegistryReady] = useState(() => {
|
|
5552
6177
|
// For local backends, registry is always ready
|
|
@@ -5908,7 +6533,9 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5908
6533
|
// Normal change when not running
|
|
5909
6534
|
onEngineChange?.(kind);
|
|
5910
6535
|
}
|
|
5911
|
-
}, children: [jsx("option", { value: "", children: "Select Engine\u2026" }), jsx("option", { value: "push", children: "Push" }), jsx("option", { value: "batched", children: "Batched" }), jsx("option", { value: "pull", children: "Pull" }), jsx("option", { value: "hybrid", children: "Hybrid" }), jsx("option", { value: "step", children: "Step" })] }), engineKind === "step" && (jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !isGraphRunning, title: "Step", children: jsx(PlayPauseIcon, { size: 24 }) })), engineKind === "batched" && (jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !isGraphRunning, title: "Flush", children: jsx(LightningIcon, { size: 24 }) })), renderStartStopButton(), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsx(TreeStructureIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsx(CornersOutIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsx(DownloadIcon, { size: 24 }) }), jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsx(UploadIcon, { size: 24 }) }),
|
|
6536
|
+
}, children: [jsx("option", { value: "", children: "Select Engine\u2026" }), jsx("option", { value: "push", children: "Push" }), jsx("option", { value: "batched", children: "Batched" }), jsx("option", { value: "pull", children: "Pull" }), jsx("option", { value: "hybrid", children: "Hybrid" }), jsx("option", { value: "step", children: "Step" })] }), engineKind === "step" && (jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !isGraphRunning, title: "Step", children: jsx(PlayPauseIcon, { size: 24 }) })), engineKind === "batched" && (jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !isGraphRunning, title: "Flush", children: jsx(LightningIcon, { size: 24 }) })), renderStartStopButton(), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsx(TreeStructureIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsx(CornersOutIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsx(DownloadIcon, { size: 24 }) }), jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsx(UploadIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: async () => {
|
|
6537
|
+
await downloadCanvasThumbnail(canvasContainerRef.current);
|
|
6538
|
+
}, title: "Download Flow Thumbnail (SVG)", children: jsx(ImageIcon, { size: 24 }) }), jsxs("label", { className: "flex items-center gap-1", children: [jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsx(BugBeetleIcon, { size: 24, weight: debug ? "fill" : undefined })] }), jsxs("label", { className: "flex items-center gap-1", children: [jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsx(ListBulletsIcon, { size: 24, weight: showValues ? "fill" : undefined })] })] }), jsxs("div", { className: "flex flex-1 min-h-0", children: [jsx("div", { className: "flex-1 min-w-0", ref: canvasContainerRef, children: jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement, getDefaultNodeSize: overrides?.getDefaultNodeSize }) }), jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, contextPanel: overrides?.contextPanel })] })] }));
|
|
5912
6539
|
}
|
|
5913
6540
|
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
|
|
5914
6541
|
const [registry, setRegistry] = useState(createSimpleGraphRegistry());
|
|
@@ -5983,5 +6610,5 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
5983
6610
|
return (jsx(WorkbenchProvider, { wb: wb, runner: runner, registry: registry, setRegistry: setRegistry, overrides: overrides, uiVersion: uiVersion, children: jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, engine: engine, onEngineChange: onEngineChange, backendKind: backendKind, onBackendKindChange: onBackendKindChangeWithDispose, httpBaseUrl: httpBaseUrl, onHttpBaseUrlChange: onHttpBaseUrlChange, wsUrl: wsUrl, onWsUrlChange: onWsUrlChange, debug: debug, onDebugChange: onDebugChange, showValues: showValues, onShowValuesChange: onShowValuesChange, hideWorkbench: hideWorkbench, onHideWorkbenchChange: onHideWorkbenchChange, overrides: overrides, onInit: onInit, onChange: onChange }) }));
|
|
5984
6611
|
}
|
|
5985
6612
|
|
|
5986
|
-
export { AbstractWorkbench, CLIWorkbench, DefaultNode, DefaultNodeContent, DefaultNodeHeader, DefaultUIExtensionRegistry, InMemoryWorkbench, Inspector, LocalGraphRunner, NodeHandles, RemoteGraphRunner, WorkbenchCanvas, WorkbenchContext, WorkbenchProvider, WorkbenchStudio, computeEffectiveHandles, countVisibleHandles, createCopyHandler, createDefaultContextMenuHandlers, createHandleBounds, createHandleLayout, createNodeContextMenuHandlers, createNodeCopyHandler, createSelectionContextMenuHandlers, download, estimateNodeSize, excludeViewportFromUIState, formatDataUrlAsLabel, formatDeclaredTypeSignature, getBakeableOutputs, getHandleBoundsX, getHandleBoundsY, getHandleClassName, getHandleLayoutY, getNodeBorderClassNames, isValidViewport, layoutNode, mergeUIState, preformatValueForDisplay, prettyHandle, resolveOutputDisplay, summarizeDeep, toReactFlow, upload, useQueryParamBoolean, useQueryParamString, useThrottledValue, useWorkbenchBridge, useWorkbenchContext, useWorkbenchGraphTick, useWorkbenchGraphUiTick, useWorkbenchVersionTick };
|
|
6613
|
+
export { AbstractWorkbench, CLIWorkbench, DefaultNode, DefaultNodeContent, DefaultNodeHeader, DefaultUIExtensionRegistry, InMemoryWorkbench, Inspector, LocalGraphRunner, NodeHandles, RemoteGraphRunner, WorkbenchCanvas, WorkbenchContext, WorkbenchProvider, WorkbenchStudio, captureCanvasThumbnail, computeEffectiveHandles, countVisibleHandles, createCopyHandler, createDefaultContextMenuHandlers, createHandleBounds, createHandleLayout, createNodeContextMenuHandlers, createNodeCopyHandler, createSelectionContextMenuHandlers, download, downloadCanvasThumbnail, estimateNodeSize, excludeViewportFromUIState, formatDataUrlAsLabel, formatDeclaredTypeSignature, getBakeableOutputs, getHandleBoundsX, getHandleBoundsY, getHandleClassName, getHandleLayoutY, getNodeBorderClassNames, isValidViewport, layoutNode, mergeUIState, preformatValueForDisplay, prettyHandle, resolveOutputDisplay, summarizeDeep, toReactFlow, upload, useQueryParamBoolean, useQueryParamString, useThrottledValue, useWorkbenchBridge, useWorkbenchContext, useWorkbenchGraphTick, useWorkbenchGraphUiTick, useWorkbenchVersionTick };
|
|
5987
6614
|
//# sourceMappingURL=index.js.map
|