@bian-womp/spark-workbench 0.2.50 → 0.2.52

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 CHANGED
@@ -103,6 +103,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
103
103
  nodes: [],
104
104
  edges: [],
105
105
  };
106
+ this.viewport = null;
106
107
  }
107
108
  setRegistry(registry) {
108
109
  this.registry = registry;
@@ -260,6 +261,43 @@ class InMemoryWorkbench extends AbstractWorkbench {
260
261
  edges: [...this.selection.edges],
261
262
  };
262
263
  }
264
+ setViewport(viewport) {
265
+ this.viewport = { ...viewport };
266
+ }
267
+ getViewport() {
268
+ return this.viewport ? { ...this.viewport } : null;
269
+ }
270
+ getUIState() {
271
+ return {
272
+ positions: Object.keys(this.positions).length > 0
273
+ ? { ...this.positions }
274
+ : undefined,
275
+ selection: this.selection.nodes.length > 0 || this.selection.edges.length > 0
276
+ ? {
277
+ nodes: [...this.selection.nodes],
278
+ edges: [...this.selection.edges],
279
+ }
280
+ : undefined,
281
+ viewport: this.viewport ? { ...this.viewport } : undefined,
282
+ };
283
+ }
284
+ setUIState(ui) {
285
+ if (!ui)
286
+ return;
287
+ if (ui.positions) {
288
+ this.positions = { ...ui.positions };
289
+ }
290
+ if (ui.selection) {
291
+ this.selection = {
292
+ nodes: [...ui.selection.nodes],
293
+ edges: [...ui.selection.edges],
294
+ };
295
+ this.emit("selectionChanged", this.selection);
296
+ }
297
+ if (ui.viewport) {
298
+ this.viewport = { ...ui.viewport };
299
+ }
300
+ }
263
301
  on(event, handler) {
264
302
  if (!this.listeners.has(event))
265
303
  this.listeners.set(event, new Set());
@@ -342,23 +380,6 @@ class AbstractGraphRunner {
342
380
  this.stop();
343
381
  }
344
382
  }
345
- setInput(nodeId, handle, value) {
346
- if (!this.stagedInputs[nodeId])
347
- this.stagedInputs[nodeId] = {};
348
- if (value === undefined) {
349
- delete this.stagedInputs[nodeId][handle];
350
- }
351
- else {
352
- this.stagedInputs[nodeId][handle] = value;
353
- }
354
- if (this.engine) {
355
- this.engine.setInput(nodeId, handle, value);
356
- }
357
- else {
358
- // Emit a value event so UI updates even when engine isn't running
359
- this.emit("value", { nodeId, handle, value, io: "input" });
360
- }
361
- }
362
383
  triggerExternal(nodeId, event) {
363
384
  this.engine?.triggerExternal(nodeId, event);
364
385
  }
@@ -1061,6 +1082,10 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1061
1082
  return value;
1062
1083
  }
1063
1084
  }
1085
+ async setExtData(data) {
1086
+ const client = await this.ensureClient();
1087
+ await client.setExtData(data);
1088
+ }
1064
1089
  async snapshotFull() {
1065
1090
  const client = await this.ensureClient();
1066
1091
  try {
@@ -3288,6 +3313,7 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
3288
3313
  const COLS = 4;
3289
3314
  const DX = 180;
3290
3315
  const DY = 160;
3316
+ const nodeIds = [];
3291
3317
  for (let idx = 0; idx < coercedItems.length; idx++) {
3292
3318
  const cv = coercedItems[idx];
3293
3319
  const col = idx % COLS;
@@ -3298,9 +3324,16 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
3298
3324
  params: {},
3299
3325
  initialInputs: { [elemTarget.inputHandle]: structuredClone(cv) },
3300
3326
  });
3327
+ nodeIds.push(newId);
3328
+ }
3329
+ if (nodeIds.length > 0) {
3301
3330
  runner.update(wb.export());
3302
3331
  await runner.whenIdle();
3303
- runner.setInputs(newId, { [elemTarget.inputHandle]: cv });
3332
+ for (let idx = 0; idx < coercedItems.length; idx++) {
3333
+ runner.setInputs(nodeIds[idx], {
3334
+ [elemTarget.inputHandle]: coercedItems[idx],
3335
+ });
3336
+ }
3304
3337
  }
3305
3338
  return;
3306
3339
  }
@@ -3621,18 +3654,48 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3621
3654
  const addNodeAt = React.useCallback((typeId, pos) => {
3622
3655
  wb.addNode({ typeId, position: pos });
3623
3656
  }, [wb]);
3624
- React.useCallback((inst) => {
3625
- rfInstanceRef.current = inst;
3626
- }, []);
3627
3657
  const onCloseMenu = React.useCallback(() => {
3628
3658
  setMenuOpen(false);
3629
3659
  }, []);
3630
3660
  const onCloseNodeMenu = React.useCallback(() => {
3631
3661
  setNodeMenuOpen(false);
3632
3662
  }, []);
3663
+ const onMoveEnd = React.useCallback(() => {
3664
+ if (rfInstanceRef.current) {
3665
+ const viewport = rfInstanceRef.current.getViewport();
3666
+ wb.setViewport({ x: viewport.x, y: viewport.y, zoom: viewport.zoom });
3667
+ }
3668
+ }, [wb]);
3669
+ const viewportRef = React.useRef(null);
3670
+ React.useEffect(() => {
3671
+ if (!rfInstanceRef.current)
3672
+ return;
3673
+ const currentViewport = wb.getViewport();
3674
+ if (currentViewport &&
3675
+ (!viewportRef.current ||
3676
+ viewportRef.current.x !== currentViewport.x ||
3677
+ viewportRef.current.y !== currentViewport.y ||
3678
+ viewportRef.current.zoom !== currentViewport.zoom)) {
3679
+ viewportRef.current = currentViewport;
3680
+ rfInstanceRef.current.setViewport({
3681
+ x: currentViewport.x,
3682
+ y: currentViewport.y,
3683
+ zoom: currentViewport.zoom,
3684
+ });
3685
+ }
3686
+ });
3633
3687
  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) => {
3634
3688
  rfInstanceRef.current = inst;
3635
- }, 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 }))] }) }) }));
3689
+ const savedViewport = wb.getViewport();
3690
+ if (savedViewport) {
3691
+ viewportRef.current = savedViewport;
3692
+ inst.setViewport({
3693
+ x: savedViewport.x,
3694
+ y: savedViewport.y,
3695
+ zoom: savedViewport.zoom,
3696
+ });
3697
+ }
3698
+ }, 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: [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 }))] }) }) }));
3636
3699
  });
3637
3700
 
3638
3701
  function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
@@ -3722,8 +3785,6 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3722
3785
  return overrides.getExamples(defaultExamples);
3723
3786
  return defaultExamples;
3724
3787
  }, [overrides, defaultExamples]);
3725
- const lastAutoLaunched = React.useRef(undefined);
3726
- const autoLayoutRan = React.useRef(false);
3727
3788
  const canvasRef = React.useRef(null);
3728
3789
  const uploadInputRef = React.useRef(null);
3729
3790
  const [registryReady, setRegistryReady] = React.useState(() => {
@@ -3748,7 +3809,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3748
3809
  }
3749
3810
  };
3750
3811
  onInit({ wb, runner, setInitialGraph });
3751
- }, [onInit, wb, runner, runAutoLayout, registry, setRegistry]);
3812
+ }, [onInit, wb, runner]);
3752
3813
  // Expose change callback on graph/value changes
3753
3814
  React.useEffect(() => {
3754
3815
  if (!onChange)
@@ -3883,7 +3944,6 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3883
3944
  }
3884
3945
  }
3885
3946
  }
3886
- runAutoLayout();
3887
3947
  }
3888
3948
  catch (err) {
3889
3949
  const message = err instanceof Error ? err.message : String(err);
@@ -3894,7 +3954,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3894
3954
  if (uploadInputRef.current)
3895
3955
  uploadInputRef.current.value = "";
3896
3956
  }
3897
- }, [wb, runner, runAutoLayout]);
3957
+ }, [wb, runner]);
3898
3958
  const triggerUpload = React.useCallback(() => {
3899
3959
  uploadInputRef.current?.click();
3900
3960
  }, []);
@@ -3936,32 +3996,13 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3936
3996
  const d = wb.export();
3937
3997
  if (!d.nodes || d.nodes.length === 0)
3938
3998
  return;
3939
- if (lastAutoLaunched.current === engine)
3940
- return;
3941
3999
  try {
3942
4000
  runner.launch(d, { engine: engine });
3943
- lastAutoLaunched.current = engine;
3944
4001
  }
3945
4002
  catch {
3946
4003
  // ignore
3947
4004
  }
3948
4005
  }, [engine, runner, isGraphRunning, wb, backendKind]);
3949
- // Registry is automatically fetched by RemoteGraphRunner when it connects
3950
- // Run auto layout after registry is hydrated (for remote backends)
3951
- React.useEffect(() => {
3952
- if (autoLayoutRan.current)
3953
- return;
3954
- // Wait for registry to be ready for remote backends
3955
- if (backendKind !== "local" && !registryReady)
3956
- return;
3957
- const cur = wb.export();
3958
- const positions = wb.getPositions();
3959
- const allMissing = cur.nodes.every((n) => !positions[n.nodeId]);
3960
- if (allMissing) {
3961
- autoLayoutRan.current = true;
3962
- runAutoLayout();
3963
- }
3964
- }, [wb, runAutoLayout, backendKind, registryReady, registry]);
3965
4006
  const baseSetInput = React.useCallback((handle, raw) => {
3966
4007
  if (!selectedNodeId)
3967
4008
  return;
@@ -3971,7 +4012,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3971
4012
  return;
3972
4013
  // If raw is undefined, pass it through to delete the input value
3973
4014
  if (raw === undefined) {
3974
- runner.setInput(selectedNodeId, handle, undefined);
4015
+ runner.setInputs(selectedNodeId, { [handle]: undefined });
3975
4016
  return;
3976
4017
  }
3977
4018
  const typeId = sparkGraph.getInputTypeId(effectiveHandles.inputs, handle);
@@ -4050,7 +4091,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
4050
4091
  value = raw;
4051
4092
  }
4052
4093
  }
4053
- runner.setInput(selectedNodeId, handle, value);
4094
+ runner.setInputs(selectedNodeId, { [handle]: value });
4054
4095
  }, [selectedNodeId, def.edges, effectiveHandles, runner]);
4055
4096
  const setInput = React.useMemo(() => {
4056
4097
  if (overrides?.setInput) {