refactor(state): separate nodes and edges

This commit is contained in:
moklick
2021-10-19 11:09:49 +02:00
parent 3dc79276ad
commit 410b12946d
22 changed files with 634 additions and 537 deletions
+4 -4
View File
@@ -52,15 +52,15 @@ export default ({
setSourceNode(nextSourceNode);
}, []);
if (!sourceNode || !isConnectable) {
if (!sourceNode || !isConnectable || !sourceNode.handleBounds?.[connectionHandleType]) {
return null;
}
const sourceHandle = handleId
? sourceNode.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId)
: sourceNode.handleBounds[connectionHandleType][0];
? sourceNode.handleBounds[connectionHandleType]!.find((d: HandleElement) => d.id === handleId)
: sourceNode.handleBounds[connectionHandleType]![0];
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNode.width! / 2;
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.height;
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.height!;
const sourceX = sourceNode.position.x + sourceHandleX;
const sourceY = sourceNode.position.y + sourceHandleY;
+3 -3
View File
@@ -23,7 +23,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
data,
onClick,
onEdgeDoubleClick,
selected,
isSelected,
animated,
label,
labelStyle,
@@ -69,7 +69,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
'react-flow__edge',
`react-flow__edge-${type}`,
className,
{ selected, animated, inactive, updating },
{ selected: isSelected, animated, inactive, updating },
]);
const edgeElement = useMemo<Edge>(() => {
@@ -219,7 +219,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
id={id}
source={source}
target={target}
selected={selected}
isSelected={isSelected}
animated={animated}
label={label}
labelStyle={labelStyle}
-31
View File
@@ -1,31 +0,0 @@
import { useEffect } from 'react';
import shallow from 'zustand/shallow';
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, setEdges } = useStore(selector, shallow);
useEffect(() => {
setNodes(nodes);
}, [nodes]);
useEffect(() => {
setEdges(edges);
}, [edges]);
return null;
};
export default ElementUpdater;
+28 -30
View File
@@ -9,9 +9,9 @@ import { NodeComponentProps, WrapNodeProps, ReactFlowState } from '../../types';
const selector = (s: ReactFlowState) => ({
addSelectedElements: s.addSelectedElements,
onNodesChange: s.onNodesChange,
unsetNodesSelection: s.unsetNodesSelection,
updateNodePosDiff: s.updateNodePosDiff,
updateNodePosition: s.updateNodePosition,
updateNodeDimensions: s.updateNodeDimensions,
});
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
@@ -22,7 +22,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
scale,
xPos,
yPos,
selected,
isSelected,
onClick,
onMouseEnter,
onMouseMove,
@@ -48,8 +48,10 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
resizeObserver,
dragHandle,
}: WrapNodeProps) => {
// const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
const { addSelectedElements, onNodesChange, unsetNodesSelection, updateNodePosDiff } = useStore(selector, shallow);
const { addSelectedElements, unsetNodesSelection, updateNodePosition, updateNodeDimensions } = useStore(
selector,
shallow
);
const nodeElement = useRef<HTMLDivElement>(null);
const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]);
@@ -57,7 +59,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const nodeStyle: CSSProperties = useMemo(
() => ({
zIndex: selected ? 10 : 3,
zIndex: isSelected ? 10 : 3,
transform: `translate(${xPos}px,${yPos}px)`,
pointerEvents:
isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave ? 'all' : 'none',
@@ -66,7 +68,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
...style,
}),
[
selected,
isSelected,
xPos,
yPos,
isSelectable,
@@ -118,7 +120,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
if (isSelectable) {
unsetNodesSelection();
if (!selected) {
if (!isSelected) {
addSelectedElements([node]);
}
}
@@ -126,7 +128,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
onClick?.(event, node);
}
},
[isSelectable, selected, isDraggable, onClick, node]
[isSelectable, isSelected, isDraggable, onClick, node]
);
const onDragStart = useCallback(
@@ -136,15 +138,15 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
if (selectNodesOnDrag && isSelectable) {
unsetNodesSelection();
if (!selected) {
if (!isSelected) {
addSelectedElements([node]);
}
} else if (!selectNodesOnDrag && !selected && isSelectable) {
} else if (!selectNodesOnDrag && !isSelected && isSelectable) {
unsetNodesSelection();
addSelectedElements([]);
}
},
[node, selected, selectNodesOnDrag, isSelectable, onNodeDragStart]
[node, isSelected, selectNodesOnDrag, isSelectable, onNodeDragStart]
);
const onDrag = useCallback(
@@ -156,7 +158,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
onNodeDrag(event as MouseEvent, node);
}
updateNodePosDiff({ id, isDragging: true, diff: { x: draggableData.deltaX, y: draggableData.deltaY } });
updateNodePosition({ id, isDragging: true, diff: { x: draggableData.deltaX, y: draggableData.deltaY } });
},
[id, node, onNodeDrag]
);
@@ -166,7 +168,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
// onDragStop also gets called when user just clicks on a node.
// Because of that we set dragging to true inside the onDrag handler and handle the click here
if (!isDragging) {
if (isSelectable && !selectNodesOnDrag && !selected) {
if (isSelectable && !selectNodesOnDrag && !isSelected) {
addSelectedElements([node]);
}
@@ -175,18 +177,14 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
return;
}
onNodesChange?.([
{
id: node.id,
change: {
isDragging: true,
},
},
]);
updateNodePosition({
id: node.id,
isDragging: false,
});
onNodeDragStop?.(event as MouseEvent, node);
},
[node, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop, isDragging, selected]
[node, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop, isDragging, isSelected]
);
const onNodeDoubleClickHandler = useCallback(
@@ -196,11 +194,11 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
[node, onNodeDoubleClick]
);
// useEffect(() => {
// if (nodeElement.current && !isHidden) {
// updateNodeDimensions([{ id, nodeElement: nodeElement.current, forceUpdate: true }]);
// }
// }, [id, isHidden, sourcePosition, targetPosition]);
useEffect(() => {
if (nodeElement.current && !isHidden && !isInitialized) {
updateNodeDimensions([{ id, nodeElement: nodeElement.current, forceUpdate: true }]);
}
}, [id, isHidden, sourcePosition, targetPosition, isInitialized]);
useEffect(() => {
if (nodeElement.current) {
@@ -220,7 +218,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
`react-flow__node-${type}`,
className,
{
selected,
selected: isSelected,
selectable: isSelectable,
},
]);
@@ -257,7 +255,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
type={type}
xPos={xPos}
yPos={yPos}
selected={selected}
isSelected={isSelected}
isConnectable={isConnectable}
sourcePosition={sourcePosition}
targetPosition={targetPosition}
+6 -6
View File
@@ -20,10 +20,10 @@ const selector = (s: ReactFlowState) => ({
transform: s.transform,
selectedNodesBbox: s.selectedNodesBbox,
selectionActive: s.selectionActive,
selectedNodes: s.nodes.filter((n) => n.selected),
selectedNodes: s.nodes.filter((n) => n.isSelected),
snapToGrid: s.snapToGrid,
snapGrid: s.snapGrid,
updateNodePosDiff: s.updateNodePosDiff,
updateNodePosition: s.updateNodePosition,
});
export default ({
@@ -32,7 +32,7 @@ export default ({
onSelectionDragStop,
onSelectionContextMenu,
}: NodesSelectionProps) => {
const { transform, selectedNodesBbox, selectionActive, selectedNodes, snapToGrid, snapGrid, updateNodePosDiff } =
const { transform, selectedNodesBbox, selectionActive, selectedNodes, snapToGrid, snapGrid, updateNodePosition } =
useStore(selector);
const [tX, tY, tScale] = transform;
const nodeRef = useRef(null);
@@ -69,7 +69,7 @@ export default ({
onSelectionDrag(event, selectedNodes);
}
updateNodePosDiff({
updateNodePosition({
diff: {
x: data.deltaX,
y: data.deltaY,
@@ -77,12 +77,12 @@ export default ({
isDragging: true,
});
},
[onSelectionDrag, selectedNodes, updateNodePosDiff]
[onSelectionDrag, selectedNodes, updateNodePosition]
);
const onStop = useCallback(
(event: MouseEvent) => {
updateNodePosDiff({
updateNodePosition({
isDragging: false,
});
+2 -2
View File
@@ -9,8 +9,8 @@ interface SelectionListenerProps {
}
const selectedElementsSelector = (s: ReactFlowState) => [
...s.nodes.filter((n) => n.selected),
...s.edges.filter((e) => e.selected),
...s.nodes.filter((n) => n.isSelected),
...s.edges.filter((e) => e.isSelected),
];
// This is just a helper component for calling the onSelectionChange listener.
+211
View File
@@ -0,0 +1,211 @@
import { useEffect } from 'react';
import shallow from 'zustand/shallow';
import { useStore } from '../../store';
import {
Node,
Edge,
ReactFlowState,
OnConnectFunc,
OnConnectStartFunc,
OnConnectStopFunc,
OnConnectEndFunc,
NodeExtent,
OnNodesChange,
OnEdgesChange,
ConnectionMode,
SnapGrid,
TranslateExtent,
} from '../../types';
interface StoreUpdaterProps {
nodes: Node[];
edges: Edge[];
onConnect?: OnConnectFunc;
onConnectStart?: OnConnectStartFunc;
onConnectStop?: OnConnectStopFunc;
onConnectEnd?: OnConnectEndFunc;
nodesDraggable?: boolean;
nodesConnectable?: boolean;
minZoom?: number;
maxZoom?: number;
nodeExtent?: NodeExtent;
onNodesChange?: OnNodesChange;
onEdgesChange?: OnEdgesChange;
elementsSelectable?: boolean;
connectionMode?: ConnectionMode;
snapToGrid?: boolean;
snapGrid?: SnapGrid;
translateExtent?: TranslateExtent;
}
const selector = (s: ReactFlowState) => ({
setNodes: s.setNodes,
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,
setMaxZoom: s.setMaxZoom,
setTranslateExtent: s.setTranslateExtent,
setNodeExtent: s.setNodeExtent,
setConnectionMode: s.setConnectionMode,
setOnNodesChange: s.setOnNodesChange,
setOnEdgesChange: s.setOnEdgesChange,
});
const StoreUpdater = ({
nodes,
edges,
onConnect,
onConnectStart,
onConnectStop,
onConnectEnd,
nodesDraggable,
nodesConnectable,
minZoom,
maxZoom,
nodeExtent,
onNodesChange,
onEdgesChange,
elementsSelectable,
connectionMode,
snapGrid,
snapToGrid,
translateExtent,
}: StoreUpdaterProps) => {
const {
setNodes,
setEdges,
setOnConnect,
setOnConnectStart,
setOnConnectStop,
setOnConnectEnd,
setNodesDraggable,
setSnapGrid,
setSnapToGrid,
setNodesConnectable,
setElementsSelectable,
setMinZoom,
setMaxZoom,
setTranslateExtent,
setNodeExtent,
setOnNodesChange,
setOnEdgesChange,
setConnectionMode,
} = useStore(selector, shallow);
useEffect(() => {
setNodes(nodes);
}, [nodes]);
useEffect(() => {
setEdges(edges, nodes);
}, [edges, nodes]);
useEffect(() => {
if (onConnect) {
setOnConnect(onConnect);
}
}, [onConnect]);
useEffect(() => {
if (onConnectStart) {
setOnConnectStart(onConnectStart);
}
}, [onConnectStart]);
useEffect(() => {
if (onConnectStop) {
setOnConnectStop(onConnectStop);
}
}, [onConnectStop]);
useEffect(() => {
if (onConnectEnd) {
setOnConnectEnd(onConnectEnd);
}
}, [onConnectEnd]);
useEffect(() => {
if (typeof snapToGrid !== 'undefined') {
setSnapToGrid(snapToGrid);
}
}, [snapToGrid]);
useEffect(() => {
if (typeof snapGrid !== 'undefined') {
setSnapGrid(snapGrid);
}
}, [snapGrid]);
useEffect(() => {
if (typeof nodesDraggable !== 'undefined') {
setNodesDraggable(nodesDraggable);
}
}, [nodesDraggable]);
useEffect(() => {
if (typeof nodesConnectable !== 'undefined') {
setNodesConnectable(nodesConnectable);
}
}, [nodesConnectable]);
useEffect(() => {
if (typeof elementsSelectable !== 'undefined') {
setElementsSelectable(elementsSelectable);
}
}, [elementsSelectable]);
useEffect(() => {
if (typeof minZoom !== 'undefined') {
setMinZoom(minZoom);
}
}, [minZoom]);
useEffect(() => {
if (typeof maxZoom !== 'undefined') {
setMaxZoom(maxZoom);
}
}, [maxZoom]);
useEffect(() => {
if (typeof translateExtent !== 'undefined') {
setTranslateExtent(translateExtent);
}
}, [translateExtent]);
useEffect(() => {
if (typeof nodeExtent !== 'undefined') {
setNodeExtent(nodeExtent);
}
}, [nodeExtent]);
useEffect(() => {
if (typeof connectionMode !== 'undefined') {
setConnectionMode(connectionMode);
}
}, [connectionMode]);
useEffect(() => {
if (typeof onNodesChange !== 'undefined') {
setOnNodesChange(onNodesChange);
}
}, [onNodesChange]);
useEffect(() => {
if (typeof onEdgesChange !== 'undefined') {
setOnEdgesChange(onEdgesChange);
}
}, [onEdgesChange]);
return null;
};
export default StoreUpdater;