From e88dfdb65070b925acf19c282c75a78af8ee9fa7 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 26 Nov 2020 14:25:39 +0100 Subject: [PATCH] refactor(nodes): use one resize observer for all nodes #723 --- src/components/Nodes/utils.ts | 11 ++++- src/components/Nodes/wrapNode.tsx | 23 +++++----- src/container/NodeRenderer/index.tsx | 17 ++++++- src/store/index.ts | 67 +++++++++++++--------------- src/types/index.ts | 1 + src/utils/index.ts | 4 +- 6 files changed, 70 insertions(+), 53 deletions(-) diff --git a/src/components/Nodes/utils.ts b/src/components/Nodes/utils.ts index 7d2a7d31..e917e853 100644 --- a/src/components/Nodes/utils.ts +++ b/src/components/Nodes/utils.ts @@ -1,7 +1,16 @@ import { HandleElement, Position } from '../../types'; import { getDimensions } from '../../utils'; -export const getHandleBounds = ( +export const getHandleBounds = (nodeElement: HTMLDivElement, scale: number) => { + const bounds = nodeElement.getBoundingClientRect(); + + return { + source: getHandleBoundsByHandleType('.source', nodeElement, bounds, scale), + target: getHandleBoundsByHandleType('.target', nodeElement, bounds, scale), + }; +}; + +export const getHandleBoundsByHandleType = ( selector: string, nodeElement: HTMLDivElement, parentBounds: ClientRect | DOMRect, diff --git a/src/components/Nodes/wrapNode.tsx b/src/components/Nodes/wrapNode.tsx index f498326a..3a3b95f4 100644 --- a/src/components/Nodes/wrapNode.tsx +++ b/src/components/Nodes/wrapNode.tsx @@ -35,6 +35,7 @@ export default (NodeComponent: ComponentType) => { snapToGrid, snapGrid, isDragging, + resizeObserver, }: WrapNodeProps) => { const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions); const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements); @@ -160,24 +161,19 @@ export default (NodeComponent: ComponentType) => { useEffect(() => { if (nodeElement.current && !isHidden) { updateNodeDimensions({ id, nodeElement: nodeElement.current }); + } + }, [id, isHidden]); - const resizeObserver = new ResizeObserver(() => { - if (nodeElement.current) { - updateNodeDimensions({ id, nodeElement: nodeElement.current }); - } - }); + useEffect(() => { + if (nodeElement.current) { + const currNode = nodeElement.current; + resizeObserver.observe(currNode); - resizeObserver.observe(nodeElement.current); - - return () => { - if (resizeObserver && nodeElement.current) { - resizeObserver.unobserve(nodeElement.current); - } - }; + return () => resizeObserver.unobserve(currNode); } return; - }, [id, isHidden]); + }, []); if (isHidden) { return null; @@ -213,6 +209,7 @@ export default (NodeComponent: ComponentType) => { onMouseLeave={onMouseLeaveHandler} onContextMenu={onContextMenuHandler} onClick={onSelectNodeHandler} + data-id={id} > { const elementsSelectable = useStoreState((state) => state.elementsSelectable); const viewportBox = useStoreState((state) => state.viewportBox); const nodes = useStoreState((state) => state.nodes); + const batchUpdateNodeDimensions = useStoreActions((actions) => actions.batchUpdateNodeDimensions); const visibleNodes = props.onlyRenderVisibleElements ? getNodesInside(nodes, viewportBox, transform, true) : nodes; @@ -37,6 +38,19 @@ const NodeRenderer = (props: NodeRendererProps) => { [transform[0], transform[1], transform[2]] ); + const resizeObserver = useMemo( + () => + new ResizeObserver((entries) => { + const updates = entries.map((entry) => ({ + id: entry.target.getAttribute('data-id') as string, + nodeElement: entry.target as HTMLDivElement, + })); + + batchUpdateNodeDimensions({ updates }); + }), + [] + ); + return (
{visibleNodes.map((node) => { @@ -81,6 +95,7 @@ const NodeRenderer = (props: NodeRendererProps) => { isDraggable={isDraggable} isSelectable={isSelectable} isConnectable={isConnectable} + resizeObserver={resizeObserver} /> ); })} diff --git a/src/store/index.ts b/src/store/index.ts index 0e46aeb2..3c3948cf 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,4 +1,4 @@ -import { createStore, Action, action, ActionOn, actionOn, Thunk, thunk, computed, Computed } from 'easy-peasy'; +import { createStore, Action, action, Thunk, thunk, computed, Computed } from 'easy-peasy'; import isEqual from 'fast-deep-equal'; import { Selection as D3Selection, ZoomBehavior } from 'd3'; @@ -33,6 +33,10 @@ type NodeDimensionUpdate = { nodeElement: HTMLDivElement; }; +type NodeDimensionUpdates = { + updates: NodeDimensionUpdate[]; +}; + type InitD3Zoom = { d3Zoom: ZoomBehavior; d3Selection: D3Selection; @@ -90,8 +94,8 @@ export interface StoreModel { setElements: Action; + batchUpdateNodeDimensions: Action; updateNodeDimensions: Action; - updateNodeHandlePositions: ActionOn; updateNodePos: Action; updateNodePosDiff: Action; @@ -248,50 +252,39 @@ export const storeModel: StoreModel = { }); }), - updateNodeDimensions: action((state, { id, nodeElement }) => { - const dimensions = getDimensions(nodeElement); - const matchingNode = state.nodes.find((n) => n.id === id); + batchUpdateNodeDimensions: action((state, { updates }) => { + updates.forEach((update) => { + const dimensions = getDimensions(update.nodeElement); + const matchingIndex = state.elements.findIndex((n) => n.id === update.id); + const matchingNode = state.elements[matchingIndex] as Node; - // only update when size change - if ( - !matchingNode || - (matchingNode.__rf.width === dimensions.width && matchingNode.__rf.height === dimensions.height) - ) { - return; - } + if ( + matchingIndex !== -1 && + dimensions.width && + dimensions.height && + (matchingNode.__rf.width !== dimensions.width || matchingNode.__rf.height !== dimensions.height) + ) { + const handleBounds = getHandleBounds(update.nodeElement, state.transform[2]); - state.elements.forEach((n) => { - if (n.id === id && isNode(n)) { - n.__rf.width = dimensions.width; - n.__rf.height = dimensions.height; + (state.elements[matchingIndex] as Node).__rf.width = dimensions.width; + (state.elements[matchingIndex] as Node).__rf.height = dimensions.height; + (state.elements[matchingIndex] as Node).__rf.handleBounds = handleBounds; } }); }), - updateNodeHandlePositions: actionOn( - (actions) => actions.updateNodeDimensions, - (state, target) => { - const { nodeElement, id } = target.payload; - const matchingNode = state.nodes.find((n) => n.id === id); + updateNodeDimensions: action((state, { id, nodeElement }) => { + const dimensions = getDimensions(nodeElement); + const matchingIndex = state.elements.findIndex((n) => n.id === id); - if (!matchingNode) { - return; - } + if (matchingIndex !== -1 && dimensions.width && dimensions.height) { + const handleBounds = getHandleBounds(nodeElement, state.transform[2]); - const bounds = nodeElement.getBoundingClientRect(); - - const handleBounds = { - source: getHandleBounds('.source', nodeElement, bounds, state.transform[2]), - target: getHandleBounds('.target', nodeElement, bounds, state.transform[2]), - }; - - state.elements.forEach((n) => { - if (n.id === id && isNode(n)) { - n.__rf.handleBounds = handleBounds; - } - }); + (state.elements[matchingIndex] as Node).__rf.width = dimensions.width; + (state.elements[matchingIndex] as Node).__rf.height = dimensions.height; + (state.elements[matchingIndex] as Node).__rf.handleBounds = handleBounds; } - ), + }), updateNodePos: action((state, { id, pos }) => { let position: XYPosition = pos; diff --git a/src/types/index.ts b/src/types/index.ts index eb94c85d..3476d7a8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -224,6 +224,7 @@ export interface WrapNodeProps { snapToGrid?: boolean; snapGrid?: SnapGrid; isDragging?: boolean; + resizeObserver: ResizeObserver; } export type FitViewParams = { diff --git a/src/utils/index.ts b/src/utils/index.ts index caffa530..13832a31 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,8 @@ import { DraggableEvent } from 'react-draggable'; import { MouseEvent as ReactMouseEvent } from 'react'; +import { Dimensions } from '../types'; + export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEvent) => { const target = e?.target as HTMLElement; @@ -9,7 +11,7 @@ export const isInputDOMNode = (e: ReactMouseEvent | DraggableEvent | KeyboardEve ); }; -export const getDimensions = (node: HTMLDivElement) => ({ +export const getDimensions = (node: HTMLDivElement): Dimensions => ({ width: node.offsetWidth, height: node.offsetHeight, });