@bian-womp/spark-workbench 0.1.17 → 0.1.18
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 +53 -8
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts +6 -2
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/value.d.ts +1 -1
- package/lib/cjs/src/misc/value.d.ts.map +1 -1
- package/lib/esm/index.js +54 -9
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts +6 -2
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/value.d.ts +1 -1
- package/lib/esm/src/misc/value.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -857,7 +857,7 @@ function preformatValueForDisplay(typeId, value, registry) {
|
|
|
857
857
|
}
|
|
858
858
|
return undefined;
|
|
859
859
|
}
|
|
860
|
-
function summarizeDeep(value
|
|
860
|
+
function summarizeDeep(value) {
|
|
861
861
|
// Strings: summarize data URLs and trim extremely long strings
|
|
862
862
|
if (typeof value === "string") {
|
|
863
863
|
if (value.startsWith("data:")) {
|
|
@@ -1661,7 +1661,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
1661
1661
|
const ROW_SIZE = 22;
|
|
1662
1662
|
const maxRows = Math.max(inputEntries.length, outputEntries.length);
|
|
1663
1663
|
const minHeight = HEADER_SIZE + maxRows * ROW_SIZE;
|
|
1664
|
-
const minWidth = data.showValues ? 320 :
|
|
1664
|
+
const minWidth = data.showValues ? 320 : 240;
|
|
1665
1665
|
const topFor = (i) => HEADER_SIZE + i * ROW_SIZE + ROW_SIZE / 2;
|
|
1666
1666
|
const hasError = !!status.lastError;
|
|
1667
1667
|
const hasValidationError = validation.issues.some((i) => i.level === "error");
|
|
@@ -1694,7 +1694,13 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
1694
1694
|
const title = vIssues
|
|
1695
1695
|
.map((v) => `${v.code}: ${v.message}`)
|
|
1696
1696
|
.join("; ");
|
|
1697
|
-
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "target", position: ReactFlow.Position.Left, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { left: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute left-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
|
|
1697
|
+
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "target", position: ReactFlow.Position.Left, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { left: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute left-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
|
|
1698
|
+
top: topFor(i) - 8,
|
|
1699
|
+
right: "50%",
|
|
1700
|
+
whiteSpace: "nowrap",
|
|
1701
|
+
overflow: "hidden",
|
|
1702
|
+
textOverflow: "ellipsis",
|
|
1703
|
+
}, title: `${entry.id}: ${entry.typeId}`, children: [entry.id, hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "ml-1", title: title })), showValues && (jsxRuntime.jsx("span", { className: "ml-1 opacity-60", children: toString(entry.typeId, inputValues?.[entry.id]) }))] })] }, `in-${entry.id}`));
|
|
1698
1704
|
}), outputEntries.map((entry, i) => {
|
|
1699
1705
|
const vIssues = validation.outputs.filter((v) => v.handle === entry.id);
|
|
1700
1706
|
const hasAny = vIssues.length > 0;
|
|
@@ -1703,7 +1709,14 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
1703
1709
|
.map((v) => `${v.code}: ${v.message}`)
|
|
1704
1710
|
.join("; ");
|
|
1705
1711
|
const resolved = resolveOutputDisplay(outputValues?.[entry.id], entry.typeId);
|
|
1706
|
-
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "source", position: ReactFlow.Position.Right, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400 !rounded-none", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { right: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute right-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
|
|
1712
|
+
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "source", position: ReactFlow.Position.Right, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400 !rounded-none", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { right: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute right-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
|
|
1713
|
+
top: topFor(i) - 8,
|
|
1714
|
+
textAlign: "right",
|
|
1715
|
+
left: "50%",
|
|
1716
|
+
whiteSpace: "nowrap",
|
|
1717
|
+
overflow: "hidden",
|
|
1718
|
+
textOverflow: "ellipsis",
|
|
1719
|
+
}, title: `${entry.id}: ${entry.typeId}`, children: [entry.id, resolved.typeId && (jsxRuntime.jsxs("span", { className: "ml-1 opacity-60", children: ["(", resolved.typeId, ")"] })), hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "ml-1", title: title })), showValues && (jsxRuntime.jsx("span", { className: "ml-1 opacity-60", children: toString(resolved.typeId, resolved.value) }))] })] }, `out-${entry.id}`));
|
|
1707
1720
|
})] }));
|
|
1708
1721
|
});
|
|
1709
1722
|
DefaultNode.displayName = "DefaultNode";
|
|
@@ -1842,11 +1855,21 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
1842
1855
|
}, 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: handleDelete, children: "Delete" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleDuplicate, children: "Duplicate" }), canRunPull && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleRunPull, 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: handleCopyId, children: "Copy Node ID" })] }));
|
|
1843
1856
|
}
|
|
1844
1857
|
|
|
1845
|
-
|
|
1858
|
+
const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement }, ref) => {
|
|
1846
1859
|
const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, } = useWorkbenchContext();
|
|
1847
1860
|
const ioValues = { inputs: inputsMap, outputs: outputsMap };
|
|
1848
1861
|
const nodeValidation = validationByNode;
|
|
1849
1862
|
const edgeValidation = validationByEdge.errors;
|
|
1863
|
+
// Expose imperative API
|
|
1864
|
+
const rfInstanceRef = React.useRef(null);
|
|
1865
|
+
React.useImperativeHandle(ref, () => ({
|
|
1866
|
+
fitView: () => {
|
|
1867
|
+
try {
|
|
1868
|
+
rfInstanceRef.current?.fitView({ padding: 0.2 });
|
|
1869
|
+
}
|
|
1870
|
+
catch { }
|
|
1871
|
+
},
|
|
1872
|
+
}));
|
|
1850
1873
|
const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete, onSelectionChange, } = useWorkbenchBridge(wb);
|
|
1851
1874
|
const { nodeTypes, resolveNodeType } = React.useMemo(() => {
|
|
1852
1875
|
// Build nodeTypes map using UI extension registry
|
|
@@ -1922,8 +1945,8 @@ function WorkbenchCanvas({ showValues, toString, toElement, }) {
|
|
|
1922
1945
|
const addNodeAt = (typeId, pos) => {
|
|
1923
1946
|
wb.addNode({ typeId, position: pos });
|
|
1924
1947
|
};
|
|
1925
|
-
return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsxs(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, selectionOnDrag: true, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onSelectionChange: onSelectionChange, deleteKeyCode: ["Backspace", "Delete"], fitView: true, children: [jsxRuntime.jsx(ReactFlow.Background, {}), jsxRuntime.jsx(ReactFlow.MiniMap, {}), jsxRuntime.jsx(ReactFlow.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: () => setMenuOpen(false) }), jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: () => setNodeMenuOpen(false) })] }) }));
|
|
1926
|
-
}
|
|
1948
|
+
return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsxs(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, selectionOnDrag: true, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onSelectionChange: onSelectionChange, deleteKeyCode: ["Backspace", "Delete"], fitView: true, onInit: (inst) => (rfInstanceRef.current = inst), children: [jsxRuntime.jsx(ReactFlow.Background, {}), jsxRuntime.jsx(ReactFlow.MiniMap, {}), jsxRuntime.jsx(ReactFlow.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: () => setMenuOpen(false) }), jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: () => setNodeMenuOpen(false) })] }) }));
|
|
1949
|
+
});
|
|
1927
1950
|
|
|
1928
1951
|
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, }) {
|
|
1929
1952
|
const { wb, runner, registry, def, selectedNodeId, runAutoLayout } = useWorkbenchContext();
|
|
@@ -1973,6 +1996,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
1973
1996
|
}, [overrides, defaultExamples]);
|
|
1974
1997
|
const lastAutoLaunched = React.useRef(undefined);
|
|
1975
1998
|
const autoLayoutRan = React.useRef(false);
|
|
1999
|
+
const canvasRef = React.useRef(null);
|
|
1976
2000
|
const applyExample = React.useCallback(async (key) => {
|
|
1977
2001
|
if (runner.isRunning()) {
|
|
1978
2002
|
alert(`Stop engine before switching example.`);
|
|
@@ -1993,6 +2017,27 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
1993
2017
|
setExampleState(key);
|
|
1994
2018
|
onExampleChange?.(key);
|
|
1995
2019
|
}, [runner, wb, onExampleChange, runAutoLayout, examples, setRegistry]);
|
|
2020
|
+
const downloadGraph = React.useCallback(() => {
|
|
2021
|
+
try {
|
|
2022
|
+
const def = wb.export();
|
|
2023
|
+
const pretty = JSON.stringify(def, null, 2);
|
|
2024
|
+
const blob = new Blob([pretty], { type: "application/json" });
|
|
2025
|
+
const url = URL.createObjectURL(blob);
|
|
2026
|
+
const a = document.createElement("a");
|
|
2027
|
+
const d = new Date();
|
|
2028
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
2029
|
+
const ts = `${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
|
|
2030
|
+
a.href = url;
|
|
2031
|
+
a.download = `spark-graph-${ts}.json`;
|
|
2032
|
+
document.body.appendChild(a);
|
|
2033
|
+
a.click();
|
|
2034
|
+
a.remove();
|
|
2035
|
+
URL.revokeObjectURL(url);
|
|
2036
|
+
}
|
|
2037
|
+
catch (err) {
|
|
2038
|
+
alert(String(err?.message ?? err));
|
|
2039
|
+
}
|
|
2040
|
+
}, [wb]);
|
|
1996
2041
|
const hydrateFromBackend = React.useCallback(async (kind, base) => {
|
|
1997
2042
|
try {
|
|
1998
2043
|
const transport = kind === "remote-http"
|
|
@@ -2291,7 +2336,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
2291
2336
|
catch (err) {
|
|
2292
2337
|
alert(String(err?.message ?? err));
|
|
2293
2338
|
}
|
|
2294
|
-
}, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Debug events" })] }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Show values in nodes" })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(WorkbenchCanvas, { showValues: showValues, toString: toString, toElement: toElement }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, toElement: toElement })] })] }));
|
|
2339
|
+
}, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2", onClick: downloadGraph, children: "Download Graph" }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Debug events" })] }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Show values in nodes" })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, toElement: toElement })] })] }));
|
|
2295
2340
|
}
|
|
2296
2341
|
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, overrides, }) {
|
|
2297
2342
|
const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
|