refactor(state): replace redux with zustand
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user