refactor(state): replace redux with zustand
This commit is contained in:
Generated
+23
-1
@@ -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
@@ -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)}`, []);
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,7 +1,6 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import EdgeText from './EdgeText';
|
||||
|
||||
import { getMarkerEnd, getCenter } from './utils';
|
||||
import { EdgeProps, Position } from '../../types';
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
},
|
||||
[
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }]);
|
||||
|
||||
@@ -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
@@ -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';
|
||||
|
||||
@@ -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
|
||||
>;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user