@bian-womp/spark-workbench 0.2.10 → 0.2.12
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 +192 -42
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/misc/NodeContextMenu.d.ts +1 -1
- package/lib/cjs/src/misc/NodeContextMenu.d.ts.map +1 -1
- 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/runtime/AbstractGraphRunner.d.ts +7 -0
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +7 -0
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +12 -0
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +7 -0
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +192 -42
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/misc/NodeContextMenu.d.ts +1 -1
- package/lib/esm/src/misc/NodeContextMenu.d.ts.map +1 -1
- 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/runtime/AbstractGraphRunner.d.ts +7 -0
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +7 -0
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts +12 -0
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +7 -0
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -411,6 +411,21 @@ class AbstractGraphRunner {
|
|
|
411
411
|
class LocalGraphRunner extends AbstractGraphRunner {
|
|
412
412
|
constructor(registry) {
|
|
413
413
|
super(registry, { kind: "local" });
|
|
414
|
+
this.setEnvironment = (env, opts) => {
|
|
415
|
+
if (!this.runtime)
|
|
416
|
+
return;
|
|
417
|
+
if (opts?.merge) {
|
|
418
|
+
const current = this.runtime.getEnvironment();
|
|
419
|
+
const next = { ...(current || {}), ...(env || {}) };
|
|
420
|
+
this.runtime.setEnvironment(next);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
this.runtime.setEnvironment(env);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
this.getEnvironment = () => {
|
|
427
|
+
return this.runtime?.getEnvironment?.();
|
|
428
|
+
};
|
|
414
429
|
this.emit("transport", { state: "local" });
|
|
415
430
|
}
|
|
416
431
|
build(def) {
|
|
@@ -509,17 +524,36 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
509
524
|
const runtimeInputs = this.runtime
|
|
510
525
|
? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
|
|
511
526
|
: {};
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
else {
|
|
516
|
-
const merged = { ...runtimeInputs, ...staged };
|
|
517
|
-
if (Object.keys(merged).length > 0)
|
|
518
|
-
out[n.nodeId] = merged;
|
|
519
|
-
}
|
|
527
|
+
const merged = { ...runtimeInputs, ...staged };
|
|
528
|
+
if (Object.keys(merged).length > 0)
|
|
529
|
+
out[n.nodeId] = merged;
|
|
520
530
|
}
|
|
521
531
|
return out;
|
|
522
532
|
}
|
|
533
|
+
async snapshotFull() {
|
|
534
|
+
const def = undefined; // UI will supply def/positions on download for local
|
|
535
|
+
const inputs = this.getInputs(this.runtime
|
|
536
|
+
? {
|
|
537
|
+
nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({ nodeId: id, typeId: "" })),
|
|
538
|
+
edges: [],
|
|
539
|
+
}
|
|
540
|
+
: { nodes: [], edges: [] });
|
|
541
|
+
const outputs = this.getOutputs(this.runtime
|
|
542
|
+
? {
|
|
543
|
+
nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({ nodeId: id, typeId: "" })),
|
|
544
|
+
edges: [],
|
|
545
|
+
}
|
|
546
|
+
: { nodes: [], edges: [] });
|
|
547
|
+
const environment = this.getEnvironment() || {};
|
|
548
|
+
return { def, environment, inputs, outputs };
|
|
549
|
+
}
|
|
550
|
+
async applySnapshotFull(payload) {
|
|
551
|
+
if (payload.def)
|
|
552
|
+
this.build(payload.def);
|
|
553
|
+
this.setEnvironment?.(payload.environment || {}, { merge: false });
|
|
554
|
+
// Hydrate via runtime for exact restore and re-emit
|
|
555
|
+
this.runtime?.hydrate({ inputs: payload.inputs || {}, outputs: payload.outputs || {} }, { reemit: true });
|
|
556
|
+
}
|
|
523
557
|
dispose() {
|
|
524
558
|
super.dispose();
|
|
525
559
|
this.runtime = undefined;
|
|
@@ -691,6 +725,36 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
691
725
|
return value;
|
|
692
726
|
}
|
|
693
727
|
}
|
|
728
|
+
async snapshotFull() {
|
|
729
|
+
const runner = await this.ensureRemoteRunner();
|
|
730
|
+
try {
|
|
731
|
+
return await runner.snapshotFull();
|
|
732
|
+
}
|
|
733
|
+
catch {
|
|
734
|
+
return { def: undefined, environment: {}, inputs: {}, outputs: {} };
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
async applySnapshotFull(payload) {
|
|
738
|
+
const runner = await this.ensureRemoteRunner();
|
|
739
|
+
await runner.applySnapshotFull(payload);
|
|
740
|
+
}
|
|
741
|
+
setEnvironment(env, opts) {
|
|
742
|
+
const t = this.transport;
|
|
743
|
+
if (!t)
|
|
744
|
+
return;
|
|
745
|
+
t.request({
|
|
746
|
+
message: {
|
|
747
|
+
type: "SetEnvironment",
|
|
748
|
+
payload: { environment: env, merge: opts?.merge },
|
|
749
|
+
},
|
|
750
|
+
}).catch(() => { });
|
|
751
|
+
}
|
|
752
|
+
getEnvironment() {
|
|
753
|
+
// Fetch from remote via lightweight command
|
|
754
|
+
// Note: returns undefined synchronously; callers needing value should use snapshotFull or call runner directly
|
|
755
|
+
// For now, we expose an async helper on RemoteRunner. Keep sync signature per interface.
|
|
756
|
+
return undefined;
|
|
757
|
+
}
|
|
694
758
|
getOutputs(def) {
|
|
695
759
|
const out = {};
|
|
696
760
|
const cache = this.valueCache;
|
|
@@ -724,7 +788,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
724
788
|
if (rec && rec.io === "input")
|
|
725
789
|
cur[h] = rec.value;
|
|
726
790
|
}
|
|
727
|
-
const merged =
|
|
791
|
+
const merged = { ...cur, ...staged };
|
|
728
792
|
if (Object.keys(merged).length > 0)
|
|
729
793
|
out[n.nodeId] = merged;
|
|
730
794
|
}
|
|
@@ -2240,14 +2304,6 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
2240
2304
|
if (open)
|
|
2241
2305
|
ref.current?.focus();
|
|
2242
2306
|
}, [open]);
|
|
2243
|
-
if (!open || !clientPos || !nodeId)
|
|
2244
|
-
return null;
|
|
2245
|
-
// clamp
|
|
2246
|
-
const MENU_MIN_WIDTH = 180;
|
|
2247
|
-
const PADDING = 16;
|
|
2248
|
-
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
2249
|
-
(MENU_MIN_WIDTH + PADDING));
|
|
2250
|
-
const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
|
|
2251
2307
|
// Bake helpers
|
|
2252
2308
|
const getBakeableOutputs = () => {
|
|
2253
2309
|
try {
|
|
@@ -2314,43 +2370,40 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
2314
2370
|
const arrTarget = isArray ? tArr?.bakeTarget : undefined;
|
|
2315
2371
|
const elemTarget = isArray ? tElem?.bakeTarget : undefined;
|
|
2316
2372
|
if (singleTarget) {
|
|
2317
|
-
const nodeDesc = registry.nodes.get(
|
|
2318
|
-
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs,
|
|
2373
|
+
const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
|
|
2374
|
+
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, singleTarget.inputHandle);
|
|
2319
2375
|
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
2320
2376
|
const newId = wb.addNode({
|
|
2321
|
-
typeId:
|
|
2377
|
+
typeId: singleTarget.nodeTypeId,
|
|
2322
2378
|
position: { x: pos.x + 180, y: pos.y },
|
|
2323
2379
|
params: {},
|
|
2324
2380
|
});
|
|
2325
2381
|
runner.update(wb.export());
|
|
2326
2382
|
await runner.whenIdle();
|
|
2327
|
-
runner.setInputs(newId, {
|
|
2328
|
-
[String(singleTarget.inputHandle || "Value")]: coerced,
|
|
2329
|
-
});
|
|
2383
|
+
runner.setInputs(newId, { [singleTarget.inputHandle]: coerced });
|
|
2330
2384
|
return;
|
|
2331
2385
|
}
|
|
2332
2386
|
if (isArray && arrTarget) {
|
|
2333
|
-
const nodeDesc = registry.nodes.get(
|
|
2334
|
-
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs,
|
|
2387
|
+
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
2388
|
+
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
2335
2389
|
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
2336
2390
|
const newId = `n${Math.random().toString(36).slice(2, 8)}`;
|
|
2337
2391
|
wb.addNode({
|
|
2338
2392
|
nodeId: newId,
|
|
2339
|
-
typeId:
|
|
2393
|
+
typeId: arrTarget.nodeTypeId,
|
|
2340
2394
|
position: { x: pos.x + 180, y: pos.y },
|
|
2341
2395
|
params: {},
|
|
2342
2396
|
});
|
|
2343
2397
|
runner.update(wb.export());
|
|
2344
2398
|
await runner.whenIdle();
|
|
2345
|
-
runner.setInputs(newId, {
|
|
2346
|
-
[String(arrTarget.inputHandle || "Value")]: coerced,
|
|
2347
|
-
});
|
|
2399
|
+
runner.setInputs(newId, { [arrTarget.inputHandle]: coerced });
|
|
2348
2400
|
return;
|
|
2349
2401
|
}
|
|
2350
|
-
if (isArray && elemTarget
|
|
2351
|
-
const nodeDesc = registry.nodes.get(
|
|
2352
|
-
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs,
|
|
2353
|
-
const
|
|
2402
|
+
if (isArray && elemTarget) {
|
|
2403
|
+
const nodeDesc = registry.nodes.get(elemTarget.nodeTypeId);
|
|
2404
|
+
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, elemTarget.inputHandle);
|
|
2405
|
+
const src = unwrap(raw);
|
|
2406
|
+
const items = Array.isArray(src) ? src : [src];
|
|
2354
2407
|
const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, inType, v)));
|
|
2355
2408
|
const COLS = 4;
|
|
2356
2409
|
const DX = 180;
|
|
@@ -2360,18 +2413,14 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
2360
2413
|
const col = idx % COLS;
|
|
2361
2414
|
const row = Math.floor(idx / COLS);
|
|
2362
2415
|
const newId = wb.addNode({
|
|
2363
|
-
typeId:
|
|
2416
|
+
typeId: elemTarget.nodeTypeId,
|
|
2364
2417
|
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
2365
2418
|
params: {},
|
|
2366
|
-
initialInputs: {
|
|
2367
|
-
[String(elemTarget.inputHandle || "Value")]: clone(cv),
|
|
2368
|
-
},
|
|
2419
|
+
initialInputs: { [elemTarget.inputHandle]: clone(cv) },
|
|
2369
2420
|
});
|
|
2370
2421
|
runner.update(wb.export());
|
|
2371
2422
|
await runner.whenIdle();
|
|
2372
|
-
runner.setInputs(newId, {
|
|
2373
|
-
[String(elemTarget.inputHandle || "Value")]: cv,
|
|
2374
|
-
});
|
|
2423
|
+
runner.setInputs(newId, { [elemTarget.inputHandle]: cv });
|
|
2375
2424
|
}
|
|
2376
2425
|
return;
|
|
2377
2426
|
}
|
|
@@ -2414,6 +2463,14 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
2414
2463
|
catch { }
|
|
2415
2464
|
onClose();
|
|
2416
2465
|
}, [nodeId, runner, onClose]);
|
|
2466
|
+
if (!open || !clientPos || !nodeId)
|
|
2467
|
+
return null;
|
|
2468
|
+
// clamp
|
|
2469
|
+
const MENU_MIN_WIDTH = 180;
|
|
2470
|
+
const PADDING = 16;
|
|
2471
|
+
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
2472
|
+
(MENU_MIN_WIDTH + PADDING));
|
|
2473
|
+
const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
|
|
2417
2474
|
const canRunPull = engineKind()?.toString() === "pull";
|
|
2418
2475
|
const outs = getBakeableOutputs();
|
|
2419
2476
|
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) => {
|
|
@@ -2463,6 +2520,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
2463
2520
|
outputValues: n.data.outputValues,
|
|
2464
2521
|
status: n.data.status,
|
|
2465
2522
|
validation: n.data.validation,
|
|
2523
|
+
inputConnected: n.data.inputConnected,
|
|
2466
2524
|
},
|
|
2467
2525
|
});
|
|
2468
2526
|
return isEqual(pick(a), pick(b));
|
|
@@ -2666,7 +2724,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
2666
2724
|
}, []);
|
|
2667
2725
|
return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsx(react.ReactFlowProvider, { children: jsxRuntime.jsxs(react.ReactFlow, { nodes: throttled.nodes, edges: throttled.edges, nodeTypes: nodeTypes, selectionOnDrag: true, onInit: (inst) => {
|
|
2668
2726
|
rfInstanceRef.current = inst;
|
|
2669
|
-
}, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 }), jsxRuntime.jsx(react.MiniMap, {}), jsxRuntime.jsx(react.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: onCloseMenu }), jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: onCloseNodeMenu })] }) }) }));
|
|
2727
|
+
}, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 }), jsxRuntime.jsx(react.MiniMap, {}), jsxRuntime.jsx(react.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: onCloseMenu }), !!nodeAtMenu && (jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: onCloseNodeMenu }))] }) }) }));
|
|
2670
2728
|
});
|
|
2671
2729
|
|
|
2672
2730
|
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
|
|
@@ -2721,6 +2779,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
2721
2779
|
const lastAutoLaunched = React.useRef(undefined);
|
|
2722
2780
|
const autoLayoutRan = React.useRef(false);
|
|
2723
2781
|
const canvasRef = React.useRef(null);
|
|
2782
|
+
const uploadInputRef = React.useRef(null);
|
|
2724
2783
|
// Expose init callback with setInitialGraph helper
|
|
2725
2784
|
const initCalled = React.useRef(false);
|
|
2726
2785
|
React.useEffect(() => {
|
|
@@ -2825,6 +2884,69 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
2825
2884
|
alert(String(err?.message ?? err));
|
|
2826
2885
|
}
|
|
2827
2886
|
}, [wb, runner]);
|
|
2887
|
+
const onUploadPicked = React.useCallback(async (e) => {
|
|
2888
|
+
try {
|
|
2889
|
+
const file = e.target.files?.[0];
|
|
2890
|
+
if (!file)
|
|
2891
|
+
return;
|
|
2892
|
+
const text = await file.text();
|
|
2893
|
+
const parsed = JSON.parse(text);
|
|
2894
|
+
// Support both Graph and Snapshot payloads
|
|
2895
|
+
const isSnapshot = parsed &&
|
|
2896
|
+
typeof parsed === "object" &&
|
|
2897
|
+
(parsed.def || parsed.inputs || parsed.outputs || parsed.environment);
|
|
2898
|
+
if (isSnapshot) {
|
|
2899
|
+
const def = parsed.def;
|
|
2900
|
+
const positions = parsed.positions || {};
|
|
2901
|
+
const environment = parsed.environment || {};
|
|
2902
|
+
const inputs = parsed.inputs || {};
|
|
2903
|
+
if (def && runner.isRunning()) {
|
|
2904
|
+
// Remote exact restore path
|
|
2905
|
+
await runner.applySnapshotFull({
|
|
2906
|
+
def,
|
|
2907
|
+
environment,
|
|
2908
|
+
inputs,
|
|
2909
|
+
outputs: parsed.outputs || {},
|
|
2910
|
+
});
|
|
2911
|
+
await wb.load(def);
|
|
2912
|
+
if (positions && typeof positions === "object")
|
|
2913
|
+
wb.setPositions(positions);
|
|
2914
|
+
}
|
|
2915
|
+
else {
|
|
2916
|
+
alert("Graph definition is empty or engine is not running");
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
else {
|
|
2920
|
+
const def = parsed?.def ?? parsed;
|
|
2921
|
+
const inputs = parsed?.inputs ?? {};
|
|
2922
|
+
await wb.load(def);
|
|
2923
|
+
try {
|
|
2924
|
+
runner.build(wb.export());
|
|
2925
|
+
}
|
|
2926
|
+
catch { }
|
|
2927
|
+
if (inputs && typeof inputs === "object") {
|
|
2928
|
+
for (const [nodeId, map] of Object.entries(inputs)) {
|
|
2929
|
+
try {
|
|
2930
|
+
runner.setInputs(nodeId, map);
|
|
2931
|
+
}
|
|
2932
|
+
catch { }
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
runAutoLayout();
|
|
2937
|
+
}
|
|
2938
|
+
catch (err) {
|
|
2939
|
+
alert(String(err?.message ?? err));
|
|
2940
|
+
}
|
|
2941
|
+
finally {
|
|
2942
|
+
// reset input so same file can be picked again
|
|
2943
|
+
if (uploadInputRef.current)
|
|
2944
|
+
uploadInputRef.current.value = "";
|
|
2945
|
+
}
|
|
2946
|
+
}, [wb, runner, runAutoLayout]);
|
|
2947
|
+
const triggerUpload = React.useCallback(() => {
|
|
2948
|
+
uploadInputRef.current?.click();
|
|
2949
|
+
}, []);
|
|
2828
2950
|
const hydrateFromBackend = React.useCallback(async (kind, base) => {
|
|
2829
2951
|
try {
|
|
2830
2952
|
const transport = kind === "remote-http"
|
|
@@ -3122,7 +3244,35 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3122
3244
|
catch (err) {
|
|
3123
3245
|
alert(String(err?.message ?? err));
|
|
3124
3246
|
}
|
|
3125
|
-
}, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded px-2 py-1.5", onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: downloadGraph, children: "Download Graph" }), jsxRuntime.
|
|
3247
|
+
}, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded px-2 py-1.5", onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: downloadGraph, children: "Download Graph" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: async () => {
|
|
3248
|
+
try {
|
|
3249
|
+
const def = wb.export();
|
|
3250
|
+
const positions = wb.getPositions();
|
|
3251
|
+
const snapshot = await runner.snapshotFull();
|
|
3252
|
+
const payload = {
|
|
3253
|
+
...snapshot,
|
|
3254
|
+
def,
|
|
3255
|
+
positions,
|
|
3256
|
+
schemaVersion: 1,
|
|
3257
|
+
};
|
|
3258
|
+
const pretty = JSON.stringify(payload, null, 2);
|
|
3259
|
+
const blob = new Blob([pretty], { type: "application/json" });
|
|
3260
|
+
const url = URL.createObjectURL(blob);
|
|
3261
|
+
const a = document.createElement("a");
|
|
3262
|
+
const d = new Date();
|
|
3263
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
3264
|
+
const ts = `${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
|
|
3265
|
+
a.href = url;
|
|
3266
|
+
a.download = `spark-snapshot-${ts}.json`;
|
|
3267
|
+
document.body.appendChild(a);
|
|
3268
|
+
a.click();
|
|
3269
|
+
a.remove();
|
|
3270
|
+
URL.revokeObjectURL(url);
|
|
3271
|
+
}
|
|
3272
|
+
catch (err) {
|
|
3273
|
+
alert(String(err?.message ?? err));
|
|
3274
|
+
}
|
|
3275
|
+
}, children: "Download Snapshot" }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: triggerUpload, children: "Upload Graph/Snapshot" }), 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, getDefaultNodeSize: overrides?.getDefaultNodeSize }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, toElement: toElement, contextPanel: overrides?.contextPanel })] })] }));
|
|
3126
3276
|
}
|
|
3127
3277
|
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, overrides, onInit, onChange, }) {
|
|
3128
3278
|
const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
|