@bian-womp/spark-workbench 0.2.85 → 0.2.87
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 +679 -84
- package/lib/cjs/index.cjs.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/layout.d.ts +3 -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/misc/thumbnail-utils.d.ts +17 -0
- package/lib/cjs/src/misc/thumbnail-utils.d.ts.map +1 -0
- package/lib/esm/index.js +679 -86
- package/lib/esm/index.js.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/layout.d.ts +3 -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/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() {
|
|
@@ -2188,19 +2188,26 @@ function createHandleLayout(args) {
|
|
|
2188
2188
|
};
|
|
2189
2189
|
}
|
|
2190
2190
|
function layoutNode(args, overrides) {
|
|
2191
|
-
const { node, registry, showValues, overrides: sizeOverrides } = args;
|
|
2191
|
+
const { node, registry, showValues, overrides: sizeOverrides, missingInputs = [], missingOutputs = [], } = args;
|
|
2192
2192
|
const { inputs, outputs } = computeEffectiveHandles(node, registry);
|
|
2193
2193
|
const inputOrder = Object.keys(inputs).filter((k) => !isInputPrivate(inputs, k));
|
|
2194
2194
|
const outputOrder = Object.keys(outputs);
|
|
2195
2195
|
const estimateNodeSizeFn = overrides?.estimateNodeSize ?? estimateNodeSize;
|
|
2196
2196
|
const createHandleBoundsFn = overrides?.createHandleBounds ?? createHandleBounds;
|
|
2197
2197
|
const createHandleLayoutFn = overrides?.createHandleLayout ?? createHandleLayout;
|
|
2198
|
-
|
|
2198
|
+
// Calculate base dimensions
|
|
2199
|
+
const baseRows = Math.max(inputOrder.length, outputOrder.length);
|
|
2200
|
+
const { width: baseWidth, height: baseHeight } = estimateNodeSizeFn({
|
|
2199
2201
|
node,
|
|
2200
2202
|
registry,
|
|
2201
2203
|
showValues,
|
|
2202
2204
|
overrides: sizeOverrides,
|
|
2203
2205
|
});
|
|
2206
|
+
// Calculate final dimensions accounting for missing handles
|
|
2207
|
+
const finalRows = Math.max(inputOrder.length + missingInputs.length, outputOrder.length + missingOutputs.length);
|
|
2208
|
+
const width = baseWidth;
|
|
2209
|
+
const height = baseHeight + Math.max(0, finalRows - baseRows) * NODE_ROW_HEIGHT_PX;
|
|
2210
|
+
// Create bounds and layouts for regular handles
|
|
2204
2211
|
const handles = [
|
|
2205
2212
|
...inputOrder.map((id, i) => createHandleBoundsFn({
|
|
2206
2213
|
id,
|
|
@@ -2231,7 +2238,51 @@ function layoutNode(args, overrides) {
|
|
|
2231
2238
|
rowIndex: i,
|
|
2232
2239
|
})),
|
|
2233
2240
|
];
|
|
2234
|
-
|
|
2241
|
+
// Create bounds and layouts for missing handles
|
|
2242
|
+
const missingHandleBounds = [
|
|
2243
|
+
...missingInputs.map((id, i) => createHandleBoundsFn({
|
|
2244
|
+
id,
|
|
2245
|
+
type: "target",
|
|
2246
|
+
position: Position.Left,
|
|
2247
|
+
rowIndex: inputOrder.length + i,
|
|
2248
|
+
nodeWidth: width,
|
|
2249
|
+
})),
|
|
2250
|
+
...missingOutputs.map((id, i) => createHandleBoundsFn({
|
|
2251
|
+
id,
|
|
2252
|
+
type: "source",
|
|
2253
|
+
position: Position.Right,
|
|
2254
|
+
rowIndex: outputOrder.length + i,
|
|
2255
|
+
nodeWidth: width,
|
|
2256
|
+
})),
|
|
2257
|
+
];
|
|
2258
|
+
const missingHandleLayout = [
|
|
2259
|
+
...missingInputs.map((id, i) => ({
|
|
2260
|
+
...createHandleLayoutFn({
|
|
2261
|
+
id,
|
|
2262
|
+
type: "target",
|
|
2263
|
+
position: Position.Left,
|
|
2264
|
+
rowIndex: inputOrder.length + i,
|
|
2265
|
+
}),
|
|
2266
|
+
missing: true,
|
|
2267
|
+
})),
|
|
2268
|
+
...missingOutputs.map((id, i) => ({
|
|
2269
|
+
...createHandleLayoutFn({
|
|
2270
|
+
id,
|
|
2271
|
+
type: "source",
|
|
2272
|
+
position: Position.Right,
|
|
2273
|
+
rowIndex: outputOrder.length + i,
|
|
2274
|
+
}),
|
|
2275
|
+
missing: true,
|
|
2276
|
+
})),
|
|
2277
|
+
];
|
|
2278
|
+
return {
|
|
2279
|
+
width,
|
|
2280
|
+
height,
|
|
2281
|
+
inputOrder,
|
|
2282
|
+
outputOrder,
|
|
2283
|
+
handles: [...handles, ...missingHandleBounds],
|
|
2284
|
+
handleLayout: [...handleLayout, ...missingHandleLayout],
|
|
2285
|
+
};
|
|
2235
2286
|
}
|
|
2236
2287
|
|
|
2237
2288
|
function useWorkbenchBridge(wb) {
|
|
@@ -2503,10 +2554,13 @@ function toReactFlow(def, positions, sizes, registry, opts) {
|
|
|
2503
2554
|
const EDGE_STYLE_MISSING = { stroke: "#f59e0b", strokeWidth: 2 }; // amber-500
|
|
2504
2555
|
const EDGE_STYLE_ERROR = { stroke: "#ef4444", strokeWidth: 2 };
|
|
2505
2556
|
const EDGE_STYLE_RUNNING = { stroke: "#3b82f6" };
|
|
2506
|
-
// Build a map of valid handles per node up-front
|
|
2557
|
+
// Build a map of valid handles per node up-front and cache handle data
|
|
2507
2558
|
const validHandleMap = {};
|
|
2559
|
+
const nodeHandlesCache = {};
|
|
2508
2560
|
for (const n of def.nodes) {
|
|
2509
|
-
const
|
|
2561
|
+
const handles = computeEffectiveHandles(n, registry);
|
|
2562
|
+
nodeHandlesCache[n.nodeId] = handles;
|
|
2563
|
+
const { inputs, outputs } = handles;
|
|
2510
2564
|
const inputOrder = Object.keys(inputs).filter((k) => !isInputPrivate(inputs, k));
|
|
2511
2565
|
const outputOrder = Object.keys(outputs);
|
|
2512
2566
|
validHandleMap[n.nodeId] = {
|
|
@@ -2535,87 +2589,36 @@ function toReactFlow(def, positions, sizes, registry, opts) {
|
|
|
2535
2589
|
(missingOutputsByNode[srcId] || (missingOutputsByNode[srcId] = new Set())).add(srcHandle);
|
|
2536
2590
|
}
|
|
2537
2591
|
}
|
|
2538
|
-
// This map is still used later for certain checks; align with valid handles
|
|
2539
|
-
const nodeHandleMap = {};
|
|
2540
|
-
Object.assign(nodeHandleMap, validHandleMap);
|
|
2541
2592
|
// Get layout function overrides from UI registry
|
|
2542
2593
|
const layoutNodeOverride = opts.ui?.getLayoutNode() ?? layoutNode;
|
|
2543
2594
|
const createHandleBoundsFn = opts.ui?.getCreateHandleBounds() ?? createHandleBounds;
|
|
2544
2595
|
const createHandleLayoutFn = opts.ui?.getCreateHandleLayout() ?? createHandleLayout;
|
|
2545
2596
|
const estimateNodeSizeFn = opts.ui?.getEstimateNodeSize() ?? estimateNodeSize;
|
|
2546
|
-
const computeLayout = (node, overrides) => {
|
|
2597
|
+
const computeLayout = (node, overrides, missingInputs, missingOutputs) => {
|
|
2547
2598
|
return layoutNodeOverride
|
|
2548
2599
|
? layoutNodeOverride({
|
|
2549
2600
|
node,
|
|
2550
2601
|
registry,
|
|
2551
2602
|
showValues: opts.showValues,
|
|
2552
2603
|
overrides,
|
|
2604
|
+
missingInputs,
|
|
2605
|
+
missingOutputs,
|
|
2553
2606
|
})
|
|
2554
2607
|
: layoutNode({
|
|
2555
2608
|
node,
|
|
2556
2609
|
registry,
|
|
2557
2610
|
showValues: opts.showValues,
|
|
2558
2611
|
overrides,
|
|
2612
|
+
missingInputs,
|
|
2613
|
+
missingOutputs,
|
|
2559
2614
|
}, {
|
|
2560
2615
|
estimateNodeSize: estimateNodeSizeFn,
|
|
2561
2616
|
createHandleBounds: createHandleBoundsFn,
|
|
2562
2617
|
createHandleLayout: createHandleLayoutFn,
|
|
2563
2618
|
});
|
|
2564
2619
|
};
|
|
2565
|
-
const calculateDimensionsWithMissingHandles = (geom, extraInputs, extraOutputs) => {
|
|
2566
|
-
const baseLeftCount = geom.inputOrder.length;
|
|
2567
|
-
const baseRightCount = geom.outputOrder.length;
|
|
2568
|
-
const baseRows = Math.max(baseLeftCount, baseRightCount);
|
|
2569
|
-
const newRows = Math.max(baseLeftCount + extraInputs.length, baseRightCount + extraOutputs.length);
|
|
2570
|
-
return {
|
|
2571
|
-
baseLeftCount,
|
|
2572
|
-
baseRightCount,
|
|
2573
|
-
baseRows,
|
|
2574
|
-
newRows,
|
|
2575
|
-
width: geom.width,
|
|
2576
|
-
height: geom.height + Math.max(0, newRows - baseRows) * NODE_ROW_HEIGHT_PX,
|
|
2577
|
-
};
|
|
2578
|
-
};
|
|
2579
|
-
const createMissingHandleLayouts = (extraInputs, extraOutputs, baseLeftCount, baseRightCount) => {
|
|
2580
|
-
const left = extraInputs.map((id, i) => ({
|
|
2581
|
-
...createHandleLayoutFn({
|
|
2582
|
-
id,
|
|
2583
|
-
type: "target",
|
|
2584
|
-
position: Position.Left,
|
|
2585
|
-
rowIndex: baseLeftCount + i,
|
|
2586
|
-
}),
|
|
2587
|
-
missing: true,
|
|
2588
|
-
}));
|
|
2589
|
-
const right = extraOutputs.map((id, i) => ({
|
|
2590
|
-
...createHandleLayoutFn({
|
|
2591
|
-
id,
|
|
2592
|
-
type: "source",
|
|
2593
|
-
position: Position.Right,
|
|
2594
|
-
rowIndex: baseRightCount + i,
|
|
2595
|
-
}),
|
|
2596
|
-
missing: true,
|
|
2597
|
-
}));
|
|
2598
|
-
return [...left, ...right];
|
|
2599
|
-
};
|
|
2600
|
-
const createMissingHandleBounds = (extraInputs, extraOutputs, baseLeftCount, baseRightCount, nodeWidth) => {
|
|
2601
|
-
const left = extraInputs.map((id, i) => createHandleBoundsFn({
|
|
2602
|
-
id,
|
|
2603
|
-
type: "target",
|
|
2604
|
-
position: Position.Left,
|
|
2605
|
-
rowIndex: baseLeftCount + i,
|
|
2606
|
-
nodeWidth,
|
|
2607
|
-
}));
|
|
2608
|
-
const right = extraOutputs.map((id, i) => createHandleBoundsFn({
|
|
2609
|
-
id,
|
|
2610
|
-
type: "source",
|
|
2611
|
-
position: Position.Right,
|
|
2612
|
-
rowIndex: baseRightCount + i,
|
|
2613
|
-
nodeWidth,
|
|
2614
|
-
}));
|
|
2615
|
-
return [...left, ...right];
|
|
2616
|
-
};
|
|
2617
2620
|
const nodes = def.nodes.map((n) => {
|
|
2618
|
-
const { inputs: inputSource, outputs: outputSource } =
|
|
2621
|
+
const { inputs: inputSource, outputs: outputSource } = nodeHandlesCache[n.nodeId];
|
|
2619
2622
|
const overrideSize = opts.getDefaultNodeSize?.(n.typeId);
|
|
2620
2623
|
const customSize = sizes?.[n.nodeId];
|
|
2621
2624
|
const sizeOverrides = customSize
|
|
@@ -2623,14 +2626,12 @@ function toReactFlow(def, positions, sizes, registry, opts) {
|
|
|
2623
2626
|
: overrideSize;
|
|
2624
2627
|
const extraInputs = Array.from(missingInputsByNode[n.nodeId] || []);
|
|
2625
2628
|
const extraOutputs = Array.from(missingOutputsByNode[n.nodeId] || []);
|
|
2626
|
-
const geom = computeLayout(n, sizeOverrides);
|
|
2627
|
-
const
|
|
2628
|
-
const
|
|
2629
|
-
const
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
? calculateDimensionsWithMissingHandles(initialGeom, extraInputs, extraOutputs)
|
|
2633
|
-
: finalDims;
|
|
2629
|
+
const geom = computeLayout(n, sizeOverrides, extraInputs, extraOutputs);
|
|
2630
|
+
const renderWidth = customSize?.width ?? geom.width;
|
|
2631
|
+
const renderHeight = customSize?.height ?? geom.height;
|
|
2632
|
+
const initialGeom = customSize
|
|
2633
|
+
? computeLayout(n, overrideSize, extraInputs, extraOutputs)
|
|
2634
|
+
: geom;
|
|
2634
2635
|
const inputHandles = geom.inputOrder.map((id) => ({
|
|
2635
2636
|
id,
|
|
2636
2637
|
typeId: getInputTypeId(inputSource, id),
|
|
@@ -2639,14 +2640,8 @@ function toReactFlow(def, positions, sizes, registry, opts) {
|
|
|
2639
2640
|
id,
|
|
2640
2641
|
typeId: formatDeclaredTypeSignature(outputSource[id]),
|
|
2641
2642
|
}));
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
outputs: new Set(outputHandles.map((h) => h.id)),
|
|
2645
|
-
};
|
|
2646
|
-
const missingHandleLayouts = createMissingHandleLayouts(extraInputs, extraOutputs, finalDims.baseLeftCount, finalDims.baseRightCount);
|
|
2647
|
-
const handleLayout = [...geom.handleLayout, ...missingHandleLayouts];
|
|
2648
|
-
const missingHandleBounds = createMissingHandleBounds(extraInputs, extraOutputs, finalDims.baseLeftCount, finalDims.baseRightCount, renderWidth);
|
|
2649
|
-
const handles = [...geom.handles, ...missingHandleBounds];
|
|
2643
|
+
const handleLayout = geom.handleLayout;
|
|
2644
|
+
const handles = geom.handles;
|
|
2650
2645
|
return {
|
|
2651
2646
|
id: n.nodeId,
|
|
2652
2647
|
data: {
|
|
@@ -2662,8 +2657,8 @@ function toReactFlow(def, positions, sizes, registry, opts) {
|
|
|
2662
2657
|
showValues: opts.showValues,
|
|
2663
2658
|
renderWidth,
|
|
2664
2659
|
renderHeight,
|
|
2665
|
-
initialWidth:
|
|
2666
|
-
initialHeight:
|
|
2660
|
+
initialWidth: initialGeom.width,
|
|
2661
|
+
initialHeight: initialGeom.height,
|
|
2667
2662
|
inputValues: opts.inputs?.[n.nodeId],
|
|
2668
2663
|
inputDefaults: opts.inputDefaults?.[n.nodeId],
|
|
2669
2664
|
outputValues: opts.outputs?.[n.nodeId],
|
|
@@ -4451,7 +4446,7 @@ function DefaultNodeHeader({ id, typeId, title, validation, right, showId, onInv
|
|
|
4451
4446
|
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: {
|
|
4452
4447
|
maxHeight: NODE_HEADER_HEIGHT_PX,
|
|
4453
4448
|
minHeight: NODE_HEADER_HEIGHT_PX,
|
|
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: `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) => {
|
|
4449
|
+
}, 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) => {
|
|
4455
4450
|
e.stopPropagation();
|
|
4456
4451
|
handleInvalidate();
|
|
4457
4452
|
}, children: jsx(ArrowClockwiseIcon, { size: 10 }) }), right, validation.issues && validation.issues.length > 0 && (jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
|
|
@@ -4462,7 +4457,7 @@ function DefaultNodeHeader({ id, typeId, title, validation, right, showId, onInv
|
|
|
4462
4457
|
}
|
|
4463
4458
|
|
|
4464
4459
|
function NodeHandleItem({ kind, id, type, position, y, isConnectable, className, labelClassName, renderLabel, }) {
|
|
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
|
|
4460
|
+
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: {
|
|
4466
4461
|
top: (y ?? 0) - 8,
|
|
4467
4462
|
...(kind === "input"
|
|
4468
4463
|
? { right: "50%" }
|
|
@@ -4574,7 +4569,7 @@ function DefaultNodeContent({ data, isConnectable, }) {
|
|
|
4574
4569
|
isDefault: false,
|
|
4575
4570
|
};
|
|
4576
4571
|
})();
|
|
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", 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 }))] }));
|
|
4572
|
+
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 }))] }));
|
|
4578
4573
|
} })] }));
|
|
4579
4574
|
}
|
|
4580
4575
|
|
|
@@ -5488,6 +5483,601 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5488
5483
|
(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))] }));
|
|
5489
5484
|
});
|
|
5490
5485
|
|
|
5486
|
+
/**
|
|
5487
|
+
* Flow thumbnail capture utility
|
|
5488
|
+
* Captures React Flow canvas as SVG image
|
|
5489
|
+
*/
|
|
5490
|
+
// ============================================================================
|
|
5491
|
+
// Utility Functions
|
|
5492
|
+
// ============================================================================
|
|
5493
|
+
/**
|
|
5494
|
+
* Parses CSS transform string to extract translate and scale values
|
|
5495
|
+
*/
|
|
5496
|
+
function parseViewportTransform(transform) {
|
|
5497
|
+
let translateX = 0;
|
|
5498
|
+
let translateY = 0;
|
|
5499
|
+
let scale = 1;
|
|
5500
|
+
if (transform && transform !== "none") {
|
|
5501
|
+
// Try translate() scale() format first
|
|
5502
|
+
const translateMatch = transform.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
|
|
5503
|
+
const scaleMatch = transform.match(/scale\(([^)]+)\)/);
|
|
5504
|
+
if (translateMatch) {
|
|
5505
|
+
translateX = parseFloat(translateMatch[1]);
|
|
5506
|
+
translateY = parseFloat(translateMatch[2]);
|
|
5507
|
+
}
|
|
5508
|
+
if (scaleMatch) {
|
|
5509
|
+
scale = parseFloat(scaleMatch[1]);
|
|
5510
|
+
}
|
|
5511
|
+
// Fallback to matrix format
|
|
5512
|
+
if (!translateMatch) {
|
|
5513
|
+
const matrixMatch = transform.match(/matrix\(([^)]+)\)/);
|
|
5514
|
+
if (matrixMatch) {
|
|
5515
|
+
const values = matrixMatch[1]
|
|
5516
|
+
.split(",")
|
|
5517
|
+
.map((v) => parseFloat(v.trim()));
|
|
5518
|
+
if (values.length >= 6) {
|
|
5519
|
+
scale = Math.sqrt(values[0] * values[0] + values[1] * values[1]);
|
|
5520
|
+
translateX = values[4];
|
|
5521
|
+
translateY = values[5];
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
return { translateX, translateY, scale };
|
|
5527
|
+
}
|
|
5528
|
+
/**
|
|
5529
|
+
* Calculates visible viewport bounds in flow coordinates
|
|
5530
|
+
*/
|
|
5531
|
+
function calculateVisibleBounds(viewportRect, transform) {
|
|
5532
|
+
const { translateX, translateY, scale } = transform;
|
|
5533
|
+
// Guard against division by zero
|
|
5534
|
+
if (scale === 0) {
|
|
5535
|
+
console.warn("[flowThumbnail] Viewport scale is 0, using default bounds");
|
|
5536
|
+
return {
|
|
5537
|
+
minX: -translateX,
|
|
5538
|
+
minY: -translateY,
|
|
5539
|
+
maxX: viewportRect.width - translateX,
|
|
5540
|
+
maxY: viewportRect.height - translateY,
|
|
5541
|
+
};
|
|
5542
|
+
}
|
|
5543
|
+
// Screen to flow: (screenX - translateX) / scale
|
|
5544
|
+
return {
|
|
5545
|
+
minX: (0 - translateX) / scale,
|
|
5546
|
+
minY: (0 - translateY) / scale,
|
|
5547
|
+
maxX: (viewportRect.width - translateX) / scale,
|
|
5548
|
+
maxY: (viewportRect.height - translateY) / scale,
|
|
5549
|
+
};
|
|
5550
|
+
}
|
|
5551
|
+
/**
|
|
5552
|
+
* Parses border radius string (px or rem) to pixels
|
|
5553
|
+
*/
|
|
5554
|
+
function parseBorderRadius(borderRadiusStr) {
|
|
5555
|
+
if (borderRadiusStr === "0px") {
|
|
5556
|
+
return 8; // default
|
|
5557
|
+
}
|
|
5558
|
+
const match = borderRadiusStr.match(/([\d.]+)(px|rem)/);
|
|
5559
|
+
if (match) {
|
|
5560
|
+
const value = parseFloat(match[1]);
|
|
5561
|
+
const unit = match[2];
|
|
5562
|
+
// Convert rem to px (assuming 16px base) or use px directly
|
|
5563
|
+
return unit === "rem" ? value * 16 : value;
|
|
5564
|
+
}
|
|
5565
|
+
// Try direct parseFloat as fallback
|
|
5566
|
+
const parsed = parseFloat(borderRadiusStr);
|
|
5567
|
+
return isNaN(parsed) ? 8 : parsed;
|
|
5568
|
+
}
|
|
5569
|
+
/**
|
|
5570
|
+
* Extracts stroke color from element, with fallback
|
|
5571
|
+
*/
|
|
5572
|
+
function extractStrokeColor(element) {
|
|
5573
|
+
if (element instanceof SVGPathElement) {
|
|
5574
|
+
return (element.getAttribute("stroke") ||
|
|
5575
|
+
window.getComputedStyle(element).stroke ||
|
|
5576
|
+
"#b1b1b7");
|
|
5577
|
+
}
|
|
5578
|
+
const style = window.getComputedStyle(element);
|
|
5579
|
+
return (style.borderColor || style.borderTopColor || "#6b7280" // gray-500 default
|
|
5580
|
+
);
|
|
5581
|
+
}
|
|
5582
|
+
/**
|
|
5583
|
+
* Extracts stroke/border width from element, ensuring minimum value
|
|
5584
|
+
*/
|
|
5585
|
+
function extractStrokeWidth(element, minWidth = 1) {
|
|
5586
|
+
if (element instanceof SVGPathElement) {
|
|
5587
|
+
const width = parseFloat(element.getAttribute("stroke-width") || "0") ||
|
|
5588
|
+
parseFloat(window.getComputedStyle(element).strokeWidth || "2");
|
|
5589
|
+
return width > 0 ? width : minWidth;
|
|
5590
|
+
}
|
|
5591
|
+
const style = window.getComputedStyle(element);
|
|
5592
|
+
const width = parseFloat(style.borderWidth || style.borderTopWidth || "0");
|
|
5593
|
+
return width > 0 ? width : minWidth;
|
|
5594
|
+
}
|
|
5595
|
+
/**
|
|
5596
|
+
* Checks if a rectangle intersects with visible bounds
|
|
5597
|
+
*/
|
|
5598
|
+
function isRectVisible(x, y, width, height, bounds) {
|
|
5599
|
+
return (x + width >= bounds.minX &&
|
|
5600
|
+
x <= bounds.maxX &&
|
|
5601
|
+
y + height >= bounds.minY &&
|
|
5602
|
+
y <= bounds.maxY);
|
|
5603
|
+
}
|
|
5604
|
+
/**
|
|
5605
|
+
* Parses path data to get bounding box
|
|
5606
|
+
* Handles M (moveTo), L (lineTo), C (cubic Bezier), Q (quadratic Bezier), and H/V (horizontal/vertical) commands
|
|
5607
|
+
*/
|
|
5608
|
+
function getPathBounds(pathData) {
|
|
5609
|
+
let minX = Infinity;
|
|
5610
|
+
let minY = Infinity;
|
|
5611
|
+
let maxX = -Infinity;
|
|
5612
|
+
let maxY = -Infinity;
|
|
5613
|
+
// Match coordinates from various path commands: M, L, C, Q, T, S, H, V
|
|
5614
|
+
// Pattern matches: command letter followed by coordinate pairs
|
|
5615
|
+
const coordPattern = /[MLCQTSHV](-?\d+\.?\d*),(-?\d+\.?\d*)/g;
|
|
5616
|
+
const coords = pathData.match(coordPattern);
|
|
5617
|
+
if (coords) {
|
|
5618
|
+
coords.forEach((coord) => {
|
|
5619
|
+
const match = coord.match(/(-?\d+\.?\d*),(-?\d+\.?\d*)/);
|
|
5620
|
+
if (match) {
|
|
5621
|
+
const x = parseFloat(match[1]);
|
|
5622
|
+
const y = parseFloat(match[2]);
|
|
5623
|
+
if (!isNaN(x) && !isNaN(y)) {
|
|
5624
|
+
minX = Math.min(minX, x);
|
|
5625
|
+
minY = Math.min(minY, y);
|
|
5626
|
+
maxX = Math.max(maxX, x);
|
|
5627
|
+
maxY = Math.max(maxY, y);
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5630
|
+
});
|
|
5631
|
+
}
|
|
5632
|
+
return { minX, minY, maxX, maxY };
|
|
5633
|
+
}
|
|
5634
|
+
// ============================================================================
|
|
5635
|
+
// Edge Extraction
|
|
5636
|
+
// ============================================================================
|
|
5637
|
+
/**
|
|
5638
|
+
* Extracts visible edge paths from React Flow viewport
|
|
5639
|
+
*/
|
|
5640
|
+
function extractEdgePaths(viewport, visibleBounds) {
|
|
5641
|
+
const edges = [];
|
|
5642
|
+
let minX = Infinity;
|
|
5643
|
+
let minY = Infinity;
|
|
5644
|
+
let maxX = -Infinity;
|
|
5645
|
+
let maxY = -Infinity;
|
|
5646
|
+
const edgePathElements = viewport.querySelectorAll(".react-flow__edge-path");
|
|
5647
|
+
edgePathElements.forEach((pathEl) => {
|
|
5648
|
+
const pathData = pathEl.getAttribute("d");
|
|
5649
|
+
if (!pathData)
|
|
5650
|
+
return;
|
|
5651
|
+
const pathBounds = getPathBounds(pathData);
|
|
5652
|
+
// Only include edge if it intersects with visible viewport
|
|
5653
|
+
if (pathBounds.maxX >= visibleBounds.minX &&
|
|
5654
|
+
pathBounds.minX <= visibleBounds.maxX &&
|
|
5655
|
+
pathBounds.maxY >= visibleBounds.minY &&
|
|
5656
|
+
pathBounds.minY <= visibleBounds.maxY) {
|
|
5657
|
+
edges.push({
|
|
5658
|
+
d: pathData,
|
|
5659
|
+
stroke: extractStrokeColor(pathEl),
|
|
5660
|
+
strokeWidth: extractStrokeWidth(pathEl, 2),
|
|
5661
|
+
});
|
|
5662
|
+
// Update bounding box
|
|
5663
|
+
minX = Math.min(minX, pathBounds.minX);
|
|
5664
|
+
minY = Math.min(minY, pathBounds.minY);
|
|
5665
|
+
maxX = Math.max(maxX, pathBounds.maxX);
|
|
5666
|
+
maxY = Math.max(maxY, pathBounds.maxY);
|
|
5667
|
+
}
|
|
5668
|
+
});
|
|
5669
|
+
return { edges, bounds: { minX, minY, maxX, maxY } };
|
|
5670
|
+
}
|
|
5671
|
+
// ============================================================================
|
|
5672
|
+
// Node Extraction
|
|
5673
|
+
// ============================================================================
|
|
5674
|
+
/**
|
|
5675
|
+
* Extracts node position from transform style
|
|
5676
|
+
*/
|
|
5677
|
+
function extractNodePosition(nodeEl) {
|
|
5678
|
+
const transformStyle = nodeEl.style.transform || "";
|
|
5679
|
+
const translateMatch = transformStyle.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
|
|
5680
|
+
if (translateMatch) {
|
|
5681
|
+
return {
|
|
5682
|
+
x: parseFloat(translateMatch[1]),
|
|
5683
|
+
y: parseFloat(translateMatch[2]),
|
|
5684
|
+
};
|
|
5685
|
+
}
|
|
5686
|
+
return { x: 0, y: 0 };
|
|
5687
|
+
}
|
|
5688
|
+
/**
|
|
5689
|
+
* Extracts node dimensions from inline styles
|
|
5690
|
+
*/
|
|
5691
|
+
function extractNodeDimensions(nodeEl) {
|
|
5692
|
+
const widthMatch = nodeEl.style.width?.match(/(\d+)px/);
|
|
5693
|
+
const heightMatch = nodeEl.style.height?.match(/(\d+)px/);
|
|
5694
|
+
return {
|
|
5695
|
+
width: widthMatch ? parseFloat(widthMatch[1]) : 150,
|
|
5696
|
+
height: heightMatch ? parseFloat(heightMatch[1]) : 40,
|
|
5697
|
+
};
|
|
5698
|
+
}
|
|
5699
|
+
/**
|
|
5700
|
+
* Extracts node styles (colors, border, radius) from computed styles
|
|
5701
|
+
*/
|
|
5702
|
+
function extractNodeStyles(nodeContent) {
|
|
5703
|
+
const computedStyle = window.getComputedStyle(nodeContent);
|
|
5704
|
+
// Use gray background for nodes in thumbnail
|
|
5705
|
+
const fill = "#f3f4f6"; // gray-100 equivalent
|
|
5706
|
+
const stroke = extractStrokeColor(nodeContent);
|
|
5707
|
+
const strokeWidth = extractStrokeWidth(nodeContent, 1);
|
|
5708
|
+
const borderRadiusStr = computedStyle.borderRadius || "8px";
|
|
5709
|
+
const rx = parseBorderRadius(borderRadiusStr);
|
|
5710
|
+
const ry = rx; // Use same radius for both x and y
|
|
5711
|
+
return { fill, stroke, strokeWidth, rx, ry };
|
|
5712
|
+
}
|
|
5713
|
+
/**
|
|
5714
|
+
* Determines if a handle is a source (output) or target (input)
|
|
5715
|
+
*/
|
|
5716
|
+
function isHandleSource(handleEl) {
|
|
5717
|
+
return (handleEl.classList.contains("react-flow__handle-right") ||
|
|
5718
|
+
handleEl.classList.contains("react-flow__handle-source"));
|
|
5719
|
+
}
|
|
5720
|
+
/**
|
|
5721
|
+
* Extracts handle position and calculates absolute coordinates
|
|
5722
|
+
*/
|
|
5723
|
+
function extractHandlePosition(handleEl, nodeX, nodeY, nodeWidth, isSource) {
|
|
5724
|
+
const handleStyle = window.getComputedStyle(handleEl);
|
|
5725
|
+
const handleTop = parseFloat(handleStyle.top || "0");
|
|
5726
|
+
const handleLeft = handleStyle.left;
|
|
5727
|
+
const handleRight = handleStyle.right;
|
|
5728
|
+
const handleY = nodeY + handleTop;
|
|
5729
|
+
let handleX;
|
|
5730
|
+
if (isSource) {
|
|
5731
|
+
// Source handles are on the right edge
|
|
5732
|
+
if (handleRight !== "auto" && handleRight !== "") {
|
|
5733
|
+
const rightValue = parseFloat(handleRight) || 0;
|
|
5734
|
+
handleX = nodeX + nodeWidth + rightValue;
|
|
5735
|
+
}
|
|
5736
|
+
else {
|
|
5737
|
+
handleX = nodeX + nodeWidth;
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
else {
|
|
5741
|
+
// Target handles are on the left edge
|
|
5742
|
+
if (handleLeft !== "auto" && handleLeft !== "") {
|
|
5743
|
+
const leftValue = parseFloat(handleLeft) || 0;
|
|
5744
|
+
handleX = nodeX + leftValue;
|
|
5745
|
+
}
|
|
5746
|
+
else {
|
|
5747
|
+
handleX = nodeX;
|
|
5748
|
+
}
|
|
5749
|
+
}
|
|
5750
|
+
return { x: handleX, y: handleY };
|
|
5751
|
+
}
|
|
5752
|
+
/**
|
|
5753
|
+
* Extracts handles from a node element
|
|
5754
|
+
*/
|
|
5755
|
+
function extractNodeHandles(nodeEl, nodeX, nodeY, nodeWidth) {
|
|
5756
|
+
const handles = [];
|
|
5757
|
+
const handleElements = nodeEl.querySelectorAll(".react-flow__handle");
|
|
5758
|
+
handleElements.forEach((handleEl) => {
|
|
5759
|
+
const handleStyle = window.getComputedStyle(handleEl);
|
|
5760
|
+
const handleWidth = parseFloat(handleStyle.width || "12");
|
|
5761
|
+
const handleHeight = parseFloat(handleStyle.height || "12");
|
|
5762
|
+
const isSource = isHandleSource(handleEl);
|
|
5763
|
+
const position = extractHandlePosition(handleEl, nodeX, nodeY, nodeWidth, isSource);
|
|
5764
|
+
handles.push({
|
|
5765
|
+
x: position.x,
|
|
5766
|
+
y: position.y,
|
|
5767
|
+
width: handleWidth,
|
|
5768
|
+
height: handleHeight,
|
|
5769
|
+
fill: handleStyle.backgroundColor || "rgba(255, 255, 255, 0.5)",
|
|
5770
|
+
stroke: extractStrokeColor(handleEl),
|
|
5771
|
+
strokeWidth: extractStrokeWidth(handleEl, 1),
|
|
5772
|
+
type: isSource ? "source" : "target",
|
|
5773
|
+
});
|
|
5774
|
+
});
|
|
5775
|
+
return handles;
|
|
5776
|
+
}
|
|
5777
|
+
/**
|
|
5778
|
+
* Extracts node title text and position
|
|
5779
|
+
*/
|
|
5780
|
+
function extractNodeTitle(nodeEl, nodeX, nodeY) {
|
|
5781
|
+
const titleElement = nodeEl.querySelector(".react-flow__node-title");
|
|
5782
|
+
if (!titleElement)
|
|
5783
|
+
return undefined;
|
|
5784
|
+
const titleText = titleElement.textContent || titleElement.innerText || "";
|
|
5785
|
+
if (!titleText.trim())
|
|
5786
|
+
return undefined;
|
|
5787
|
+
const titleStyle = window.getComputedStyle(titleElement);
|
|
5788
|
+
const titleRect = titleElement.getBoundingClientRect();
|
|
5789
|
+
const nodeRect = nodeEl.getBoundingClientRect();
|
|
5790
|
+
// Calculate title position relative to node (in flow coordinates)
|
|
5791
|
+
const titleRelativeX = titleRect.left - nodeRect.left;
|
|
5792
|
+
const titleRelativeY = titleRect.top - nodeRect.top;
|
|
5793
|
+
// Get font properties
|
|
5794
|
+
const fontSize = parseFloat(titleStyle.fontSize || "14");
|
|
5795
|
+
const fill = titleStyle.color || "#374151"; // gray-700 default
|
|
5796
|
+
const fontWeight = titleStyle.fontWeight || "600"; // bold default
|
|
5797
|
+
// Calculate text position (SVG text uses baseline)
|
|
5798
|
+
const lineHeight = parseFloat(titleStyle.lineHeight || String(fontSize * 1.2));
|
|
5799
|
+
const textBaselineOffset = lineHeight * 0.8; // Approximate baseline offset
|
|
5800
|
+
return {
|
|
5801
|
+
text: titleText.trim(),
|
|
5802
|
+
x: nodeX + titleRelativeX,
|
|
5803
|
+
y: nodeY + titleRelativeY + textBaselineOffset,
|
|
5804
|
+
fontSize,
|
|
5805
|
+
fill,
|
|
5806
|
+
fontWeight,
|
|
5807
|
+
};
|
|
5808
|
+
}
|
|
5809
|
+
/**
|
|
5810
|
+
* Extracts visible nodes from React Flow viewport
|
|
5811
|
+
*/
|
|
5812
|
+
function extractNodes(viewport, visibleBounds) {
|
|
5813
|
+
const nodes = [];
|
|
5814
|
+
let minX = Infinity;
|
|
5815
|
+
let minY = Infinity;
|
|
5816
|
+
let maxX = -Infinity;
|
|
5817
|
+
let maxY = -Infinity;
|
|
5818
|
+
const nodeElements = viewport.querySelectorAll(".react-flow__node");
|
|
5819
|
+
nodeElements.forEach((nodeEl) => {
|
|
5820
|
+
const position = extractNodePosition(nodeEl);
|
|
5821
|
+
const dimensions = extractNodeDimensions(nodeEl);
|
|
5822
|
+
// Get the actual node content div (first child)
|
|
5823
|
+
const nodeContent = nodeEl.firstElementChild;
|
|
5824
|
+
if (!nodeContent)
|
|
5825
|
+
return;
|
|
5826
|
+
const styles = extractNodeStyles(nodeContent);
|
|
5827
|
+
const handles = extractNodeHandles(nodeEl, position.x, position.y, dimensions.width);
|
|
5828
|
+
const title = extractNodeTitle(nodeEl, position.x, position.y);
|
|
5829
|
+
// Only include node if it's within visible viewport
|
|
5830
|
+
if (isRectVisible(position.x, position.y, dimensions.width, dimensions.height, visibleBounds)) {
|
|
5831
|
+
nodes.push({
|
|
5832
|
+
x: position.x,
|
|
5833
|
+
y: position.y,
|
|
5834
|
+
width: dimensions.width,
|
|
5835
|
+
height: dimensions.height,
|
|
5836
|
+
...styles,
|
|
5837
|
+
handles,
|
|
5838
|
+
title,
|
|
5839
|
+
});
|
|
5840
|
+
// Update bounding box
|
|
5841
|
+
minX = Math.min(minX, position.x);
|
|
5842
|
+
minY = Math.min(minY, position.y);
|
|
5843
|
+
maxX = Math.max(maxX, position.x + dimensions.width);
|
|
5844
|
+
maxY = Math.max(maxY, position.y + dimensions.height);
|
|
5845
|
+
// Update bounding box to include handles
|
|
5846
|
+
handles.forEach((handle) => {
|
|
5847
|
+
minX = Math.min(minX, handle.x);
|
|
5848
|
+
minY = Math.min(minY, handle.y);
|
|
5849
|
+
maxX = Math.max(maxX, handle.x + handle.width);
|
|
5850
|
+
maxY = Math.max(maxY, handle.y + handle.height);
|
|
5851
|
+
});
|
|
5852
|
+
}
|
|
5853
|
+
});
|
|
5854
|
+
return { nodes, bounds: { minX, minY, maxX, maxY } };
|
|
5855
|
+
}
|
|
5856
|
+
// ============================================================================
|
|
5857
|
+
// SVG Rendering
|
|
5858
|
+
// ============================================================================
|
|
5859
|
+
/**
|
|
5860
|
+
* Creates SVG element with dot pattern background (matching React Flow)
|
|
5861
|
+
*/
|
|
5862
|
+
function createSVGElement(width, height) {
|
|
5863
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
5864
|
+
svg.setAttribute("width", String(width));
|
|
5865
|
+
svg.setAttribute("height", String(height));
|
|
5866
|
+
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
5867
|
+
// Create defs section for patterns
|
|
5868
|
+
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
5869
|
+
// Create dot pattern (matching React Flow's BackgroundVariant.Dots)
|
|
5870
|
+
// React Flow uses gap={12} and size={1} by default
|
|
5871
|
+
const pattern = document.createElementNS("http://www.w3.org/2000/svg", "pattern");
|
|
5872
|
+
pattern.setAttribute("id", "dot-pattern");
|
|
5873
|
+
pattern.setAttribute("x", "0");
|
|
5874
|
+
pattern.setAttribute("y", "0");
|
|
5875
|
+
pattern.setAttribute("width", "24"); // gap between dots (matching React Flow default)
|
|
5876
|
+
pattern.setAttribute("height", "24");
|
|
5877
|
+
pattern.setAttribute("patternUnits", "userSpaceOnUse");
|
|
5878
|
+
// Create a circle for the dot (centered in the pattern cell)
|
|
5879
|
+
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
5880
|
+
circle.setAttribute("cx", "12"); // Center of 24x24 pattern cell
|
|
5881
|
+
circle.setAttribute("cy", "12");
|
|
5882
|
+
circle.setAttribute("r", "1"); // dot radius = 1 (matching React Flow size={1})
|
|
5883
|
+
circle.setAttribute("fill", "#f1f1f1"); // gray color matching React Flow default
|
|
5884
|
+
pattern.appendChild(circle);
|
|
5885
|
+
defs.appendChild(pattern);
|
|
5886
|
+
svg.appendChild(defs);
|
|
5887
|
+
// Create background rectangle with white base
|
|
5888
|
+
const bgRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
5889
|
+
bgRect.setAttribute("width", String(width));
|
|
5890
|
+
bgRect.setAttribute("height", String(height));
|
|
5891
|
+
bgRect.setAttribute("fill", "#ffffff"); // Base background color
|
|
5892
|
+
svg.appendChild(bgRect);
|
|
5893
|
+
// Create pattern overlay rectangle
|
|
5894
|
+
const patternRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
5895
|
+
patternRect.setAttribute("width", String(width));
|
|
5896
|
+
patternRect.setAttribute("height", String(height));
|
|
5897
|
+
patternRect.setAttribute("fill", "url(#dot-pattern)");
|
|
5898
|
+
svg.appendChild(patternRect);
|
|
5899
|
+
// Create group with transform
|
|
5900
|
+
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
5901
|
+
svg.appendChild(group);
|
|
5902
|
+
return { svg, group };
|
|
5903
|
+
}
|
|
5904
|
+
/**
|
|
5905
|
+
* Renders a node rectangle to SVG group
|
|
5906
|
+
*/
|
|
5907
|
+
function renderNodeRect(group, node) {
|
|
5908
|
+
const rectEl = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
5909
|
+
rectEl.setAttribute("x", String(node.x));
|
|
5910
|
+
rectEl.setAttribute("y", String(node.y));
|
|
5911
|
+
rectEl.setAttribute("width", String(node.width));
|
|
5912
|
+
rectEl.setAttribute("height", String(node.height));
|
|
5913
|
+
rectEl.setAttribute("rx", String(node.rx));
|
|
5914
|
+
rectEl.setAttribute("ry", String(node.ry));
|
|
5915
|
+
rectEl.setAttribute("fill", node.fill);
|
|
5916
|
+
rectEl.setAttribute("stroke", node.stroke);
|
|
5917
|
+
rectEl.setAttribute("stroke-width", String(node.strokeWidth));
|
|
5918
|
+
group.appendChild(rectEl);
|
|
5919
|
+
}
|
|
5920
|
+
/**
|
|
5921
|
+
* Renders a handle to SVG group
|
|
5922
|
+
*/
|
|
5923
|
+
function renderHandle(group, handle) {
|
|
5924
|
+
const handleEl = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
5925
|
+
// Handles are centered on edges
|
|
5926
|
+
handleEl.setAttribute("x", String(handle.x - handle.width / 2));
|
|
5927
|
+
handleEl.setAttribute("y", String(handle.y - handle.height / 2));
|
|
5928
|
+
handleEl.setAttribute("width", String(handle.width));
|
|
5929
|
+
handleEl.setAttribute("height", String(handle.height));
|
|
5930
|
+
handleEl.setAttribute("rx", String(handle.width / 2)); // Make handles circular/rounded
|
|
5931
|
+
handleEl.setAttribute("ry", String(handle.height / 2));
|
|
5932
|
+
handleEl.setAttribute("fill", handle.fill);
|
|
5933
|
+
handleEl.setAttribute("stroke", handle.stroke);
|
|
5934
|
+
handleEl.setAttribute("stroke-width", String(handle.strokeWidth));
|
|
5935
|
+
group.appendChild(handleEl);
|
|
5936
|
+
}
|
|
5937
|
+
/**
|
|
5938
|
+
* Renders node title text to SVG group
|
|
5939
|
+
*/
|
|
5940
|
+
function renderNodeTitle(group, title) {
|
|
5941
|
+
const textEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
5942
|
+
textEl.setAttribute("x", String(title.x));
|
|
5943
|
+
textEl.setAttribute("y", String(title.y));
|
|
5944
|
+
textEl.setAttribute("font-size", String(title.fontSize));
|
|
5945
|
+
textEl.setAttribute("fill", title.fill);
|
|
5946
|
+
textEl.setAttribute("font-weight", title.fontWeight);
|
|
5947
|
+
textEl.setAttribute("font-family", "system-ui, -apple-system, sans-serif");
|
|
5948
|
+
textEl.textContent = title.text;
|
|
5949
|
+
group.appendChild(textEl);
|
|
5950
|
+
}
|
|
5951
|
+
/**
|
|
5952
|
+
* Renders an edge path to SVG group
|
|
5953
|
+
*/
|
|
5954
|
+
function renderEdgePath(group, edge) {
|
|
5955
|
+
const pathEl = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
5956
|
+
pathEl.setAttribute("d", edge.d);
|
|
5957
|
+
pathEl.setAttribute("fill", "none");
|
|
5958
|
+
pathEl.setAttribute("stroke", edge.stroke);
|
|
5959
|
+
pathEl.setAttribute("stroke-width", String(edge.strokeWidth));
|
|
5960
|
+
group.appendChild(pathEl);
|
|
5961
|
+
}
|
|
5962
|
+
/**
|
|
5963
|
+
* Renders all nodes, handles, titles, and edges to SVG group
|
|
5964
|
+
*/
|
|
5965
|
+
function renderContentToSVG(group, nodes, edges, transformX, transformY) {
|
|
5966
|
+
group.setAttribute("transform", `translate(${transformX}, ${transformY})`);
|
|
5967
|
+
// Render nodes
|
|
5968
|
+
nodes.forEach((node) => {
|
|
5969
|
+
renderNodeRect(group, node);
|
|
5970
|
+
node.handles.forEach((handle) => renderHandle(group, handle));
|
|
5971
|
+
if (node.title) {
|
|
5972
|
+
renderNodeTitle(group, node.title);
|
|
5973
|
+
}
|
|
5974
|
+
});
|
|
5975
|
+
// Render edges
|
|
5976
|
+
edges.forEach((edge) => renderEdgePath(group, edge));
|
|
5977
|
+
}
|
|
5978
|
+
// ============================================================================
|
|
5979
|
+
// Main Capture Function
|
|
5980
|
+
// ============================================================================
|
|
5981
|
+
/**
|
|
5982
|
+
* Captures a React Flow container element as an SVG image and returns data URL
|
|
5983
|
+
* @param containerElement - The React Flow container DOM element
|
|
5984
|
+
* @returns Promise resolving to SVG data URL string, or null if capture fails
|
|
5985
|
+
*/
|
|
5986
|
+
async function captureCanvasThumbnail(containerElement) {
|
|
5987
|
+
if (!containerElement) {
|
|
5988
|
+
console.warn("[flowThumbnail] Container element is null");
|
|
5989
|
+
return null;
|
|
5990
|
+
}
|
|
5991
|
+
try {
|
|
5992
|
+
// Find the React Flow viewport element
|
|
5993
|
+
const reactFlowViewport = containerElement.querySelector(".react-flow__viewport");
|
|
5994
|
+
if (!reactFlowViewport) {
|
|
5995
|
+
console.warn("[flowThumbnail] React Flow viewport not found");
|
|
5996
|
+
return null;
|
|
5997
|
+
}
|
|
5998
|
+
// Parse viewport transform
|
|
5999
|
+
const viewportStyle = window.getComputedStyle(reactFlowViewport);
|
|
6000
|
+
const viewportTransform = viewportStyle.transform || viewportStyle.getPropertyValue("transform");
|
|
6001
|
+
const transform = parseViewportTransform(viewportTransform);
|
|
6002
|
+
// Calculate visible bounds
|
|
6003
|
+
const viewportRect = reactFlowViewport.getBoundingClientRect();
|
|
6004
|
+
const visibleBounds = calculateVisibleBounds(viewportRect, transform);
|
|
6005
|
+
// Extract edges and nodes
|
|
6006
|
+
const { edges, bounds: edgeBounds } = extractEdgePaths(reactFlowViewport, visibleBounds);
|
|
6007
|
+
const { nodes, bounds: nodeBounds } = extractNodes(reactFlowViewport, visibleBounds);
|
|
6008
|
+
// Calculate overall bounding box
|
|
6009
|
+
// Handle case where one or both bounds might be Infinity (no content)
|
|
6010
|
+
let minX = Infinity;
|
|
6011
|
+
let minY = Infinity;
|
|
6012
|
+
let maxX = -Infinity;
|
|
6013
|
+
let maxY = -Infinity;
|
|
6014
|
+
if (edgeBounds.minX !== Infinity) {
|
|
6015
|
+
minX = Math.min(minX, edgeBounds.minX);
|
|
6016
|
+
minY = Math.min(minY, edgeBounds.minY);
|
|
6017
|
+
maxX = Math.max(maxX, edgeBounds.maxX);
|
|
6018
|
+
maxY = Math.max(maxY, edgeBounds.maxY);
|
|
6019
|
+
}
|
|
6020
|
+
if (nodeBounds.minX !== Infinity) {
|
|
6021
|
+
minX = Math.min(minX, nodeBounds.minX);
|
|
6022
|
+
minY = Math.min(minY, nodeBounds.minY);
|
|
6023
|
+
maxX = Math.max(maxX, nodeBounds.maxX);
|
|
6024
|
+
maxY = Math.max(maxY, nodeBounds.maxY);
|
|
6025
|
+
}
|
|
6026
|
+
// If no visible content, use the visible viewport bounds
|
|
6027
|
+
if (minX === Infinity || (nodes.length === 0 && edges.length === 0)) {
|
|
6028
|
+
minX = visibleBounds.minX;
|
|
6029
|
+
minY = visibleBounds.minY;
|
|
6030
|
+
maxX = visibleBounds.maxX;
|
|
6031
|
+
maxY = visibleBounds.maxY;
|
|
6032
|
+
}
|
|
6033
|
+
// Use the visible viewport bounds exactly (what the user sees)
|
|
6034
|
+
const contentMinX = visibleBounds.minX;
|
|
6035
|
+
const contentMinY = visibleBounds.minY;
|
|
6036
|
+
const contentMaxX = visibleBounds.maxX;
|
|
6037
|
+
const contentMaxY = visibleBounds.maxY;
|
|
6038
|
+
const contentWidth = contentMaxX - contentMinX;
|
|
6039
|
+
const contentHeight = contentMaxY - contentMinY;
|
|
6040
|
+
// Create SVG
|
|
6041
|
+
const { svg, group } = createSVGElement(contentWidth, contentHeight);
|
|
6042
|
+
// Render content
|
|
6043
|
+
renderContentToSVG(group, nodes, edges, -contentMinX, -contentMinY);
|
|
6044
|
+
// Serialize SVG to string
|
|
6045
|
+
const serializer = new XMLSerializer();
|
|
6046
|
+
const svgString = serializer.serializeToString(svg);
|
|
6047
|
+
// Return SVG data URL
|
|
6048
|
+
const svgDataUrl = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgString)))}`;
|
|
6049
|
+
return svgDataUrl;
|
|
6050
|
+
}
|
|
6051
|
+
catch (error) {
|
|
6052
|
+
console.error("[flowThumbnail] Failed to capture thumbnail:", error);
|
|
6053
|
+
return null;
|
|
6054
|
+
}
|
|
6055
|
+
}
|
|
6056
|
+
/**
|
|
6057
|
+
* Captures a React Flow container element as an SVG image and downloads it
|
|
6058
|
+
* @param containerElement - The React Flow container DOM element
|
|
6059
|
+
* @returns Promise resolving to true if successful, false otherwise
|
|
6060
|
+
*/
|
|
6061
|
+
async function downloadCanvasThumbnail(containerElement) {
|
|
6062
|
+
const svgDataUrl = await captureCanvasThumbnail(containerElement);
|
|
6063
|
+
if (!svgDataUrl) {
|
|
6064
|
+
return false;
|
|
6065
|
+
}
|
|
6066
|
+
// Create blob and download
|
|
6067
|
+
const base64Data = svgDataUrl.split(",")[1];
|
|
6068
|
+
const svgString = atob(base64Data);
|
|
6069
|
+
const blob = new Blob([svgString], { type: "image/svg+xml" });
|
|
6070
|
+
const url = URL.createObjectURL(blob);
|
|
6071
|
+
const link = document.createElement("a");
|
|
6072
|
+
link.href = url;
|
|
6073
|
+
link.download = `flow-thumbnail-${Date.now()}.svg`;
|
|
6074
|
+
document.body.appendChild(link);
|
|
6075
|
+
link.click();
|
|
6076
|
+
document.body.removeChild(link);
|
|
6077
|
+
URL.revokeObjectURL(url);
|
|
6078
|
+
return true;
|
|
6079
|
+
}
|
|
6080
|
+
|
|
5491
6081
|
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
|
|
5492
6082
|
const { wb, runner, registry, selectedNodeId, runAutoLayout } = useWorkbenchContext();
|
|
5493
6083
|
const [transportStatus, setTransportStatus] = useState({
|
|
@@ -5576,6 +6166,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5576
6166
|
return defaultExamples;
|
|
5577
6167
|
}, [overrides, defaultExamples]);
|
|
5578
6168
|
const canvasRef = useRef(null);
|
|
6169
|
+
const canvasContainerRef = useRef(null);
|
|
5579
6170
|
const uploadInputRef = useRef(null);
|
|
5580
6171
|
const [registryReady, setRegistryReady] = useState(() => {
|
|
5581
6172
|
// For local backends, registry is always ready
|
|
@@ -5937,7 +6528,9 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5937
6528
|
// Normal change when not running
|
|
5938
6529
|
onEngineChange?.(kind);
|
|
5939
6530
|
}
|
|
5940
|
-
}, 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 }) }),
|
|
6531
|
+
}, 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 () => {
|
|
6532
|
+
await downloadCanvasThumbnail(canvasContainerRef.current);
|
|
6533
|
+
}, 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 })] })] }));
|
|
5941
6534
|
}
|
|
5942
6535
|
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
|
|
5943
6536
|
const [registry, setRegistry] = useState(createSimpleGraphRegistry());
|
|
@@ -6012,5 +6605,5 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
6012
6605
|
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 }) }));
|
|
6013
6606
|
}
|
|
6014
6607
|
|
|
6015
|
-
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 };
|
|
6608
|
+
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 };
|
|
6016
6609
|
//# sourceMappingURL=index.js.map
|