@bian-womp/spark-workbench 0.2.65 → 0.2.66
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 +453 -215
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts +4 -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 +41 -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 +448 -216
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts +4 -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 +41 -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
|
@@ -307,6 +307,22 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
307
307
|
edges: [...this.selection.edges],
|
|
308
308
|
};
|
|
309
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Delete all selected nodes and edges.
|
|
312
|
+
*/
|
|
313
|
+
deleteSelection() {
|
|
314
|
+
const selection = this.getSelection();
|
|
315
|
+
// Delete all selected nodes (this will also remove connected edges)
|
|
316
|
+
for (const nodeId of selection.nodes) {
|
|
317
|
+
this.removeNode(nodeId);
|
|
318
|
+
}
|
|
319
|
+
// Delete remaining selected edges (edges not connected to deleted nodes)
|
|
320
|
+
for (const edgeId of selection.edges) {
|
|
321
|
+
this.disconnect(edgeId);
|
|
322
|
+
}
|
|
323
|
+
// Clear selection
|
|
324
|
+
this.setSelection({ nodes: [], edges: [] });
|
|
325
|
+
}
|
|
310
326
|
setViewport(viewport, opts) {
|
|
311
327
|
this.viewport = { ...viewport };
|
|
312
328
|
this.emit("graphUiChanged", {
|
|
@@ -3049,6 +3065,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3049
3065
|
updateEdgeType,
|
|
3050
3066
|
triggerExternal,
|
|
3051
3067
|
uiVersion,
|
|
3068
|
+
overrides,
|
|
3052
3069
|
}), [
|
|
3053
3070
|
wb,
|
|
3054
3071
|
runner,
|
|
@@ -3088,10 +3105,272 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3088
3105
|
wb,
|
|
3089
3106
|
runner,
|
|
3090
3107
|
uiVersion,
|
|
3108
|
+
overrides,
|
|
3091
3109
|
]);
|
|
3092
3110
|
return (jsxRuntime.jsx(WorkbenchContext.Provider, { value: value, children: children }));
|
|
3093
3111
|
}
|
|
3094
3112
|
|
|
3113
|
+
function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose, getDefaultNodeSize, onCopyResult) {
|
|
3114
|
+
const doBake = async (handleId) => {
|
|
3115
|
+
try {
|
|
3116
|
+
const typeId = outputTypesMap?.[nodeId]?.[handleId];
|
|
3117
|
+
const raw = outputsMap?.[nodeId]?.[handleId];
|
|
3118
|
+
if (!typeId || raw === undefined)
|
|
3119
|
+
return;
|
|
3120
|
+
const unwrap = (v) => sparkGraph.isTypedOutput(v) ? sparkGraph.getTypedOutputValue(v) : v;
|
|
3121
|
+
const coerceIfNeeded = async (fromType, toType, value) => {
|
|
3122
|
+
if (!toType || toType === fromType || !runner?.coerce)
|
|
3123
|
+
return value;
|
|
3124
|
+
try {
|
|
3125
|
+
return await runner.coerce(fromType, toType, value);
|
|
3126
|
+
}
|
|
3127
|
+
catch {
|
|
3128
|
+
return value;
|
|
3129
|
+
}
|
|
3130
|
+
};
|
|
3131
|
+
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3132
|
+
const isArray = typeId.endsWith("[]");
|
|
3133
|
+
const baseTypeId = isArray ? typeId.slice(0, -2) : typeId;
|
|
3134
|
+
const tArr = isArray ? registry.types.get(typeId) : undefined;
|
|
3135
|
+
const tElem = registry.types.get(baseTypeId);
|
|
3136
|
+
const singleTarget = !isArray ? tElem?.bakeTarget : undefined;
|
|
3137
|
+
const arrTarget = isArray ? tArr?.bakeTarget : undefined;
|
|
3138
|
+
const elemTarget = isArray ? tElem?.bakeTarget : undefined;
|
|
3139
|
+
if (singleTarget) {
|
|
3140
|
+
const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
|
|
3141
|
+
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, singleTarget.inputHandle);
|
|
3142
|
+
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3143
|
+
wb.addNode({
|
|
3144
|
+
typeId: singleTarget.nodeTypeId,
|
|
3145
|
+
position: { x: pos.x + 180, y: pos.y },
|
|
3146
|
+
}, { inputs: { [singleTarget.inputHandle]: coerced } });
|
|
3147
|
+
return;
|
|
3148
|
+
}
|
|
3149
|
+
if (isArray && arrTarget) {
|
|
3150
|
+
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
3151
|
+
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
3152
|
+
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3153
|
+
wb.addNode({
|
|
3154
|
+
typeId: arrTarget.nodeTypeId,
|
|
3155
|
+
position: { x: pos.x + 180, y: pos.y },
|
|
3156
|
+
}, { inputs: { [arrTarget.inputHandle]: coerced } });
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
if (isArray && elemTarget) {
|
|
3160
|
+
const nodeDesc = registry.nodes.get(elemTarget.nodeTypeId);
|
|
3161
|
+
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, elemTarget.inputHandle);
|
|
3162
|
+
const src = unwrap(raw);
|
|
3163
|
+
const items = Array.isArray(src) ? src : [src];
|
|
3164
|
+
const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, inType, v)));
|
|
3165
|
+
const COLS = 4;
|
|
3166
|
+
const DX = 180;
|
|
3167
|
+
const DY = 160;
|
|
3168
|
+
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3169
|
+
const col = idx % COLS;
|
|
3170
|
+
const row = Math.floor(idx / COLS);
|
|
3171
|
+
wb.addNode({
|
|
3172
|
+
typeId: elemTarget.nodeTypeId,
|
|
3173
|
+
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
3174
|
+
}, { inputs: { [elemTarget.inputHandle]: coercedItems[idx] } });
|
|
3175
|
+
}
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
catch { }
|
|
3180
|
+
};
|
|
3181
|
+
return {
|
|
3182
|
+
onDelete: () => {
|
|
3183
|
+
wb.removeNode(nodeId);
|
|
3184
|
+
onClose();
|
|
3185
|
+
},
|
|
3186
|
+
onDuplicate: async () => {
|
|
3187
|
+
const def = wb.export();
|
|
3188
|
+
const n = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3189
|
+
if (!n)
|
|
3190
|
+
return onClose();
|
|
3191
|
+
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3192
|
+
const inboundHandles = new Set(def.edges
|
|
3193
|
+
.filter((e) => e.target.nodeId === nodeId)
|
|
3194
|
+
.map((e) => e.target.handle));
|
|
3195
|
+
const allInputs = runner.getInputs(def)[nodeId] || {};
|
|
3196
|
+
const inputsWithoutBindings = Object.fromEntries(Object.entries(allInputs).filter(([handle]) => !inboundHandles.has(handle)));
|
|
3197
|
+
const newNodeId = wb.addNode({
|
|
3198
|
+
typeId: n.typeId,
|
|
3199
|
+
params: n.params,
|
|
3200
|
+
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3201
|
+
resolvedHandles: n.resolvedHandles,
|
|
3202
|
+
}, {
|
|
3203
|
+
inputs: inputsWithoutBindings,
|
|
3204
|
+
copyOutputsFrom: nodeId,
|
|
3205
|
+
dry: true,
|
|
3206
|
+
});
|
|
3207
|
+
// Select the newly duplicated node
|
|
3208
|
+
wb.setSelection({
|
|
3209
|
+
nodes: [newNodeId],
|
|
3210
|
+
edges: [],
|
|
3211
|
+
});
|
|
3212
|
+
onClose();
|
|
3213
|
+
},
|
|
3214
|
+
onDuplicateWithEdges: async () => {
|
|
3215
|
+
const def = wb.export();
|
|
3216
|
+
const n = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3217
|
+
if (!n)
|
|
3218
|
+
return onClose();
|
|
3219
|
+
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3220
|
+
// Get inputs without bindings (literal values only)
|
|
3221
|
+
const inputs = runner.getInputs(def)[nodeId] || {};
|
|
3222
|
+
// Add the duplicated node
|
|
3223
|
+
const newNodeId = wb.addNode({
|
|
3224
|
+
typeId: n.typeId,
|
|
3225
|
+
params: n.params,
|
|
3226
|
+
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3227
|
+
resolvedHandles: n.resolvedHandles,
|
|
3228
|
+
}, {
|
|
3229
|
+
inputs,
|
|
3230
|
+
copyOutputsFrom: nodeId,
|
|
3231
|
+
dry: true,
|
|
3232
|
+
});
|
|
3233
|
+
// Find all incoming edges (edges where target is the original node)
|
|
3234
|
+
const incomingEdges = def.edges.filter((e) => e.target.nodeId === nodeId);
|
|
3235
|
+
// Duplicate each incoming edge to point to the new node
|
|
3236
|
+
for (const edge of incomingEdges) {
|
|
3237
|
+
wb.connect({
|
|
3238
|
+
source: edge.source, // Keep the same source
|
|
3239
|
+
target: { nodeId: newNodeId, handle: edge.target.handle }, // Point to new node
|
|
3240
|
+
typeId: edge.typeId,
|
|
3241
|
+
}, { dry: true });
|
|
3242
|
+
}
|
|
3243
|
+
// Select the newly duplicated node and edges
|
|
3244
|
+
wb.setSelection({
|
|
3245
|
+
nodes: [newNodeId],
|
|
3246
|
+
edges: [],
|
|
3247
|
+
});
|
|
3248
|
+
onClose();
|
|
3249
|
+
},
|
|
3250
|
+
onRunPull: async () => {
|
|
3251
|
+
try {
|
|
3252
|
+
await runner.computeNode(nodeId);
|
|
3253
|
+
}
|
|
3254
|
+
catch { }
|
|
3255
|
+
onClose();
|
|
3256
|
+
},
|
|
3257
|
+
onBake: async (handleId) => {
|
|
3258
|
+
await doBake(handleId);
|
|
3259
|
+
onClose();
|
|
3260
|
+
},
|
|
3261
|
+
onCopy: () => {
|
|
3262
|
+
const copyHandler = createNodeCopyHandler(wb, runner, nodeId, getDefaultNodeSize, onCopyResult);
|
|
3263
|
+
copyHandler();
|
|
3264
|
+
onClose();
|
|
3265
|
+
},
|
|
3266
|
+
onCopyId: async () => {
|
|
3267
|
+
try {
|
|
3268
|
+
await navigator.clipboard.writeText(nodeId);
|
|
3269
|
+
}
|
|
3270
|
+
catch { }
|
|
3271
|
+
onClose();
|
|
3272
|
+
},
|
|
3273
|
+
onClose,
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
3276
|
+
/**
|
|
3277
|
+
* Creates a copy handler that copies the current selection and optionally stores the result.
|
|
3278
|
+
*/
|
|
3279
|
+
function createCopyHandler(wb, runner, getDefaultNodeSize, onCopyResult) {
|
|
3280
|
+
return () => {
|
|
3281
|
+
const getNodeSize = getDefaultNodeSize
|
|
3282
|
+
? (nodeId, typeId) => getDefaultNodeSize(typeId)
|
|
3283
|
+
: undefined;
|
|
3284
|
+
const data = wb.copySelection(runner, getNodeSize);
|
|
3285
|
+
if (onCopyResult) {
|
|
3286
|
+
onCopyResult(data);
|
|
3287
|
+
}
|
|
3288
|
+
};
|
|
3289
|
+
}
|
|
3290
|
+
/**
|
|
3291
|
+
* Creates a copy handler for a single node (temporarily selects it, copies, then restores selection).
|
|
3292
|
+
*/
|
|
3293
|
+
function createNodeCopyHandler(wb, runner, nodeId, getDefaultNodeSize, onCopyResult) {
|
|
3294
|
+
return () => {
|
|
3295
|
+
// Select the node first, then copy
|
|
3296
|
+
const currentSelection = wb.getSelection();
|
|
3297
|
+
wb.setSelection({ nodes: [nodeId], edges: [] });
|
|
3298
|
+
const getNodeSize = getDefaultNodeSize
|
|
3299
|
+
? (nodeId, typeId) => getDefaultNodeSize(typeId)
|
|
3300
|
+
: undefined;
|
|
3301
|
+
const data = wb.copySelection(runner, getNodeSize);
|
|
3302
|
+
// Restore original selection
|
|
3303
|
+
wb.setSelection(currentSelection);
|
|
3304
|
+
if (onCopyResult) {
|
|
3305
|
+
onCopyResult(data);
|
|
3306
|
+
}
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
/**
|
|
3310
|
+
* Creates base selection context menu handlers.
|
|
3311
|
+
*/
|
|
3312
|
+
function createSelectionContextMenuHandlers(wb, onClose, getDefaultNodeSize, onCopyResult, runner) {
|
|
3313
|
+
return {
|
|
3314
|
+
onCopy: runner
|
|
3315
|
+
? createCopyHandler(wb, runner, getDefaultNodeSize, onCopyResult)
|
|
3316
|
+
: () => {
|
|
3317
|
+
// No-op if runner not available
|
|
3318
|
+
onClose();
|
|
3319
|
+
},
|
|
3320
|
+
onDelete: () => {
|
|
3321
|
+
wb.deleteSelection();
|
|
3322
|
+
onClose();
|
|
3323
|
+
},
|
|
3324
|
+
onClose,
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* Creates base default context menu handlers.
|
|
3329
|
+
*/
|
|
3330
|
+
function createDefaultContextMenuHandlers(onAddNode, onClose, onPaste) {
|
|
3331
|
+
return {
|
|
3332
|
+
onAddNode,
|
|
3333
|
+
onPaste,
|
|
3334
|
+
onClose,
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
function getBakeableOutputs(nodeId, wb, registry, outputTypesMap) {
|
|
3338
|
+
try {
|
|
3339
|
+
const def = wb.export();
|
|
3340
|
+
const node = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3341
|
+
if (!node)
|
|
3342
|
+
return [];
|
|
3343
|
+
const desc = registry.nodes.get(node.typeId);
|
|
3344
|
+
const handles = Object.keys(desc?.outputs || {});
|
|
3345
|
+
const out = [];
|
|
3346
|
+
for (const h of handles) {
|
|
3347
|
+
const tId = outputTypesMap?.[nodeId]?.[h];
|
|
3348
|
+
if (!tId)
|
|
3349
|
+
continue;
|
|
3350
|
+
if (tId.endsWith("[]")) {
|
|
3351
|
+
const base = tId.slice(0, -2);
|
|
3352
|
+
const tArr = registry.types.get(tId);
|
|
3353
|
+
const tElem = registry.types.get(base);
|
|
3354
|
+
const arrT = tArr?.bakeTarget;
|
|
3355
|
+
const elemT = tElem?.bakeTarget;
|
|
3356
|
+
if ((arrT && registry.nodes.has(arrT.nodeTypeId)) ||
|
|
3357
|
+
(elemT && registry.nodes.has(elemT.nodeTypeId)))
|
|
3358
|
+
out.push(h);
|
|
3359
|
+
}
|
|
3360
|
+
else {
|
|
3361
|
+
const t = registry.types.get(tId);
|
|
3362
|
+
const bt = t?.bakeTarget;
|
|
3363
|
+
if (bt && registry.nodes.has(bt.nodeTypeId))
|
|
3364
|
+
out.push(h);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
return out;
|
|
3368
|
+
}
|
|
3369
|
+
catch {
|
|
3370
|
+
return [];
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3095
3374
|
function IssueBadge({ level, title, size = 12, className, }) {
|
|
3096
3375
|
const colorClass = level === "error" ? "text-red-600" : "text-amber-600";
|
|
3097
3376
|
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 +3583,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
3304
3583
|
}
|
|
3305
3584
|
catch { }
|
|
3306
3585
|
};
|
|
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) => {
|
|
3586
|
+
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
3587
|
e.stopPropagation();
|
|
3309
3588
|
deleteEdgeById(m.data?.edgeId);
|
|
3310
3589
|
}, 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 +3651,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
3372
3651
|
const title = inIssues
|
|
3373
3652
|
.map((v) => `${v.code}: ${v.message}`)
|
|
3374
3653
|
.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
|
|
3654
|
+
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
3655
|
? String(current)
|
|
3377
3656
|
: "", onChange: (e) => {
|
|
3378
3657
|
const val = e.target.value;
|
|
@@ -3386,7 +3665,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
3386
3665
|
? `Default: ${placeholder}`
|
|
3387
3666
|
: "(select)" }), registry.enums
|
|
3388
3667
|
.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
|
|
3668
|
+
?.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
3669
|
? `Default: ${placeholder}`
|
|
3391
3670
|
: undefined, value: displayValue, onChange: (e) => onChangeText(e.target.value), onBlur: commit, onKeyDown: (e) => {
|
|
3392
3671
|
if (e.key === "Enter")
|
|
@@ -3639,6 +3918,13 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, }) {
|
|
|
3639
3918
|
handlers.onAddNode(typeId, { position: p });
|
|
3640
3919
|
handlers.onClose();
|
|
3641
3920
|
};
|
|
3921
|
+
const handlePaste = () => {
|
|
3922
|
+
if (!handlers.onPaste)
|
|
3923
|
+
return;
|
|
3924
|
+
const p = rf.screenToFlowPosition({ x: clientPos.x, y: clientPos.y });
|
|
3925
|
+
handlers.onPaste(p);
|
|
3926
|
+
handlers.onClose();
|
|
3927
|
+
};
|
|
3642
3928
|
const renderTree = (tree, path = []) => {
|
|
3643
3929
|
const entries = Object.entries(tree?.__children ?? {}).sort((a, b) => a[0].localeCompare(b[0]));
|
|
3644
3930
|
return (jsxRuntime.jsx("div", { children: entries.map(([key, child]) => {
|
|
@@ -3653,10 +3939,10 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, }) {
|
|
|
3653
3939
|
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
3940
|
}) }));
|
|
3655
3941
|
};
|
|
3656
|
-
return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-
|
|
3942
|
+
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
3943
|
e.preventDefault();
|
|
3658
3944
|
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" })) })] }));
|
|
3945
|
+
}, 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
3946
|
}
|
|
3661
3947
|
|
|
3662
3948
|
function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeableOutputs, }) {
|
|
@@ -3694,209 +3980,55 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeab
|
|
|
3694
3980
|
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
3695
3981
|
(MENU_MIN_WIDTH + PADDING));
|
|
3696
3982
|
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) => {
|
|
3983
|
+
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
3984
|
e.preventDefault();
|
|
3699
3985
|
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" }),
|
|
3986
|
+
}, 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
3987
|
}
|
|
3702
3988
|
|
|
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
|
-
}
|
|
3989
|
+
function SelectionContextMenu({ open, clientPos, handlers, }) {
|
|
3990
|
+
const ref = React.useRef(null);
|
|
3991
|
+
// Close on outside click and on ESC
|
|
3992
|
+
React.useEffect(() => {
|
|
3993
|
+
if (!open)
|
|
3994
|
+
return;
|
|
3995
|
+
const onDown = (e) => {
|
|
3996
|
+
if (!ref.current)
|
|
3766
3997
|
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
|
-
}
|
|
3998
|
+
if (!ref.current.contains(e.target))
|
|
3999
|
+
handlers.onClose();
|
|
4000
|
+
};
|
|
4001
|
+
const onKey = (e) => {
|
|
4002
|
+
if (e.key === "Escape")
|
|
4003
|
+
handlers.onClose();
|
|
4004
|
+
};
|
|
4005
|
+
window.addEventListener("mousedown", onDown, true);
|
|
4006
|
+
window.addEventListener("keydown", onKey);
|
|
4007
|
+
return () => {
|
|
4008
|
+
window.removeEventListener("mousedown", onDown, true);
|
|
4009
|
+
window.removeEventListener("keydown", onKey);
|
|
4010
|
+
};
|
|
4011
|
+
}, [open, handlers]);
|
|
4012
|
+
React.useEffect(() => {
|
|
4013
|
+
if (open)
|
|
4014
|
+
ref.current?.focus();
|
|
4015
|
+
}, [open]);
|
|
4016
|
+
if (!open || !clientPos)
|
|
4017
|
+
return null;
|
|
4018
|
+
// Clamp menu position to viewport
|
|
4019
|
+
const MENU_MIN_WIDTH = 180;
|
|
4020
|
+
const PADDING = 16;
|
|
4021
|
+
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
4022
|
+
(MENU_MIN_WIDTH + PADDING));
|
|
4023
|
+
const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 100);
|
|
4024
|
+
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) => {
|
|
4025
|
+
e.preventDefault();
|
|
4026
|
+
e.stopPropagation();
|
|
4027
|
+
}, 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
4028
|
}
|
|
3897
4029
|
|
|
3898
4030
|
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();
|
|
4031
|
+
const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, runner, engineKind, overrides, } = useWorkbenchContext();
|
|
3900
4032
|
const nodeValidation = validationByNode;
|
|
3901
4033
|
const edgeValidation = validationByEdge.errors;
|
|
3902
4034
|
const [registryVersion, setRegistryVersion] = React.useState(0);
|
|
@@ -4135,23 +4267,100 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4135
4267
|
const [nodeMenuOpen, setNodeMenuOpen] = React.useState(false);
|
|
4136
4268
|
const [nodeMenuPos, setNodeMenuPos] = React.useState(null);
|
|
4137
4269
|
const [nodeAtMenu, setNodeAtMenu] = React.useState(null);
|
|
4270
|
+
const [selectionMenuPos, setSelectionMenuPos] = React.useState(null);
|
|
4271
|
+
const [selectionMenuOpen, setSelectionMenuOpen] = React.useState(false);
|
|
4272
|
+
// Compute the rectangular screen-space bounds of the current selection
|
|
4273
|
+
const getSelectionScreenBounds = () => {
|
|
4274
|
+
if (typeof document === "undefined")
|
|
4275
|
+
return null;
|
|
4276
|
+
const selection = wb.getSelection();
|
|
4277
|
+
if (!selection.nodes.length)
|
|
4278
|
+
return null;
|
|
4279
|
+
let bounds = null;
|
|
4280
|
+
for (const nodeId of selection.nodes) {
|
|
4281
|
+
const el = document.querySelector(`.react-flow__node[data-id="${nodeId}"]`);
|
|
4282
|
+
if (!el)
|
|
4283
|
+
continue;
|
|
4284
|
+
const rect = el.getBoundingClientRect();
|
|
4285
|
+
if (!bounds) {
|
|
4286
|
+
bounds = {
|
|
4287
|
+
left: rect.left,
|
|
4288
|
+
top: rect.top,
|
|
4289
|
+
right: rect.right,
|
|
4290
|
+
bottom: rect.bottom,
|
|
4291
|
+
};
|
|
4292
|
+
}
|
|
4293
|
+
else {
|
|
4294
|
+
bounds.left = Math.min(bounds.left, rect.left);
|
|
4295
|
+
bounds.top = Math.min(bounds.top, rect.top);
|
|
4296
|
+
bounds.right = Math.max(bounds.right, rect.right);
|
|
4297
|
+
bounds.bottom = Math.max(bounds.bottom, rect.bottom);
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
return bounds;
|
|
4301
|
+
};
|
|
4138
4302
|
const onContextMenu = (e) => {
|
|
4139
4303
|
e.preventDefault();
|
|
4140
|
-
//
|
|
4304
|
+
// First, check if the cursor is inside the rectangular bounds of the current selection
|
|
4305
|
+
const selectionBounds = getSelectionScreenBounds();
|
|
4306
|
+
if (selectionBounds) {
|
|
4307
|
+
const { left, top, right, bottom } = selectionBounds;
|
|
4308
|
+
if (e.clientX >= left &&
|
|
4309
|
+
e.clientX <= right &&
|
|
4310
|
+
e.clientY >= top &&
|
|
4311
|
+
e.clientY <= bottom) {
|
|
4312
|
+
setSelectionMenuPos({ x: e.clientX, y: e.clientY });
|
|
4313
|
+
setSelectionMenuOpen(true);
|
|
4314
|
+
setMenuOpen(false);
|
|
4315
|
+
setNodeMenuOpen(false);
|
|
4316
|
+
return;
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
// Determine if right-clicked over a node by hit-testing
|
|
4141
4320
|
const target = e.target?.closest(".react-flow__node");
|
|
4142
4321
|
if (target) {
|
|
4143
4322
|
// Resolve node id from data-id attribute React Flow sets
|
|
4144
4323
|
const nodeId = target.getAttribute("data-id");
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4324
|
+
const selection = wb.getSelection();
|
|
4325
|
+
const isSelected = nodeId && selection.nodes.includes(nodeId);
|
|
4326
|
+
if (isSelected) {
|
|
4327
|
+
// Right-clicked on a selected node - show selection menu
|
|
4328
|
+
setSelectionMenuPos({ x: e.clientX, y: e.clientY });
|
|
4329
|
+
setSelectionMenuOpen(true);
|
|
4330
|
+
setMenuOpen(false);
|
|
4331
|
+
setNodeMenuOpen(false);
|
|
4332
|
+
return;
|
|
4333
|
+
}
|
|
4334
|
+
else {
|
|
4335
|
+
// Right-clicked on a non-selected node - show node menu
|
|
4336
|
+
setNodeAtMenu(nodeId);
|
|
4337
|
+
setNodeMenuPos({ x: e.clientX, y: e.clientY });
|
|
4338
|
+
setNodeMenuOpen(true);
|
|
4339
|
+
setMenuOpen(false);
|
|
4340
|
+
setSelectionMenuOpen(false);
|
|
4341
|
+
return;
|
|
4342
|
+
}
|
|
4149
4343
|
}
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4344
|
+
// Check if right-clicked on a selected edge
|
|
4345
|
+
const edgeTarget = e.target?.closest(".react-flow__edge");
|
|
4346
|
+
if (edgeTarget) {
|
|
4347
|
+
const edgeId = edgeTarget.getAttribute("data-id");
|
|
4348
|
+
const selection = wb.getSelection();
|
|
4349
|
+
const isSelected = edgeId && selection.edges.includes(edgeId);
|
|
4350
|
+
if (isSelected) {
|
|
4351
|
+
// Right-clicked on a selected edge - show selection menu
|
|
4352
|
+
setSelectionMenuPos({ x: e.clientX, y: e.clientY });
|
|
4353
|
+
setSelectionMenuOpen(true);
|
|
4354
|
+
setMenuOpen(false);
|
|
4355
|
+
setNodeMenuOpen(false);
|
|
4356
|
+
return;
|
|
4357
|
+
}
|
|
4154
4358
|
}
|
|
4359
|
+
// Right-clicked on empty space - show default menu
|
|
4360
|
+
setMenuPos({ x: e.clientX, y: e.clientY });
|
|
4361
|
+
setMenuOpen(true);
|
|
4362
|
+
setNodeMenuOpen(false);
|
|
4363
|
+
setSelectionMenuOpen(false);
|
|
4155
4364
|
};
|
|
4156
4365
|
const addNodeAt = React.useCallback(async (typeId, opts) => wb.addNode({ typeId, position: opts.position }, { inputs: opts.inputs }), [wb]);
|
|
4157
4366
|
const onCloseMenu = React.useCallback(() => {
|
|
@@ -4160,6 +4369,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4160
4369
|
const onCloseNodeMenu = React.useCallback(() => {
|
|
4161
4370
|
setNodeMenuOpen(false);
|
|
4162
4371
|
}, []);
|
|
4372
|
+
const onCloseSelectionMenu = React.useCallback(() => {
|
|
4373
|
+
setSelectionMenuOpen(false);
|
|
4374
|
+
}, []);
|
|
4163
4375
|
React.useEffect(() => {
|
|
4164
4376
|
const off = runner.on("registry", () => {
|
|
4165
4377
|
setRegistryVersion((v) => v + 1);
|
|
@@ -4167,14 +4379,32 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4167
4379
|
return () => off();
|
|
4168
4380
|
}, [runner]);
|
|
4169
4381
|
const nodeIds = React.useMemo(() => Array.from(registry.nodes.keys()), [registry, registryVersion]);
|
|
4170
|
-
const defaultContextMenuHandlers = React.useMemo(() =>
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4382
|
+
const defaultContextMenuHandlers = React.useMemo(() => {
|
|
4383
|
+
const baseHandlers = createDefaultContextMenuHandlers(addNodeAt, onCloseMenu);
|
|
4384
|
+
if (overrides?.getDefaultContextMenuHandlers) {
|
|
4385
|
+
return overrides.getDefaultContextMenuHandlers(wb, baseHandlers);
|
|
4386
|
+
}
|
|
4387
|
+
return baseHandlers;
|
|
4388
|
+
}, [addNodeAt, onCloseMenu, overrides, wb]);
|
|
4389
|
+
const selectionContextMenuHandlers = React.useMemo(() => {
|
|
4390
|
+
const baseHandlers = createSelectionContextMenuHandlers(wb, onCloseSelectionMenu, overrides?.getDefaultNodeSize, undefined, // onCopyResult - will be provided by overrides
|
|
4391
|
+
runner);
|
|
4392
|
+
if (overrides?.getSelectionContextMenuHandlers) {
|
|
4393
|
+
const selection = wb.getSelection();
|
|
4394
|
+
return overrides.getSelectionContextMenuHandlers(wb, selection, baseHandlers, {
|
|
4395
|
+
getDefaultNodeSize: overrides.getDefaultNodeSize,
|
|
4396
|
+
});
|
|
4397
|
+
}
|
|
4398
|
+
return baseHandlers;
|
|
4399
|
+
}, [wb, runner, overrides, onCloseSelectionMenu]);
|
|
4174
4400
|
const nodeContextMenuHandlers = React.useMemo(() => {
|
|
4175
4401
|
if (!nodeAtMenu)
|
|
4176
4402
|
return null;
|
|
4177
|
-
|
|
4403
|
+
const baseHandlers = createNodeContextMenuHandlers(nodeAtMenu, wb, runner, registry, outputsMap, outputTypesMap, onCloseNodeMenu, overrides?.getDefaultNodeSize);
|
|
4404
|
+
if (overrides?.getNodeContextMenuHandlers) {
|
|
4405
|
+
return overrides.getNodeContextMenuHandlers(wb, nodeAtMenu, baseHandlers);
|
|
4406
|
+
}
|
|
4407
|
+
return baseHandlers;
|
|
4178
4408
|
}, [
|
|
4179
4409
|
nodeAtMenu,
|
|
4180
4410
|
wb,
|
|
@@ -4183,6 +4413,8 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4183
4413
|
outputsMap,
|
|
4184
4414
|
outputTypesMap,
|
|
4185
4415
|
onCloseNodeMenu,
|
|
4416
|
+
overrides?.getDefaultNodeSize,
|
|
4417
|
+
overrides?.getNodeContextMenuHandlers,
|
|
4186
4418
|
]);
|
|
4187
4419
|
const canRunPull = React.useMemo(() => engineKind()?.toString() === "pull", [engineKind]);
|
|
4188
4420
|
const bakeableOutputs = React.useMemo(() => {
|
|
@@ -4238,7 +4470,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4238
4470
|
}
|
|
4239
4471
|
}, 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
4472
|
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 })))] }) }) }));
|
|
4473
|
+
(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
4474
|
});
|
|
4243
4475
|
|
|
4244
4476
|
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 +5024,18 @@ exports.WorkbenchProvider = WorkbenchProvider;
|
|
|
4792
5024
|
exports.WorkbenchStudio = WorkbenchStudio;
|
|
4793
5025
|
exports.computeEffectiveHandles = computeEffectiveHandles;
|
|
4794
5026
|
exports.countVisibleHandles = countVisibleHandles;
|
|
5027
|
+
exports.createCopyHandler = createCopyHandler;
|
|
5028
|
+
exports.createDefaultContextMenuHandlers = createDefaultContextMenuHandlers;
|
|
4795
5029
|
exports.createHandleBounds = createHandleBounds;
|
|
4796
5030
|
exports.createHandleLayout = createHandleLayout;
|
|
5031
|
+
exports.createNodeContextMenuHandlers = createNodeContextMenuHandlers;
|
|
5032
|
+
exports.createNodeCopyHandler = createNodeCopyHandler;
|
|
5033
|
+
exports.createSelectionContextMenuHandlers = createSelectionContextMenuHandlers;
|
|
4797
5034
|
exports.download = download;
|
|
4798
5035
|
exports.estimateNodeSize = estimateNodeSize;
|
|
4799
5036
|
exports.formatDataUrlAsLabel = formatDataUrlAsLabel;
|
|
4800
5037
|
exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
|
|
5038
|
+
exports.getBakeableOutputs = getBakeableOutputs;
|
|
4801
5039
|
exports.getHandleBoundsX = getHandleBoundsX;
|
|
4802
5040
|
exports.getHandleBoundsY = getHandleBoundsY;
|
|
4803
5041
|
exports.getHandleClassName = getHandleClassName;
|