refactor(state): replace redux with zustand

This commit is contained in:
moklick
2021-10-13 12:54:01 +02:00
parent e01249a87c
commit ee0d29029a
38 changed files with 786 additions and 896 deletions
+23 -1
View File
@@ -19,7 +19,8 @@
"react-draggable": "^4.4.4",
"react-redux": "^7.2.5",
"redux": "^4.1.1",
"redux-thunk": "^2.3.0"
"redux-thunk": "^2.3.0",
"zustand": "^3.5.13"
},
"devDependencies": {
"@babel/core": "^7.15.5",
@@ -11257,6 +11258,22 @@
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
},
"node_modules/zustand": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-3.5.13.tgz",
"integrity": "sha512-orO/XcYwSWffsrPVTdCtuKM/zkUaOIyKDasOk/lecsD3R0euELsj+cB65uKZ1KyinrK2STHIuUhRoLpH8QprQg==",
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"react": ">=16.8"
},
"peerDependenciesMeta": {
"react": {
"optional": true
}
}
}
},
"dependencies": {
@@ -19593,6 +19610,11 @@
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
},
"zustand": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-3.5.13.tgz",
"integrity": "sha512-orO/XcYwSWffsrPVTdCtuKM/zkUaOIyKDasOk/lecsD3R0euELsj+cB65uKZ1KyinrK2STHIuUhRoLpH8QprQg=="
}
}
}
+2 -1
View File
@@ -41,7 +41,8 @@
"react-draggable": "^4.4.4",
"react-redux": "^7.2.5",
"redux": "^4.1.1",
"redux-thunk": "^2.3.0"
"redux-thunk": "^2.3.0",
"zustand": "^3.5.13"
},
"devDependencies": {
"@babel/core": "^7.15.5",
@@ -1,8 +1,8 @@
import React, { memo, useMemo, FC, HTMLAttributes } from 'react';
import cc from 'classcat';
import { useStoreState } from '../../store/hooks';
import { BackgroundVariant } from '../../types';
import { useStore } from '../../store';
import { BackgroundVariant, ReactFlowState } from '../../types';
import { createGridLinesPath, createGridDotsPath } from './utils';
export interface BackgroundProps extends HTMLAttributes<SVGElement> {
@@ -17,6 +17,8 @@ const defaultColors = {
[BackgroundVariant.Lines]: '#eee',
};
const transformSelector = (s: ReactFlowState) => s.transform;
const Background: FC<BackgroundProps> = ({
variant = BackgroundVariant.Dots,
gap = 15,
@@ -25,7 +27,7 @@ const Background: FC<BackgroundProps> = ({
style,
className,
}) => {
const [x, y, scale] = useStoreState((s) => s.transform);
const [x, y, scale] = useStore(transformSelector);
// when there are multiple flows on a page we need to make sure that every background gets its own pattern.
const patternId = useMemo(() => `pattern-${Math.floor(Math.random() * 100000)}`, []);
+7 -4
View File
@@ -1,7 +1,7 @@
import React, { memo, useCallback, HTMLAttributes, FC, useEffect, useState } from 'react';
import cc from 'classcat';
import { useStoreState, useStoreActions } from '../../store/hooks';
import { useStore } from '../../store';
import PlusIcon from '../../../assets/icons/plus.svg';
import MinusIcon from '../../../assets/icons/minus.svg';
@@ -10,7 +10,7 @@ import LockIcon from '../../../assets/icons/lock.svg';
import UnlockIcon from '../../../assets/icons/unlock.svg';
import useZoomPanHelper from '../../hooks/useZoomPanHelper';
import { FitViewParams } from '../../types';
import { FitViewParams, ReactFlowState } from '../../types';
export interface ControlProps extends HTMLAttributes<HTMLDivElement> {
showZoom?: boolean;
@@ -31,6 +31,9 @@ export const ControlButton: FC<ControlButtonProps> = ({ children, className, ...
</button>
);
const setInteractiveSelector = (s: ReactFlowState) => s.setInteractive;
const isInteractiveSelector = (s: ReactFlowState) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable;
const Controls: FC<ControlProps> = ({
style,
showZoom = true,
@@ -45,10 +48,10 @@ const Controls: FC<ControlProps> = ({
children,
}) => {
const [isVisible, setIsVisible] = useState<boolean>(false);
const setInteractive = useStoreActions((actions) => actions.setInteractive);
const setInteractive = useStore(setInteractiveSelector);
const isInteractive = useStore(isInteractiveSelector);
const { zoomIn, zoomOut, fitView } = useZoomPanHelper();
const isInteractive = useStoreState((s) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable);
const mapClasses = cc(['react-flow__controls', className]);
const onZoomInHandler = useCallback(() => {
+6 -6
View File
@@ -1,9 +1,9 @@
import React, { memo, HTMLAttributes } from 'react';
import cc from 'classcat';
import { useStoreState } from '../../store/hooks';
import { useStore } from '../../store';
import { getRectOfNodes, getBoundsofRects } from '../../utils/graph';
import { Node, Rect } from '../../types';
import { Node, Rect, ReactFlowState } from '../../types';
import MiniMapNode from './MiniMapNode';
type StringFunc = (node: Node) => string;
@@ -22,6 +22,8 @@ declare const window: any;
const defaultWidth = 200;
const defaultHeight = 150;
const selector = (s: ReactFlowState) => ({ width: s.width, height: s.height, transform: s.transform, nodes: s.nodes });
const MiniMap = ({
style,
className,
@@ -32,10 +34,8 @@ const MiniMap = ({
nodeStrokeWidth = 2,
maskColor = 'rgb(240, 242, 243, 0.7)',
}: MiniMapProps) => {
const containerWidth = useStoreState((s) => s.width);
const containerHeight = useStoreState((s) => s.height);
const [tX, tY, tScale] = useStoreState((s) => s.transform);
const nodes = useStoreState((s) => s.nodes);
const { width: containerWidth, height: containerHeight, transform, nodes } = useStore(selector);
const [tX, tY, tScale] = transform;
const mapClasses = cc(['react-flow__minimap', className]);
const elementWidth = (style?.width || defaultWidth)! as number;
@@ -1,16 +1,8 @@
import React, { FC, useMemo } from 'react';
import { Provider } from 'react-redux';
import React, { FC } from 'react';
import { initialState } from '../../store';
import configureStore from '../../store/configure-store';
import { Provider, createStore } from '../../store';
const ReactFlowProvider: FC = ({ children }) => {
const store = useMemo(() => {
return configureStore(initialState);
}, []);
return <Provider store={store}>{children}</Provider>;
};
const ReactFlowProvider: FC = ({ children }) => <Provider createStore={createStore}>{children}</Provider>;
ReactFlowProvider.displayName = 'ReactFlowProvider';
+5 -2
View File
@@ -1,6 +1,6 @@
import React, { useEffect, useState, CSSProperties } from 'react';
import { useStoreState } from '../../store/hooks';
import { useStore } from '../../store';
import { getBezierPath } from '../Edges/BezierEdge';
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
import {
@@ -12,6 +12,7 @@ import {
ConnectionLineType,
ConnectionLineComponent,
HandleType,
ReactFlowState,
} from '../../types';
interface ConnectionLineProps {
@@ -27,6 +28,8 @@ interface ConnectionLineProps {
CustomConnectionLineComponent?: ConnectionLineComponent;
}
const nodesSelector = (s: ReactFlowState) => s.nodes;
export default ({
connectionNodeId,
connectionHandleId,
@@ -39,7 +42,7 @@ export default ({
isConnectable,
CustomConnectionLineComponent,
}: ConnectionLineProps) => {
const nodes = useStoreState((state) => state.nodes);
const nodes = useStore(nodesSelector);
const [sourceNode, setSourceNode] = useState<Node | null>(null);
const nodeId = connectionNodeId;
const handleId = connectionHandleId;
-1
View File
@@ -1,7 +1,6 @@
import React, { memo } from 'react';
import EdgeText from './EdgeText';
import { getMarkerEnd, getCenter } from './utils';
import { EdgeProps, Position } from '../../types';
+28 -10
View File
@@ -1,11 +1,19 @@
import React, { memo, ComponentType, useCallback, useState, useMemo } from 'react';
import cc from 'classcat';
import { useStoreActions, useStoreState } from '../../store/hooks';
import { Edge, EdgeProps, WrapEdgeProps } from '../../types';
import { useStore, useStoreApi } from '../../store';
import { Edge, EdgeProps, WrapEdgeProps, ReactFlowState } from '../../types';
import { onMouseDown } from '../../components/Handle/handler';
import { EdgeAnchor } from './EdgeAnchor';
const selector = (s: ReactFlowState) => ({
addSelectedElements: s.addSelectedElements,
setConnectionNodeId: s.setConnectionNodeId,
unsetNodesSelection: s.unsetNodesSelection,
setPosition: s.setConnectionPosition,
connectionMode: s.connectionMode,
});
export default (EdgeComponent: ComponentType<EdgeProps>) => {
const EdgeWrapper = ({
id,
@@ -47,11 +55,9 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
onEdgeUpdateStart,
onEdgeUpdateEnd,
}: WrapEdgeProps): JSX.Element | null => {
const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
const connectionMode = useStoreState((state) => state.connectionMode);
const store = useStoreApi();
const { addSelectedElements, setConnectionNodeId, unsetNodesSelection, setPosition, connectionMode } =
useStore(selector);
const [updating, setUpdating] = useState<boolean>(false);
@@ -90,7 +96,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
(event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
if (elementsSelectable) {
unsetNodesSelection();
addSelectedElements(edgeElement);
addSelectedElements([edgeElement]);
}
onClick?.(event, edgeElement);
@@ -157,10 +163,22 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
isValidConnection,
connectionMode,
isSourceHandle ? 'target' : 'source',
_onEdgeUpdate
_onEdgeUpdate,
store.getState
);
},
[id, source, target, type, sourceHandleId, targetHandleId, setConnectionNodeId, setPosition, edgeElement, onConnectEdge]
[
id,
source,
target,
type,
sourceHandleId,
targetHandleId,
setConnectionNodeId,
setPosition,
edgeElement,
onConnectEdge,
]
);
const onEdgeUpdaterSourceMouseDown = useCallback(
+8 -4
View File
@@ -1,16 +1,20 @@
import { useEffect } from 'react';
import { useStoreActions } from '../../store/hooks';
import { Node, Edge } from '../../types';
import { useStore } from '../../store';
import { Node, Edge, ReactFlowState } from '../../types';
interface ElementUpdaterProps {
nodes: Node[];
edges: Edge[];
}
const selector = (s: ReactFlowState) => ({
setNodes: s.setNodes,
setEdges: s.setEdges,
});
const ElementUpdater = ({ nodes, edges }: ElementUpdaterProps) => {
const setNodes = useStoreActions((actions) => actions.setNodes);
const setEdges = useStoreActions((actions) => actions.setEdges);
const { setNodes, setEdges } = useStore(selector);
useEffect(() => {
setNodes(nodes);
+4 -4
View File
@@ -1,9 +1,8 @@
import { MouseEvent as ReactMouseEvent } from 'react';
import { Store } from 'redux';
import { GetState } from 'zustand';
import { getHostForElement } from '../../utils';
import { ReactFlowState } from '../../types';
import { ReactFlowAction } from '../../store/actions';
import {
ElementId,
@@ -107,7 +106,7 @@ export function onMouseDown(
onConnectStart?: OnConnectStartFunc,
onConnectStop?: OnConnectStopFunc,
onConnectEnd?: OnConnectEndFunc,
store?: Store<ReactFlowState, ReactFlowAction>
getState?: GetState<ReactFlowState>
): void {
const reactFlowNode = (event.target as Element).closest('.react-flow');
// when react-flow is used inside a shadow root we can't use document
@@ -181,7 +180,8 @@ export function onMouseDown(
onConnectStop?.(event);
if (isValid) {
onConnect?.(connection, store?.getState().nodes || []);
const nodes = getState?.().nodes;
onConnect?.(connection, nodes || []);
}
onConnectEnd?.(event);
+23 -12
View File
@@ -1,9 +1,9 @@
import React, { memo, useContext, useCallback, HTMLAttributes, forwardRef } from 'react';
import cc from 'classcat';
import { useStoreActions, useStoreState, useStore } from '../../store/hooks';
import { useStore } from '../../store';
import NodeIdContext from '../../contexts/NodeIdContext';
import { HandleProps, Connection, ElementId, Position, Node } from '../../types';
import { HandleProps, Connection, ElementId, Position, Node, ReactFlowState } from '../../types';
import { onMouseDown, SetSourceIdFunc, SetPosition } from './handler';
@@ -11,6 +11,16 @@ const alwaysValid = () => true;
export type HandleComponentProps = HandleProps & Omit<HTMLAttributes<HTMLDivElement>, 'id'>;
const selector = (s: ReactFlowState) => ({
setPosition: s.setConnectionPosition,
setConnectionNodeId: s.setConnectionNodeId,
onConnectAction: s.onConnect,
onConnectStart: s.onConnectStart,
onConnectStop: s.onConnectStop,
onConnectEnd: s.onConnectEnd,
connectionMode: s.connectionMode,
});
const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
(
{
@@ -26,15 +36,17 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
},
ref
) => {
const store = useStore();
const nodeId = useContext(NodeIdContext) as ElementId;
const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
const onConnectAction = useStoreState((state) => state.onConnect);
const onConnectStart = useStoreState((state) => state.onConnectStart);
const onConnectStop = useStoreState((state) => state.onConnectStop);
const onConnectEnd = useStoreState((state) => state.onConnectEnd);
const connectionMode = useStoreState((state) => state.connectionMode);
const {
setPosition,
setConnectionNodeId,
onConnectAction,
onConnectStart,
onConnectStop,
onConnectEnd,
connectionMode,
} = useStore(selector);
const handleId = id || null;
const isTarget = type === 'target';
@@ -62,8 +74,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
undefined,
onConnectStart,
onConnectStop,
onConnectEnd,
store
onConnectEnd
);
},
[
+9 -7
View File
@@ -8,13 +8,15 @@ const DefaultNode = ({
isConnectable,
targetPosition = Position.Top,
sourcePosition = Position.Bottom,
}: NodeProps) => (
<>
<Handle type="target" position={targetPosition} isConnectable={isConnectable} />
{data.label}
<Handle type="source" position={sourcePosition} isConnectable={isConnectable} />
</>
);
}: NodeProps) => {
return (
<>
<Handle type="target" position={targetPosition} isConnectable={isConnectable} />
{data.label}
<Handle type="source" position={sourcePosition} isConnectable={isConnectable} />
</>
);
};
DefaultNode.displayName = 'DefaultNode';
+14 -16
View File
@@ -13,7 +13,7 @@ export const getHandleBounds = (nodeElement: HTMLDivElement, scale: number) => {
export const getHandleBoundsByHandleType = (
selector: string,
nodeElement: HTMLDivElement,
parentBounds: ClientRect | DOMRect,
parentBounds: DOMRect,
k: number
): HandleElement[] | null => {
const handles = nodeElement.querySelectorAll(selector);
@@ -24,20 +24,18 @@ export const getHandleBoundsByHandleType = (
const handlesArray = Array.from(handles) as HTMLDivElement[];
return handlesArray.map(
(handle): HandleElement => {
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
const handleId = handle.getAttribute('data-handleid');
const handlePosition = (handle.getAttribute('data-handlepos') as unknown) as Position;
return handlesArray.map((handle): HandleElement => {
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
const handleId = handle.getAttribute('data-handleid');
const handlePosition = handle.getAttribute('data-handlepos') as unknown as Position;
return {
id: handleId,
position: handlePosition,
x: (bounds.left - parentBounds.left) / k,
y: (bounds.top - parentBounds.top) / k,
...dimensions,
};
}
);
return {
id: handleId,
position: handlePosition,
x: (bounds.left - parentBounds.left) / k,
y: (bounds.top - parentBounds.top) / k,
...dimensions,
};
});
};
+12 -9
View File
@@ -2,9 +2,15 @@ import React, { useEffect, useRef, memo, ComponentType, CSSProperties, useMemo,
import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable';
import cc from 'classcat';
import { useStoreActions, useStoreState } from '../../store/hooks';
import { useStore } from '../../store';
import { Provider } from '../../contexts/NodeIdContext';
import { NodeComponentProps, WrapNodeProps } from '../../types';
import { NodeComponentProps, WrapNodeProps, ReactFlowState } from '../../types';
const selector = (s: ReactFlowState) => ({
addSelectedElements: s.addSelectedElements,
onNodesChange: s.onNodesChange,
unsetNodesSelection: s.unsetNodesSelection,
});
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const NodeWrapper = ({
@@ -41,10 +47,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
dragHandle,
}: WrapNodeProps) => {
// const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
const onNodesChange = useStoreState((state) => state.onNodesChange);
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
const { addSelectedElements, onNodesChange, unsetNodesSelection } = useStore(selector);
const nodeElement = useRef<HTMLDivElement>(null);
const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]);
@@ -114,7 +117,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
unsetNodesSelection();
if (!selected) {
addSelectedElements(node);
addSelectedElements([node]);
}
}
@@ -132,7 +135,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
unsetNodesSelection();
if (!selected) {
addSelectedElements(node);
addSelectedElements([node]);
}
} else if (!selectNodesOnDrag && !selected && isSelectable) {
unsetNodesSelection();
@@ -173,7 +176,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
// Because of that we set dragging to true inside the onDrag handler and handle the click here
if (!isDragging) {
if (isSelectable && !selectNodesOnDrag && !selected) {
addSelectedElements(node);
addSelectedElements([node]);
}
onClick?.(event as MouseEvent, node);
+24 -11
View File
@@ -6,9 +6,9 @@
import React, { useMemo, useCallback, useRef, MouseEvent } from 'react';
import ReactDraggable, { DraggableData } from 'react-draggable';
import { useStoreState, useStoreActions } from '../../store/hooks';
import { useStore } from '../../store';
import { isNode } from '../../utils/graph';
import { Node } from '../../types';
import { Node, ReactFlowState } from '../../types';
export interface NodesSelectionProps {
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void;
@@ -17,21 +17,34 @@ export interface NodesSelectionProps {
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void;
}
const selector = (s: ReactFlowState) => ({
transform: s.transform,
selectedNodesBbox: s.selectedNodesBbox,
selectionActive: s.selectionActive,
selectedElements: s.selectedElements,
snapToGrid: s.snapToGrid,
snapGrid: s.snapGrid,
nodes: s.nodes,
updateNodePosDiff: s.updateNodePosDiff,
});
export default ({
onSelectionDragStart,
onSelectionDrag,
onSelectionDragStop,
onSelectionContextMenu,
}: NodesSelectionProps) => {
const [tX, tY, tScale] = useStoreState((state) => state.transform);
const selectedNodesBbox = useStoreState((state) => state.selectedNodesBbox);
const selectionActive = useStoreState((state) => state.selectionActive);
const selectedElements = useStoreState((state) => state.selectedElements);
const snapToGrid = useStoreState((state) => state.snapToGrid);
const snapGrid = useStoreState((state) => state.snapGrid);
const nodes = useStoreState((state) => state.nodes);
const updateNodePosDiff = useStoreActions((actions) => actions.updateNodePosDiff);
const {
transform,
selectedNodesBbox,
selectionActive,
selectedElements,
snapToGrid,
snapGrid,
nodes,
updateNodePosDiff,
} = useStore(selector);
const [tX, tY, tScale] = transform;
const nodeRef = useRef(null);
+5 -5
View File
@@ -1,18 +1,18 @@
import { useEffect } from 'react';
import { Elements } from '../../types';
import { useStoreState } from '../../store/hooks';
import { Elements, ReactFlowState } from '../../types';
import { useStore } from '../../store';
interface SelectionListenerProps {
onSelectionChange: (elements: Elements | null) => void;
}
const selectedElementsSelector = (s: ReactFlowState) => s.selectedElements;
// This is just a helper component for calling the onSelectionChange listener.
// As soon as easy-peasy has implemented the effectOn hook, we can remove this component
// and use the hook instead. https://github.com/ctrlplusb/easy-peasy/pull/459
export default ({ onSelectionChange }: SelectionListenerProps) => {
const selectedElements = useStoreState((s) => s.selectedElements);
const selectedElements = useStore(selectedElementsSelector);
useEffect(() => {
onSelectionChange(selectedElements);
+22 -9
View File
@@ -4,8 +4,8 @@
import React, { memo } from 'react';
import { useStoreActions, useStoreState } from '../../store/hooks';
import { XYPosition } from '../../types';
import { useStore } from '../../store';
import { XYPosition, ReactFlowState } from '../../types';
type UserSelectionProps = {
selectionKeyPressed: boolean;
@@ -25,8 +25,10 @@ function getMousePosition(event: React.MouseEvent): XYPosition | void {
};
}
const userSelectionRectSelector = (state: ReactFlowState) => state.userSelectionRect;
const SelectionRect = () => {
const userSelectionRect = useStoreState((state) => state.userSelectionRect);
const userSelectionRect = useStore(userSelectionRectSelector);
if (!userSelectionRect.draw) {
return null;
@@ -44,14 +46,25 @@ const SelectionRect = () => {
);
};
const selector = (s: ReactFlowState) => ({
selectionActive: s.selectionActive,
elementsSelectable: s.elementsSelectable,
setUserSelection: s.setUserSelection,
updateUserSelection: s.updateUserSelection,
unsetUserSelection: s.unsetUserSelection,
unsetNodesSelection: s.unsetNodesSelection,
});
export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
const selectionActive = useStoreState((state) => state.selectionActive);
const elementsSelectable = useStoreState((state) => state.elementsSelectable);
const {
selectionActive,
elementsSelectable,
setUserSelection,
updateUserSelection,
unsetUserSelection,
unsetNodesSelection,
} = useStore(selector);
const setUserSelection = useStoreActions((actions) => actions.setUserSelection);
const updateUserSelection = useStoreActions((actions) => actions.updateUserSelection);
const unsetUserSelection = useStoreActions((actions) => actions.unsetUserSelection);
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
const renderUserSelectionPane = selectionActive || selectionKeyPressed;
if (!elementsSelectable || !renderUserSelectionPane) {
+29 -12
View File
@@ -1,6 +1,6 @@
import React, { memo, CSSProperties, useCallback } from 'react';
import { useStoreState } from '../../store/hooks';
import { useStore } from '../../store';
import ConnectionLine from '../../components/ConnectionLine/index';
import { isEdge } from '../../utils/graph';
import MarkerDefinitions from './MarkerDefinitions';
@@ -15,6 +15,7 @@ import {
ConnectionLineComponent,
ConnectionMode,
OnEdgeUpdateFunc,
ReactFlowState,
} from '../../types';
interface EdgeRendererProps {
@@ -199,18 +200,34 @@ const Edge = memo(
}
);
const selector = (s: ReactFlowState) => ({
transform: s.transform,
edges: s.edges,
connectionNodeId: s.connectionNodeId,
connectionHandleId: s.connectionHandleId,
connectionHandleType: s.connectionHandleType,
connectionPosition: s.connectionPosition,
selectedElements: s.selectedElements,
nodesConnectable: s.nodesConnectable,
elementsSelectable: s.elementsSelectable,
width: s.width,
height: s.height,
});
const EdgeRenderer = (props: EdgeRendererProps) => {
const transform = useStoreState((state) => state.transform);
const edges = useStoreState((state) => state.edges);
const connectionNodeId = useStoreState((state) => state.connectionNodeId);
const connectionHandleId = useStoreState((state) => state.connectionHandleId);
const connectionHandleType = useStoreState((state) => state.connectionHandleType);
const connectionPosition = useStoreState((state) => state.connectionPosition);
const selectedElements = useStoreState((state) => state.selectedElements);
const nodesConnectable = useStoreState((state) => state.nodesConnectable);
const elementsSelectable = useStoreState((state) => state.elementsSelectable);
const width = useStoreState((state) => state.width);
const height = useStoreState((state) => state.height);
const {
transform,
edges,
connectionNodeId,
connectionHandleId,
connectionHandleType,
connectionPosition,
selectedElements,
nodesConnectable,
elementsSelectable,
width,
height,
} = useStore(selector);
if (!width) {
return null;
+9 -5
View File
@@ -1,13 +1,13 @@
import React, { useCallback, memo, ReactNode, WheelEvent, MouseEvent } from 'react';
import { useStoreActions, useStoreState } from '../../store/hooks';
import { useStore } from '../../store';
import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler';
import useKeyPress from '../../hooks/useKeyPress';
import { GraphViewProps } from '../GraphView';
import ZoomPane from '../ZoomPane';
import UserSelection from '../../components/UserSelection';
import NodesSelection from '../../components/NodesSelection';
import { ReactFlowState } from '../../types';
interface FlowRendererProps
extends Omit<
@@ -25,6 +25,12 @@ interface FlowRendererProps
children: ReactNode;
}
const selector = (s: ReactFlowState) => ({
unsetNodesSelection: s.unsetNodesSelection,
resetSelectedElements: s.resetSelectedElements,
nodesSelectionActive: s.nodesSelectionActive,
});
const FlowRenderer = ({
children,
onPaneClick,
@@ -54,9 +60,7 @@ const FlowRenderer = ({
onSelectionDragStop,
onSelectionContextMenu,
}: FlowRendererProps) => {
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
const resetSelectedElements = useStoreActions((actions) => actions.resetSelectedElements);
const nodesSelectionActive = useStoreState((state) => state.nodesSelectionActive);
const { unsetNodesSelection, resetSelectedElements, nodesSelectionActive } = useStore(selector);
const selectionKeyPressed = useKeyPress(selectionKeyCode);
+21 -21
View File
@@ -1,6 +1,6 @@
import React, { useEffect, useRef, memo } from 'react';
import { useStoreActions, useStore } from '../../store/hooks';
import { useStore, useStoreApi } from '../../store';
import FlowRenderer from '../FlowRenderer';
import NodeRenderer from '../NodeRenderer';
import EdgeRenderer from '../EdgeRenderer';
@@ -98,24 +98,24 @@ const GraphView = ({
onEdgesChange,
}: GraphViewProps) => {
const isInitialized = useRef<boolean>(false);
const setOnConnect = useStoreActions((actions) => actions.setOnConnect);
const setOnConnectStart = useStoreActions((actions) => actions.setOnConnectStart);
const setOnConnectStop = useStoreActions((actions) => actions.setOnConnectStop);
const setOnConnectEnd = useStoreActions((actions) => actions.setOnConnectEnd);
const setSnapGrid = useStoreActions((actions) => actions.setSnapGrid);
const setSnapToGrid = useStoreActions((actions) => actions.setSnapToGrid);
const setNodesDraggable = useStoreActions((actions) => actions.setNodesDraggable);
const setNodesConnectable = useStoreActions((actions) => actions.setNodesConnectable);
const setElementsSelectable = useStoreActions((actions) => actions.setElementsSelectable);
const setMinZoom = useStoreActions((actions) => actions.setMinZoom);
const setMaxZoom = useStoreActions((actions) => actions.setMaxZoom);
const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent);
const setNodeExtent = useStoreActions((actions) => actions.setNodeExtent);
const setConnectionMode = useStoreActions((actions) => actions.setConnectionMode);
const setOnNodesChange = useStoreActions((actions) => actions.setOnNodesChange);
const setOnEdgesChange = useStoreActions((actions) => actions.setOnEdgesChange);
const store = useStoreApi();
const setOnConnect = useStore((s) => s.setOnConnect);
const setOnConnectStart = useStore((s) => s.setOnConnectStart);
const setOnConnectStop = useStore((s) => s.setOnConnectStop);
const setOnConnectEnd = useStore((s) => s.setOnConnectEnd);
const setSnapGrid = useStore((s) => s.setSnapGrid);
const setSnapToGrid = useStore((s) => s.setSnapToGrid);
const setNodesDraggable = useStore((s) => s.setNodesDraggable);
const setNodesConnectable = useStore((s) => s.setNodesConnectable);
const setElementsSelectable = useStore((s) => s.setElementsSelectable);
const setMinZoom = useStore((s) => s.setMinZoom);
const setMaxZoom = useStore((s) => s.setMaxZoom);
const setTranslateExtent = useStore((s) => s.setTranslateExtent);
const setNodeExtent = useStore((s) => s.setNodeExtent);
const setConnectionMode = useStore((s) => s.setConnectionMode);
const setOnNodesChange = useStore((s) => s.setOnNodesChange);
const setOnEdgesChange = useStore((s) => s.setOnEdgesChange);
const currentStore = useStore();
const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper();
useEffect(() => {
@@ -127,9 +127,9 @@ const GraphView = ({
zoomOut,
zoomTo,
setTransform: transform,
project: onLoadProject(currentStore),
getElements: onLoadGetElements(currentStore),
toObject: onLoadToObject(currentStore),
project: onLoadProject(store.getState),
getElements: onLoadGetElements(store.getState),
toObject: onLoadToObject(store.getState),
});
}
+21 -9
View File
@@ -1,7 +1,7 @@
import React, { memo, useMemo, ComponentType, MouseEvent } from 'react';
import { useStoreState, useStoreActions } from '../../store/hooks';
import { Node, NodeTypesType, WrapNodeProps, Edge } from '../../types';
import { useStore } from '../../store';
import { Node, NodeTypesType, ReactFlowState, Edge, WrapNodeProps } from '../../types';
interface NodeRendererProps {
nodeTypes: NodeTypesType;
selectNodesOnDrag: boolean;
@@ -19,14 +19,26 @@ interface NodeRendererProps {
onlyRenderVisibleElements: boolean;
}
const selector = (s: ReactFlowState) => ({
transform: s.transform,
selectedElements: s.selectedElements,
nodesDraggable: s.nodesDraggable,
nodesConnectable: s.nodesConnectable,
elementsSelectable: s.elementsSelectable,
nodes: s.nodes,
updateNodeDimensions: s.updateNodeDimensions,
});
const NodeRenderer = (props: NodeRendererProps) => {
const transform = useStoreState((state) => state.transform);
const selectedElements = useStoreState((state) => state.selectedElements);
const nodesDraggable = useStoreState((state) => state.nodesDraggable);
const nodesConnectable = useStoreState((state) => state.nodesConnectable);
const elementsSelectable = useStoreState((state) => state.elementsSelectable);
const nodes = useStoreState((state) => state.nodes);
const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
const {
transform,
selectedElements,
nodesDraggable,
nodesConnectable,
elementsSelectable,
nodes,
updateNodeDimensions,
} = useStore(selector);
// const visibleNodes = props.onlyRenderVisibleElements
// ? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true)
+18 -13
View File
@@ -1,21 +1,26 @@
import React, { FC, useContext, useMemo } from 'react';
import { Provider, ReactReduxContext } from 'react-redux';
import React, { FC } from 'react';
import store from '../../store';
import { Provider, createStore } from '../../store';
// import { ReactFlowState } from '../../types';
// const reactFlowVersionSelector = (s: ReactFlowState) => s.reactFlowVersion;
const Wrapper: FC = ({ children }) => {
const contextValue = useContext(ReactReduxContext);
const isWrappedWithReactFlowProvider = useMemo(() => contextValue?.store?.getState()?.reactFlowVersion, [
contextValue,
]);
// let isWrapped = useRef<boolean>(true);
if (isWrappedWithReactFlowProvider) {
// we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
return <>{children}</>;
}
// try {
// useStoreApi();
// } catch {
// isWrapped.current = false;
// }
return <Provider store={store}>{children}</Provider>;
// if (isWrapped) {
// // we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
// // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
// return <>{children}</>;
// }
return <Provider createStore={createStore}>{children}</Provider>;
};
Wrapper.displayName = 'ReactFlowWrapper';
+13 -11
View File
@@ -5,8 +5,8 @@ import { select, pointer } from 'd3-selection';
import { clamp } from '../../utils';
import useKeyPress from '../../hooks/useKeyPress';
import useResizeHandler from '../../hooks/useResizeHandler';
import { useStoreState, useStoreActions, useStore } from '../../store/hooks';
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode } from '../../types';
import { useStore, useStoreApi } from '../../store';
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode, ReactFlowState } from '../../types';
interface ZoomPaneProps {
selectionKeyPressed: boolean;
@@ -42,6 +42,15 @@ const eventToFlowTransform = (eventTransform: any): FlowTransform => ({
const hasNoWheelClass = (event: any) => event.target.closest('.nowheel');
const selector = (s: ReactFlowState) => ({
d3Zoom: s.d3Zoom,
d3Selection: s.d3Selection,
d3ZoomHandler: s.d3ZoomHandler,
initD3Zoom: s.initD3Zoom,
updateTransform: s.updateTransform,
});
const ZoomPane = ({
onMove,
onMoveStart,
@@ -62,17 +71,10 @@ const ZoomPane = ({
preventScrolling = true,
children,
}: ZoomPaneProps) => {
const store = useStoreApi();
const zoomPane = useRef<HTMLDivElement>(null);
const prevTransform = useRef<FlowTransform>({ x: 0, y: 0, zoom: 0 });
const store = useStore();
const d3Zoom = useStoreState((s) => s.d3Zoom);
const d3Selection = useStoreState((s) => s.d3Selection);
const d3ZoomHandler = useStoreState((s) => s.d3ZoomHandler);
const initD3Zoom = useStoreActions((actions) => actions.initD3Zoom);
const updateTransform = useStoreActions((actions) => actions.updateTransform);
const { d3Zoom, d3Selection, d3ZoomHandler, initD3Zoom, updateTransform } = useStore(selector);
const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
useResizeHandler(zoomPane);
+13 -9
View File
@@ -1,23 +1,27 @@
import { useEffect } from 'react';
import { useStore, useStoreActions, useStoreState } from '../store/hooks';
import { useStore, useStoreApi } from '../store';
import useKeyPress from './useKeyPress';
import { isNode, isEdge, getConnectedEdges } from '../utils/graph';
import { KeyCode } from '../types';
import { KeyCode, ReactFlowState } from '../types';
interface HookParams {
deleteKeyCode: KeyCode;
multiSelectionKeyCode: KeyCode;
}
export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
const store = useStore();
const selector = (s: ReactFlowState) => ({
unsetNodesSelection: s.unsetNodesSelection,
setMultiSelectionActive: s.setMultiSelectionActive,
resetSelectedElements: s.resetSelectedElements,
onNodesChange: s.onNodesChange,
onEdgesChange: s.onEdgesChange,
});
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
const setMultiSelectionActive = useStoreActions((actions) => actions.setMultiSelectionActive);
const resetSelectedElements = useStoreActions((actions) => actions.resetSelectedElements);
const onNodesChange = useStoreState((state) => state.onNodesChange);
const onEdgesChange = useStoreState((state) => state.onEdgesChange);
export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
const store = useStoreApi();
const { unsetNodesSelection, setMultiSelectionActive, resetSelectedElements, onNodesChange, onEdgesChange } =
useStore(selector);
const deleteKeyPressed = useKeyPress(deleteKeyCode);
const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
+5 -2
View File
@@ -1,10 +1,13 @@
import { useEffect, MutableRefObject } from 'react';
import { useStoreActions } from '../store/hooks';
import { useStore } from '../store';
import { getDimensions } from '../utils';
import { ReactFlowState } from '../types';
const updateSizeSelector = (state: ReactFlowState) => state.updateSize;
export default (rendererNode: MutableRefObject<HTMLDivElement | null>) => {
const updateSize = useStoreActions((actions) => actions.updateSize);
const updateSize = useStore(updateSizeSelector);
useEffect(() => {
let resizeObserver: ResizeObserver;
+6 -4
View File
@@ -1,13 +1,15 @@
import { useCallback } from 'react';
import { useStoreActions } from '../store/hooks';
import { ElementId, UpdateNodeInternals } from '../types';
import { useStore } from '../store';
import { ElementId, UpdateNodeInternals, ReactFlowState } from '../types';
const updateNodeDimsSelector = (state: ReactFlowState) => state.updateNodeDimensions;
function useUpdateNodeInternals(): UpdateNodeInternals {
const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
const updateNodeDimensions = useStore(updateNodeDimsSelector);
return useCallback<UpdateNodeInternals>((id: ElementId) => {
const nodeElement = document.querySelector(`.react-flow__node[data-id="${id}"]`);
const nodeElement = document.querySelector(`.react-flow__node[data-id="${id}"]`) as HTMLDivElement;
if (nodeElement) {
updateNodeDimensions([{ id, nodeElement, forceUpdate: true }]);
+9 -5
View File
@@ -1,9 +1,9 @@
import { useMemo } from 'react';
import { zoomIdentity } from 'd3-zoom';
import { useStoreState, useStore } from '../store/hooks';
import { useStoreApi, useStore } from '../store';
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph';
import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, Rect, XYPosition } from '../types';
import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, Rect, XYPosition, ReactFlowState } from '../types';
const DEFAULT_PADDING = 0.1;
@@ -19,10 +19,14 @@ const initialZoomPanHelper: ZoomPanHelperFunctions = {
initialized: false,
};
const selector = (s: ReactFlowState) => ({
d3Zoom: s.d3Zoom,
d3Selection: s.d3Selection,
});
const useZoomPanHelper = (): ZoomPanHelperFunctions => {
const store = useStore();
const d3Zoom = useStoreState((s) => s.d3Zoom);
const d3Selection = useStoreState((s) => s.d3Selection);
const store = useStoreApi();
const { d3Zoom, d3Selection } = useStore(selector);
const zoomPanHelperFunctions = useMemo<ZoomPanHelperFunctions>(() => {
if (d3Selection && d3Zoom) {
+1 -1
View File
@@ -28,7 +28,7 @@ export { default as useZoomPanHelper } from './hooks/useZoomPanHelper';
export { default as useUpdateNodeInternals } from './hooks/useUpdateNodeInternals';
export * from './additional-components';
export * from './store/hooks';
export { useStore, useStoreApi } from './store';
export * from './types';
export { ReactFlowProps } from './container/ReactFlow';
-176
View File
@@ -1,176 +0,0 @@
import { createAction } from './utils';
import {
Node,
Edge,
Elements,
OnConnectEndFunc,
OnConnectFunc,
OnConnectStartFunc,
OnConnectStopFunc,
NodeDimensionUpdate,
NodePosUpdate,
NodeDiffUpdate,
XYPosition,
Transform,
Dimensions,
InitD3ZoomPayload,
TranslateExtent,
SetConnectionId,
SnapGrid,
ConnectionMode,
NodeExtent,
OnElementsChange,
} from '../types';
import * as constants from './contants';
export const setOnConnect = (onConnect: OnConnectFunc) =>
createAction(constants.SET_ON_CONNECT, {
onConnect,
});
export const setOnConnectStart = (onConnectStart: OnConnectStartFunc) =>
createAction(constants.SET_ON_CONNECT_START, {
onConnectStart,
});
export const setOnConnectStop = (onConnectStop: OnConnectStopFunc) =>
createAction(constants.SET_ON_CONNECT_STOP, {
onConnectStop,
});
export const setOnConnectEnd = (onConnectEnd: OnConnectEndFunc) =>
createAction(constants.SET_ON_CONNECT_END, {
onConnectEnd,
});
export const setNodes = (nodes: Node[]) => createAction(constants.SET_NODES, nodes);
export const setEdges = (edges: Edge[]) => createAction(constants.SET_EDGES, edges);
export const updateNodeDimensions = (updates: NodeDimensionUpdate[]) =>
createAction(constants.UPDATE_NODE_DIMENSIONS, updates);
export const updateNodePos = (payload: NodePosUpdate) => createAction(constants.UPDATE_NODE_POS, payload);
export const updateNodePosDiff = (payload: NodeDiffUpdate) => createAction(constants.UPDATE_NODE_POS_DIFF, payload);
export const setUserSelection = (mousePos: XYPosition) => createAction(constants.SET_USER_SELECTION, mousePos);
export const updateUserSelection = (mousePos: XYPosition) => createAction(constants.UPDATE_USER_SELECTION, mousePos);
export const unsetUserSelection = () => createAction(constants.UNSET_USER_SELECTION);
export const setSelection = (selectionActive: boolean) =>
createAction(constants.SET_SELECTION, {
selectionActive,
});
export const unsetNodesSelection = () =>
createAction(constants.UNSET_NODES_SELECTION, {
nodesSelectionActive: false,
});
export const resetSelectedElements = () =>
createAction(constants.RESET_SELECTED_ELEMENTS, {
selectedElements: null,
});
export const setSelectedElements = (elements: Elements) => createAction(constants.SET_SELECTED_ELEMENTS, elements);
export const addSelectedElements = (elements: Elements) => createAction(constants.ADD_SELECTED_ELEMENTS, elements);
export const updateTransform = (transform: Transform) => createAction(constants.UPDATE_TRANSFORM, { transform });
export const updateSize = (size: Dimensions) =>
createAction(constants.UPDATE_SIZE, {
width: size.width || 500,
height: size.height || 500,
});
export const initD3Zoom = (payload: InitD3ZoomPayload) => createAction(constants.INIT_D3ZOOM, payload);
export const setMinZoom = (minZoom: number) => createAction(constants.SET_MINZOOM, minZoom);
export const setMaxZoom = (maxZoom: number) => createAction(constants.SET_MAXZOOM, maxZoom);
export const setTranslateExtent = (translateExtent: TranslateExtent) =>
createAction(constants.SET_TRANSLATEEXTENT, translateExtent);
export const setConnectionPosition = (connectionPosition: XYPosition) =>
createAction(constants.SET_CONNECTION_POSITION, { connectionPosition });
export const setConnectionNodeId = (payload: SetConnectionId) => createAction(constants.SET_CONNECTION_NODEID, payload);
export const setSnapToGrid = (snapToGrid: boolean) => createAction(constants.SET_SNAPTOGRID, { snapToGrid });
export const setSnapGrid = (snapGrid: SnapGrid) => createAction(constants.SET_SNAPGRID, { snapGrid });
export const setInteractive = (isInteractive: boolean) =>
createAction(constants.SET_INTERACTIVE, {
nodesDraggable: isInteractive,
nodesConnectable: isInteractive,
elementsSelectable: isInteractive,
});
export const setNodesDraggable = (nodesDraggable: boolean) =>
createAction(constants.SET_NODES_DRAGGABLE, { nodesDraggable });
export const setNodesConnectable = (nodesConnectable: boolean) =>
createAction(constants.SET_NODES_CONNECTABLE, { nodesConnectable });
export const setElementsSelectable = (elementsSelectable: boolean) =>
createAction(constants.SET_ELEMENTS_SELECTABLE, { elementsSelectable });
export const setMultiSelectionActive = (multiSelectionActive: boolean) =>
createAction(constants.SET_MULTI_SELECTION_ACTIVE, { multiSelectionActive });
export const setConnectionMode = (connectionMode: ConnectionMode) =>
createAction(constants.SET_CONNECTION_MODE, { connectionMode });
export const setNodeExtent = (nodeExtent: NodeExtent) => createAction(constants.SET_NODE_EXTENT, nodeExtent);
export const setOnNodesChange = (onNodesChange: OnElementsChange) =>
createAction(constants.SET_ON_NODES_CHANGE, { onNodesChange });
export const setOnEdgesChange = (onEdgesChange: OnElementsChange) =>
createAction(constants.SET_ON_EDGES_CHANGE, { onEdgesChange });
export type ReactFlowAction = ReturnType<
| typeof setOnConnect
| typeof setOnConnectStart
| typeof setOnConnectStop
| typeof setOnConnectEnd
| typeof setNodes
| typeof setEdges
| typeof updateNodeDimensions
| typeof updateNodePos
| typeof updateNodePosDiff
| typeof setUserSelection
| typeof updateUserSelection
| typeof unsetUserSelection
| typeof setSelection
| typeof unsetNodesSelection
| typeof resetSelectedElements
| typeof setSelectedElements
| typeof addSelectedElements
| typeof updateTransform
| typeof updateSize
| typeof initD3Zoom
| typeof setMinZoom
| typeof setMaxZoom
| typeof setTranslateExtent
| typeof setConnectionPosition
| typeof setConnectionNodeId
| typeof setSnapToGrid
| typeof setSnapGrid
| typeof setInteractive
| typeof setNodesDraggable
| typeof setNodesConnectable
| typeof setElementsSelectable
| typeof setMultiSelectionActive
| typeof setConnectionMode
| typeof setNodeExtent
| typeof setOnNodesChange
| typeof setOnEdgesChange
>;
-11
View File
@@ -1,11 +0,0 @@
import { createStore, applyMiddleware, Store } from 'redux';
import thunk from 'redux-thunk';
import { ReactFlowState } from '../types';
import { ReactFlowAction } from './actions';
import reactFlowReducer from './reducer';
export default function configureStore(preloadedState: ReactFlowState): Store<ReactFlowState, ReactFlowAction> {
const store = createStore(reactFlowReducer, preloadedState, applyMiddleware(thunk));
return store;
}
-36
View File
@@ -1,36 +0,0 @@
export const SET_ON_CONNECT = 'SET_ON_CONNECT';
export const SET_ON_CONNECT_START = 'SET_ON_CONNECT_START';
export const SET_ON_CONNECT_STOP = 'SET_ON_CONNECT_STOP';
export const SET_ON_CONNECT_END = 'SET_ON_CONNECT_END';
export const SET_NODES = 'SET_NODES';
export const SET_EDGES = 'SET_EDGES';
export const UPDATE_NODE_DIMENSIONS = 'UPDATE_NODE_DIMENSIONS';
export const UPDATE_NODE_POS = 'UPDATE_NODE_POS';
export const UPDATE_NODE_POS_DIFF = 'UPDATE_NODE_POS_DIFF';
export const SET_USER_SELECTION = 'SET_USER_SELECTION';
export const UPDATE_USER_SELECTION = 'UPDATE_USER_SELECTION';
export const UNSET_USER_SELECTION = 'UNSET_USER_SELECTION';
export const SET_SELECTION = 'SET_SELECTION';
export const UNSET_NODES_SELECTION = 'UNSET_NODES_SELECTION';
export const SET_SELECTED_ELEMENTS = 'SET_SELECTED_ELEMENTS';
export const RESET_SELECTED_ELEMENTS = 'RESET_SELECTED_ELEMENTS';
export const ADD_SELECTED_ELEMENTS = 'ADD_SELECTED_ELEMENTS';
export const UPDATE_TRANSFORM = 'UPDATE_TRANSFORM';
export const UPDATE_SIZE = 'UPDATE_SIZE';
export const INIT_D3ZOOM = 'INIT_D3ZOOM';
export const SET_MINZOOM = 'SET_MINZOOM';
export const SET_MAXZOOM = 'SET_MAXZOOM';
export const SET_TRANSLATEEXTENT = 'SET_TRANSLATEEXTENT';
export const SET_CONNECTION_POSITION = 'SET_CONNECTION_POSITION';
export const SET_CONNECTION_NODEID = 'SET_CONNECTION_NODEID';
export const SET_SNAPTOGRID = 'SET_SNAPTOGRID';
export const SET_SNAPGRID = 'SET_SNAPGRID';
export const SET_INTERACTIVE = 'SET_INTERACTIVE';
export const SET_NODES_DRAGGABLE = 'SET_NODES_DRAGGABLE';
export const SET_NODES_CONNECTABLE = 'SET_NODES_CONNECTABLE';
export const SET_ELEMENTS_SELECTABLE = 'SET_ELEMENTS_SELECTABLE';
export const SET_MULTI_SELECTION_ACTIVE = 'SET_MULTI_SELECTION_ACTIVE';
export const SET_CONNECTION_MODE = 'SET_CONNECTION_MODE';
export const SET_NODE_EXTENT = 'SET_NODE_EXTENT';
export const SET_ON_NODES_CHANGE = 'SET_ON_NODES_CHANGE';
export const SET_ON_EDGES_CHANGE = 'SET_ON_EDGES_CHANGE';
-48
View File
@@ -1,48 +0,0 @@
import { bindActionCreators, Store, ActionCreator, ActionCreatorsMapObject } from 'redux';
import {
useStore as useStoreRedux,
useSelector,
useDispatch as reduxUseDispatch,
TypedUseSelectorHook,
} from 'react-redux';
import { useMemo } from 'react';
import { ReactFlowDispatch } from './index';
import * as actions from './actions';
import { ReactFlowAction } from './actions';
import { ReactFlowState } from '../types';
export const useTypedSelector: TypedUseSelectorHook<ReactFlowState> = useSelector;
export type ActionCreatorSelector<Action> = (acts: typeof actions) => ActionCreator<Action>;
export type ActionMapObjectSelector<Action> = (acts: typeof actions) => ActionCreatorsMapObject<Action>;
export type ActionSelector<Action> = (acts: typeof actions) => ActionCreatorsMapObject<Action> | ActionCreator<Action>;
export function useStoreActions<Action extends ReactFlowAction>(
actionSelector: ActionCreatorSelector<Action>
): ActionCreator<Action>;
export function useStoreActions<Action extends ReactFlowAction>(
actionSelector: ActionMapObjectSelector<Action>
): ActionCreatorsMapObject<Action>;
export function useStoreActions<Action extends ReactFlowAction>(actionSelector: ActionSelector<Action>) {
const dispatch: ReactFlowDispatch = reduxUseDispatch();
const currAction = actionSelector(actions);
const action = useMemo(() => {
// this looks weird but required if both ActionSelector and ActionMapObjectSelector are supported
return typeof currAction === 'function'
? bindActionCreators(currAction, dispatch)
: bindActionCreators(currAction, dispatch);
}, [dispatch, currAction]);
return action;
}
export const useStoreState = useTypedSelector;
export const useStore = (): Store<ReactFlowState, ReactFlowAction> => {
const store = useStoreRedux<ReactFlowState, ReactFlowAction>();
return store;
};
export const useDispatch: ReactFlowDispatch = reduxUseDispatch;
+398 -54
View File
@@ -1,66 +1,410 @@
import configureStore from './configure-store';
import create from 'zustand';
import createContext from 'zustand/context';
import isEqual from 'fast-deep-equal';
import { ReactFlowState, ConnectionMode } from '../types';
import { clampPosition, getDimensions } from '../utils';
import {
ReactFlowState,
ConnectionMode,
Node,
Edge,
ElementChange,
NodeDimensionUpdate,
NodeDiffUpdate,
XYPosition,
Elements,
InitD3ZoomPayload,
TranslateExtent,
NodeExtent,
Transform,
Dimensions,
OnConnectFunc,
OnConnectStartFunc,
OnConnectStopFunc,
OnConnectEndFunc,
SetConnectionId,
SnapGrid,
OnElementsChange,
} from '../types';
import { parseNode, parseEdge, isNode, getRectOfNodes, getNodesInside, getConnectedEdges } from '../utils/graph';
import { getSourceTargetNodes } from '../container/EdgeRenderer/utils';
import { getHandleBounds } from '../components/Nodes/utils';
export const initialState: ReactFlowState = {
width: 0,
height: 0,
transform: [0, 0, 1],
nodes: [],
edges: [],
onNodesChange: null,
onEdgesChange: null,
const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
selectedElements: null,
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
d3Zoom: null,
d3Selection: null,
d3ZoomHandler: undefined,
minZoom: 0.5,
maxZoom: 2,
translateExtent: [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
],
nodeExtent: [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
],
nodesSelectionActive: false,
selectionActive: false,
userSelectionRect: {
startX: 0,
startY: 0,
x: 0,
y: 0,
const createStore = () =>
create<ReactFlowState>((set, get) => ({
width: 0,
height: 0,
draw: false,
},
connectionNodeId: null,
connectionHandleId: null,
connectionHandleType: 'source',
connectionPosition: { x: 0, y: 0 },
connectionMode: ConnectionMode.Strict,
transform: [0, 0, 1],
nodes: [],
edges: [],
onNodesChange: null,
onEdgesChange: null,
snapGrid: [15, 15],
snapToGrid: false,
selectedElements: null,
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
nodesDraggable: true,
nodesConnectable: true,
elementsSelectable: true,
d3Zoom: null,
d3Selection: null,
d3ZoomHandler: undefined,
minZoom: 0.5,
maxZoom: 2,
translateExtent: [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
],
multiSelectionActive: false,
nodeExtent: [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
],
reactFlowVersion: typeof __REACT_FLOW_VERSION__ !== 'undefined' ? __REACT_FLOW_VERSION__ : '-',
};
nodesSelectionActive: false,
selectionActive: false,
const store = configureStore(initialState);
userSelectionRect: {
startX: 0,
startY: 0,
x: 0,
y: 0,
width: 0,
height: 0,
draw: false,
},
connectionNodeId: null,
connectionHandleId: null,
connectionHandleType: 'source',
connectionPosition: { x: 0, y: 0 },
connectionMode: ConnectionMode.Strict,
export type ReactFlowDispatch = typeof store.dispatch;
snapGrid: [15, 15],
snapToGrid: false,
export default store;
nodesDraggable: true,
nodesConnectable: true,
elementsSelectable: true,
multiSelectionActive: false,
reactFlowVersion: typeof __REACT_FLOW_VERSION__ !== 'undefined' ? __REACT_FLOW_VERSION__ : '-',
setNodes: (propNodes: Node[]) => {
const { nodes, edges, nodeExtent } = get();
const nextNodes = propNodes.map((propNode: Node) => {
const storeNode = nodes.find((node) => node.id === propNode.id);
if (storeNode) {
if (typeof propNode.type !== 'undefined' && propNode.type !== storeNode.type) {
const updatedNode: Node = {
...storeNode,
...propNode,
};
// we reset the elements dimensions here in order to force a re-calculation of the bounds.
// When the type of a node changes it is possible that the number or positions of handles changes too.
updatedNode.width = null;
return updatedNode;
}
}
return parseNode(propNode, nodeExtent);
});
const updatedEdges = edges.map((edge) => {
const { sourceNode, targetNode } = getSourceTargetNodes(edge, nextNodes);
if (sourceNode) {
edge.sourceNode = sourceNode;
}
if (targetNode) {
edge.targetNode = targetNode;
}
return edge;
});
set({
nodes: nextNodes,
edges: updatedEdges,
});
},
setEdges: (propEdges: Edge[]) => {
const { edges, nodes } = get();
const nextEdges = propEdges.map((propEdge: Edge) => {
const storeEdge = edges.find((se) => se.id === propEdge.id);
if (storeEdge) {
return parseEdge(propEdge);
} else {
const parsedEdge = parseEdge(propEdge);
const { sourceNode, targetNode } = getSourceTargetNodes(parsedEdge, nodes);
if (sourceNode) {
parsedEdge.sourceNode = sourceNode;
}
if (targetNode) {
parsedEdge.targetNode = targetNode;
}
return parsedEdge;
}
});
set({ edges: nextEdges });
},
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
const { onNodesChange, nodes, transform } = get();
const initialChanges: ElementChange[] = [];
const nodesToChange: ElementChange[] = nodes.reduce((res, node) => {
const update = updates.find((u) => u.id === node.id);
if (update) {
const dimensions = getDimensions(update.nodeElement);
const doUpdate =
dimensions.width &&
dimensions.height &&
(node.width !== dimensions.width || node.height !== dimensions.height || update.forceUpdate);
if (doUpdate) {
const handleBounds = getHandleBounds(update.nodeElement, transform[2]);
const change = {
id: node.id,
change: {
...dimensions,
handleBounds,
},
} as ElementChange;
res.push(change);
}
}
return res;
}, initialChanges);
if (onNodesChange) {
onNodesChange(nodesToChange);
}
},
updateNodePosDiff: ({ id, diff, isDragging }: NodeDiffUpdate) => {
const { onNodesChange, nodes } = get();
if (onNodesChange && id && diff) {
const matchingNode = nodes.find((n) => n.id === id);
if (matchingNode) {
requestAnimationFrame(() =>
onNodesChange([
{
id,
change: {
position: {
x: matchingNode.position.x + diff.x,
y: matchingNode.position.y + diff.y,
isDragging,
},
},
},
])
);
}
}
},
setUserSelection: (mousePos: XYPosition) => {
set({
selectionActive: true,
userSelectionRect: {
width: 0,
height: 0,
startX: mousePos.x,
startY: mousePos.y,
x: mousePos.x,
y: mousePos.y,
draw: true,
},
});
},
updateUserSelection: (mousePos: XYPosition) => {
const { userSelectionRect, nodes, edges, transform, selectedElements } = get();
const startX = userSelectionRect.startX ?? 0;
const startY = userSelectionRect.startY ?? 0;
const nextUserSelectRect = {
...userSelectionRect,
x: mousePos.x < startX ? mousePos.x : userSelectionRect.x,
y: mousePos.y < startY ? mousePos.y : userSelectionRect.y,
width: Math.abs(mousePos.x - startX),
height: Math.abs(mousePos.y - startY),
};
const selectedNodes = getNodesInside(nodes, nextUserSelectRect, transform, false, true);
const selectedEdges = getConnectedEdges(selectedNodes, edges);
const nextSelectedElements = [...selectedNodes, ...selectedEdges];
const selectedElementsChanged = !isEqual(nextSelectedElements, selectedElements);
if (selectedElementsChanged) {
set({
selectedElements: nextSelectedElements.length > 0 ? nextSelectedElements : null,
userSelectionRect: nextUserSelectRect,
});
} else {
set({
userSelectionRect: nextUserSelectRect,
});
}
},
unsetUserSelection: () => {
const { selectedElements, userSelectionRect } = get();
const selectedNodes = selectedElements?.filter((node) => isNode(node) && node.position) as Node[];
const stateUpdate = {
selectionActive: false,
userSelectionRect: {
...userSelectionRect,
draw: false,
},
selectedElements: null,
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
nodesSelectionActive: false,
};
if (selectedNodes && selectedNodes.length > 0) {
const selectedNodesBbox = getRectOfNodes(selectedNodes);
stateUpdate.selectedNodesBbox = selectedNodesBbox;
stateUpdate.nodesSelectionActive = true;
}
set(stateUpdate);
},
setSelectedElements: (elements: Elements) => {
const { selectedElements } = get();
const selectedElementsArr = Array.isArray(elements) ? elements : [elements];
const selectedElementsUpdated = !isEqual(selectedElementsArr, selectedElements);
set({
selectedElements: selectedElementsUpdated ? selectedElementsArr : selectedElements,
});
},
addSelectedElements: (elements: Elements) => {
const { multiSelectionActive, selectedElements } = get();
const selectedElementsArr = Array.isArray(elements) ? elements : [elements];
let nextElements = selectedElementsArr;
if (multiSelectionActive) {
nextElements = selectedElements ? [...selectedElements, ...selectedElementsArr] : selectedElementsArr;
}
const selectedElementsUpdated = !isEqual(nextElements, selectedElements);
set({ selectedElements: selectedElementsUpdated ? nextElements : selectedElements });
},
initD3Zoom: ({ d3Zoom, d3Selection, d3ZoomHandler, transform }: InitD3ZoomPayload) => {
set({
d3Zoom,
d3Selection,
d3ZoomHandler,
transform,
});
},
setMinZoom: (minZoom: number) => {
const { d3Zoom, maxZoom } = get();
d3Zoom?.scaleExtent([minZoom, maxZoom]);
set({ minZoom });
},
setMaxZoom: (maxZoom: number) => {
const { d3Zoom, minZoom } = get();
d3Zoom?.scaleExtent([minZoom, maxZoom]);
set({ maxZoom });
},
setTranslateExtent: (translateExtent: TranslateExtent) => {
const { d3Zoom } = get();
d3Zoom?.translateExtent(translateExtent);
set({ translateExtent });
},
setNodeExtent: (nodeExtent: NodeExtent) => {
set({
nodeExtent,
nodes: get().nodes.map((node) => {
return {
...node,
position: clampPosition(node.position, nodeExtent),
__rf: {
...node.__rf,
},
};
}),
});
},
unsetNodesSelection: () => {
set({ nodesSelectionActive: false });
},
resetSelectedElements: () => {
set({ selectedElements: null });
},
updateTransform: (transform: Transform) => {
set({ transform });
},
updateSize: (size: Dimensions) => {
set({ width: size.width || 500, height: size.height || 500 });
},
setOnConnect: (onConnect: OnConnectFunc) => {
set({ onConnect });
},
setOnConnectStart: (onConnectStart: OnConnectStartFunc) => {
set({ onConnectStart });
},
setOnConnectStop: (onConnectStop: OnConnectStopFunc) => {
set({ onConnectStop });
},
setOnConnectEnd: (onConnectEnd: OnConnectEndFunc) => {
set({ onConnectEnd });
},
setConnectionPosition: (connectionPosition: XYPosition) => {
set({ connectionPosition });
},
setConnectionNodeId: (params: SetConnectionId) => {
set({ ...params });
},
setSnapToGrid: (snapToGrid: boolean) => {
set({ snapToGrid });
},
setSnapGrid: (snapGrid: SnapGrid) => {
set({ snapGrid });
},
setInteractive: (isInteractive: boolean) => {
set({
nodesDraggable: isInteractive,
nodesConnectable: isInteractive,
elementsSelectable: isInteractive,
});
},
setNodesDraggable: (nodesDraggable: boolean) => {
set({ nodesDraggable });
},
setNodesConnectable: (nodesConnectable: boolean) => {
set({ nodesConnectable });
},
setElementsSelectable: (elementsSelectable: boolean) => {
set({ elementsSelectable });
},
setMultiSelectionActive: (multiSelectionActive: boolean) => {
set({ multiSelectionActive });
},
setConnectionMode: (connectionMode: ConnectionMode) => {
set({ connectionMode });
},
setOnNodesChange: (onNodesChange: OnElementsChange) => {
set({ onNodesChange });
},
setOnEdgesChange: (onEdgesChange: OnElementsChange) => {
set({ onEdgesChange });
},
}));
export { Provider, useStore, createStore, useStoreApi };
-350
View File
@@ -1,350 +0,0 @@
import isEqual from 'fast-deep-equal';
import { clampPosition, getDimensions } from '../utils';
import { getNodesInside, getConnectedEdges, getRectOfNodes, isNode, parseNode, parseEdge } from '../utils/graph';
import { getHandleBounds } from '../components/Nodes/utils';
import { getSourceTargetNodes } from '../container/EdgeRenderer/utils';
import { ReactFlowState, Node, XYPosition, Edge, ElementChange } from '../types';
import * as constants from './contants';
import { ReactFlowAction } from './actions';
import { initialState } from './index';
export default function reactFlowReducer(state = initialState, action: ReactFlowAction): ReactFlowState {
switch (action.type) {
case constants.SET_NODES: {
const propNodes = action.payload;
const nextNodes = propNodes.map((propNode: Node) => {
const storeNode = state.nodes.find((node) => node.id === propNode.id);
if (storeNode) {
if (typeof propNode.type !== 'undefined' && propNode.type !== storeNode.type) {
const updatedNode: Node = {
...storeNode,
...propNode,
};
// we reset the elements dimensions here in order to force a re-calculation of the bounds.
// When the type of a node changes it is possible that the number or positions of handles changes too.
updatedNode.width = null;
return updatedNode;
}
}
return parseNode(propNode, state.nodeExtent);
});
const updatedEdges = state.edges.map((edge) => {
const { sourceNode, targetNode } = getSourceTargetNodes(edge, nextNodes);
if (sourceNode) {
edge.sourceNode = sourceNode;
}
if (targetNode) {
edge.targetNode = targetNode;
}
return edge;
});
return { ...state, nodes: nextNodes, edges: updatedEdges };
}
case constants.SET_EDGES: {
const propElements = action.payload;
const nextEdges = propElements.map((propEdge: Edge) => {
const storeEdge = state.edges.find((se) => se.id === propEdge.id);
if (storeEdge) {
return parseEdge(propEdge);
} else {
const parsedEdge = parseEdge(propEdge);
const { sourceNode, targetNode } = getSourceTargetNodes(parsedEdge, state.nodes);
if (sourceNode) {
parsedEdge.sourceNode = sourceNode;
}
if (targetNode) {
parsedEdge.targetNode = targetNode;
}
return parsedEdge;
}
});
return { ...state, edges: nextEdges };
}
case constants.UPDATE_NODE_DIMENSIONS: {
const initialChanges: ElementChange[] = [];
const nodesToChange: ElementChange[] = state.nodes.reduce((res, node) => {
const update = action.payload.find((u) => u.id === node.id);
if (update) {
const dimensions = getDimensions(update.nodeElement);
const doUpdate =
dimensions.width &&
dimensions.height &&
(node.width !== dimensions.width || node.height !== dimensions.height || update.forceUpdate);
if (doUpdate) {
const handleBounds = getHandleBounds(update.nodeElement, state.transform[2]);
const change = {
id: node.id,
change: {
...dimensions,
handleBounds,
},
} as ElementChange;
res.push(change);
}
}
return res;
}, initialChanges);
if (state.onNodesChange) {
requestAnimationFrame(() => state.onNodesChange?.(nodesToChange));
}
return state;
}
case constants.UPDATE_NODE_POS: {
const { id, pos } = action.payload;
let position: XYPosition = pos;
if (state.snapToGrid) {
const [gridSizeX, gridSizeY] = state.snapGrid;
position = {
x: gridSizeX * Math.round(pos.x / gridSizeX),
y: gridSizeY * Math.round(pos.y / gridSizeY),
};
}
if (state.onNodesChange) {
state.onNodesChange([{ id, change: { position } }]);
return state;
}
const nextNodes = state.nodes.map((node) => {
if (node.id === id) {
return {
...node,
position,
__rf: {
...node.__rf,
},
};
}
return node;
});
return { ...state, nodes: nextNodes };
}
case constants.UPDATE_NODE_POS_DIFF: {
const { id, diff, isDragging } = action.payload;
if (state.onNodesChange && id && diff) {
const matchingNode = state.nodes.find((n) => n.id === id);
if (matchingNode) {
requestAnimationFrame(() =>
state.onNodesChange?.([
{
id,
change: {
position: {
x: matchingNode.position.x + diff.x,
y: matchingNode.position.y + diff.y,
isDragging,
},
},
},
])
);
}
}
return state;
}
case constants.SET_USER_SELECTION: {
const mousePos = action.payload;
return {
...state,
selectionActive: true,
userSelectionRect: {
width: 0,
height: 0,
startX: mousePos.x,
startY: mousePos.y,
x: mousePos.x,
y: mousePos.y,
draw: true,
},
};
}
case constants.UPDATE_USER_SELECTION: {
const mousePos = action.payload;
const startX = state.userSelectionRect.startX ?? 0;
const startY = state.userSelectionRect.startY ?? 0;
const nextUserSelectRect = {
...state.userSelectionRect,
x: mousePos.x < startX ? mousePos.x : state.userSelectionRect.x,
y: mousePos.y < startY ? mousePos.y : state.userSelectionRect.y,
width: Math.abs(mousePos.x - startX),
height: Math.abs(mousePos.y - startY),
};
const selectedNodes = getNodesInside(state.nodes, nextUserSelectRect, state.transform, false, true);
const selectedEdges = getConnectedEdges(selectedNodes, state.edges);
const nextSelectedElements = [...selectedNodes, ...selectedEdges];
const selectedElementsChanged = !isEqual(nextSelectedElements, state.selectedElements);
const selectedElementsUpdate = selectedElementsChanged
? {
selectedElements: nextSelectedElements.length > 0 ? nextSelectedElements : null,
}
: {};
return {
...state,
...selectedElementsUpdate,
userSelectionRect: nextUserSelectRect,
};
}
case constants.UNSET_USER_SELECTION: {
const selectedNodes = state.selectedElements?.filter((node) => isNode(node) && node.__rf) as Node[];
const stateUpdate = {
...state,
selectionActive: false,
userSelectionRect: {
...state.userSelectionRect,
draw: false,
},
};
if (!selectedNodes || selectedNodes.length === 0) {
stateUpdate.selectedElements = null;
stateUpdate.nodesSelectionActive = false;
} else {
const selectedNodesBbox = getRectOfNodes(selectedNodes);
stateUpdate.selectedNodesBbox = selectedNodesBbox;
stateUpdate.nodesSelectionActive = true;
}
return stateUpdate;
}
case constants.SET_SELECTED_ELEMENTS: {
const elements = action.payload;
const selectedElementsArr = Array.isArray(elements) ? elements : [elements];
const selectedElementsUpdated = !isEqual(selectedElementsArr, state.selectedElements);
const selectedElements = selectedElementsUpdated ? selectedElementsArr : state.selectedElements;
return {
...state,
selectedElements,
};
}
case constants.ADD_SELECTED_ELEMENTS: {
const { multiSelectionActive, selectedElements } = state;
const elements = action.payload;
const selectedElementsArr = Array.isArray(elements) ? elements : [elements];
let nextElements = selectedElementsArr;
if (multiSelectionActive) {
nextElements = selectedElements ? [...selectedElements, ...selectedElementsArr] : selectedElementsArr;
}
const selectedElementsUpdated = !isEqual(nextElements, state.selectedElements);
const nextSelectedElements = selectedElementsUpdated ? nextElements : state.selectedElements;
return { ...state, selectedElements: nextSelectedElements };
}
case constants.INIT_D3ZOOM: {
const { d3Zoom, d3Selection, d3ZoomHandler, transform } = action.payload;
return {
...state,
d3Zoom,
d3Selection,
d3ZoomHandler,
transform,
};
}
case constants.SET_MINZOOM: {
const minZoom = action.payload;
state.d3Zoom?.scaleExtent([minZoom, state.maxZoom]);
return {
...state,
minZoom,
};
}
case constants.SET_MAXZOOM: {
const maxZoom = action.payload;
state.d3Zoom?.scaleExtent([state.minZoom, maxZoom]);
return {
...state,
maxZoom,
};
}
case constants.SET_TRANSLATEEXTENT: {
const translateExtent = action.payload;
state.d3Zoom?.translateExtent(translateExtent);
return {
...state,
translateExtent,
};
}
case constants.SET_NODE_EXTENT: {
const nodeExtent = action.payload;
return {
...state,
nodeExtent,
nodes: state.nodes.map((node) => {
return {
...node,
position: clampPosition(node.position, nodeExtent),
__rf: {
...node.__rf,
},
};
}),
};
}
case constants.SET_ON_CONNECT:
case constants.SET_ON_CONNECT_START:
case constants.SET_ON_CONNECT_STOP:
case constants.SET_ON_CONNECT_END:
case constants.RESET_SELECTED_ELEMENTS:
case constants.UNSET_NODES_SELECTION:
case constants.UPDATE_TRANSFORM:
case constants.UPDATE_SIZE:
case constants.SET_CONNECTION_POSITION:
case constants.SET_CONNECTION_NODEID:
case constants.SET_SNAPTOGRID:
case constants.SET_SNAPGRID:
case constants.SET_INTERACTIVE:
case constants.SET_NODES_DRAGGABLE:
case constants.SET_NODES_CONNECTABLE:
case constants.SET_ELEMENTS_SELECTABLE:
case constants.SET_MULTI_SELECTION_ACTIVE:
case constants.SET_CONNECTION_MODE:
case constants.SET_ON_NODES_CHANGE:
case constants.SET_ON_EDGES_CHANGE:
return { ...state, ...action.payload };
default:
return state;
}
}
-5
View File
@@ -1,5 +0,0 @@
export function createAction<T extends string>(type: T): { type: T };
export function createAction<T extends string, P extends any>(type: T, payload: P): { type: T; payload: P };
export function createAction(type: string, payload?: any) {
return { type, payload };
}
+35
View File
@@ -453,6 +453,41 @@ export interface ReactFlowState {
reactFlowVersion: string;
setNodes: (nodes: Node[]) => void;
setEdges: (edges: Edge[]) => void;
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
updateNodePosDiff: (update: NodeDiffUpdate) => void;
setUserSelection: (mousePos: XYPosition) => void;
updateUserSelection: (mousePos: XYPosition) => void;
unsetUserSelection: () => void;
unsetNodesSelection: () => void;
resetSelectedElements: () => void;
setSelectedElements: (elements: Elements) => void;
addSelectedElements: (elements: Elements) => void;
updateTransform: (transform: Transform) => void;
updateSize: (size: Dimensions) => void;
initD3Zoom: (payload: InitD3ZoomPayload) => void;
setMinZoom: (minZoom: number) => void;
setMaxZoom: (maxZoom: number) => void;
setTranslateExtent: (translateExtent: TranslateExtent) => void;
setNodeExtent: (nodeExtent: NodeExtent) => void;
setOnConnect: (onConnectFunction: OnConnectFunc) => void;
setOnConnectStart: (onConnectFunction: OnConnectStartFunc) => void;
setOnConnectStop: (onConnectFunction: OnConnectStopFunc) => void;
setOnConnectEnd: (onConnectFunction: OnConnectEndFunc) => void;
setConnectionPosition: (connectionPosition: XYPosition) => void;
setConnectionNodeId: (payload: SetConnectionId) => void;
setSnapToGrid: (snapToGrid: boolean) => void;
setSnapGrid: (snapGrid: SnapGrid) => void;
setInteractive: (isInteractive: boolean) => void;
setNodesDraggable: (nodesDraggable: boolean) => void;
setNodesConnectable: (nodesConnectable: boolean) => void;
setElementsSelectable: (elementsSelectable: boolean) => void;
setMultiSelectionActive: (multiSelectionActive: boolean) => void;
setConnectionMode: (connectionMode: ConnectionMode) => void;
setOnNodesChange: (onNodesChange: OnElementsChange) => void;
setOnEdgesChange: (onEdgesChange: OnElementsChange) => void;
onConnect?: OnConnectFunc;
onConnectStart?: OnConnectStartFunc;
onConnectStop?: OnConnectStopFunc;
+8 -8
View File
@@ -1,6 +1,7 @@
import { Store } from 'redux';
import { GetState } from 'zustand';
import { clampPosition, clamp } from '../utils';
import { ReactFlowState } from '../types';
import {
ElementId,
@@ -13,7 +14,6 @@ import {
Box,
Connection,
FlowExportObject,
ReactFlowState,
NodeExtent,
ElementChange,
} from '../types';
@@ -139,9 +139,9 @@ export const pointToRendererPoint = (
return position;
};
export const onLoadProject = (currentStore: Store<ReactFlowState>) => {
export const onLoadProject = (getState: GetState<ReactFlowState>) => {
return (position: XYPosition): XYPosition => {
const { transform, snapToGrid, snapGrid } = currentStore.getState();
const { transform, snapToGrid, snapGrid } = getState();
return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
};
@@ -256,17 +256,17 @@ const parseElements = (nodes: Node[], edges: Edge[]): Elements => {
return [...nodes.map((n) => ({ ...n })), ...edges.map((e) => ({ ...e }))];
};
export const onLoadGetElements = (currentStore: Store<ReactFlowState>) => {
export const onLoadGetElements = (getState: GetState<ReactFlowState>) => {
return (): Elements => {
const { nodes = [], edges = [] } = currentStore.getState();
const { nodes = [], edges = [] } = getState();
return parseElements(nodes, edges);
};
};
export const onLoadToObject = (currentStore: Store<ReactFlowState>) => {
export const onLoadToObject = (getState: GetState<ReactFlowState>) => {
return (): FlowExportObject => {
const { nodes = [], edges = [], transform } = currentStore.getState();
const { nodes = [], edges = [], transform } = getState();
return {
elements: parseElements(nodes, edges),