refactor(store): rename nodeLookup to nodeInternals

This commit is contained in:
moklick
2021-11-04 18:13:21 +01:00
parent cc7debf8de
commit 996b1b51f5
13 changed files with 142 additions and 128 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
import { useState, useMemo, MouseEvent, useCallback } from 'react'; import { useState, MouseEvent, useCallback } from 'react';
import ReactFlow, { import ReactFlow, {
addEdge, addEdge,
+9 -3
View File
@@ -29,7 +29,7 @@ const selector = (s: ReactFlowState) => ({
height: s.height, height: s.height,
transform: s.transform, transform: s.transform,
nodes: s.nodes, nodes: s.nodes,
nodeLookup: s.nodeLookup, nodeInternals: s.nodeInternals,
}); });
const MiniMap = ({ const MiniMap = ({
@@ -42,7 +42,13 @@ const MiniMap = ({
nodeStrokeWidth = 2, nodeStrokeWidth = 2,
maskColor = 'rgb(240, 242, 243, 0.7)', maskColor = 'rgb(240, 242, 243, 0.7)',
}: MiniMapProps) => { }: MiniMapProps) => {
const { width: containerWidth, height: containerHeight, transform, nodes, nodeLookup } = useStore(selector, shallow); const {
width: containerWidth,
height: containerHeight,
transform,
nodes,
nodeInternals,
} = useStore(selector, shallow);
const [tX, tY, tScale] = transform; const [tX, tY, tScale] = transform;
const mapClasses = cc(['react-flow__minimap', className]); const mapClasses = cc(['react-flow__minimap', className]);
@@ -85,7 +91,7 @@ const MiniMap = ({
{nodes {nodes
.filter((node) => !node.isHidden && node.width && node.height) .filter((node) => !node.isHidden && node.width && node.height)
.map((node) => { .map((node) => {
const positionAbsolute = nodeLookup.get(node.id)?.positionAbsolute; const positionAbsolute = nodeInternals.get(node.id)?.positionAbsolute;
return ( return (
<MiniMapNode <MiniMapNode
+4 -4
View File
@@ -6,7 +6,7 @@ import { getBezierPath } from '../Edges/BezierEdge';
import { getSmoothStepPath } from '../Edges/SmoothStepEdge'; import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
import { import {
ElementId, ElementId,
NodeLookupItem, NodeInternalsItem,
HandleElement, HandleElement,
Position, Position,
ConnectionLineType, ConnectionLineType,
@@ -28,7 +28,7 @@ interface ConnectionLineProps {
CustomConnectionLineComponent?: ConnectionLineComponent; CustomConnectionLineComponent?: ConnectionLineComponent;
} }
const selector = (s: ReactFlowState) => ({ nodeLookup: s.nodeLookup, nodes: s.nodes, transform: s.transform }); const selector = (s: ReactFlowState) => ({ nodeInternals: s.nodeInternals, nodes: s.nodes, transform: s.transform });
export default ({ export default ({
connectionNodeId, connectionNodeId,
@@ -44,8 +44,8 @@ export default ({
const nodeId = connectionNodeId; const nodeId = connectionNodeId;
const handleId = connectionHandleId; const handleId = connectionHandleId;
const { nodeLookup, nodes, transform } = useStore(selector, shallow); const { nodeInternals, nodes, transform } = useStore(selector, shallow);
const sourceNodeInternals = useRef<NodeLookupItem | undefined>(nodeLookup.get(nodeId)); const sourceNodeInternals = useRef<NodeInternalsItem | undefined>(nodeInternals.get(nodeId));
const sourceNode = useRef<Node | undefined>(nodes.find((n) => n.id === nodeId)); const sourceNode = useRef<Node | undefined>(nodes.find((n) => n.id === nodeId));
if ( if (
+2 -2
View File
@@ -66,12 +66,11 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const nodeStyle: CSSProperties = useMemo( const nodeStyle: CSSProperties = useMemo(
() => ({ () => ({
zIndex, zIndex,
// zIndex: isSelected ? zIndex + 1 : zIndex,
transform: `translate(${xPos}px,${yPos}px)`, transform: `translate(${xPos}px,${yPos}px)`,
pointerEvents: pointerEvents:
isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave ? 'all' : 'none', isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave ? 'all' : 'none',
// prevents jumping of nodes on start // prevents jumping of nodes on start
// opacity: isInitialized ? 1 : 0, opacity: isInitialized ? 1 : 0,
...style, ...style,
}), }),
[ [
@@ -87,6 +86,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
onMouseMove, onMouseMove,
onMouseLeave, onMouseLeave,
isParentNode, isParentNode,
zIndex,
] ]
); );
+5 -5
View File
@@ -216,7 +216,7 @@ const selector = (s: ReactFlowState) => ({
width: s.width, width: s.width,
height: s.height, height: s.height,
connectionMode: s.connectionMode, connectionMode: s.connectionMode,
nodeLookup: s.nodeLookup, nodeInternals: s.nodeInternals,
}); });
const EdgeRenderer = (props: EdgeRendererProps) => { const EdgeRenderer = (props: EdgeRendererProps) => {
@@ -230,9 +230,9 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
width, width,
height, height,
connectionMode, connectionMode,
nodeLookup, nodeInternals,
} = useStore(selector, shallow); } = useStore(selector, shallow);
const edgeTree = useVisibleEdges(props.onlyRenderVisibleElements, nodeLookup); const edgeTree = useVisibleEdges(props.onlyRenderVisibleElements, nodeInternals);
if (!width) { if (!width) {
return null; return null;
@@ -254,8 +254,8 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} />} {isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} />}
<g> <g>
{edges.map((edge: Edge) => { {edges.map((edge: Edge) => {
const sourceNode = nodeLookup.get(edge.source); const sourceNode = nodeInternals.get(edge.source);
const targetNode = nodeLookup.get(edge.target); const targetNode = nodeInternals.get(edge.target);
return ( return (
<Edge <Edge
+7 -7
View File
@@ -4,7 +4,7 @@ import shallow from 'zustand/shallow';
import { useStore } from '../../store'; import { useStore } from '../../store';
import { Node, NodeTypesType, ReactFlowState, WrapNodeProps } from '../../types'; import { Node, NodeTypesType, ReactFlowState, WrapNodeProps } from '../../types';
import useVisibleNodes from '../../hooks/useVisibleNodes'; import useVisibleNodes from '../../hooks/useVisibleNodes';
import useNodeLookupRef from '../../hooks/useNodeLookupRef'; import useNodeInternalsRef from '../../hooks/useNodeInternalsRef';
interface NodeRendererProps { interface NodeRendererProps {
nodeTypes: NodeTypesType; nodeTypes: NodeTypesType;
@@ -34,7 +34,7 @@ const selector = (s: ReactFlowState) => ({
const NodeRenderer = (props: NodeRendererProps) => { const NodeRenderer = (props: NodeRendererProps) => {
const { scale, nodesDraggable, nodesConnectable, elementsSelectable, updateNodeDimensions, snapGrid, snapToGrid } = const { scale, nodesDraggable, nodesConnectable, elementsSelectable, updateNodeDimensions, snapGrid, snapToGrid } =
useStore(selector, shallow); useStore(selector, shallow);
const nodeLookup = useNodeLookupRef(); const nodeInternals = useNodeInternalsRef();
const nodes = useVisibleNodes(props.onlyRenderVisibleElements); const nodes = useVisibleNodes(props.onlyRenderVisibleElements);
const resizeObserver = useMemo(() => { const resizeObserver = useMemo(() => {
@@ -56,7 +56,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
<div className="react-flow__nodes react-flow__container"> <div className="react-flow__nodes react-flow__container">
{nodes.map((node) => { {nodes.map((node) => {
const nodeType = node.type || 'default'; const nodeType = node.type || 'default';
const lookupNode = nodeLookup.current.get(node.id); const internals = nodeInternals.current.get(node.id);
if (!props.nodeTypes[nodeType]) { if (!props.nodeTypes[nodeType]) {
console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`); console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`);
@@ -83,8 +83,8 @@ const NodeRenderer = (props: NodeRendererProps) => {
sourcePosition={node.sourcePosition} sourcePosition={node.sourcePosition}
targetPosition={node.targetPosition} targetPosition={node.targetPosition}
isHidden={node.isHidden} isHidden={node.isHidden}
xPos={lookupNode?.positionAbsolute?.x || 0} xPos={internals?.positionAbsolute?.x || 0}
yPos={lookupNode?.positionAbsolute?.y || 0} yPos={internals?.positionAbsolute?.y || 0}
isDragging={node.isDragging} isDragging={node.isDragging}
isInitialized={isInitialized} isInitialized={isInitialized}
snapGrid={snapGrid} snapGrid={snapGrid}
@@ -106,8 +106,8 @@ const NodeRenderer = (props: NodeRendererProps) => {
isConnectable={isConnectable} isConnectable={isConnectable}
resizeObserver={resizeObserver} resizeObserver={resizeObserver}
dragHandle={node.dragHandle} dragHandle={node.dragHandle}
zIndex={lookupNode?.treeLevel || 0} zIndex={internals?.treeLevel || 0}
isParentNode={!!lookupNode?.isParentNode} isParentNode={!!internals?.isParentNode}
/> />
); );
})} })}
+14
View File
@@ -0,0 +1,14 @@
import { useRef, useEffect } from 'react';
import { useStoreApi } from '../store';
function useNodeInternalsRef() {
const store = useStoreApi();
const nodeInternals = useRef(store.getState().nodeInternals);
useEffect(() => store.subscribe((state) => (nodeInternals.current = state.nodeInternals)), []);
return nodeInternals;
}
export default useNodeInternalsRef;
-14
View File
@@ -1,14 +0,0 @@
import { useRef, useEffect } from 'react';
import { useStoreApi } from '../store';
function useNodeLookupRef() {
const store = useStoreApi();
const nodeLookup = useRef(store.getState().nodeLookup);
useEffect(() => store.subscribe((state) => (nodeLookup.current = state.nodeLookup)), []);
return nodeLookup;
}
export default useNodeLookupRef;
+9 -9
View File
@@ -2,15 +2,15 @@ import { useCallback } from 'react';
import { useStore } from '../store'; import { useStore } from '../store';
import { isEdgeVisible } from '../container/EdgeRenderer/utils'; import { isEdgeVisible } from '../container/EdgeRenderer/utils';
import { ReactFlowState, NodeLookup, Edge } from '../types'; import { ReactFlowState, NodeInternals, Edge } from '../types';
function groupEdgesByTreeLevel(edges: Edge[], nodeLookup: NodeLookup) { function groupEdgesByTreeLevel(edges: Edge[], nodeInternals: NodeInternals) {
let maxLevel = -1; let maxLevel = -1;
const levelLookup = edges.reduce<Record<string, Edge[]>>((tree, edge) => { const levelLookup = edges.reduce<Record<string, Edge[]>>((tree, edge) => {
const treeLevel = Math.max( const treeLevel = Math.max(
nodeLookup.get(edge.source)?.treeLevel || 0, nodeInternals.get(edge.source)?.treeLevel || 0,
nodeLookup.get(edge.target)?.treeLevel || 0 nodeInternals.get(edge.target)?.treeLevel || 0
); );
if (tree[treeLevel]) { if (tree[treeLevel]) {
tree[treeLevel].push(edge); tree[treeLevel].push(edge);
@@ -34,7 +34,7 @@ function groupEdgesByTreeLevel(edges: Edge[], nodeLookup: NodeLookup) {
}); });
} }
function useVisibleEdges(onlyRenderVisible: boolean, nodeLookup: NodeLookup) { function useVisibleEdges(onlyRenderVisible: boolean, nodeInternals: NodeInternals) {
const edges = useStore( const edges = useStore(
useCallback( useCallback(
(s: ReactFlowState) => { (s: ReactFlowState) => {
@@ -43,8 +43,8 @@ function useVisibleEdges(onlyRenderVisible: boolean, nodeLookup: NodeLookup) {
} }
return s.edges.filter((e) => { return s.edges.filter((e) => {
const sourceNode = nodeLookup.get(e.source); const sourceNode = nodeInternals.get(e.source);
const targetNode = nodeLookup.get(e.target); const targetNode = nodeInternals.get(e.target);
return ( return (
sourceNode?.width && sourceNode?.width &&
@@ -65,11 +65,11 @@ function useVisibleEdges(onlyRenderVisible: boolean, nodeLookup: NodeLookup) {
); );
}); });
}, },
[onlyRenderVisible, nodeLookup] [onlyRenderVisible, nodeInternals]
) )
); );
return groupEdgesByTreeLevel(edges, nodeLookup); return groupEdgesByTreeLevel(edges, nodeInternals);
} }
export default useVisibleEdges; export default useVisibleEdges;
+10 -80
View File
@@ -26,12 +26,10 @@ import {
OnEdgesChange, OnEdgesChange,
EdgeChange, EdgeChange,
NodeDimensionChange, NodeDimensionChange,
NodeLookup,
NodeLookupItem,
ElementId,
} from '../types'; } from '../types';
import { isNode, isEdge, getRectOfNodes, getNodesInside, getConnectedEdges } from '../utils/graph'; import { isNode, isEdge, getRectOfNodes, getNodesInside, getConnectedEdges } from '../utils/graph';
import { getHandleBounds } from '../components/Nodes/utils'; import { getHandleBounds } from '../components/Nodes/utils';
import { createNodeInternals } from './utils';
const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>(); const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
@@ -41,34 +39,6 @@ const createNodeOrEdgeSelectionChange = (isSelected: boolean) => (item: Node | E
isSelected, isSelected,
}); });
type XYPosAndTreeLevel = XYPosition & { treeLevel: number };
function addPositions(a: XYPosAndTreeLevel, b: XYPosition): XYPosAndTreeLevel {
return {
x: (a.x ?? 0) + (b.x ?? 0),
y: (a.y ?? 0) + (b.y ?? 0),
treeLevel: a.treeLevel + 1,
};
}
function getAbsolutePositionAndTreeLevel(
node: NodeLookupItem,
nodeLookup: NodeLookup,
result: XYPosAndTreeLevel
): XYPosAndTreeLevel {
const parentNode = node.parentNode ? nodeLookup.get(node.parentNode) : false;
if (!parentNode) {
return result;
}
return getAbsolutePositionAndTreeLevel(
parentNode,
nodeLookup,
addPositions(result, parentNode.position || { x: 0, y: 0 })
);
}
const createStore = () => const createStore = () =>
create<ReactFlowState>((set, get) => ({ create<ReactFlowState>((set, get) => ({
width: 0, width: 0,
@@ -125,58 +95,18 @@ const createStore = () =>
reactFlowVersion: typeof __REACT_FLOW_VERSION__ !== 'undefined' ? __REACT_FLOW_VERSION__ : '-', reactFlowVersion: typeof __REACT_FLOW_VERSION__ !== 'undefined' ? __REACT_FLOW_VERSION__ : '-',
nodeLookup: new Map(), nodeInternals: new Map(),
setNodes: (nodes: Node[]) => { setNodes: (nodes: Node[]) => {
const { nodeLookup } = get(); const nodeInternals = createNodeInternals(nodes, get().nodeInternals);
const nextNodeLookup = new Map<ElementId, NodeLookupItem>();
nodes.forEach((node) => { set({ nodes, nodeInternals });
const lookupNode: NodeLookupItem = {
...nodeLookup.get(node.id),
width: node.width || null,
height: node.height || null,
position: node.position,
positionAbsolute: node.position,
treeLevel: node.zIndex || 0,
};
if (node.parentNode) {
lookupNode.parentNode = node.parentNode;
}
nextNodeLookup.set(node.id, lookupNode);
});
nodes
.filter((node) => node.parentNode)
.forEach((node) => {
const positionAbsoluteAndTreeLevel = getAbsolutePositionAndTreeLevel(node, nextNodeLookup, {
...node.position,
treeLevel: node.zIndex || 0,
});
nextNodeLookup.set(node.parentNode!, { ...nextNodeLookup.get(node.parentNode!), isParentNode: true });
if (positionAbsoluteAndTreeLevel) {
const { treeLevel, x, y } = positionAbsoluteAndTreeLevel;
nextNodeLookup.set(node.id, {
...nextNodeLookup.get(node.id),
positionAbsolute: {
x,
y,
},
treeLevel,
});
}
});
set({ nodes, nodeLookup: nextNodeLookup });
}, },
setEdges: (edges: Edge[]) => { setEdges: (edges: Edge[]) => {
set({ edges }); set({ edges });
}, },
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => { updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
const { onNodesChange, nodes, transform, nodeLookup } = get(); const { onNodesChange, nodes, transform, nodeInternals } = get();
const nodesToChange: NodeChange[] = updates.reduce<NodeChange[]>((res, update) => { const nodesToChange: NodeChange[] = updates.reduce<NodeChange[]>((res, update) => {
const node = nodes.find((n) => n.id === update.id); const node = nodes.find((n) => n.id === update.id);
@@ -190,8 +120,8 @@ const createStore = () =>
if (doUpdate) { if (doUpdate) {
const handleBounds = getHandleBounds(update.nodeElement, transform[2]); const handleBounds = getHandleBounds(update.nodeElement, transform[2]);
nodeLookup.set(node.id, { nodeInternals.set(node.id, {
...nodeLookup.get(node.id), ...nodeInternals.get(node.id),
handleBounds, handleBounds,
...dimensions, ...dimensions,
}); });
@@ -208,7 +138,7 @@ const createStore = () =>
return res; return res;
}, []); }, []);
set({ nodeLookup: new Map(nodeLookup) }); set({ nodeInternals: new Map(nodeInternals) });
onNodesChange?.(nodesToChange); onNodesChange?.(nodesToChange);
}, },
@@ -216,11 +146,11 @@ const createStore = () =>
const { onNodesChange, nodes, nodeExtent } = get(); const { onNodesChange, nodes, nodeExtent } = get();
if (onNodesChange) { if (onNodesChange) {
const matchingNodes = nodes.filter((n) => !!n.isSelected || n.id === id); const matchingNodes = nodes.filter((n) => !!(n.isSelected || n.id === id));
if (matchingNodes?.length) { if (matchingNodes?.length) {
onNodesChange( onNodesChange(
matchingNodes.map((node) => { matchingNodes?.map((node) => {
const change: NodeDimensionChange = { const change: NodeDimensionChange = {
id: node.id, id: node.id,
type: 'dimensions', type: 'dimensions',
+77
View File
@@ -0,0 +1,77 @@
import { ElementId, Node, NodeInternals, NodeInternalsItem, XYPosition } from '../types';
type XYPosAndTreeLevel = XYPosition & { treeLevel: number };
function addPositions(a: XYPosAndTreeLevel, b: XYPosition): XYPosAndTreeLevel {
return {
x: (a.x ?? 0) + (b.x ?? 0),
y: (a.y ?? 0) + (b.y ?? 0),
treeLevel: a.treeLevel + 1,
};
}
function getAbsolutePosAndTreeLevel(
node: NodeInternalsItem,
nodeInternals: NodeInternals,
result: XYPosAndTreeLevel
): XYPosAndTreeLevel {
const parentNode = node.parentNode ? nodeInternals.get(node.parentNode) : false;
if (!parentNode) {
return result;
}
return getAbsolutePosAndTreeLevel(
parentNode,
nodeInternals,
addPositions(result, parentNode.position || { x: 0, y: 0 })
);
}
export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals): NodeInternals {
const nextNodeInternals = new Map<ElementId, NodeInternalsItem>();
const parentNodes: Record<ElementId, boolean> = {};
nodes.forEach((node) => {
const internals: NodeInternalsItem = {
...nodeInternals.get(node.id),
width: node.width || null,
height: node.height || null,
position: node.position,
positionAbsolute: node.position,
treeLevel: node.zIndex || 0,
};
if (node.parentNode) {
internals.parentNode = node.parentNode;
parentNodes[node.parentNode] = true;
}
nextNodeInternals.set(node.id, internals);
});
nodes.forEach((node) => {
const updatedInternals: NodeInternalsItem = nextNodeInternals.get(node.id)!;
if (node.parentNode) {
const positionAbsoluteAndTreeLevel = getAbsolutePosAndTreeLevel(node, nextNodeInternals, {
...node.position,
treeLevel: node.zIndex || 0,
});
const { treeLevel, x, y } = positionAbsoluteAndTreeLevel;
nextNodeInternals.set(node.parentNode!, { ...nextNodeInternals.get(node.parentNode!), isParentNode: true });
updatedInternals.positionAbsolute = {
x,
y,
};
updatedInternals.treeLevel = treeLevel;
}
if ((node.isDragging || node.isSelected) && !parentNodes[node.id]) {
nextNodeInternals.set(node.id, { ...updatedInternals, treeLevel: 1000 });
} else {
nextNodeInternals.set(node.id, { ...updatedInternals, treeLevel: updatedInternals?.treeLevel || 0 });
}
});
return nextNodeInternals;
}
+1
View File
@@ -20,6 +20,7 @@
.react-flow__viewport { .react-flow__viewport {
transform-origin: 0 0; transform-origin: 0 0;
z-index: 2; z-index: 2;
pointer-events: none;
} }
.react-flow__renderer { .react-flow__renderer {
+3 -3
View File
@@ -457,7 +457,7 @@ export type InitD3ZoomPayload = {
export type OnNodesChange = (nodes: NodeChange[]) => void; export type OnNodesChange = (nodes: NodeChange[]) => void;
export type OnEdgesChange = (nodes: EdgeChange[]) => void; export type OnEdgesChange = (nodes: EdgeChange[]) => void;
export type NodeLookupItem = { export type NodeInternalsItem = {
width?: number | null; width?: number | null;
height?: number | null; height?: number | null;
parentNode?: ElementId; parentNode?: ElementId;
@@ -468,14 +468,14 @@ export type NodeLookupItem = {
isParentNode?: boolean; isParentNode?: boolean;
}; };
export type NodeLookup = Map<ElementId, NodeLookupItem>; export type NodeInternals = Map<ElementId, NodeInternalsItem>;
export interface ReactFlowState { export interface ReactFlowState {
width: number; width: number;
height: number; height: number;
transform: Transform; transform: Transform;
nodes: Node[]; nodes: Node[];
nodeLookup: NodeLookup; nodeInternals: NodeInternals;
edges: Edge[]; edges: Edge[];
selectedNodesBbox: Rect; selectedNodesBbox: Rect;
onNodesChange: OnNodesChange | null; onNodesChange: OnNodesChange | null;