@bian-womp/spark-workbench 0.2.79 → 0.2.81
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 +155 -4
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts +16 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/contracts.d.ts +3 -0
- package/lib/cjs/src/core/contracts.d.ts.map +1 -1
- package/lib/cjs/src/index.d.ts +2 -0
- package/lib/cjs/src/index.d.ts.map +1 -1
- package/lib/cjs/src/misc/DefaultNode.d.ts +3 -2
- package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +2 -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/cjs/src/misc/load.d.ts.map +1 -1
- package/lib/cjs/src/misc/merge-utils.d.ts +7 -0
- package/lib/cjs/src/misc/merge-utils.d.ts.map +1 -0
- package/lib/cjs/src/misc/types.d.ts +14 -0
- package/lib/cjs/src/misc/types.d.ts.map +1 -0
- package/lib/esm/index.js +155 -5
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts +16 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/contracts.d.ts +3 -0
- package/lib/esm/src/core/contracts.d.ts.map +1 -1
- package/lib/esm/src/index.d.ts +2 -0
- package/lib/esm/src/index.d.ts.map +1 -1
- package/lib/esm/src/misc/DefaultNode.d.ts +3 -2
- package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts +2 -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/lib/esm/src/misc/load.d.ts.map +1 -1
- package/lib/esm/src/misc/merge-utils.d.ts +7 -0
- package/lib/esm/src/misc/merge-utils.d.ts.map +1 -0
- package/lib/esm/src/misc/types.d.ts +14 -0
- package/lib/esm/src/misc/types.d.ts.map +1 -0
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -130,8 +130,9 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
130
130
|
nodes: [],
|
|
131
131
|
edges: [],
|
|
132
132
|
};
|
|
133
|
-
this.
|
|
133
|
+
this.nodeNames = {};
|
|
134
134
|
this.runtimeState = null;
|
|
135
|
+
this.viewport = null;
|
|
135
136
|
this.historyState = undefined;
|
|
136
137
|
this.copiedData = null;
|
|
137
138
|
}
|
|
@@ -228,6 +229,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
228
229
|
this._def.nodes = this._def.nodes.filter((n) => n.nodeId !== nodeId);
|
|
229
230
|
this._def.edges = this._def.edges.filter((e) => e.source.nodeId !== nodeId && e.target.nodeId !== nodeId);
|
|
230
231
|
delete this.positions[nodeId];
|
|
232
|
+
delete this.nodeNames[nodeId];
|
|
231
233
|
this.emit("graphChanged", {
|
|
232
234
|
def: this._def,
|
|
233
235
|
change: { type: "removeNode", nodeId },
|
|
@@ -350,6 +352,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
350
352
|
const filteredPositions = Object.fromEntries(Object.entries(this.positions).filter(([id]) => defNodeIds.has(id)));
|
|
351
353
|
const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
|
|
352
354
|
const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
|
|
355
|
+
const filteredNodeNames = Object.fromEntries(Object.entries(this.nodeNames).filter(([id]) => defNodeIds.has(id)));
|
|
353
356
|
return {
|
|
354
357
|
positions: Object.keys(filteredPositions).length > 0
|
|
355
358
|
? filteredPositions
|
|
@@ -361,6 +364,9 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
361
364
|
}
|
|
362
365
|
: undefined,
|
|
363
366
|
viewport: this.viewport ? { ...this.viewport } : undefined,
|
|
367
|
+
nodeNames: Object.keys(filteredNodeNames).length > 0
|
|
368
|
+
? filteredNodeNames
|
|
369
|
+
: undefined,
|
|
364
370
|
};
|
|
365
371
|
}
|
|
366
372
|
setUIState(ui) {
|
|
@@ -379,6 +385,9 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
379
385
|
if (ui.viewport) {
|
|
380
386
|
this.viewport = { ...ui.viewport };
|
|
381
387
|
}
|
|
388
|
+
if (ui.nodeNames !== undefined) {
|
|
389
|
+
this.nodeNames = { ...ui.nodeNames };
|
|
390
|
+
}
|
|
382
391
|
}
|
|
383
392
|
getRuntimeState() {
|
|
384
393
|
return this.runtimeState ? { ...this.runtimeState } : null;
|
|
@@ -748,6 +757,29 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
748
757
|
setCopiedData(data) {
|
|
749
758
|
this.copiedData = data;
|
|
750
759
|
}
|
|
760
|
+
/**
|
|
761
|
+
* Get the custom name for a node, if set.
|
|
762
|
+
*/
|
|
763
|
+
getNodeName(nodeId) {
|
|
764
|
+
return this.nodeNames[nodeId];
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Set a custom name for a node. Empty string or undefined removes the custom name.
|
|
768
|
+
* This is included in undo/redo history via extData.ui.
|
|
769
|
+
*/
|
|
770
|
+
setNodeName(nodeId, name, options) {
|
|
771
|
+
if (name === undefined || name.trim() === "") {
|
|
772
|
+
delete this.nodeNames[nodeId];
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
this.nodeNames[nodeId] = name.trim();
|
|
776
|
+
}
|
|
777
|
+
this.emit("graphUiChanged", {
|
|
778
|
+
def: this._def,
|
|
779
|
+
change: { type: "nodeName", nodeId },
|
|
780
|
+
...options,
|
|
781
|
+
});
|
|
782
|
+
}
|
|
751
783
|
}
|
|
752
784
|
|
|
753
785
|
class CLIWorkbench {
|
|
@@ -2740,6 +2772,50 @@ async function upload(parsed, wb, runner) {
|
|
|
2740
2772
|
}
|
|
2741
2773
|
}
|
|
2742
2774
|
|
|
2775
|
+
/**
|
|
2776
|
+
* Merge UI state from source into target, remapping node IDs using nodeIdMap.
|
|
2777
|
+
* Preserves target state and adds/updates source state with remapped IDs.
|
|
2778
|
+
*/
|
|
2779
|
+
function mergeUIState(targetUI, sourceUI, nodeIdMap) {
|
|
2780
|
+
const result = {
|
|
2781
|
+
...targetUI,
|
|
2782
|
+
};
|
|
2783
|
+
if (!sourceUI)
|
|
2784
|
+
return result;
|
|
2785
|
+
// Merge positions (already handled by mergeSnapshotData, but included for completeness)
|
|
2786
|
+
if (sourceUI.positions) {
|
|
2787
|
+
result.positions = {
|
|
2788
|
+
...(targetUI?.positions || {}),
|
|
2789
|
+
...Object.fromEntries(Object.entries(sourceUI.positions).map(([oldId, pos]) => [
|
|
2790
|
+
nodeIdMap[oldId] || oldId,
|
|
2791
|
+
pos,
|
|
2792
|
+
])),
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
// Merge selection: remap node IDs and edge IDs
|
|
2796
|
+
if (sourceUI.selection) {
|
|
2797
|
+
const remappedNodes = (sourceUI.selection.nodes || [])
|
|
2798
|
+
.map((id) => nodeIdMap[id] || id)
|
|
2799
|
+
.filter((id) => id); // Filter out invalid mappings
|
|
2800
|
+
const remappedEdges = sourceUI.selection.edges || []; // Edge IDs don't need remapping typically
|
|
2801
|
+
result.selection = {
|
|
2802
|
+
nodes: [...(targetUI?.selection?.nodes || []), ...remappedNodes],
|
|
2803
|
+
edges: [...(targetUI?.selection?.edges || []), ...remappedEdges],
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
// Merge nodeNames: remap node IDs
|
|
2807
|
+
if (sourceUI.nodeNames) {
|
|
2808
|
+
result.nodeNames = {
|
|
2809
|
+
...(targetUI?.nodeNames || {}),
|
|
2810
|
+
...Object.fromEntries(Object.entries(sourceUI.nodeNames).map(([oldId, name]) => [
|
|
2811
|
+
nodeIdMap[oldId] || oldId,
|
|
2812
|
+
name,
|
|
2813
|
+
])),
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
return result;
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2743
2819
|
const WorkbenchContext = React.createContext(null);
|
|
2744
2820
|
function useWorkbenchContext() {
|
|
2745
2821
|
const ctx = React.useContext(WorkbenchContext);
|
|
@@ -2947,6 +3023,19 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2947
3023
|
}, [wb, wb.def, registry, overrides?.getDefaultNodeSize]);
|
|
2948
3024
|
const updateEdgeType = React.useCallback((edgeId, typeId) => wb.updateEdgeType(edgeId, typeId), [wb]);
|
|
2949
3025
|
const triggerExternal = React.useCallback((nodeId, event) => runner.triggerExternal(nodeId, event), [runner]);
|
|
3026
|
+
const getNodeDisplayName = React.useCallback((nodeId) => {
|
|
3027
|
+
const customName = wb.getNodeName(nodeId);
|
|
3028
|
+
if (customName)
|
|
3029
|
+
return customName;
|
|
3030
|
+
const node = wb.def.nodes.find((n) => n.nodeId === nodeId);
|
|
3031
|
+
if (!node)
|
|
3032
|
+
return nodeId;
|
|
3033
|
+
const desc = registry.nodes.get(node.typeId);
|
|
3034
|
+
return desc?.displayName || node.typeId;
|
|
3035
|
+
}, [wb, registry]);
|
|
3036
|
+
const setNodeName = React.useCallback((nodeId, name) => {
|
|
3037
|
+
wb.setNodeName(nodeId, name, { commit: true, reason: "rename-node" });
|
|
3038
|
+
}, [wb]);
|
|
2950
3039
|
// Helper to save runtime metadata and UI state to extData
|
|
2951
3040
|
const saveUiRuntimeMetadata = React.useCallback(async (workbench, graphRunner) => {
|
|
2952
3041
|
try {
|
|
@@ -3608,6 +3697,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3608
3697
|
triggerExternal,
|
|
3609
3698
|
uiVersion,
|
|
3610
3699
|
overrides,
|
|
3700
|
+
getNodeDisplayName,
|
|
3701
|
+
setNodeName,
|
|
3611
3702
|
}), [
|
|
3612
3703
|
wb,
|
|
3613
3704
|
runner,
|
|
@@ -3647,6 +3738,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3647
3738
|
runner,
|
|
3648
3739
|
uiVersion,
|
|
3649
3740
|
overrides,
|
|
3741
|
+
getNodeDisplayName,
|
|
3742
|
+
setNodeName,
|
|
3650
3743
|
]);
|
|
3651
3744
|
return (jsxRuntime.jsx(WorkbenchContext.Provider, { value: value, children: children }));
|
|
3652
3745
|
}
|
|
@@ -4235,11 +4328,27 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
4235
4328
|
position: "relative",
|
|
4236
4329
|
minWidth: typeof data.renderWidth === "number" ? data.renderWidth : undefined,
|
|
4237
4330
|
minHeight: typeof data.renderHeight === "number" ? data.renderHeight : undefined,
|
|
4238
|
-
}, children: [jsxRuntime.jsx(DefaultNodeHeader, { id: id,
|
|
4331
|
+
}, children: [jsxRuntime.jsx(DefaultNodeHeader, { id: id, typeId: typeId, validation: validation, showId: data.showValues }), jsxRuntime.jsx(DefaultNodeContent, { data: data, isConnectable: isConnectable })] }));
|
|
4239
4332
|
});
|
|
4240
4333
|
DefaultNode.displayName = "DefaultNode";
|
|
4241
|
-
function DefaultNodeHeader({ id, title, validation, right, showId, onInvalidate, }) {
|
|
4334
|
+
function DefaultNodeHeader({ id, typeId, title, validation, right, showId, onInvalidate, }) {
|
|
4242
4335
|
const ctx = useWorkbenchContext();
|
|
4336
|
+
const [isEditing, setIsEditing] = React.useState(false);
|
|
4337
|
+
const [editValue, setEditValue] = React.useState("");
|
|
4338
|
+
const inputRef = React.useRef(null);
|
|
4339
|
+
// Use getNodeDisplayName if typeId is provided, otherwise use title prop
|
|
4340
|
+
const displayName = typeId ? ctx.getNodeDisplayName(id) : title ?? id;
|
|
4341
|
+
const effectiveTypeId = typeId ?? title ?? id;
|
|
4342
|
+
// Get the default display name (without custom name) for comparison
|
|
4343
|
+
const getDefaultDisplayName = React.useCallback(() => {
|
|
4344
|
+
if (!typeId)
|
|
4345
|
+
return title ?? id;
|
|
4346
|
+
const node = ctx.wb.def.nodes.find((n) => n.nodeId === id);
|
|
4347
|
+
if (!node)
|
|
4348
|
+
return id;
|
|
4349
|
+
const desc = ctx.registry.nodes.get(node.typeId);
|
|
4350
|
+
return desc?.displayName || node.typeId;
|
|
4351
|
+
}, [ctx, id, typeId, title]);
|
|
4243
4352
|
const handleInvalidate = React.useCallback(() => {
|
|
4244
4353
|
try {
|
|
4245
4354
|
if (onInvalidate)
|
|
@@ -4252,10 +4361,51 @@ function DefaultNodeHeader({ id, title, validation, right, showId, onInvalidate,
|
|
|
4252
4361
|
}
|
|
4253
4362
|
catch { }
|
|
4254
4363
|
}, [ctx, id, onInvalidate]);
|
|
4364
|
+
const handleDoubleClick = React.useCallback((e) => {
|
|
4365
|
+
// Only allow editing if typeId is provided (enables renaming)
|
|
4366
|
+
if (!typeId)
|
|
4367
|
+
return;
|
|
4368
|
+
e.stopPropagation();
|
|
4369
|
+
setIsEditing(true);
|
|
4370
|
+
setEditValue(displayName);
|
|
4371
|
+
}, [typeId, displayName]);
|
|
4372
|
+
const handleSave = React.useCallback(() => {
|
|
4373
|
+
if (!typeId)
|
|
4374
|
+
return;
|
|
4375
|
+
const trimmed = editValue.trim();
|
|
4376
|
+
const defaultDisplayName = getDefaultDisplayName();
|
|
4377
|
+
// If the trimmed value matches the default display name or typeId, clear the custom name
|
|
4378
|
+
ctx.setNodeName(id, trimmed === defaultDisplayName || trimmed === effectiveTypeId
|
|
4379
|
+
? undefined
|
|
4380
|
+
: trimmed);
|
|
4381
|
+
setIsEditing(false);
|
|
4382
|
+
}, [ctx, id, editValue, getDefaultDisplayName, effectiveTypeId, typeId]);
|
|
4383
|
+
const handleCancel = React.useCallback(() => {
|
|
4384
|
+
setIsEditing(false);
|
|
4385
|
+
setEditValue(displayName);
|
|
4386
|
+
}, [displayName]);
|
|
4387
|
+
const handleKeyDown = React.useCallback((e) => {
|
|
4388
|
+
if (e.key === "Enter") {
|
|
4389
|
+
e.preventDefault();
|
|
4390
|
+
e.stopPropagation();
|
|
4391
|
+
handleSave();
|
|
4392
|
+
}
|
|
4393
|
+
else if (e.key === "Escape") {
|
|
4394
|
+
e.preventDefault();
|
|
4395
|
+
e.stopPropagation();
|
|
4396
|
+
handleCancel();
|
|
4397
|
+
}
|
|
4398
|
+
}, [handleSave, handleCancel]);
|
|
4399
|
+
React.useEffect(() => {
|
|
4400
|
+
if (isEditing && inputRef.current) {
|
|
4401
|
+
inputRef.current.focus();
|
|
4402
|
+
inputRef.current.select();
|
|
4403
|
+
}
|
|
4404
|
+
}, [isEditing]);
|
|
4255
4405
|
return (jsxRuntime.jsxs("div", { className: "flex items-center justify-center px-2 border-b border-solid border-gray-500 dark:border-gray-400 text-gray-600 dark:text-gray-300", style: {
|
|
4256
4406
|
maxHeight: NODE_HEADER_HEIGHT_PX,
|
|
4257
4407
|
minHeight: NODE_HEADER_HEIGHT_PX,
|
|
4258
|
-
}, children: [jsxRuntime.jsx("
|
|
4408
|
+
}, children: [isEditing ? (jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: editValue, onChange: (e) => setEditValue(e.target.value), onBlur: handleSave, onKeyDown: handleKeyDown, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), className: "flex-1 h-full text-sm bg-transparent border border-blue-500 rounded px-1 outline-none wb-nodrag", style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` } })) : (jsxRuntime.jsx("strong", { className: `flex-1 h-full text-sm select-none truncate ${typeId ? "cursor-text" : ""}`, style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` }, onDoubleClick: handleDoubleClick, title: typeId ? "Double-click to rename" : undefined, children: displayName })), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("button", { className: "w-4 h-4 border border-gray-400 rounded text-[10px] leading-3 flex items-center justify-center", title: "Invalidate and re-run", onClick: (e) => {
|
|
4259
4409
|
e.stopPropagation();
|
|
4260
4410
|
handleInvalidate();
|
|
4261
4411
|
}, children: jsxRuntime.jsx(react$1.ArrowClockwiseIcon, { size: 10 }) }), right, validation.issues && validation.issues.length > 0 && (jsxRuntime.jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
|
|
@@ -5765,6 +5915,7 @@ exports.getHandleLayoutY = getHandleLayoutY;
|
|
|
5765
5915
|
exports.getNodeBorderClassNames = getNodeBorderClassNames;
|
|
5766
5916
|
exports.isValidViewport = isValidViewport;
|
|
5767
5917
|
exports.layoutNode = layoutNode;
|
|
5918
|
+
exports.mergeUIState = mergeUIState;
|
|
5768
5919
|
exports.preformatValueForDisplay = preformatValueForDisplay;
|
|
5769
5920
|
exports.prettyHandle = prettyHandle;
|
|
5770
5921
|
exports.resolveOutputDisplay = resolveOutputDisplay;
|