@bian-womp/spark-workbench 0.2.65 → 0.2.67
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 +500 -215
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts +13 -0
- 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/DefaultContextMenu.d.ts.map +1 -1
- package/lib/cjs/src/misc/NodeContextMenu.d.ts.map +1 -1
- package/lib/cjs/src/misc/SelectionContextMenu.d.ts +3 -0
- package/lib/cjs/src/misc/SelectionContextMenu.d.ts.map +1 -0
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/ContextMenuHandlers.d.ts +18 -0
- package/lib/cjs/src/misc/context/ContextMenuHandlers.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/ContextMenuHelpers.d.ts +39 -2
- package/lib/cjs/src/misc/context/ContextMenuHelpers.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +46 -0
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/esm/index.js +495 -216
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts +13 -0
- 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/DefaultContextMenu.d.ts.map +1 -1
- package/lib/esm/src/misc/NodeContextMenu.d.ts.map +1 -1
- package/lib/esm/src/misc/SelectionContextMenu.d.ts +3 -0
- package/lib/esm/src/misc/SelectionContextMenu.d.ts.map +1 -0
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/context/ContextMenuHandlers.d.ts +18 -0
- package/lib/esm/src/misc/context/ContextMenuHandlers.d.ts.map +1 -1
- package/lib/esm/src/misc/context/ContextMenuHelpers.d.ts +39 -2
- package/lib/esm/src/misc/context/ContextMenuHelpers.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts +46 -0
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -131,6 +131,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
131
131
|
edges: [],
|
|
132
132
|
};
|
|
133
133
|
this.viewport = null;
|
|
134
|
+
this.copiedData = null;
|
|
134
135
|
}
|
|
135
136
|
setRegistry(registry) {
|
|
136
137
|
this.registry = registry;
|
|
@@ -307,6 +308,22 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
307
308
|
edges: [...this.selection.edges],
|
|
308
309
|
};
|
|
309
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Delete all selected nodes and edges.
|
|
313
|
+
*/
|
|
314
|
+
deleteSelection() {
|
|
315
|
+
const selection = this.getSelection();
|
|
316
|
+
// Delete all selected nodes (this will also remove connected edges)
|
|
317
|
+
for (const nodeId of selection.nodes) {
|
|
318
|
+
this.removeNode(nodeId);
|
|
319
|
+
}
|
|
320
|
+
// Delete remaining selected edges (edges not connected to deleted nodes)
|
|
321
|
+
for (const edgeId of selection.edges) {
|
|
322
|
+
this.disconnect(edgeId);
|
|
323
|
+
}
|
|
324
|
+
// Clear selection
|
|
325
|
+
this.setSelection({ nodes: [], edges: [] });
|
|
326
|
+
}
|
|
310
327
|
setViewport(viewport, opts) {
|
|
311
328
|
this.viewport = { ...viewport };
|
|
312
329
|
this.emit("graphUiChanged", {
|
|
@@ -503,6 +520,18 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
503
520
|
});
|
|
504
521
|
return { nodeIdMap, edgeIds };
|
|
505
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Get the currently copied graph data.
|
|
525
|
+
*/
|
|
526
|
+
getCopiedData() {
|
|
527
|
+
return this.copiedData;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Set the copied graph data (used for paste functionality).
|
|
531
|
+
*/
|
|
532
|
+
setCopiedData(data) {
|
|
533
|
+
this.copiedData = data;
|
|
534
|
+
}
|
|
506
535
|
}
|
|
507
536
|
|
|
508
537
|
class CLIWorkbench {
|
|
@@ -3049,6 +3078,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3049
3078
|
updateEdgeType,
|
|
3050
3079
|
triggerExternal,
|
|
3051
3080
|
uiVersion,
|
|
3081
|
+
overrides,
|
|
3052
3082
|
}), [
|
|
3053
3083
|
wb,
|
|
3054
3084
|
runner,
|
|
@@ -3088,10 +3118,272 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3088
3118
|
wb,
|
|
3089
3119
|
runner,
|
|
3090
3120
|
uiVersion,
|
|
3121
|
+
overrides,
|
|
3091
3122
|
]);
|
|
3092
3123
|
return (jsxRuntime.jsx(WorkbenchContext.Provider, { value: value, children: children }));
|
|
3093
3124
|
}
|
|
3094
3125
|
|
|
3126
|
+
function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose, getDefaultNodeSize, onCopyResult) {
|
|
3127
|
+
const doBake = async (handleId) => {
|
|
3128
|
+
try {
|
|
3129
|
+
const typeId = outputTypesMap?.[nodeId]?.[handleId];
|
|
3130
|
+
const raw = outputsMap?.[nodeId]?.[handleId];
|
|
3131
|
+
if (!typeId || raw === undefined)
|
|
3132
|
+
return;
|
|
3133
|
+
const unwrap = (v) => sparkGraph.isTypedOutput(v) ? sparkGraph.getTypedOutputValue(v) : v;
|
|
3134
|
+
const coerceIfNeeded = async (fromType, toType, value) => {
|
|
3135
|
+
if (!toType || toType === fromType || !runner?.coerce)
|
|
3136
|
+
return value;
|
|
3137
|
+
try {
|
|
3138
|
+
return await runner.coerce(fromType, toType, value);
|
|
3139
|
+
}
|
|
3140
|
+
catch {
|
|
3141
|
+
return value;
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3145
|
+
const isArray = typeId.endsWith("[]");
|
|
3146
|
+
const baseTypeId = isArray ? typeId.slice(0, -2) : typeId;
|
|
3147
|
+
const tArr = isArray ? registry.types.get(typeId) : undefined;
|
|
3148
|
+
const tElem = registry.types.get(baseTypeId);
|
|
3149
|
+
const singleTarget = !isArray ? tElem?.bakeTarget : undefined;
|
|
3150
|
+
const arrTarget = isArray ? tArr?.bakeTarget : undefined;
|
|
3151
|
+
const elemTarget = isArray ? tElem?.bakeTarget : undefined;
|
|
3152
|
+
if (singleTarget) {
|
|
3153
|
+
const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
|
|
3154
|
+
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, singleTarget.inputHandle);
|
|
3155
|
+
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3156
|
+
wb.addNode({
|
|
3157
|
+
typeId: singleTarget.nodeTypeId,
|
|
3158
|
+
position: { x: pos.x + 180, y: pos.y },
|
|
3159
|
+
}, { inputs: { [singleTarget.inputHandle]: coerced } });
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
if (isArray && arrTarget) {
|
|
3163
|
+
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
3164
|
+
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
3165
|
+
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3166
|
+
wb.addNode({
|
|
3167
|
+
typeId: arrTarget.nodeTypeId,
|
|
3168
|
+
position: { x: pos.x + 180, y: pos.y },
|
|
3169
|
+
}, { inputs: { [arrTarget.inputHandle]: coerced } });
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
if (isArray && elemTarget) {
|
|
3173
|
+
const nodeDesc = registry.nodes.get(elemTarget.nodeTypeId);
|
|
3174
|
+
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, elemTarget.inputHandle);
|
|
3175
|
+
const src = unwrap(raw);
|
|
3176
|
+
const items = Array.isArray(src) ? src : [src];
|
|
3177
|
+
const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, inType, v)));
|
|
3178
|
+
const COLS = 4;
|
|
3179
|
+
const DX = 180;
|
|
3180
|
+
const DY = 160;
|
|
3181
|
+
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3182
|
+
const col = idx % COLS;
|
|
3183
|
+
const row = Math.floor(idx / COLS);
|
|
3184
|
+
wb.addNode({
|
|
3185
|
+
typeId: elemTarget.nodeTypeId,
|
|
3186
|
+
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
3187
|
+
}, { inputs: { [elemTarget.inputHandle]: coercedItems[idx] } });
|
|
3188
|
+
}
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
catch { }
|
|
3193
|
+
};
|
|
3194
|
+
return {
|
|
3195
|
+
onDelete: () => {
|
|
3196
|
+
wb.removeNode(nodeId);
|
|
3197
|
+
onClose();
|
|
3198
|
+
},
|
|
3199
|
+
onDuplicate: async () => {
|
|
3200
|
+
const def = wb.export();
|
|
3201
|
+
const n = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3202
|
+
if (!n)
|
|
3203
|
+
return onClose();
|
|
3204
|
+
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3205
|
+
const inboundHandles = new Set(def.edges
|
|
3206
|
+
.filter((e) => e.target.nodeId === nodeId)
|
|
3207
|
+
.map((e) => e.target.handle));
|
|
3208
|
+
const allInputs = runner.getInputs(def)[nodeId] || {};
|
|
3209
|
+
const inputsWithoutBindings = Object.fromEntries(Object.entries(allInputs).filter(([handle]) => !inboundHandles.has(handle)));
|
|
3210
|
+
const newNodeId = wb.addNode({
|
|
3211
|
+
typeId: n.typeId,
|
|
3212
|
+
params: n.params,
|
|
3213
|
+
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3214
|
+
resolvedHandles: n.resolvedHandles,
|
|
3215
|
+
}, {
|
|
3216
|
+
inputs: inputsWithoutBindings,
|
|
3217
|
+
copyOutputsFrom: nodeId,
|
|
3218
|
+
dry: true,
|
|
3219
|
+
});
|
|
3220
|
+
// Select the newly duplicated node
|
|
3221
|
+
wb.setSelection({
|
|
3222
|
+
nodes: [newNodeId],
|
|
3223
|
+
edges: [],
|
|
3224
|
+
});
|
|
3225
|
+
onClose();
|
|
3226
|
+
},
|
|
3227
|
+
onDuplicateWithEdges: async () => {
|
|
3228
|
+
const def = wb.export();
|
|
3229
|
+
const n = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3230
|
+
if (!n)
|
|
3231
|
+
return onClose();
|
|
3232
|
+
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3233
|
+
// Get inputs without bindings (literal values only)
|
|
3234
|
+
const inputs = runner.getInputs(def)[nodeId] || {};
|
|
3235
|
+
// Add the duplicated node
|
|
3236
|
+
const newNodeId = wb.addNode({
|
|
3237
|
+
typeId: n.typeId,
|
|
3238
|
+
params: n.params,
|
|
3239
|
+
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3240
|
+
resolvedHandles: n.resolvedHandles,
|
|
3241
|
+
}, {
|
|
3242
|
+
inputs,
|
|
3243
|
+
copyOutputsFrom: nodeId,
|
|
3244
|
+
dry: true,
|
|
3245
|
+
});
|
|
3246
|
+
// Find all incoming edges (edges where target is the original node)
|
|
3247
|
+
const incomingEdges = def.edges.filter((e) => e.target.nodeId === nodeId);
|
|
3248
|
+
// Duplicate each incoming edge to point to the new node
|
|
3249
|
+
for (const edge of incomingEdges) {
|
|
3250
|
+
wb.connect({
|
|
3251
|
+
source: edge.source, // Keep the same source
|
|
3252
|
+
target: { nodeId: newNodeId, handle: edge.target.handle }, // Point to new node
|
|
3253
|
+
typeId: edge.typeId,
|
|
3254
|
+
}, { dry: true });
|
|
3255
|
+
}
|
|
3256
|
+
// Select the newly duplicated node and edges
|
|
3257
|
+
wb.setSelection({
|
|
3258
|
+
nodes: [newNodeId],
|
|
3259
|
+
edges: [],
|
|
3260
|
+
});
|
|
3261
|
+
onClose();
|
|
3262
|
+
},
|
|
3263
|
+
onRunPull: async () => {
|
|
3264
|
+
try {
|
|
3265
|
+
await runner.computeNode(nodeId);
|
|
3266
|
+
}
|
|
3267
|
+
catch { }
|
|
3268
|
+
onClose();
|
|
3269
|
+
},
|
|
3270
|
+
onBake: async (handleId) => {
|
|
3271
|
+
await doBake(handleId);
|
|
3272
|
+
onClose();
|
|
3273
|
+
},
|
|
3274
|
+
onCopy: () => {
|
|
3275
|
+
const copyHandler = createNodeCopyHandler(wb, runner, nodeId, getDefaultNodeSize, onCopyResult);
|
|
3276
|
+
copyHandler();
|
|
3277
|
+
onClose();
|
|
3278
|
+
},
|
|
3279
|
+
onCopyId: async () => {
|
|
3280
|
+
try {
|
|
3281
|
+
await navigator.clipboard.writeText(nodeId);
|
|
3282
|
+
}
|
|
3283
|
+
catch { }
|
|
3284
|
+
onClose();
|
|
3285
|
+
},
|
|
3286
|
+
onClose,
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
/**
|
|
3290
|
+
* Creates a copy handler that copies the current selection and optionally stores the result.
|
|
3291
|
+
*/
|
|
3292
|
+
function createCopyHandler(wb, runner, getDefaultNodeSize, onCopyResult) {
|
|
3293
|
+
return () => {
|
|
3294
|
+
const getNodeSize = getDefaultNodeSize
|
|
3295
|
+
? (nodeId, typeId) => getDefaultNodeSize(typeId)
|
|
3296
|
+
: undefined;
|
|
3297
|
+
const data = wb.copySelection(runner, getNodeSize);
|
|
3298
|
+
if (onCopyResult) {
|
|
3299
|
+
onCopyResult(data);
|
|
3300
|
+
}
|
|
3301
|
+
};
|
|
3302
|
+
}
|
|
3303
|
+
/**
|
|
3304
|
+
* Creates a copy handler for a single node (temporarily selects it, copies, then restores selection).
|
|
3305
|
+
*/
|
|
3306
|
+
function createNodeCopyHandler(wb, runner, nodeId, getDefaultNodeSize, onCopyResult) {
|
|
3307
|
+
return () => {
|
|
3308
|
+
// Select the node first, then copy
|
|
3309
|
+
const currentSelection = wb.getSelection();
|
|
3310
|
+
wb.setSelection({ nodes: [nodeId], edges: [] });
|
|
3311
|
+
const getNodeSize = getDefaultNodeSize
|
|
3312
|
+
? (nodeId, typeId) => getDefaultNodeSize(typeId)
|
|
3313
|
+
: undefined;
|
|
3314
|
+
const data = wb.copySelection(runner, getNodeSize);
|
|
3315
|
+
// Restore original selection
|
|
3316
|
+
wb.setSelection(currentSelection);
|
|
3317
|
+
if (onCopyResult) {
|
|
3318
|
+
onCopyResult(data);
|
|
3319
|
+
}
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* Creates base selection context menu handlers.
|
|
3324
|
+
*/
|
|
3325
|
+
function createSelectionContextMenuHandlers(wb, onClose, getDefaultNodeSize, onCopyResult, runner) {
|
|
3326
|
+
return {
|
|
3327
|
+
onCopy: runner
|
|
3328
|
+
? createCopyHandler(wb, runner, getDefaultNodeSize, onCopyResult)
|
|
3329
|
+
: () => {
|
|
3330
|
+
// No-op if runner not available
|
|
3331
|
+
onClose();
|
|
3332
|
+
},
|
|
3333
|
+
onDelete: () => {
|
|
3334
|
+
wb.deleteSelection();
|
|
3335
|
+
onClose();
|
|
3336
|
+
},
|
|
3337
|
+
onClose,
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
/**
|
|
3341
|
+
* Creates base default context menu handlers.
|
|
3342
|
+
*/
|
|
3343
|
+
function createDefaultContextMenuHandlers(onAddNode, onClose, onPaste) {
|
|
3344
|
+
return {
|
|
3345
|
+
onAddNode,
|
|
3346
|
+
onPaste,
|
|
3347
|
+
onClose,
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
function getBakeableOutputs(nodeId, wb, registry, outputTypesMap) {
|
|
3351
|
+
try {
|
|
3352
|
+
const def = wb.export();
|
|
3353
|
+
const node = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3354
|
+
if (!node)
|
|
3355
|
+
return [];
|
|
3356
|
+
const desc = registry.nodes.get(node.typeId);
|
|
3357
|
+
const handles = Object.keys(desc?.outputs || {});
|
|
3358
|
+
const out = [];
|
|
3359
|
+
for (const h of handles) {
|
|
3360
|
+
const tId = outputTypesMap?.[nodeId]?.[h];
|
|
3361
|
+
if (!tId)
|
|
3362
|
+
continue;
|
|
3363
|
+
if (tId.endsWith("[]")) {
|
|
3364
|
+
const base = tId.slice(0, -2);
|
|
3365
|
+
const tArr = registry.types.get(tId);
|
|
3366
|
+
const tElem = registry.types.get(base);
|
|
3367
|
+
const arrT = tArr?.bakeTarget;
|
|
3368
|
+
const elemT = tElem?.bakeTarget;
|
|
3369
|
+
if ((arrT && registry.nodes.has(arrT.nodeTypeId)) ||
|
|
3370
|
+
(elemT && registry.nodes.has(elemT.nodeTypeId)))
|
|
3371
|
+
out.push(h);
|
|
3372
|
+
}
|
|
3373
|
+
else {
|
|
3374
|
+
const t = registry.types.get(tId);
|
|
3375
|
+
const bt = t?.bakeTarget;
|
|
3376
|
+
if (bt && registry.nodes.has(bt.nodeTypeId))
|
|
3377
|
+
out.push(h);
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
return out;
|
|
3381
|
+
}
|
|
3382
|
+
catch {
|
|
3383
|
+
return [];
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3095
3387
|
function IssueBadge({ level, title, size = 12, className, }) {
|
|
3096
3388
|
const colorClass = level === "error" ? "text-red-600" : "text-amber-600";
|
|
3097
3389
|
return (jsxRuntime.jsx("button", { type: "button", className: `inline-flex items-center justify-center shrink-0 ${colorClass} ${className ?? ""}`, title: title, style: { width: size, height: size }, children: level === "error" ? (jsxRuntime.jsx(react$1.XCircleIcon, { size: size, weight: "fill" })) : (jsxRuntime.jsx(react$1.WarningCircleIcon, { size: size, weight: "fill" })) }));
|
|
@@ -3304,7 +3596,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
3304
3596
|
}
|
|
3305
3597
|
catch { }
|
|
3306
3598
|
};
|
|
3307
|
-
return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-auto`, children: [jsxRuntime.jsxs("div", { className: "flex-1 overflow-auto", children: [contextPanel && jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel }), inputValidationErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [inputValidationErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Input Validation Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message }), jsxRuntime.jsxs("div", { className: "text-[10px] text-red-600 mt-1", children: [err.nodeId, ".", err.handle, " (type: ", err.typeId, ")"] })] }), jsxRuntime.jsx("button", { className: "text-red-500 hover:text-red-700 text-[10px] px-1", onClick: () => removeInputValidationError(i), title: "Dismiss", children: jsxRuntime.jsx(react$1.XIcon, { size: 10 }) })] }, i))), inputValidationErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-red-600 hover:text-red-800 underline", onClick: clearInputValidationErrors, children: "Clear all" }))] })), systemErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [systemErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: err.code ? `Error ${err.code}` : "Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message })] }), jsxRuntime.jsx("button", { className: "text-red-500 hover:text-red-700 text-[10px] px-1", onClick: () => removeSystemError(i), title: "Dismiss", children: jsxRuntime.jsx(react$1.XIcon, { size: 10 }) })] }, i))), systemErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-red-600 hover:text-red-800 underline", onClick: clearSystemErrors, children: "Clear all" }))] })), registryErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [registryErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Registry Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message }), err.attempt && err.maxAttempts && (jsxRuntime.jsxs("div", { className: "text-[10px] text-amber-600 mt-1", children: ["Attempt ", err.attempt, " of ", err.maxAttempts] }))] }), jsxRuntime.jsx("button", { className: "text-amber-500 hover:text-amber-700 text-[10px] px-1", onClick: () => removeRegistryError(i), title: "Dismiss", children: jsxRuntime.jsx(react$1.XIcon, { size: 10 }) })] }, i))), registryErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-amber-600 hover:text-amber-800 underline", onClick: clearRegistryErrors, children: "Clear all" }))] })), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsxRuntime.jsx("div", { className: "flex-1", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` }), !!m.data?.edgeId && (jsxRuntime.jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
|
|
3599
|
+
return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-auto select-none`, children: [jsxRuntime.jsxs("div", { className: "flex-1 overflow-auto", children: [contextPanel && jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel }), inputValidationErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [inputValidationErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Input Validation Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message }), jsxRuntime.jsxs("div", { className: "text-[10px] text-red-600 mt-1", children: [err.nodeId, ".", err.handle, " (type: ", err.typeId, ")"] })] }), jsxRuntime.jsx("button", { className: "text-red-500 hover:text-red-700 text-[10px] px-1", onClick: () => removeInputValidationError(i), title: "Dismiss", children: jsxRuntime.jsx(react$1.XIcon, { size: 10 }) })] }, i))), inputValidationErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-red-600 hover:text-red-800 underline", onClick: clearInputValidationErrors, children: "Clear all" }))] })), systemErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [systemErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: err.code ? `Error ${err.code}` : "Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message })] }), jsxRuntime.jsx("button", { className: "text-red-500 hover:text-red-700 text-[10px] px-1", onClick: () => removeSystemError(i), title: "Dismiss", children: jsxRuntime.jsx(react$1.XIcon, { size: 10 }) })] }, i))), systemErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-red-600 hover:text-red-800 underline", onClick: clearSystemErrors, children: "Clear all" }))] })), registryErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [registryErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Registry Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message }), err.attempt && err.maxAttempts && (jsxRuntime.jsxs("div", { className: "text-[10px] text-amber-600 mt-1", children: ["Attempt ", err.attempt, " of ", err.maxAttempts] }))] }), jsxRuntime.jsx("button", { className: "text-amber-500 hover:text-amber-700 text-[10px] px-1", onClick: () => removeRegistryError(i), title: "Dismiss", children: jsxRuntime.jsx(react$1.XIcon, { size: 10 }) })] }, i))), registryErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-amber-600 hover:text-amber-800 underline", onClick: clearRegistryErrors, children: "Clear all" }))] })), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsxRuntime.jsx("div", { className: "flex-1", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` }), !!m.data?.edgeId && (jsxRuntime.jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
|
|
3308
3600
|
e.stopPropagation();
|
|
3309
3601
|
deleteEdgeById(m.data?.edgeId);
|
|
3310
3602
|
}, title: "Delete referenced edge", children: "Delete edge" }))] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), renderEdgeStatus(), jsxRuntime.jsx("div", { className: "mt-1", children: jsxRuntime.jsx("button", { className: "text-xs px-2 py-1 border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
|
|
@@ -3372,7 +3664,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
3372
3664
|
const title = inIssues
|
|
3373
3665
|
.map((v) => `${v.code}: ${v.message}`)
|
|
3374
3666
|
.join("; ");
|
|
3375
|
-
return (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsxs("label", { className: "w-32 flex flex-col", children: [jsxRuntime.jsx("span", { children: prettyHandle(h) }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: typeId })] }), hasValidation && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: title })), isEnum ? (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1", value: current !== undefined && current !== null
|
|
3667
|
+
return (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsxs("label", { className: "w-32 flex flex-col", children: [jsxRuntime.jsx("span", { children: prettyHandle(h) }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: typeId })] }), hasValidation && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: title })), isEnum ? (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1 select-text", value: current !== undefined && current !== null
|
|
3376
3668
|
? String(current)
|
|
3377
3669
|
: "", onChange: (e) => {
|
|
3378
3670
|
const val = e.target.value;
|
|
@@ -3386,7 +3678,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
3386
3678
|
? `Default: ${placeholder}`
|
|
3387
3679
|
: "(select)" }), registry.enums
|
|
3388
3680
|
.get(typeId)
|
|
3389
|
-
?.options.map((opt) => (jsxRuntime.jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] }), hasValue && !isLinked && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsxRuntime.jsx(react$1.XCircleIcon, { size: 16 }) }))] })) : isLinked ? (jsxRuntime.jsx("div", { className: "flex items-center gap-1 flex-1", children: jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: renderLinkedInputDisplay(typeId, current) }) })) : (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxRuntime.jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1", placeholder: placeholder
|
|
3681
|
+
?.options.map((opt) => (jsxRuntime.jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] }), hasValue && !isLinked && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsxRuntime.jsx(react$1.XCircleIcon, { size: 16 }) }))] })) : isLinked ? (jsxRuntime.jsx("div", { className: "flex items-center gap-1 flex-1", children: jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: renderLinkedInputDisplay(typeId, current) }) })) : (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxRuntime.jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1 select-text", placeholder: placeholder
|
|
3390
3682
|
? `Default: ${placeholder}`
|
|
3391
3683
|
: undefined, value: displayValue, onChange: (e) => onChangeText(e.target.value), onBlur: commit, onKeyDown: (e) => {
|
|
3392
3684
|
if (e.key === "Enter")
|
|
@@ -3639,6 +3931,13 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, }) {
|
|
|
3639
3931
|
handlers.onAddNode(typeId, { position: p });
|
|
3640
3932
|
handlers.onClose();
|
|
3641
3933
|
};
|
|
3934
|
+
const handlePaste = () => {
|
|
3935
|
+
if (!handlers.onPaste)
|
|
3936
|
+
return;
|
|
3937
|
+
const p = rf.screenToFlowPosition({ x: clientPos.x, y: clientPos.y });
|
|
3938
|
+
handlers.onPaste(p);
|
|
3939
|
+
handlers.onClose();
|
|
3940
|
+
};
|
|
3642
3941
|
const renderTree = (tree, path = []) => {
|
|
3643
3942
|
const entries = Object.entries(tree?.__children ?? {}).sort((a, b) => a[0].localeCompare(b[0]));
|
|
3644
3943
|
return (jsxRuntime.jsx("div", { children: entries.map(([key, child]) => {
|
|
@@ -3653,10 +3952,10 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, }) {
|
|
|
3653
3952
|
return (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "px-2 py-1 text-[11px] uppercase tracking-wide text-gray-400", children: label }), child.__self && (jsxRuntime.jsx("button", { onClick: () => handleClick(child.__self), className: "block w-full text-left px-3 py-1 hover:bg-gray-100 cursor-pointer", title: child.__self, children: label })), jsxRuntime.jsx("div", { className: "pl-2 border-l border-gray-200 ml-2", children: renderTree(child, [...path, key]) })] }, idKey));
|
|
3654
3953
|
}) }));
|
|
3655
3954
|
};
|
|
3656
|
-
return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-
|
|
3955
|
+
return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700 select-none", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
|
|
3657
3956
|
e.preventDefault();
|
|
3658
3957
|
e.stopPropagation();
|
|
3659
|
-
}, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node", " ", jsxRuntime.jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsxRuntime.jsx("div", { className: "px-2 pb-1", children: jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Filter nodes...", className: "w-full border border-gray-300 px-2 py-1 text-sm outline-none focus:border-gray-400", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation() }) }), jsxRuntime.jsx("div", { className: "max-h-60 overflow-auto", children: totalCount > 0 ? (renderTree(root)) : (jsxRuntime.jsx("div", { className: "px-3 py-2 text-gray-400", children: "No matches" })) })] }));
|
|
3958
|
+
}, children: [handlers.onPaste && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed", onClick: handlePaste, children: "Paste" })), handlers.onPaste && jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node", " ", jsxRuntime.jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsxRuntime.jsx("div", { className: "px-2 pb-1", children: jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Filter nodes...", className: "w-full border border-gray-300 px-2 py-1 text-sm outline-none focus:border-gray-400 select-text", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation() }) }), jsxRuntime.jsx("div", { className: "max-h-60 overflow-auto", children: totalCount > 0 ? (renderTree(root)) : (jsxRuntime.jsx("div", { className: "px-3 py-2 text-gray-400", children: "No matches" })) })] }));
|
|
3660
3959
|
}
|
|
3661
3960
|
|
|
3662
3961
|
function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeableOutputs, }) {
|
|
@@ -3694,209 +3993,55 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeab
|
|
|
3694
3993
|
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
3695
3994
|
(MENU_MIN_WIDTH + PADDING));
|
|
3696
3995
|
const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
|
|
3697
|
-
return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
|
|
3996
|
+
return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700 select-none", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
|
|
3698
3997
|
e.preventDefault();
|
|
3699
3998
|
e.stopPropagation();
|
|
3700
|
-
}, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDelete, children: "Delete" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDuplicate, children: "Duplicate" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDuplicateWithEdges, children: "Duplicate with edges" }), canRunPull && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunPull, children: "Run (pull)" })), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }),
|
|
3999
|
+
}, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDelete, children: "Delete" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDuplicate, children: "Duplicate" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDuplicateWithEdges, children: "Duplicate with edges" }), canRunPull && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunPull, children: "Run (pull)" })), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopy, children: "Copy" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopyId, children: "Copy Node ID" }), bakeableOutputs.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxRuntime.jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h)))] }))] }));
|
|
3701
4000
|
}
|
|
3702
4001
|
|
|
3703
|
-
function
|
|
3704
|
-
const
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
const coerceIfNeeded = async (fromType, toType, value) => {
|
|
3712
|
-
if (!toType || toType === fromType || !runner?.coerce)
|
|
3713
|
-
return value;
|
|
3714
|
-
try {
|
|
3715
|
-
return await runner.coerce(fromType, toType, value);
|
|
3716
|
-
}
|
|
3717
|
-
catch {
|
|
3718
|
-
return value;
|
|
3719
|
-
}
|
|
3720
|
-
};
|
|
3721
|
-
const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
|
|
3722
|
-
const isArray = typeId.endsWith("[]");
|
|
3723
|
-
const baseTypeId = isArray ? typeId.slice(0, -2) : typeId;
|
|
3724
|
-
const tArr = isArray ? registry.types.get(typeId) : undefined;
|
|
3725
|
-
const tElem = registry.types.get(baseTypeId);
|
|
3726
|
-
const singleTarget = !isArray ? tElem?.bakeTarget : undefined;
|
|
3727
|
-
const arrTarget = isArray ? tArr?.bakeTarget : undefined;
|
|
3728
|
-
const elemTarget = isArray ? tElem?.bakeTarget : undefined;
|
|
3729
|
-
if (singleTarget) {
|
|
3730
|
-
const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
|
|
3731
|
-
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, singleTarget.inputHandle);
|
|
3732
|
-
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3733
|
-
wb.addNode({
|
|
3734
|
-
typeId: singleTarget.nodeTypeId,
|
|
3735
|
-
position: { x: pos.x + 180, y: pos.y },
|
|
3736
|
-
}, { inputs: { [singleTarget.inputHandle]: coerced } });
|
|
3737
|
-
return;
|
|
3738
|
-
}
|
|
3739
|
-
if (isArray && arrTarget) {
|
|
3740
|
-
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
3741
|
-
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
3742
|
-
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3743
|
-
wb.addNode({
|
|
3744
|
-
typeId: arrTarget.nodeTypeId,
|
|
3745
|
-
position: { x: pos.x + 180, y: pos.y },
|
|
3746
|
-
}, { inputs: { [arrTarget.inputHandle]: coerced } });
|
|
3747
|
-
return;
|
|
3748
|
-
}
|
|
3749
|
-
if (isArray && elemTarget) {
|
|
3750
|
-
const nodeDesc = registry.nodes.get(elemTarget.nodeTypeId);
|
|
3751
|
-
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, elemTarget.inputHandle);
|
|
3752
|
-
const src = unwrap(raw);
|
|
3753
|
-
const items = Array.isArray(src) ? src : [src];
|
|
3754
|
-
const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, inType, v)));
|
|
3755
|
-
const COLS = 4;
|
|
3756
|
-
const DX = 180;
|
|
3757
|
-
const DY = 160;
|
|
3758
|
-
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3759
|
-
const col = idx % COLS;
|
|
3760
|
-
const row = Math.floor(idx / COLS);
|
|
3761
|
-
wb.addNode({
|
|
3762
|
-
typeId: elemTarget.nodeTypeId,
|
|
3763
|
-
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
3764
|
-
}, { inputs: { [elemTarget.inputHandle]: coercedItems[idx] } });
|
|
3765
|
-
}
|
|
4002
|
+
function SelectionContextMenu({ open, clientPos, handlers, }) {
|
|
4003
|
+
const ref = React.useRef(null);
|
|
4004
|
+
// Close on outside click and on ESC
|
|
4005
|
+
React.useEffect(() => {
|
|
4006
|
+
if (!open)
|
|
4007
|
+
return;
|
|
4008
|
+
const onDown = (e) => {
|
|
4009
|
+
if (!ref.current)
|
|
3766
4010
|
return;
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
// Select the newly duplicated node
|
|
3798
|
-
wb.setSelection({
|
|
3799
|
-
nodes: [newNodeId],
|
|
3800
|
-
edges: [],
|
|
3801
|
-
});
|
|
3802
|
-
onClose();
|
|
3803
|
-
},
|
|
3804
|
-
onDuplicateWithEdges: async () => {
|
|
3805
|
-
const def = wb.export();
|
|
3806
|
-
const n = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3807
|
-
if (!n)
|
|
3808
|
-
return onClose();
|
|
3809
|
-
const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
|
|
3810
|
-
// Get inputs without bindings (literal values only)
|
|
3811
|
-
const inputs = runner.getInputs(def)[nodeId] || {};
|
|
3812
|
-
// Add the duplicated node
|
|
3813
|
-
const newNodeId = wb.addNode({
|
|
3814
|
-
typeId: n.typeId,
|
|
3815
|
-
params: n.params,
|
|
3816
|
-
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3817
|
-
resolvedHandles: n.resolvedHandles,
|
|
3818
|
-
}, {
|
|
3819
|
-
inputs,
|
|
3820
|
-
copyOutputsFrom: nodeId,
|
|
3821
|
-
dry: true,
|
|
3822
|
-
});
|
|
3823
|
-
// Find all incoming edges (edges where target is the original node)
|
|
3824
|
-
const incomingEdges = def.edges.filter((e) => e.target.nodeId === nodeId);
|
|
3825
|
-
// Duplicate each incoming edge to point to the new node
|
|
3826
|
-
for (const edge of incomingEdges) {
|
|
3827
|
-
wb.connect({
|
|
3828
|
-
source: edge.source, // Keep the same source
|
|
3829
|
-
target: { nodeId: newNodeId, handle: edge.target.handle }, // Point to new node
|
|
3830
|
-
typeId: edge.typeId,
|
|
3831
|
-
}, { dry: true });
|
|
3832
|
-
}
|
|
3833
|
-
// Select the newly duplicated node and edges
|
|
3834
|
-
wb.setSelection({
|
|
3835
|
-
nodes: [newNodeId],
|
|
3836
|
-
edges: [],
|
|
3837
|
-
});
|
|
3838
|
-
onClose();
|
|
3839
|
-
},
|
|
3840
|
-
onRunPull: async () => {
|
|
3841
|
-
try {
|
|
3842
|
-
await runner.computeNode(nodeId);
|
|
3843
|
-
}
|
|
3844
|
-
catch { }
|
|
3845
|
-
onClose();
|
|
3846
|
-
},
|
|
3847
|
-
onBake: async (handleId) => {
|
|
3848
|
-
await doBake(handleId);
|
|
3849
|
-
onClose();
|
|
3850
|
-
},
|
|
3851
|
-
onCopyId: async () => {
|
|
3852
|
-
try {
|
|
3853
|
-
await navigator.clipboard.writeText(nodeId);
|
|
3854
|
-
}
|
|
3855
|
-
catch { }
|
|
3856
|
-
onClose();
|
|
3857
|
-
},
|
|
3858
|
-
onClose,
|
|
3859
|
-
};
|
|
3860
|
-
}
|
|
3861
|
-
function getBakeableOutputs(nodeId, wb, registry, outputTypesMap) {
|
|
3862
|
-
try {
|
|
3863
|
-
const def = wb.export();
|
|
3864
|
-
const node = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3865
|
-
if (!node)
|
|
3866
|
-
return [];
|
|
3867
|
-
const desc = registry.nodes.get(node.typeId);
|
|
3868
|
-
const handles = Object.keys(desc?.outputs || {});
|
|
3869
|
-
const out = [];
|
|
3870
|
-
for (const h of handles) {
|
|
3871
|
-
const tId = outputTypesMap?.[nodeId]?.[h];
|
|
3872
|
-
if (!tId)
|
|
3873
|
-
continue;
|
|
3874
|
-
if (tId.endsWith("[]")) {
|
|
3875
|
-
const base = tId.slice(0, -2);
|
|
3876
|
-
const tArr = registry.types.get(tId);
|
|
3877
|
-
const tElem = registry.types.get(base);
|
|
3878
|
-
const arrT = tArr?.bakeTarget;
|
|
3879
|
-
const elemT = tElem?.bakeTarget;
|
|
3880
|
-
if ((arrT && registry.nodes.has(arrT.nodeTypeId)) ||
|
|
3881
|
-
(elemT && registry.nodes.has(elemT.nodeTypeId)))
|
|
3882
|
-
out.push(h);
|
|
3883
|
-
}
|
|
3884
|
-
else {
|
|
3885
|
-
const t = registry.types.get(tId);
|
|
3886
|
-
const bt = t?.bakeTarget;
|
|
3887
|
-
if (bt && registry.nodes.has(bt.nodeTypeId))
|
|
3888
|
-
out.push(h);
|
|
3889
|
-
}
|
|
3890
|
-
}
|
|
3891
|
-
return out;
|
|
3892
|
-
}
|
|
3893
|
-
catch {
|
|
3894
|
-
return [];
|
|
3895
|
-
}
|
|
4011
|
+
if (!ref.current.contains(e.target))
|
|
4012
|
+
handlers.onClose();
|
|
4013
|
+
};
|
|
4014
|
+
const onKey = (e) => {
|
|
4015
|
+
if (e.key === "Escape")
|
|
4016
|
+
handlers.onClose();
|
|
4017
|
+
};
|
|
4018
|
+
window.addEventListener("mousedown", onDown, true);
|
|
4019
|
+
window.addEventListener("keydown", onKey);
|
|
4020
|
+
return () => {
|
|
4021
|
+
window.removeEventListener("mousedown", onDown, true);
|
|
4022
|
+
window.removeEventListener("keydown", onKey);
|
|
4023
|
+
};
|
|
4024
|
+
}, [open, handlers]);
|
|
4025
|
+
React.useEffect(() => {
|
|
4026
|
+
if (open)
|
|
4027
|
+
ref.current?.focus();
|
|
4028
|
+
}, [open]);
|
|
4029
|
+
if (!open || !clientPos)
|
|
4030
|
+
return null;
|
|
4031
|
+
// Clamp menu position to viewport
|
|
4032
|
+
const MENU_MIN_WIDTH = 180;
|
|
4033
|
+
const PADDING = 16;
|
|
4034
|
+
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
4035
|
+
(MENU_MIN_WIDTH + PADDING));
|
|
4036
|
+
const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 100);
|
|
4037
|
+
return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700 select-none", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
|
|
4038
|
+
e.preventDefault();
|
|
4039
|
+
e.stopPropagation();
|
|
4040
|
+
}, children: [jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Selection" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopy, children: "Copy" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDelete, children: "Delete" })] }));
|
|
3896
4041
|
}
|
|
3897
4042
|
|
|
3898
4043
|
const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
|
|
3899
|
-
const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, runner, engineKind, } = useWorkbenchContext();
|
|
4044
|
+
const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, runner, engineKind, overrides, } = useWorkbenchContext();
|
|
3900
4045
|
const nodeValidation = validationByNode;
|
|
3901
4046
|
const edgeValidation = validationByEdge.errors;
|
|
3902
4047
|
const [registryVersion, setRegistryVersion] = React.useState(0);
|
|
@@ -4135,23 +4280,100 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4135
4280
|
const [nodeMenuOpen, setNodeMenuOpen] = React.useState(false);
|
|
4136
4281
|
const [nodeMenuPos, setNodeMenuPos] = React.useState(null);
|
|
4137
4282
|
const [nodeAtMenu, setNodeAtMenu] = React.useState(null);
|
|
4283
|
+
const [selectionMenuPos, setSelectionMenuPos] = React.useState(null);
|
|
4284
|
+
const [selectionMenuOpen, setSelectionMenuOpen] = React.useState(false);
|
|
4285
|
+
// Compute the rectangular screen-space bounds of the current selection
|
|
4286
|
+
const getSelectionScreenBounds = () => {
|
|
4287
|
+
if (typeof document === "undefined")
|
|
4288
|
+
return null;
|
|
4289
|
+
const selection = wb.getSelection();
|
|
4290
|
+
if (!selection.nodes.length)
|
|
4291
|
+
return null;
|
|
4292
|
+
let bounds = null;
|
|
4293
|
+
for (const nodeId of selection.nodes) {
|
|
4294
|
+
const el = document.querySelector(`.react-flow__node[data-id="${nodeId}"]`);
|
|
4295
|
+
if (!el)
|
|
4296
|
+
continue;
|
|
4297
|
+
const rect = el.getBoundingClientRect();
|
|
4298
|
+
if (!bounds) {
|
|
4299
|
+
bounds = {
|
|
4300
|
+
left: rect.left,
|
|
4301
|
+
top: rect.top,
|
|
4302
|
+
right: rect.right,
|
|
4303
|
+
bottom: rect.bottom,
|
|
4304
|
+
};
|
|
4305
|
+
}
|
|
4306
|
+
else {
|
|
4307
|
+
bounds.left = Math.min(bounds.left, rect.left);
|
|
4308
|
+
bounds.top = Math.min(bounds.top, rect.top);
|
|
4309
|
+
bounds.right = Math.max(bounds.right, rect.right);
|
|
4310
|
+
bounds.bottom = Math.max(bounds.bottom, rect.bottom);
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4313
|
+
return bounds;
|
|
4314
|
+
};
|
|
4138
4315
|
const onContextMenu = (e) => {
|
|
4139
4316
|
e.preventDefault();
|
|
4140
|
-
//
|
|
4317
|
+
// First, check if the cursor is inside the rectangular bounds of the current selection
|
|
4318
|
+
const selectionBounds = getSelectionScreenBounds();
|
|
4319
|
+
if (selectionBounds) {
|
|
4320
|
+
const { left, top, right, bottom } = selectionBounds;
|
|
4321
|
+
if (e.clientX >= left &&
|
|
4322
|
+
e.clientX <= right &&
|
|
4323
|
+
e.clientY >= top &&
|
|
4324
|
+
e.clientY <= bottom) {
|
|
4325
|
+
setSelectionMenuPos({ x: e.clientX, y: e.clientY });
|
|
4326
|
+
setSelectionMenuOpen(true);
|
|
4327
|
+
setMenuOpen(false);
|
|
4328
|
+
setNodeMenuOpen(false);
|
|
4329
|
+
return;
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
// Determine if right-clicked over a node by hit-testing
|
|
4141
4333
|
const target = e.target?.closest(".react-flow__node");
|
|
4142
4334
|
if (target) {
|
|
4143
4335
|
// Resolve node id from data-id attribute React Flow sets
|
|
4144
4336
|
const nodeId = target.getAttribute("data-id");
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4337
|
+
const selection = wb.getSelection();
|
|
4338
|
+
const isSelected = nodeId && selection.nodes.includes(nodeId);
|
|
4339
|
+
if (isSelected) {
|
|
4340
|
+
// Right-clicked on a selected node - show selection menu
|
|
4341
|
+
setSelectionMenuPos({ x: e.clientX, y: e.clientY });
|
|
4342
|
+
setSelectionMenuOpen(true);
|
|
4343
|
+
setMenuOpen(false);
|
|
4344
|
+
setNodeMenuOpen(false);
|
|
4345
|
+
return;
|
|
4346
|
+
}
|
|
4347
|
+
else {
|
|
4348
|
+
// Right-clicked on a non-selected node - show node menu
|
|
4349
|
+
setNodeAtMenu(nodeId);
|
|
4350
|
+
setNodeMenuPos({ x: e.clientX, y: e.clientY });
|
|
4351
|
+
setNodeMenuOpen(true);
|
|
4352
|
+
setMenuOpen(false);
|
|
4353
|
+
setSelectionMenuOpen(false);
|
|
4354
|
+
return;
|
|
4355
|
+
}
|
|
4149
4356
|
}
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4357
|
+
// Check if right-clicked on a selected edge
|
|
4358
|
+
const edgeTarget = e.target?.closest(".react-flow__edge");
|
|
4359
|
+
if (edgeTarget) {
|
|
4360
|
+
const edgeId = edgeTarget.getAttribute("data-id");
|
|
4361
|
+
const selection = wb.getSelection();
|
|
4362
|
+
const isSelected = edgeId && selection.edges.includes(edgeId);
|
|
4363
|
+
if (isSelected) {
|
|
4364
|
+
// Right-clicked on a selected edge - show selection menu
|
|
4365
|
+
setSelectionMenuPos({ x: e.clientX, y: e.clientY });
|
|
4366
|
+
setSelectionMenuOpen(true);
|
|
4367
|
+
setMenuOpen(false);
|
|
4368
|
+
setNodeMenuOpen(false);
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4154
4371
|
}
|
|
4372
|
+
// Right-clicked on empty space - show default menu
|
|
4373
|
+
setMenuPos({ x: e.clientX, y: e.clientY });
|
|
4374
|
+
setMenuOpen(true);
|
|
4375
|
+
setNodeMenuOpen(false);
|
|
4376
|
+
setSelectionMenuOpen(false);
|
|
4155
4377
|
};
|
|
4156
4378
|
const addNodeAt = React.useCallback(async (typeId, opts) => wb.addNode({ typeId, position: opts.position }, { inputs: opts.inputs }), [wb]);
|
|
4157
4379
|
const onCloseMenu = React.useCallback(() => {
|
|
@@ -4160,6 +4382,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4160
4382
|
const onCloseNodeMenu = React.useCallback(() => {
|
|
4161
4383
|
setNodeMenuOpen(false);
|
|
4162
4384
|
}, []);
|
|
4385
|
+
const onCloseSelectionMenu = React.useCallback(() => {
|
|
4386
|
+
setSelectionMenuOpen(false);
|
|
4387
|
+
}, []);
|
|
4163
4388
|
React.useEffect(() => {
|
|
4164
4389
|
const off = runner.on("registry", () => {
|
|
4165
4390
|
setRegistryVersion((v) => v + 1);
|
|
@@ -4167,14 +4392,65 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4167
4392
|
return () => off();
|
|
4168
4393
|
}, [runner]);
|
|
4169
4394
|
const nodeIds = React.useMemo(() => Array.from(registry.nodes.keys()), [registry, registryVersion]);
|
|
4170
|
-
const defaultContextMenuHandlers = React.useMemo(() =>
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4395
|
+
const defaultContextMenuHandlers = React.useMemo(() => {
|
|
4396
|
+
// Get storage from override or use workbench's internal storage
|
|
4397
|
+
const storage = overrides?.getCopiedDataStorage
|
|
4398
|
+
? overrides.getCopiedDataStorage()
|
|
4399
|
+
: {
|
|
4400
|
+
get: () => wb.getCopiedData(),
|
|
4401
|
+
set: (data) => wb.setCopiedData(data),
|
|
4402
|
+
};
|
|
4403
|
+
const baseHandlers = createDefaultContextMenuHandlers(addNodeAt, onCloseMenu,
|
|
4404
|
+
// Paste handler - checks storage dynamically when called
|
|
4405
|
+
// Only provide handler if storage has data or might have data (for dynamic checking)
|
|
4406
|
+
(position) => {
|
|
4407
|
+
const data = storage.get();
|
|
4408
|
+
if (!data)
|
|
4409
|
+
return;
|
|
4410
|
+
wb.pasteCopiedData(data, position);
|
|
4411
|
+
onCloseMenu();
|
|
4412
|
+
});
|
|
4413
|
+
if (overrides?.getDefaultContextMenuHandlers) {
|
|
4414
|
+
return overrides.getDefaultContextMenuHandlers(wb, baseHandlers);
|
|
4415
|
+
}
|
|
4416
|
+
return baseHandlers;
|
|
4417
|
+
}, [addNodeAt, onCloseMenu, overrides, wb]);
|
|
4418
|
+
const selectionContextMenuHandlers = React.useMemo(() => {
|
|
4419
|
+
// Get storage from override or use workbench's internal storage
|
|
4420
|
+
const storage = overrides?.getCopiedDataStorage
|
|
4421
|
+
? overrides.getCopiedDataStorage()
|
|
4422
|
+
: {
|
|
4423
|
+
get: () => wb.getCopiedData(),
|
|
4424
|
+
set: (data) => wb.setCopiedData(data),
|
|
4425
|
+
};
|
|
4426
|
+
const baseHandlers = createSelectionContextMenuHandlers(wb, onCloseSelectionMenu, overrides?.getDefaultNodeSize, (data) => {
|
|
4427
|
+
storage.set(data);
|
|
4428
|
+
}, runner);
|
|
4429
|
+
if (overrides?.getSelectionContextMenuHandlers) {
|
|
4430
|
+
const selection = wb.getSelection();
|
|
4431
|
+
return overrides.getSelectionContextMenuHandlers(wb, selection, baseHandlers, {
|
|
4432
|
+
getDefaultNodeSize: overrides.getDefaultNodeSize,
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
return baseHandlers;
|
|
4436
|
+
}, [wb, runner, overrides, onCloseSelectionMenu]);
|
|
4174
4437
|
const nodeContextMenuHandlers = React.useMemo(() => {
|
|
4175
4438
|
if (!nodeAtMenu)
|
|
4176
4439
|
return null;
|
|
4177
|
-
|
|
4440
|
+
// Get storage from override or use workbench's internal storage
|
|
4441
|
+
const storage = overrides?.getCopiedDataStorage
|
|
4442
|
+
? overrides.getCopiedDataStorage()
|
|
4443
|
+
: {
|
|
4444
|
+
get: () => wb.getCopiedData(),
|
|
4445
|
+
set: (data) => wb.setCopiedData(data),
|
|
4446
|
+
};
|
|
4447
|
+
const baseHandlers = createNodeContextMenuHandlers(nodeAtMenu, wb, runner, registry, outputsMap, outputTypesMap, onCloseNodeMenu, overrides?.getDefaultNodeSize, (data) => {
|
|
4448
|
+
storage.set(data);
|
|
4449
|
+
});
|
|
4450
|
+
if (overrides?.getNodeContextMenuHandlers) {
|
|
4451
|
+
return overrides.getNodeContextMenuHandlers(wb, nodeAtMenu, baseHandlers);
|
|
4452
|
+
}
|
|
4453
|
+
return baseHandlers;
|
|
4178
4454
|
}, [
|
|
4179
4455
|
nodeAtMenu,
|
|
4180
4456
|
wb,
|
|
@@ -4183,6 +4459,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4183
4459
|
outputsMap,
|
|
4184
4460
|
outputTypesMap,
|
|
4185
4461
|
onCloseNodeMenu,
|
|
4462
|
+
overrides?.getDefaultNodeSize,
|
|
4463
|
+
overrides?.getNodeContextMenuHandlers,
|
|
4464
|
+
overrides?.getCopiedDataStorage,
|
|
4186
4465
|
]);
|
|
4187
4466
|
const canRunPull = React.useMemo(() => engineKind()?.toString() === "pull", [engineKind]);
|
|
4188
4467
|
const bakeableOutputs = React.useMemo(() => {
|
|
@@ -4238,7 +4517,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4238
4517
|
}
|
|
4239
4518
|
}, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [BackgroundRenderer ? (jsxRuntime.jsx(BackgroundRenderer, {})) : (jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 })), MinimapRenderer ? jsxRuntime.jsx(MinimapRenderer, {}) : jsxRuntime.jsx(react.MiniMap, {}), ControlsRenderer ? jsxRuntime.jsx(ControlsRenderer, {}) : jsxRuntime.jsx(react.Controls, {}), DefaultContextMenuRenderer ? (jsxRuntime.jsx(DefaultContextMenuRenderer, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds })) : (jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds })), !!nodeAtMenu &&
|
|
4240
4519
|
nodeContextMenuHandlers &&
|
|
4241
|
-
(NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs })) : (jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs })))] }) }) }));
|
|
4520
|
+
(NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs })) : (jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs }))), selectionMenuOpen && selectionMenuPos && (jsxRuntime.jsx(SelectionContextMenu, { open: selectionMenuOpen, clientPos: selectionMenuPos, handlers: selectionContextMenuHandlers }))] }) }) }));
|
|
4242
4521
|
});
|
|
4243
4522
|
|
|
4244
4523
|
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
|
|
@@ -4792,12 +5071,18 @@ exports.WorkbenchProvider = WorkbenchProvider;
|
|
|
4792
5071
|
exports.WorkbenchStudio = WorkbenchStudio;
|
|
4793
5072
|
exports.computeEffectiveHandles = computeEffectiveHandles;
|
|
4794
5073
|
exports.countVisibleHandles = countVisibleHandles;
|
|
5074
|
+
exports.createCopyHandler = createCopyHandler;
|
|
5075
|
+
exports.createDefaultContextMenuHandlers = createDefaultContextMenuHandlers;
|
|
4795
5076
|
exports.createHandleBounds = createHandleBounds;
|
|
4796
5077
|
exports.createHandleLayout = createHandleLayout;
|
|
5078
|
+
exports.createNodeContextMenuHandlers = createNodeContextMenuHandlers;
|
|
5079
|
+
exports.createNodeCopyHandler = createNodeCopyHandler;
|
|
5080
|
+
exports.createSelectionContextMenuHandlers = createSelectionContextMenuHandlers;
|
|
4797
5081
|
exports.download = download;
|
|
4798
5082
|
exports.estimateNodeSize = estimateNodeSize;
|
|
4799
5083
|
exports.formatDataUrlAsLabel = formatDataUrlAsLabel;
|
|
4800
5084
|
exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
|
|
5085
|
+
exports.getBakeableOutputs = getBakeableOutputs;
|
|
4801
5086
|
exports.getHandleBoundsX = getHandleBoundsX;
|
|
4802
5087
|
exports.getHandleBoundsY = getHandleBoundsY;
|
|
4803
5088
|
exports.getHandleClassName = getHandleClassName;
|