@bian-womp/spark-workbench 0.3.62 → 0.3.64

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
@@ -416,8 +416,9 @@ class InMemoryWorkbench extends AbstractWorkbench {
416
416
  setViewport(viewport) {
417
417
  if (lod.isEqual(this.viewport, viewport))
418
418
  return;
419
+ const init = this.viewport === null;
419
420
  this.viewport = { ...viewport };
420
- this.emit("graphUiChanged", { change: { type: "viewport" } });
421
+ this.emit("graphUiChanged", { change: { type: "viewport" }, init });
421
422
  }
422
423
  getViewport() {
423
424
  return this.viewport ? { ...this.viewport } : null;
@@ -1687,7 +1688,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
1687
1688
  this.clientPromise = promise = (async () => {
1688
1689
  // Build client config from backend config
1689
1690
  const clientConfig = this.buildClientConfig(backend);
1690
- // Wrap custom event handler to intercept flow-viewport events and emit viewport event
1691
+ // Wrap custom event handler to intercept viewport events and emit viewport event
1691
1692
  const wrappedOnCustomEvent = (event) => {
1692
1693
  const msg = event?.message;
1693
1694
  if (msg &&
@@ -5890,220 +5891,22 @@ function useKeyboardShortcutToast() {
5890
5891
  return { toast, showToast, hideToast };
5891
5892
  }
5892
5893
 
5893
- const SelectionBoundOverlay = ({ selection, rfInstance, viewportTick }) => {
5894
- const overlayRef = React.useRef(null);
5895
- const [parentRect, setParentRect] = React.useState(null);
5896
- const [isDragging, setIsDragging] = React.useState(false);
5897
- const [bounds, setBounds] = React.useState(null);
5898
- const dragStateRef = React.useRef(null);
5899
- const moveListenerRef = React.useRef(null);
5900
- const upListenerRef = React.useRef(null);
5901
- React.useRef(null);
5902
- const { wb } = useWorkbenchContext();
5894
+ const selectionActiveSelector = (state) => state.nodesSelectionActive;
5895
+ const selectionDragActiveSelector = (state) => state.userSelectionActive;
5896
+ const SelectionActiveSync = ({ selection }) => {
5897
+ const store = react.useStoreApi();
5898
+ const currentActive = react.useStore(selectionActiveSelector);
5899
+ const draggingSelection = react.useStore(selectionDragActiveSelector);
5900
+ const active = selection.nodes.length > 1;
5903
5901
  React.useEffect(() => {
5904
- if (!overlayRef.current)
5905
- return;
5906
- const parent = overlayRef.current.parentElement;
5907
- if (!parent)
5908
- return;
5909
- const updateRect = () => {
5910
- setParentRect(parent.getBoundingClientRect());
5911
- };
5912
- updateRect();
5913
- const resizeObserver = new ResizeObserver(updateRect);
5914
- resizeObserver.observe(parent);
5915
- const scrollHandler = () => updateRect();
5916
- window.addEventListener("scroll", scrollHandler, true);
5917
- return () => {
5918
- resizeObserver.disconnect();
5919
- window.removeEventListener("scroll", scrollHandler, true);
5920
- };
5921
- }, [viewportTick]);
5922
- const cleanupDragListeners = React.useCallback(() => {
5923
- if (moveListenerRef.current) {
5924
- window.removeEventListener("mousemove", moveListenerRef.current);
5925
- moveListenerRef.current = null;
5926
- }
5927
- if (upListenerRef.current) {
5928
- window.removeEventListener("mouseup", upListenerRef.current);
5929
- upListenerRef.current = null;
5930
- }
5931
- dragStateRef.current = null;
5932
- setIsDragging(false);
5933
- }, []);
5934
- React.useEffect(() => cleanupDragListeners, [cleanupDragListeners]);
5935
- const handleMouseMove = React.useCallback((e) => {
5936
- if (!rfInstance || !wb || !parentRect)
5937
- return;
5938
- const dragState = dragStateRef.current;
5939
- if (!dragState)
5940
- return;
5941
- e.preventDefault();
5942
- e.stopPropagation();
5943
- const current = rfInstance.screenToFlowPosition({
5944
- x: e.clientX,
5945
- y: e.clientY,
5946
- });
5947
- const dx = current.x - dragState.startFlow.x;
5948
- const dy = current.y - dragState.startFlow.y;
5949
- // Update nodes directly via React Flow for immediate visual feedback
5950
- const nodes = rfInstance.getNodes();
5951
- const updatedNodes = nodes.map((node) => {
5952
- if (dragState.initialPositions[node.id]) {
5953
- const initialPos = dragState.initialPositions[node.id];
5954
- return {
5955
- ...node,
5956
- position: {
5957
- x: initialPos.x + dx,
5958
- y: initialPos.y + dy,
5959
- },
5960
- };
5961
- }
5962
- return node;
5963
- });
5964
- rfInstance.setNodes(updatedNodes);
5965
- // Also update workbench state
5966
- const nextPositions = {};
5967
- for (const [nodeId, pos] of Object.entries(dragState.initialPositions)) {
5968
- nextPositions[nodeId] = { x: pos.x + dx, y: pos.y + dy };
5969
- }
5970
- if (Object.keys(nextPositions).length) {
5971
- wb.setPositions(nextPositions, { commit: false });
5972
- }
5973
- }, [rfInstance, wb, parentRect]);
5974
- const handleMouseUp = React.useCallback((e) => {
5975
- if (!rfInstance || !wb) {
5976
- cleanupDragListeners();
5902
+ if (draggingSelection)
5977
5903
  return;
5904
+ if (currentActive !== active) {
5905
+ console.log("[SelectionActiveSync] setting active selection to", active);
5906
+ store.setState({ nodesSelectionActive: active });
5978
5907
  }
5979
- const dragState = dragStateRef.current;
5980
- if (!dragState) {
5981
- cleanupDragListeners();
5982
- return;
5983
- }
5984
- e.preventDefault();
5985
- e.stopPropagation();
5986
- const current = rfInstance.screenToFlowPosition({
5987
- x: e.clientX,
5988
- y: e.clientY,
5989
- });
5990
- const dx = current.x - dragState.startFlow.x;
5991
- const dy = current.y - dragState.startFlow.y;
5992
- const nextPositions = {};
5993
- for (const [nodeId, pos] of Object.entries(dragState.initialPositions)) {
5994
- nextPositions[nodeId] = { x: pos.x + dx, y: pos.y + dy };
5995
- }
5996
- if (Object.keys(nextPositions).length) {
5997
- wb.setPositions(nextPositions, { commit: true });
5998
- }
5999
- cleanupDragListeners();
6000
- }, [cleanupDragListeners, rfInstance, wb]);
6001
- const handleMouseDown = React.useCallback((e) => {
6002
- if (e.button !== 0)
6003
- return;
6004
- if (!rfInstance || !wb)
6005
- return;
6006
- if (selection.nodes.length < 2)
6007
- return;
6008
- e.preventDefault();
6009
- e.stopPropagation();
6010
- const startFlow = rfInstance.screenToFlowPosition({
6011
- x: e.clientX,
6012
- y: e.clientY,
6013
- });
6014
- const positions = wb.getPositions();
6015
- const initialPositions = {};
6016
- for (const nodeId of selection.nodes) {
6017
- const pos = positions[nodeId];
6018
- if (pos) {
6019
- initialPositions[nodeId] = pos;
6020
- }
6021
- }
6022
- if (!Object.keys(initialPositions).length)
6023
- return;
6024
- dragStateRef.current = { startFlow, initialPositions };
6025
- setIsDragging(true);
6026
- moveListenerRef.current = handleMouseMove;
6027
- upListenerRef.current = handleMouseUp;
6028
- window.addEventListener("mousemove", handleMouseMove, { passive: false });
6029
- window.addEventListener("mouseup", handleMouseUp, { passive: false });
6030
- }, [handleMouseMove, handleMouseUp, rfInstance, selection.nodes, wb]);
6031
- // Continuous bounds update loop
6032
- React.useEffect(() => {
6033
- if (typeof document === "undefined" ||
6034
- !rfInstance ||
6035
- !parentRect ||
6036
- selection.nodes.length < 2) {
6037
- setBounds(null);
6038
- return;
6039
- }
6040
- let animationFrameId = null;
6041
- let isActive = true;
6042
- const updateBounds = () => {
6043
- if (!isActive)
6044
- return;
6045
- let calculatedBounds = null;
6046
- for (const nodeId of selection.nodes) {
6047
- const el = document.querySelector(`.react-flow__node[data-id="${nodeId}"]`);
6048
- if (!el)
6049
- continue;
6050
- const rect = el.getBoundingClientRect();
6051
- const relativeLeft = rect.left - parentRect.left;
6052
- const relativeTop = rect.top - parentRect.top;
6053
- const relativeRight = rect.right - parentRect.left;
6054
- const relativeBottom = rect.bottom - parentRect.top;
6055
- if (!calculatedBounds) {
6056
- calculatedBounds = {
6057
- left: relativeLeft,
6058
- top: relativeTop,
6059
- right: relativeRight,
6060
- bottom: relativeBottom,
6061
- };
6062
- }
6063
- else {
6064
- calculatedBounds.left = Math.min(calculatedBounds.left, relativeLeft);
6065
- calculatedBounds.top = Math.min(calculatedBounds.top, relativeTop);
6066
- calculatedBounds.right = Math.max(calculatedBounds.right, relativeRight);
6067
- calculatedBounds.bottom = Math.max(calculatedBounds.bottom, relativeBottom);
6068
- }
6069
- }
6070
- setBounds(calculatedBounds);
6071
- // Continue the animation loop
6072
- if (isActive) {
6073
- animationFrameId = requestAnimationFrame(updateBounds);
6074
- }
6075
- };
6076
- // Start the animation loop
6077
- animationFrameId = requestAnimationFrame(updateBounds);
6078
- return () => {
6079
- isActive = false;
6080
- if (animationFrameId !== null) {
6081
- cancelAnimationFrame(animationFrameId);
6082
- }
6083
- };
6084
- }, [selection.nodes, rfInstance, viewportTick, parentRect]);
6085
- if (!bounds || selection.nodes.length < 2) {
6086
- return jsxRuntime.jsx("div", { ref: overlayRef, style: { display: "none" } });
6087
- }
6088
- const { left, top, right, bottom } = bounds;
6089
- const width = right - left;
6090
- const height = bottom - top;
6091
- return (jsxRuntime.jsx("div", { ref: overlayRef, onMouseDown: handleMouseDown, style: {
6092
- position: "absolute",
6093
- left: `${left}px`,
6094
- top: `${top}px`,
6095
- width: `${width}px`,
6096
- height: `${height}px`,
6097
- border: isDragging ? "2px solid #0ea5e9" : "1px dashed #0ea5e9",
6098
- backgroundColor: isDragging
6099
- ? "rgba(14, 165, 233, 0.05)"
6100
- : "transparent",
6101
- pointerEvents: "auto",
6102
- cursor: isDragging ? "grabbing" : "move",
6103
- zIndex: 4,
6104
- boxSizing: "border-box",
6105
- transition: isDragging ? "none" : "border 0.1s ease",
6106
- } }));
5908
+ }, [active, currentActive, draggingSelection, store]);
5909
+ return null;
6107
5910
  };
6108
5911
 
6109
5912
  const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
@@ -6746,10 +6549,6 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6746
6549
  connectionLineRenderer: ui.getConnectionLineRenderer(),
6747
6550
  };
6748
6551
  }, [ui, uiVersion]);
6749
- const [selectionOverlayTick, setSelectionOverlayTick] = React.useState(0);
6750
- const onMove = React.useCallback(() => {
6751
- setSelectionOverlayTick((t) => t + 1);
6752
- }, []);
6753
6552
  const onMoveEnd = React.useCallback(() => {
6754
6553
  if (rfInstanceRef.current) {
6755
6554
  const viewport = rfInstanceRef.current.getViewport();
@@ -6781,11 +6580,11 @@ const WorkbenchCanvasComponent = React.forwardRef((props, ref) => {
6781
6580
  if (userOnInit) {
6782
6581
  userOnInit(inst);
6783
6582
  }
6784
- }, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMove: onMove, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", children: [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, {}), menuState?.type === "default" &&
6583
+ }, 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", children: [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, {}), menuState?.type === "default" &&
6785
6584
  (DefaultContextMenuRenderer ? (jsxRuntime.jsx(DefaultContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, keyboardShortcuts: keyboardShortcuts })) : (jsxRuntime.jsx(DefaultContextMenu, { open: true, clientPos: menuState.menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, keyboardShortcuts: keyboardShortcuts }))), menuState?.type === "node" &&
6786
6585
  nodeContextMenuHandlers &&
6787
6586
  (NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: true, clientPos: menuState.menuPos, nodeId: menuState.nodeId, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode, wb: wb, keyboardShortcuts: keyboardShortcuts })) : (jsxRuntime.jsx(NodeContextMenu, { open: true, clientPos: menuState.menuPos, nodeId: menuState.nodeId, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode }))), menuState?.type === "selection" &&
6788
- (SelectionContextMenuRenderer ? (jsxRuntime.jsx(SelectionContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, keyboardShortcuts: keyboardShortcuts })) : (jsxRuntime.jsx(SelectionContextMenu, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, keyboardShortcuts: keyboardShortcuts })))] }), jsxRuntime.jsx(SelectionBoundOverlay, { selection: selection, rfInstance: rfInstanceRef.current, viewportTick: selectionOverlayTick })] }), toast && (jsxRuntime.jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id))] }));
6587
+ (SelectionContextMenuRenderer ? (jsxRuntime.jsx(SelectionContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, keyboardShortcuts: keyboardShortcuts })) : (jsxRuntime.jsx(SelectionContextMenu, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, keyboardShortcuts: keyboardShortcuts })))] }), jsxRuntime.jsx(SelectionActiveSync, { selection: selection })] }), toast && (jsxRuntime.jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id))] }));
6789
6588
  });
6790
6589
  const WorkbenchCanvas = WorkbenchCanvasComponent;
6791
6590