refactor(store): use store.setState directly instead of actions

This commit is contained in:
moklick
2021-12-11 05:22:41 +01:00
parent 1a2ac5aa3e
commit 895e0bc524
14 changed files with 110 additions and 245 deletions
+9 -5
View File
@@ -1,7 +1,7 @@
import React, { memo, useCallback, HTMLAttributes, FC, useEffect, useState } from 'react'; import React, { memo, useCallback, HTMLAttributes, FC, useEffect, useState } from 'react';
import cc from 'classcat'; import cc from 'classcat';
import { useStore } from '../../store'; import { useStore, useStoreApi } from '../../store';
import PlusIcon from '../../../assets/icons/plus.svg'; import PlusIcon from '../../../assets/icons/plus.svg';
import MinusIcon from '../../../assets/icons/minus.svg'; import MinusIcon from '../../../assets/icons/minus.svg';
@@ -31,7 +31,6 @@ export const ControlButton: FC<ControlButtonProps> = ({ children, className, ...
</button> </button>
); );
const setInteractiveSelector = (s: ReactFlowState) => s.setInteractive;
const isInteractiveSelector = (s: ReactFlowState) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable; const isInteractiveSelector = (s: ReactFlowState) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable;
const Controls: FC<ControlProps> = ({ const Controls: FC<ControlProps> = ({
@@ -47,8 +46,8 @@ const Controls: FC<ControlProps> = ({
className, className,
children, children,
}) => { }) => {
const store = useStoreApi();
const [isVisible, setIsVisible] = useState<boolean>(false); const [isVisible, setIsVisible] = useState<boolean>(false);
const setInteractive = useStore(setInteractiveSelector);
const isInteractive = useStore(isInteractiveSelector); const isInteractive = useStore(isInteractiveSelector);
const { zoomIn, zoomOut, fitView } = useZoomPanHelper(); const { zoomIn, zoomOut, fitView } = useZoomPanHelper();
@@ -70,9 +69,14 @@ const Controls: FC<ControlProps> = ({
}, [fitView, fitViewParams, onFitView]); }, [fitView, fitViewParams, onFitView]);
const onInteractiveChangeHandler = useCallback(() => { const onInteractiveChangeHandler = useCallback(() => {
setInteractive?.(!isInteractive); store.setState({
nodesDraggable: !isInteractive,
nodesConnectable: !isInteractive,
elementsSelectable: !isInteractive,
});
onInteractiveChange?.(!isInteractive); onInteractiveChange?.(!isInteractive);
}, [isInteractive, setInteractive, onInteractiveChange]); }, [isInteractive, onInteractiveChange]);
useEffect(() => { useEffect(() => {
setIsVisible(true); setIsVisible(true);
+4 -22
View File
@@ -10,9 +10,6 @@ import { getMarkerId } from '../../utils/graph';
const selector = (s: ReactFlowState) => ({ const selector = (s: ReactFlowState) => ({
addSelectedEdges: s.addSelectedEdges, addSelectedEdges: s.addSelectedEdges,
setConnectionNodeId: s.setConnectionNodeId,
setNodesSelectionActive: s.setNodesSelectionActive,
setPosition: s.setConnectionPosition,
connectionMode: s.connectionMode, connectionMode: s.connectionMode,
}); });
@@ -57,10 +54,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
markerStart, markerStart,
}: WrapEdgeProps): JSX.Element | null => { }: WrapEdgeProps): JSX.Element | null => {
const store = useStoreApi(); const store = useStoreApi();
const { addSelectedEdges, setConnectionNodeId, setNodesSelectionActive, setPosition, connectionMode } = useStore( const { addSelectedEdges, connectionMode } = useStore(selector, shallow);
selector,
shallow
);
const [updating, setUpdating] = useState<boolean>(false); const [updating, setUpdating] = useState<boolean>(false);
@@ -99,7 +93,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
const onEdgeClick = useCallback( const onEdgeClick = useCallback(
(event: React.MouseEvent<SVGGElement, MouseEvent>): void => { (event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
if (elementsSelectable) { if (elementsSelectable) {
setNodesSelectionActive(false); store.setState({ nodesSelectionActive: false });
addSelectedEdges([edgeElement.id]); addSelectedEdges([edgeElement.id]);
} }
@@ -169,8 +163,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
event, event,
handleId, handleId,
nodeId, nodeId,
setConnectionNodeId, store.setState,
setPosition,
onConnectEdge, onConnectEdge,
isTarget, isTarget,
isValidConnection, isValidConnection,
@@ -180,18 +173,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
store.getState store.getState
); );
}, },
[ [id, source, target, type, sourceHandleId, targetHandleId, edgeElement, onEdgeUpdate]
id,
source,
target,
type,
sourceHandleId,
targetHandleId,
setConnectionNodeId,
setPosition,
edgeElement,
onEdgeUpdate,
]
); );
const onEdgeUpdaterSourceMouseDown = useCallback( const onEdgeUpdaterSourceMouseDown = useCallback(
+21 -16
View File
@@ -1,23 +1,19 @@
import { MouseEvent as ReactMouseEvent } from 'react'; import { MouseEvent as ReactMouseEvent } from 'react';
import { SetState } from 'zustand';
import { getHostForElement } from '../../utils'; import { getHostForElement } from '../../utils';
import { import {
OnConnect, OnConnect,
OnConnectStart, OnConnectStart,
OnConnectStop, OnConnectStop,
OnConnectEnd, OnConnectEnd,
ConnectionMode, ConnectionMode,
SetConnectionId,
Connection, Connection,
HandleType, HandleType,
XYPosition, ReactFlowState,
} from '../../types'; } from '../../types';
type ValidConnectionFunc = (connection: Connection) => boolean; type ValidConnectionFunc = (connection: Connection) => boolean;
export type SetSourceIdFunc = (params: SetConnectionId) => void;
export type SetPosition = (pos: XYPosition) => void;
type Result = { type Result = {
elementBelow: Element | null; elementBelow: Element | null;
@@ -90,8 +86,7 @@ export function onMouseDown(
event: ReactMouseEvent, event: ReactMouseEvent,
handleId: string | null, handleId: string | null,
nodeId: string, nodeId: string,
setConnectionNodeId: SetSourceIdFunc, setState: SetState<ReactFlowState>,
setPosition: SetPosition,
onConnect: OnConnect, onConnect: OnConnect,
isTarget: boolean, isTarget: boolean,
isValidConnection: ValidConnectionFunc, isValidConnection: ValidConnectionFunc,
@@ -122,18 +117,24 @@ export function onMouseDown(
const containerBounds = reactFlowNode.getBoundingClientRect(); const containerBounds = reactFlowNode.getBoundingClientRect();
let recentHoveredHandle: Element; let recentHoveredHandle: Element;
setPosition({ setState({
x: event.clientX - containerBounds.left, connectionPosition: {
y: event.clientY - containerBounds.top, x: event.clientX - containerBounds.left,
y: event.clientY - containerBounds.top,
},
connectionNodeId: nodeId,
connectionHandleId: handleId,
connectionHandleType: handleType,
}); });
setConnectionNodeId({ connectionNodeId: nodeId, connectionHandleId: handleId, connectionHandleType: handleType });
onConnectStart?.(event, { nodeId, handleId, handleType }); onConnectStart?.(event, { nodeId, handleId, handleType });
function onMouseMove(event: MouseEvent) { function onMouseMove(event: MouseEvent) {
setPosition({ setState({
x: event.clientX - containerBounds.left, connectionPosition: {
y: event.clientY - containerBounds.top, x: event.clientX - containerBounds.left,
y: event.clientY - containerBounds.top,
},
}); });
const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid( const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid(
@@ -183,7 +184,11 @@ export function onMouseDown(
} }
resetRecentHandle(recentHoveredHandle); resetRecentHandle(recentHoveredHandle);
setConnectionNodeId({ connectionNodeId: null, connectionHandleId: null, connectionHandleType: null }); setState({
connectionNodeId: null,
connectionHandleId: null,
connectionHandleType: null,
});
doc.removeEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject); doc.removeEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject);
doc.removeEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject); doc.removeEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject);
+8 -17
View File
@@ -2,18 +2,16 @@ import React, { memo, useContext, useCallback, HTMLAttributes, forwardRef } from
import cc from 'classcat'; import cc from 'classcat';
import shallow from 'zustand/shallow'; import shallow from 'zustand/shallow';
import { useStore } from '../../store'; import { useStore, useStoreApi } from '../../store';
import NodeIdContext from '../../contexts/NodeIdContext'; import NodeIdContext from '../../contexts/NodeIdContext';
import { HandleProps, Connection, ReactFlowState, Position } from '../../types'; import { HandleProps, Connection, ReactFlowState, Position } from '../../types';
import { onMouseDown, SetSourceIdFunc, SetPosition } from './handler'; import { onMouseDown } from './handler';
const alwaysValid = () => true; const alwaysValid = () => true;
export type HandleComponentProps = HandleProps & Omit<HTMLAttributes<HTMLDivElement>, 'id'>; export type HandleComponentProps = HandleProps & Omit<HTMLAttributes<HTMLDivElement>, 'id'>;
const selector = (s: ReactFlowState) => ({ const selector = (s: ReactFlowState) => ({
setPosition: s.setConnectionPosition,
setConnectionNodeId: s.setConnectionNodeId,
onConnectAction: s.onConnect, onConnectAction: s.onConnect,
onConnectStart: s.onConnectStart, onConnectStart: s.onConnectStart,
onConnectStop: s.onConnectStop, onConnectStop: s.onConnectStop,
@@ -36,16 +34,12 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
}, },
ref ref
) => { ) => {
const store = useStoreApi();
const nodeId = useContext(NodeIdContext) as string; const nodeId = useContext(NodeIdContext) as string;
const { const { onConnectAction, onConnectStart, onConnectStop, onConnectEnd, connectionMode } = useStore(
setPosition, selector,
setConnectionNodeId, shallow
onConnectAction, );
onConnectStart,
onConnectStop,
onConnectEnd,
connectionMode,
} = useStore(selector, shallow);
const handleId = id || null; const handleId = id || null;
const isTarget = type === 'target'; const isTarget = type === 'target';
@@ -64,8 +58,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
event, event,
handleId, handleId,
nodeId, nodeId,
setConnectionNodeId as unknown as SetSourceIdFunc, store.setState,
setPosition as unknown as SetPosition,
onConnectExtended, onConnectExtended,
isTarget, isTarget,
isValidConnection, isValidConnection,
@@ -80,8 +73,6 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
[ [
handleId, handleId,
nodeId, nodeId,
setConnectionNodeId,
setPosition,
onConnectExtended, onConnectExtended,
isTarget, isTarget,
isValidConnection, isValidConnection,
+6 -9
View File
@@ -3,13 +3,12 @@ import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable';
import cc from 'classcat'; import cc from 'classcat';
import shallow from 'zustand/shallow'; import shallow from 'zustand/shallow';
import { useStore } from '../../store'; import { useStore, useStoreApi } from '../../store';
import { Provider } from '../../contexts/NodeIdContext'; import { Provider } from '../../contexts/NodeIdContext';
import { NodeProps, WrapNodeProps, ReactFlowState } from '../../types'; import { NodeProps, WrapNodeProps, ReactFlowState } from '../../types';
const selector = (s: ReactFlowState) => ({ const selector = (s: ReactFlowState) => ({
addSelectedNodes: s.addSelectedNodes, addSelectedNodes: s.addSelectedNodes,
setNodesSelectionActive: s.setNodesSelectionActive,
updateNodePosition: s.updateNodePosition, updateNodePosition: s.updateNodePosition,
unselectNodesAndEdges: s.unselectNodesAndEdges, unselectNodesAndEdges: s.unselectNodesAndEdges,
}); });
@@ -52,10 +51,8 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
noPanClassName, noPanClassName,
noDragClassName, noDragClassName,
}: WrapNodeProps) => { }: WrapNodeProps) => {
const { addSelectedNodes, unselectNodesAndEdges, setNodesSelectionActive, updateNodePosition } = useStore( const store = useStoreApi();
selector, const { addSelectedNodes, unselectNodesAndEdges, updateNodePosition } = useStore(selector, shallow);
shallow
);
const nodeElement = useRef<HTMLDivElement>(null); const nodeElement = useRef<HTMLDivElement>(null);
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave; const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
const nodeStyle: CSSProperties = useMemo( const nodeStyle: CSSProperties = useMemo(
@@ -109,7 +106,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
(event: MouseEvent) => { (event: MouseEvent) => {
if (!isDraggable) { if (!isDraggable) {
if (isSelectable) { if (isSelectable) {
setNodesSelectionActive(false); store.setState({ nodesSelectionActive: false });
if (!selected) { if (!selected) {
addSelectedNodes([node.id]); addSelectedNodes([node.id]);
@@ -127,14 +124,14 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
onNodeDragStart?.(event as MouseEvent, node); onNodeDragStart?.(event as MouseEvent, node);
if (selectNodesOnDrag && isSelectable) { if (selectNodesOnDrag && isSelectable) {
setNodesSelectionActive(false); store.setState({ nodesSelectionActive: false });
if (!selected) { if (!selected) {
addSelectedNodes([node.id]); addSelectedNodes([node.id]);
} }
} else if (!selectNodesOnDrag && !selected && isSelectable) { } else if (!selectNodesOnDrag && !selected && isSelectable) {
unselectNodesAndEdges(); unselectNodesAndEdges();
setNodesSelectionActive(false); store.setState({ nodesSelectionActive: false });
} }
}, },
[node, selected, selectNodesOnDrag, isSelectable, onNodeDragStart] [node, selected, selectNodesOnDrag, isSelectable, onNodeDragStart]
+30 -50
View File
@@ -1,7 +1,8 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { SetState } from 'zustand';
import shallow from 'zustand/shallow'; import shallow from 'zustand/shallow';
import { useStore } from '../../store'; import { useStore, useStoreApi } from '../../store';
import { import {
Node, Node,
Edge, Edge,
@@ -42,24 +43,11 @@ interface StoreUpdaterProps {
const selector = (s: ReactFlowState) => ({ const selector = (s: ReactFlowState) => ({
setNodes: s.setNodes, setNodes: s.setNodes,
setEdges: s.setEdges, setEdges: s.setEdges,
setOnConnect: s.setOnConnect,
setOnConnectStart: s.setOnConnectStart,
setOnConnectStop: s.setOnConnectStop,
setOnConnectEnd: s.setOnConnectEnd,
setSnapGrid: s.setSnapGrid,
setSnapToGrid: s.setSnapToGrid,
setNodesDraggable: s.setNodesDraggable,
setNodesConnectable: s.setNodesConnectable,
setElementsSelectable: s.setElementsSelectable,
setMinZoom: s.setMinZoom, setMinZoom: s.setMinZoom,
setMaxZoom: s.setMaxZoom, setMaxZoom: s.setMaxZoom,
setTranslateExtent: s.setTranslateExtent, setTranslateExtent: s.setTranslateExtent,
setNodeExtent: s.setNodeExtent, setNodeExtent: s.setNodeExtent,
setConnectionMode: s.setConnectionMode,
setOnNodesChange: s.setOnNodesChange,
setOnEdgesChange: s.setOnEdgesChange,
reset: s.reset, reset: s.reset,
setFitViewOnInit: s.setFitViewOnInit,
}); });
function useStoreUpdater<T>(value: T | undefined, setStoreState: (param: T) => void) { function useStoreUpdater<T>(value: T | undefined, setStoreState: (param: T) => void) {
@@ -70,6 +58,15 @@ function useStoreUpdater<T>(value: T | undefined, setStoreState: (param: T) => v
}, [value]); }, [value]);
} }
function useDirectStoreUpdater(key: keyof ReactFlowState, value: any, setState: SetState<ReactFlowState>) {
useEffect(() => {
if (typeof value !== 'undefined') {
// @ts-ignore
setState({ [key]: value });
}
}, [value]);
}
const StoreUpdater = ({ const StoreUpdater = ({
nodes, nodes,
edges, edges,
@@ -91,28 +88,11 @@ const StoreUpdater = ({
translateExtent, translateExtent,
fitViewOnInit, fitViewOnInit,
}: StoreUpdaterProps) => { }: StoreUpdaterProps) => {
const { const { setNodes, setEdges, setMinZoom, setMaxZoom, setTranslateExtent, setNodeExtent, reset } = useStore(
setNodes, selector,
setEdges, shallow
setOnConnect, );
setOnConnectStart, const store = useStoreApi();
setOnConnectStop,
setOnConnectEnd,
setNodesDraggable,
setSnapGrid,
setSnapToGrid,
setNodesConnectable,
setElementsSelectable,
setMinZoom,
setMaxZoom,
setTranslateExtent,
setNodeExtent,
setOnNodesChange,
setOnEdgesChange,
setConnectionMode,
reset,
setFitViewOnInit,
} = useStore(selector, shallow);
useEffect(() => { useEffect(() => {
return () => { return () => {
@@ -122,24 +102,24 @@ const StoreUpdater = ({
useStoreUpdater<Node[]>(nodes, setNodes); useStoreUpdater<Node[]>(nodes, setNodes);
useStoreUpdater<Edge[]>(edges, setEdges); useStoreUpdater<Edge[]>(edges, setEdges);
useStoreUpdater<OnConnect>(onConnect, setOnConnect);
useStoreUpdater<OnConnect>(onConnect, setOnConnect);
useStoreUpdater<OnConnectStart>(onConnectStart, setOnConnectStart);
useStoreUpdater<OnConnectStop>(onConnectStop, setOnConnectStop);
useStoreUpdater<OnConnectEnd>(onConnectEnd, setOnConnectEnd);
useStoreUpdater<boolean>(snapToGrid, setSnapToGrid);
useStoreUpdater<SnapGrid>(snapGrid, setSnapGrid);
useStoreUpdater<boolean>(nodesDraggable, setNodesDraggable);
useStoreUpdater<boolean>(nodesConnectable, setNodesConnectable);
useStoreUpdater<boolean>(elementsSelectable, setElementsSelectable);
useStoreUpdater<number>(minZoom, setMinZoom); useStoreUpdater<number>(minZoom, setMinZoom);
useStoreUpdater<number>(maxZoom, setMaxZoom); useStoreUpdater<number>(maxZoom, setMaxZoom);
useStoreUpdater<CoordinateExtent>(translateExtent, setTranslateExtent); useStoreUpdater<CoordinateExtent>(translateExtent, setTranslateExtent);
useStoreUpdater<CoordinateExtent>(nodeExtent, setNodeExtent); useStoreUpdater<CoordinateExtent>(nodeExtent, setNodeExtent);
useStoreUpdater<ConnectionMode>(connectionMode, setConnectionMode);
useStoreUpdater<OnNodesChange>(onNodesChange, setOnNodesChange); useDirectStoreUpdater('connectionMode', connectionMode, store.setState);
useStoreUpdater<OnEdgesChange>(onEdgesChange, setOnEdgesChange); useDirectStoreUpdater('onConnect', onConnect, store.setState);
useStoreUpdater<boolean>(fitViewOnInit, setFitViewOnInit); useDirectStoreUpdater('onConnectStart', onConnectStart, store.setState);
useDirectStoreUpdater('onConnectStop', onConnectStop, store.setState);
useDirectStoreUpdater('onConnectEnd', onConnectEnd, store.setState);
useDirectStoreUpdater('nodesDraggable', nodesDraggable, store.setState);
useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState);
useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState);
useDirectStoreUpdater('fitViewOnInit', fitViewOnInit, store.setState);
useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState);
useDirectStoreUpdater('snapGrid', snapGrid, store.setState);
useDirectStoreUpdater('onNodesChange', onNodesChange, store.setState);
useDirectStoreUpdater('onEdgesChange', onEdgesChange, store.setState);
return null; return null;
}; };
+8 -11
View File
@@ -30,8 +30,6 @@ function getMousePosition(event: React.MouseEvent, containerBounds: DOMRect): XY
const selector = (s: ReactFlowState) => ({ const selector = (s: ReactFlowState) => ({
userSelectionActive: s.userSelectionActive, userSelectionActive: s.userSelectionActive,
elementsSelectable: s.elementsSelectable, elementsSelectable: s.elementsSelectable,
setUserSelectionActive: s.setUserSelectionActive,
setNodesSelectionActive: s.setNodesSelectionActive,
}); });
const initialRect: SelectionRect = { const initialRect: SelectionRect = {
@@ -50,16 +48,14 @@ export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
const prevSelectedEdgesCount = useRef<number>(0); const prevSelectedEdgesCount = useRef<number>(0);
const containerBounds = useRef<DOMRect>(); const containerBounds = useRef<DOMRect>();
const [userSelectionRect, setUserSelectionRect] = useState<SelectionRect>(initialRect); const [userSelectionRect, setUserSelectionRect] = useState<SelectionRect>(initialRect);
const { userSelectionActive, elementsSelectable, setUserSelectionActive, setNodesSelectionActive } = useStore( const { userSelectionActive, elementsSelectable } = useStore(selector, shallow);
selector,
shallow
);
const renderUserSelectionPane = userSelectionActive || selectionKeyPressed; const renderUserSelectionPane = userSelectionActive || selectionKeyPressed;
const resetUserSelection = useCallback(() => { const resetUserSelection = useCallback(() => {
setUserSelectionRect(initialRect); setUserSelectionRect(initialRect);
setUserSelectionActive(false);
store.setState({ userSelectionActive: false });
prevSelectedNodesCount.current = 0; prevSelectedNodesCount.current = 0;
prevSelectedEdgesCount.current = 0; prevSelectedEdgesCount.current = 0;
@@ -81,8 +77,7 @@ export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
draw: true, draw: true,
}); });
setUserSelectionActive(true); store.setState({ userSelectionActive: true, nodesSelectionActive: false });
setNodesSelectionActive(false);
}, []); }, []);
const onMouseMove = (event: React.MouseEvent): void => { const onMouseMove = (event: React.MouseEvent): void => {
@@ -128,12 +123,14 @@ export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
}; };
const onMouseUp = useCallback(() => { const onMouseUp = useCallback(() => {
setNodesSelectionActive(prevSelectedNodesCount.current > 0); store.setState({ nodesSelectionActive: prevSelectedNodesCount.current > 0 });
resetUserSelection(); resetUserSelection();
}, []); }, []);
const onMouseLeave = useCallback(() => { const onMouseLeave = useCallback(() => {
setNodesSelectionActive(false); store.setState({ nodesSelectionActive: false });
resetUserSelection(); resetUserSelection();
}, []); }, []);
+5 -4
View File
@@ -1,7 +1,7 @@
import React, { useCallback, memo, ReactNode, WheelEvent, MouseEvent } from 'react'; import React, { useCallback, memo, ReactNode, WheelEvent, MouseEvent } from 'react';
import shallow from 'zustand/shallow'; import shallow from 'zustand/shallow';
import { useStore } from '../../store'; import { useStore, useStoreApi } from '../../store';
import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler'; import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler';
import useKeyPress from '../../hooks/useKeyPress'; import useKeyPress from '../../hooks/useKeyPress';
import { GraphViewProps } from '../GraphView'; import { GraphViewProps } from '../GraphView';
@@ -27,7 +27,6 @@ interface FlowRendererProps
} }
const selector = (s: ReactFlowState) => ({ const selector = (s: ReactFlowState) => ({
setNodesSelectionActive: s.setNodesSelectionActive,
resetSelectedElements: s.resetSelectedElements, resetSelectedElements: s.resetSelectedElements,
nodesSelectionActive: s.nodesSelectionActive, nodesSelectionActive: s.nodesSelectionActive,
}); });
@@ -62,7 +61,8 @@ const FlowRenderer = ({
noWheelClassName, noWheelClassName,
noPanClassName, noPanClassName,
}: FlowRendererProps) => { }: FlowRendererProps) => {
const { setNodesSelectionActive, resetSelectedElements, nodesSelectionActive } = useStore(selector, shallow); const store = useStoreApi();
const { resetSelectedElements, nodesSelectionActive } = useStore(selector, shallow);
const selectionKeyPressed = useKeyPress(selectionKeyCode); const selectionKeyPressed = useKeyPress(selectionKeyCode);
useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode }); useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode });
@@ -70,8 +70,9 @@ const FlowRenderer = ({
const onClick = useCallback( const onClick = useCallback(
(event: MouseEvent) => { (event: MouseEvent) => {
onPaneClick?.(event); onPaneClick?.(event);
setNodesSelectionActive(false);
resetSelectedElements(); resetSelectedElements();
store.setState({ nodesSelectionActive: false });
}, },
[onPaneClick] [onPaneClick]
); );
+4 -7
View File
@@ -48,9 +48,6 @@ const selector = (s: ReactFlowState) => ({
d3Zoom: s.d3Zoom, d3Zoom: s.d3Zoom,
d3Selection: s.d3Selection, d3Selection: s.d3Selection,
d3ZoomHandler: s.d3ZoomHandler, d3ZoomHandler: s.d3ZoomHandler,
initD3Zoom: s.initD3Zoom,
updateTransform: s.updateTransform,
}); });
const ZoomPane = ({ const ZoomPane = ({
@@ -77,7 +74,7 @@ const ZoomPane = ({
const store = useStoreApi(); const store = useStoreApi();
const zoomPane = useRef<HTMLDivElement>(null); const zoomPane = useRef<HTMLDivElement>(null);
const prevTransform = useRef<FlowTransform>({ x: 0, y: 0, zoom: 0 }); const prevTransform = useRef<FlowTransform>({ x: 0, y: 0, zoom: 0 });
const { d3Zoom, d3Selection, d3ZoomHandler, initD3Zoom, updateTransform } = useStore(selector, shallow); const { d3Zoom, d3Selection, d3ZoomHandler } = useStore(selector, shallow);
const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode); const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
useResizeHandler(zoomPane); useResizeHandler(zoomPane);
@@ -95,7 +92,7 @@ const ZoomPane = ({
d3ZoomInstance.transform(selection, updatedTransform); d3ZoomInstance.transform(selection, updatedTransform);
initD3Zoom({ store.setState({
d3Zoom: d3ZoomInstance, d3Zoom: d3ZoomInstance,
d3Selection: selection, d3Selection: selection,
d3ZoomHandler: selection.on('wheel.zoom'), d3ZoomHandler: selection.on('wheel.zoom'),
@@ -171,7 +168,7 @@ const ZoomPane = ({
d3Zoom.on('zoom', null); d3Zoom.on('zoom', null);
} else { } else {
d3Zoom.on('zoom', (event: any) => { d3Zoom.on('zoom', (event: any) => {
updateTransform([event.transform.x, event.transform.y, event.transform.k]); store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] });
if (onMove) { if (onMove) {
const flowTransform = eventToFlowTransform(event.transform); const flowTransform = eventToFlowTransform(event.transform);
@@ -180,7 +177,7 @@ const ZoomPane = ({
}); });
} }
} }
}, [selectionKeyPressed, d3Zoom, updateTransform, onMove]); }, [selectionKeyPressed, d3Zoom, onMove]);
useEffect(() => { useEffect(() => {
if (d3Zoom) { if (d3Zoom) {
+4 -6
View File
@@ -12,8 +12,6 @@ interface HookParams {
} }
const selector = (s: ReactFlowState) => ({ const selector = (s: ReactFlowState) => ({
setNodesSelectionActive: s.setNodesSelectionActive,
setMultiSelectionActive: s.setMultiSelectionActive,
resetSelectedElements: s.resetSelectedElements, resetSelectedElements: s.resetSelectedElements,
onNodesChange: s.onNodesChange, onNodesChange: s.onNodesChange,
onEdgesChange: s.onEdgesChange, onEdgesChange: s.onEdgesChange,
@@ -21,8 +19,7 @@ const selector = (s: ReactFlowState) => ({
export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => { export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
const store = useStoreApi(); const store = useStoreApi();
const { setNodesSelectionActive, setMultiSelectionActive, resetSelectedElements, onNodesChange, onEdgesChange } = const { resetSelectedElements, onNodesChange, onEdgesChange } = useStore(selector, shallow);
useStore(selector, shallow);
const deleteKeyPressed = useKeyPress(deleteKeyCode); const deleteKeyPressed = useKeyPress(deleteKeyCode);
const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode); const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
@@ -46,12 +43,13 @@ export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
onNodesChange?.(nodeChanges); onNodesChange?.(nodeChanges);
onEdgesChange?.(edgeChanges); onEdgesChange?.(edgeChanges);
setNodesSelectionActive(false); store.setState({ nodesSelectionActive: false });
resetSelectedElements(); resetSelectedElements();
} }
}, [deleteKeyPressed, onNodesChange, onEdgesChange]); }, [deleteKeyPressed, onNodesChange, onEdgesChange]);
useEffect(() => { useEffect(() => {
setMultiSelectionActive(multiSelectionKeyPressed); store.setState({ multiSelectionActive: multiSelectionKeyPressed });
}, [multiSelectionKeyPressed]); }, [multiSelectionKeyPressed]);
}; };
+3 -6
View File
@@ -1,13 +1,10 @@
import { useEffect, MutableRefObject } from 'react'; import { useEffect, MutableRefObject } from 'react';
import { useStore } from '../store'; import { useStoreApi } from '../store';
import { getDimensions } from '../utils'; import { getDimensions } from '../utils';
import { ReactFlowState } from '../types';
const updateSizeSelector = (state: ReactFlowState) => state.updateSize;
function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>): void { function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>): void {
const updateSize = useStore(updateSizeSelector); const store = useStoreApi();
useEffect(() => { useEffect(() => {
let resizeObserver: ResizeObserver; let resizeObserver: ResizeObserver;
@@ -23,7 +20,7 @@ function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>)
console.warn('The React Flow parent container needs a width and a height to render the graph.'); console.warn('The React Flow parent container needs a width and a height to render the graph.');
} }
updateSize(size); store.setState({ width: size.width || 500, height: size.height || 500 });
}; };
updateDimensions(); updateDimensions();
+2 -2
View File
@@ -3,10 +3,10 @@ import { useCallback } from 'react';
import { useStore } from '../store'; import { useStore } from '../store';
import { UpdateNodeInternals, ReactFlowState } from '../types'; import { UpdateNodeInternals, ReactFlowState } from '../types';
const updateNodeDimsSelector = (state: ReactFlowState) => state.updateNodeDimensions; const selector = (state: ReactFlowState) => state.updateNodeDimensions;
function useUpdateNodeInternals(): UpdateNodeInternals { function useUpdateNodeInternals(): UpdateNodeInternals {
const updateNodeDimensions = useStore(updateNodeDimsSelector); const updateNodeDimensions = useStore(selector);
return useCallback<UpdateNodeInternals>((id: string) => { return useCallback<UpdateNodeInternals>((id: string) => {
const nodeElement = document.querySelector(`.react-flow__node[data-id="${id}"]`) as HTMLDivElement; const nodeElement = document.querySelector(`.react-flow__node[data-id="${id}"]`) as HTMLDivElement;
-48
View File
@@ -4,27 +4,14 @@ import createContext from 'zustand/context';
import { clampPosition, getDimensions } from '../utils'; import { clampPosition, getDimensions } from '../utils';
import { import {
ReactFlowState, ReactFlowState,
ConnectionMode,
Node, Node,
Edge, Edge,
NodeDimensionUpdate, NodeDimensionUpdate,
NodeDiffUpdate, NodeDiffUpdate,
InitD3ZoomPayload,
CoordinateExtent, CoordinateExtent,
OnConnect,
OnConnectStart,
OnConnectStop,
OnConnectEnd,
SetConnectionId,
SnapGrid,
NodeChange, NodeChange,
OnNodesChange,
OnEdgesChange,
EdgeChange, EdgeChange,
NodeDimensionChange, NodeDimensionChange,
Transform,
Dimensions,
XYPosition,
} from '../types'; } from '../types';
import { getHandleBounds } from '../components/Nodes/utils'; import { getHandleBounds } from '../components/Nodes/utils';
import { createSelectionChange, getSelectionChanges } from '../utils/changes'; import { createSelectionChange, getSelectionChanges } from '../utils/changes';
@@ -36,7 +23,6 @@ const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
const createStore = () => const createStore = () =>
create<ReactFlowState>((set, get) => ({ create<ReactFlowState>((set, get) => ({
...initialState, ...initialState,
setNodes: (nodes: Node[]) => { setNodes: (nodes: Node[]) => {
const nodeInternals = createNodeInternals(nodes, get().nodeInternals); const nodeInternals = createNodeInternals(nodes, get().nodeInternals);
@@ -158,13 +144,6 @@ const createStore = () =>
onEdgesChange?.(edgesToUnselect); onEdgesChange?.(edgesToUnselect);
} }
}, },
initD3Zoom: ({ d3Zoom, d3Selection, d3ZoomHandler, transform }: InitD3ZoomPayload) =>
set({
d3Zoom,
d3Selection,
d3ZoomHandler,
transform,
}),
setMinZoom: (minZoom: number) => { setMinZoom: (minZoom: number) => {
const { d3Zoom, maxZoom } = get(); const { d3Zoom, maxZoom } = get();
d3Zoom?.scaleExtent([minZoom, maxZoom]); d3Zoom?.scaleExtent([minZoom, maxZoom]);
@@ -183,7 +162,6 @@ const createStore = () =>
set({ translateExtent }); set({ translateExtent });
}, },
resetSelectedElements: () => { resetSelectedElements: () => {
const { nodeInternals, edges, onNodesChange, onEdgesChange } = get(); const { nodeInternals, edges, onNodesChange, onEdgesChange } = get();
// @TODO: work with nodeInternals instead of converting it to an array // @TODO: work with nodeInternals instead of converting it to an array
@@ -210,33 +188,7 @@ const createStore = () =>
nodeInternals: new Map(nodeInternals), nodeInternals: new Map(nodeInternals),
}); });
}, },
setUserSelectionActive: (userSelectionActive: boolean) => set({ userSelectionActive }),
setNodesSelectionActive: (nodesSelectionActive: boolean) => set({ nodesSelectionActive }),
updateTransform: (transform: Transform) => set({ transform }),
updateSize: (size: Dimensions) => set({ width: size.width || 500, height: size.height || 500 }),
setOnConnect: (onConnect: OnConnect) => set({ onConnect }),
setOnConnectStart: (onConnectStart: OnConnectStart) => set({ onConnectStart }),
setOnConnectStop: (onConnectStop: OnConnectStop) => set({ onConnectStop }),
setOnConnectEnd: (onConnectEnd: OnConnectEnd) => 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: OnNodesChange) => set({ onNodesChange }),
setOnEdgesChange: (onEdgesChange: OnEdgesChange) => set({ onEdgesChange }),
reset: () => set({ ...initialState }), reset: () => set({ ...initialState }),
setFitViewOnInit: (fitViewOnInit: boolean) => set({ fitViewOnInit }),
})); }));
export { Provider, useStore, createStore, useStoreApi }; export { Provider, useStore, createStore, useStoreApi };
+6 -42
View File
@@ -1,7 +1,7 @@
import { MouseEvent as ReactMouseEvent, ReactNode } from 'react'; import { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
import { Selection as D3Selection, ZoomBehavior } from 'd3'; import { Selection as D3Selection, ZoomBehavior } from 'd3';
import { XYPosition, Rect, Transform, CoordinateExtent, Dimensions } from './utils'; import { XYPosition, Rect, Transform, CoordinateExtent } from './utils';
import { NodeChange, EdgeChange } from './changes'; import { NodeChange, EdgeChange } from './changes';
import { Node, NodeInternals, NodeDimensionUpdate, NodeDiffUpdate } from './nodes'; import { Node, NodeInternals, NodeDimensionUpdate, NodeDiffUpdate } from './nodes';
import { Edge } from './edges'; import { Edge } from './edges';
@@ -90,12 +90,6 @@ export type OnConnectStop = (event: MouseEvent) => void;
export type OnConnectEnd = (event: MouseEvent) => void; export type OnConnectEnd = (event: MouseEvent) => void;
export type SetConnectionId = {
connectionNodeId: string | null;
connectionHandleId: string | null;
connectionHandleType: HandleType | null;
};
export enum BackgroundVariant { export enum BackgroundVariant {
Lines = 'lines', Lines = 'lines',
Dots = 'dots', Dots = 'dots',
@@ -143,13 +137,6 @@ export interface ZoomPanHelperFunctions {
initialized: boolean; initialized: boolean;
} }
export type InitD3ZoomPayload = {
d3Zoom: ZoomBehavior<Element, unknown>;
d3Selection: D3Selection<Element, unknown, null, undefined>;
d3ZoomHandler: ((this: Element, event: any, d: unknown) => void) | undefined;
transform: Transform;
};
export type ReactFlowStore = { export type ReactFlowStore = {
width: number; width: number;
height: number; height: number;
@@ -190,6 +177,11 @@ export type ReactFlowStore = {
fitViewOnInit: boolean; fitViewOnInit: boolean;
fitViewOnInitDone: boolean; fitViewOnInitDone: boolean;
onConnect?: OnConnect;
onConnectStart?: OnConnectStart;
onConnectStop?: OnConnectStop;
onConnectEnd?: OnConnectEnd;
}; };
export type ReactFlowActions = { export type ReactFlowActions = {
@@ -197,43 +189,15 @@ export type ReactFlowActions = {
setEdges: (edges: Edge[]) => void; setEdges: (edges: Edge[]) => void;
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void; updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
updateNodePosition: (update: NodeDiffUpdate) => void; updateNodePosition: (update: NodeDiffUpdate) => void;
setNodesSelectionActive: (isActive: boolean) => void;
setUserSelectionActive: (isActive: boolean) => void;
resetSelectedElements: () => void; resetSelectedElements: () => void;
unselectNodesAndEdges: () => void; unselectNodesAndEdges: () => void;
addSelectedNodes: (nodeIds: string[]) => void; addSelectedNodes: (nodeIds: string[]) => void;
addSelectedEdges: (edgeIds: string[]) => void; addSelectedEdges: (edgeIds: string[]) => void;
updateTransform: (transform: Transform) => void;
updateSize: (size: Dimensions) => void;
initD3Zoom: (payload: InitD3ZoomPayload) => void;
setMinZoom: (minZoom: number) => void; setMinZoom: (minZoom: number) => void;
setMaxZoom: (maxZoom: number) => void; setMaxZoom: (maxZoom: number) => void;
setTranslateExtent: (translateExtent: CoordinateExtent) => void; setTranslateExtent: (translateExtent: CoordinateExtent) => void;
setNodeExtent: (nodeExtent: CoordinateExtent) => void; setNodeExtent: (nodeExtent: CoordinateExtent) => void;
setOnConnect: (onConnectFunction: OnConnect) => void;
setOnConnectStart: (onConnectFunction: OnConnectStart) => void;
setOnConnectStop: (onConnectFunction: OnConnectStop) => void;
setOnConnectEnd: (onConnectFunction: OnConnectEnd) => 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: OnNodesChange) => void;
setOnEdgesChange: (onEdgesChange: OnEdgesChange) => void;
onConnect?: OnConnect;
onConnectStart?: OnConnectStart;
onConnectStop?: OnConnectStop;
onConnectEnd?: OnConnectEnd;
reset: () => void; reset: () => void;
setFitViewOnInit: (fitViewOnInit: boolean) => void;
}; };
export type ReactFlowState = ReactFlowStore & ReactFlowActions; export type ReactFlowState = ReactFlowStore & ReactFlowActions;