From d17e6ebaeac3ada154e46721d6146d4151633e20 Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 11 Sep 2020 16:36:23 +0200 Subject: [PATCH 1/2] refactor(wrapNode): simplify dragging behaviour --- src/components/Nodes/wrapNode.tsx | 72 ++++++++++------------------ src/container/GraphView/index.tsx | 2 + src/container/NodeRenderer/index.tsx | 4 ++ src/types/index.ts | 2 + 4 files changed, 32 insertions(+), 48 deletions(-) diff --git a/src/components/Nodes/wrapNode.tsx b/src/components/Nodes/wrapNode.tsx index 4634eb90..df85651f 100644 --- a/src/components/Nodes/wrapNode.tsx +++ b/src/components/Nodes/wrapNode.tsx @@ -11,16 +11,12 @@ import React, { } from 'react'; import { DraggableCore } from 'react-draggable'; import cc from 'classcat'; - import { ResizeObserver } from 'resize-observer'; -import { useStoreActions } from '../../store/hooks'; +import { useStoreActions } from '../../store/hooks'; import { Provider } from '../../contexts/NodeIdContext'; import { Node, NodeComponentProps, WrapNodeProps } from '../../types'; -const getMouseEvent = (event: MouseEvent | TouchEvent) => - typeof TouchEvent !== 'undefined' && event instanceof TouchEvent ? event.touches[0] : (event as MouseEvent); - export default (NodeComponent: ComponentType) => { const NodeWrapper = ({ id, @@ -47,18 +43,20 @@ export default (NodeComponent: ComponentType) => { targetPosition, isHidden, isInitialized, + snapToGrid, + snapGrid, }: WrapNodeProps) => { const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions); const setSelectedElements = useStoreActions((actions) => actions.setSelectedElements); - const updateNodePos = useStoreActions((actions) => actions.updateNodePos); + const updateNodePosDiff = useStoreActions((actions) => actions.updateNodePosDiff); const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection); const nodeElement = useRef(null); - const [offset, setOffset] = useState({ x: 0, y: 0 }); const [isDragging, setDragging] = useState(false); - const position = useMemo(() => ({ x: xPos, y: yPos }), [xPos, yPos]); - const node = useMemo(() => ({ id, type, position, data }), [id, type, position, data]); + const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]); + const grid = useMemo(() => (snapToGrid ? snapGrid : [1, 1])! as [number, number], [snapToGrid, snapGrid]); + const nodeStyle: CSSProperties = useMemo( () => ({ zIndex: selected ? 10 : 3, @@ -109,9 +107,7 @@ export default (NodeComponent: ComponentType) => { setSelectedElements({ id: node.id, type: node.type } as Node); } - if (onClick) { - onClick(event, node); - } + onClick?.(event, node); } }, [isSelectable, isDraggable, onClick, node] @@ -119,66 +115,47 @@ export default (NodeComponent: ComponentType) => { const onDragStart = useCallback( (event) => { - const startEvent = getMouseEvent(event); - - const scaledClientX = startEvent.clientX / transform[2]; - const scaledClientY = startEvent.clientY / transform[2]; - - const offsetX = scaledClientX - position.x - transform[0]; - const offsetY = scaledClientY - position.y - transform[1]; - - setOffset({ x: offsetX, y: offsetY }); - - if (onNodeDragStart) { - onNodeDragStart(event as MouseEvent, node); - } + onNodeDragStart?.(event as MouseEvent, node); if (selectNodesOnDrag && isSelectable) { unsetNodesSelection(); setSelectedElements({ id: node.id, type: node.type } as Node); } }, - [node, transform, position, selectNodesOnDrag, isSelectable, onNodeDragStart] + [node, transform, selectNodesOnDrag, isSelectable, onNodeDragStart] ); const onDrag = useCallback( - (event) => { - const dragEvent = getMouseEvent(event); - - const scaledClientX = dragEvent.clientX / transform[2]; - const scaledClientY = dragEvent.clientY / transform[2]; - + (_, data) => { setDragging(true); - updateNodePos({ + updateNodePosDiff({ id, - pos: { - x: scaledClientX - transform[0] - offset.x, - y: scaledClientY - transform[1] - offset.y, + diff: { + x: data.deltaX, + y: data.deltaY, }, }); }, - [id, transform, offset] + [id, transform] ); const onDragStop = useCallback( (event) => { + // 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) { setSelectedElements({ id: node.id, type: node.type } as Node); } - if (onClick) { - onClick(event as MouseEvent, node); - } + onClick?.(event as MouseEvent, node); return; } setDragging(false); - if (onNodeDragStop) { - onNodeDragStop(event as MouseEvent, node); - } + onNodeDragStop?.(event as MouseEvent, node); }, [node, isDragging, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop] ); @@ -187,11 +164,9 @@ export default (NodeComponent: ComponentType) => { if (nodeElement.current) { updateNodeDimensions({ id, nodeElement: nodeElement.current }); - const resizeObserver = new ResizeObserver((entries) => { - for (let _ of entries) { - if (nodeElement.current) { - updateNodeDimensions({ id, nodeElement: nodeElement.current }); - } + const resizeObserver = new ResizeObserver(() => { + if (nodeElement.current) { + updateNodeDimensions({ id, nodeElement: nodeElement.current }); } }); @@ -230,6 +205,7 @@ export default (NodeComponent: ComponentType) => { disabled={!isDraggable} cancel=".nodrag" nodeRef={nodeElement} + grid={grid} >
void; onNodeDragStop?: (event: MouseEvent, node: Node) => void; onlyRenderVisibleNodes?: boolean; + snapToGrid?: boolean; + snapGrid?: [number, number]; } function renderNode( @@ -66,6 +68,8 @@ function renderNode( selectNodesOnDrag={props.selectNodesOnDrag} isHidden={node.isHidden} isInitialized={isInitialized} + snapGrid={props.snapGrid} + snapToGrid={props.snapToGrid} /> ); } diff --git a/src/types/index.ts b/src/types/index.ts index a9471d46..ae9f53c7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -172,6 +172,8 @@ export interface WrapNodeProps { targetPosition?: Position; isHidden?: boolean; isInitialized?: boolean; + snapToGrid?: boolean; + snapGrid?: [number, number]; } export type FitViewParams = { From 8a0a928e17dc9631fda9d63322d3a85c14127f25 Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 11 Sep 2020 17:12:09 +0200 Subject: [PATCH 2/2] refactor(nodes): always render nodes that are dragged closes #477 --- src/components/Nodes/wrapNode.tsx | 4 +++ src/components/NodesSelection/index.tsx | 7 ++++++ src/container/NodeRenderer/index.tsx | 1 + src/store/index.ts | 33 +++++++++++-------------- src/types/index.ts | 4 ++- src/utils/graph.ts | 5 ++-- 6 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/components/Nodes/wrapNode.tsx b/src/components/Nodes/wrapNode.tsx index df85651f..6a13af1a 100644 --- a/src/components/Nodes/wrapNode.tsx +++ b/src/components/Nodes/wrapNode.tsx @@ -154,6 +154,10 @@ export default (NodeComponent: ComponentType) => { } setDragging(false); + updateNodePosDiff({ + id, + isDragging: false, + }); onNodeDragStop?.(event as MouseEvent, node); }, diff --git a/src/components/NodesSelection/index.tsx b/src/components/NodesSelection/index.tsx index c46e7b9e..4ac117b6 100644 --- a/src/components/NodesSelection/index.tsx +++ b/src/components/NodesSelection/index.tsx @@ -73,6 +73,13 @@ export default ({ const onStop = useCallback( (event: MouseEvent) => { + selectedNodes?.forEach((node) => { + updateNodePosDiff({ + id: node.id, + isDragging: false, + }); + }); + onSelectionDragStop?.(event, selectedNodes); }, [selectedNodes, onSelectionDragStop] diff --git a/src/container/NodeRenderer/index.tsx b/src/container/NodeRenderer/index.tsx index bf2187a9..18674df8 100644 --- a/src/container/NodeRenderer/index.tsx +++ b/src/container/NodeRenderer/index.tsx @@ -49,6 +49,7 @@ function renderNode( data={node.data} xPos={node.__rf.position.x} yPos={node.__rf.position.y} + isDragging={node.__rf.isDragging} onClick={props.onElementClick} onMouseEnter={props.onNodeMouseEnter} onMouseMove={props.onNodeMouseMove} diff --git a/src/store/index.ts b/src/store/index.ts index ec39cac5..85e08fba 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -227,11 +227,9 @@ export const storeModel: StoreModel = { state.elements.forEach((n) => { if (n.id === id && isNode(n)) { - n.__rf = { - ...n.__rf, - ...dimensions, - handleBounds, - }; + n.__rf.width = dimensions.width; + n.__rf.height = dimensions.height; + n.__rf.handleBounds = handleBounds; } }); }), @@ -249,24 +247,21 @@ export const storeModel: StoreModel = { state.elements.forEach((n) => { if (n.id === id && isNode(n)) { - n.__rf = { - ...n.__rf, - position, - }; + n.__rf.position = position; } }); }), - updateNodePosDiff: action((state, { id, diff }) => { + updateNodePosDiff: action((state, { id, diff = null, isDragging = true }) => { state.elements.forEach((n) => { if (n.id === id && isNode(n)) { - n.__rf = { - ...n.__rf, - position: { + if (diff) { + n.__rf.position = { x: n.__rf.position.x + diff.x, y: n.__rf.position.y + diff.y, - }, - }; + }; + } + n.__rf.isDragging = isDragging; } }); }), @@ -316,7 +311,7 @@ export const storeModel: StoreModel = { if (!selectedNodes) { state.selectionActive = false; - state.userSelectionRect = { ...state.userSelectionRect, draw: false }; + state.userSelectionRect.draw = false; state.nodesSelectionActive = false; state.selectedElements = null; @@ -328,7 +323,7 @@ export const storeModel: StoreModel = { state.nodesSelectionActive = true; state.selectedNodesBbox = selectedNodesBbox; - state.userSelectionRect = { ...state.userSelectionRect, draw: false }; + state.userSelectionRect.draw = false; state.selectionActive = false; }), @@ -454,7 +449,9 @@ export const storeModel: StoreModel = { // we need to sync the d3 zoom transform with the fitted transform d3Selection.property('__zoom', fittedTransform); - state.transform = [fittedTransform.x, fittedTransform.y, fittedTransform.k]; + state.transform[0] = fittedTransform.x; + state.transform[1] = fittedTransform.y; + state.transform[2] = fittedTransform.k; }), zoomTo: action((state, zoomLevel) => { diff --git a/src/types/index.ts b/src/types/index.ts index ae9f53c7..96730e61 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -174,6 +174,7 @@ export interface WrapNodeProps { isInitialized?: boolean; snapToGrid?: boolean; snapGrid?: [number, number]; + isDragging?: boolean; } export type FitViewParams = { @@ -271,7 +272,8 @@ export type NodePosUpdate = { export type NodeDiffUpdate = { id: ElementId; - diff: XYPosition; + diff?: XYPosition; + isDragging?: boolean; }; export type FlowTransform = { diff --git a/src/utils/graph.ts b/src/utils/graph.ts index 6f341ac9..d31c00cb 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -126,6 +126,7 @@ export const parseElement = (element: Node | Edge): Node | Edge => { width: null, height: null, handleBounds: {}, + isDragging: false, }, } as Node; }; @@ -182,13 +183,13 @@ export const getNodesInside = ( height: rect.height / tScale, }); - return nodes.filter(({ __rf: { position, width, height } }) => { + return nodes.filter(({ __rf: { position, width, height, isDragging } }) => { const nBox = rectToBox({ ...position, width, height }); const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x)); const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y)); const overlappingArea = xOverlap * yOverlap; - if (width === null || height === null) { + if (width === null || height === null || isDragging) { // at the beginnning all nodes have width & height === 0 return true; }