From 407e34b1cb18d39b417fda797d317115dfe198b8 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 4 Jun 2020 23:46:37 +0200 Subject: [PATCH 1/6] refactor(controls): dont import store but use actions --- example/src/Provider/index.js | 6 ++- src/additional-components/Controls/index.tsx | 14 ++++-- src/components/Edges/wrapEdge.tsx | 5 +- src/hooks/useD3Zoom.ts | 12 +---- src/store/index.ts | 53 +++++++++++++++++++- src/types/index.ts | 2 +- src/utils/graph.ts | 2 +- 7 files changed, 73 insertions(+), 21 deletions(-) diff --git a/example/src/Provider/index.js b/example/src/Provider/index.js index 63f16334..77bee534 100644 --- a/example/src/Provider/index.js +++ b/example/src/Provider/index.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import ReactFlow, { ReactFlowProvider, addEdge, removeElements } from 'react-flow-renderer'; +import ReactFlow, { ReactFlowProvider, addEdge, removeElements, Controls } from 'react-flow-renderer'; import Sidebar from './Sidebar'; @@ -31,7 +31,9 @@ const ProviderFlow = () => { onElementClick={onElementClick} onConnect={onConnect} onElementsRemove={onElementsRemove} - /> + > + + diff --git a/src/additional-components/Controls/index.tsx b/src/additional-components/Controls/index.tsx index 6c110bd8..b6bbeb7b 100644 --- a/src/additional-components/Controls/index.tsx +++ b/src/additional-components/Controls/index.tsx @@ -1,7 +1,6 @@ import React from 'react'; import classnames from 'classnames'; -import { fitView, zoomIn, zoomOut } from '../../utils/graph'; import { useStoreState, useStoreActions } from '../../store/hooks'; import PlusIcon from '../../../assets/icons/plus.svg'; @@ -20,6 +19,10 @@ interface ControlProps extends React.HTMLAttributes { const Controls = ({ style, showZoom = true, showFitView = true, showInteractive = true, className }: ControlProps) => { const setInteractive = useStoreActions((actions) => actions.setInteractive); + const fitView = useStoreActions((actions) => actions.fitView); + const zoomIn = useStoreActions((actions) => actions.zoomIn); + const zoomOut = useStoreActions((actions) => actions.zoomOut); + const isInteractive = useStoreState((s) => s.isInteractive); const mapClasses = classnames('react-flow__controls', className); @@ -27,16 +30,19 @@ const Controls = ({ style, showZoom = true, showFitView = true, showInteractive
{showZoom && ( <> -
+
zoomIn()}>
-
+
zoomOut()}>
)} {showFitView && ( -
fitView()}> +
fitView({ padding: 0.1 })} + >
)} diff --git a/src/components/Edges/wrapEdge.tsx b/src/components/Edges/wrapEdge.tsx index df6d1158..9b9d7640 100644 --- a/src/components/Edges/wrapEdge.tsx +++ b/src/components/Edges/wrapEdge.tsx @@ -1,7 +1,7 @@ import React, { memo, ComponentType, CSSProperties } from 'react'; import cx from 'classnames'; -import store from '../../store'; +import { useStoreActions } from '../../store/hooks'; import { ElementId, Edge, EdgeCompProps } from '../../types'; interface EdgeWrapperProps { @@ -38,6 +38,7 @@ export default (EdgeComponent: ComponentType) => { className, ...rest }: EdgeWrapperProps) => { + const setSelectedElements = useStoreActions((a) => a.setSelectedElements); const edgeClasses = cx('react-flow__edge', `react-flow__edge-${type}`, className, { selected, animated }); const edgeGroupStyle: CSSProperties = { pointerEvents: isInteractive ? 'all' : 'none', @@ -47,7 +48,7 @@ export default (EdgeComponent: ComponentType) => { return; } - store.dispatch.setSelectedElements({ id, source, target }); + setSelectedElements({ id, source, target }); if (onClick) { onClick({ id, source, target, type }); diff --git a/src/hooks/useD3Zoom.ts b/src/hooks/useD3Zoom.ts index 95f3a0d9..20f66780 100644 --- a/src/hooks/useD3Zoom.ts +++ b/src/hooks/useD3Zoom.ts @@ -1,5 +1,5 @@ import { useEffect, MutableRefObject } from 'react'; -import { zoom, zoomIdentity } from 'd3-zoom'; +import { zoom } from 'd3-zoom'; import { select, event } from 'd3-selection'; import { useStoreState, useStoreActions } from '../store/hooks'; @@ -11,8 +11,6 @@ interface UseD3ZoomParams { } export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): void => { - const transform = useStoreState((s) => s.transform); - const d3Selection = useStoreState((s) => s.d3Selection); const d3Zoom = useStoreState((s) => s.d3Zoom); const initD3 = useStoreActions((actions) => actions.initD3); @@ -35,7 +33,7 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi d3Zoom.on('zoom', null); } else { d3Zoom.on('zoom', () => { - if (event.sourceEvent && event.sourceEvent.target !== zoomPane.current) { + if (!event.sourceEvent || (event.sourceEvent && event.sourceEvent.target !== zoomPane.current)) { return; } @@ -45,12 +43,6 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi onMove(); } }); - - if (d3Selection && d3Zoom) { - // we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced - const graphTransform = zoomIdentity.translate(transform[0], transform[1]).scale(transform[2]); - d3Selection.call(d3Zoom.transform, graphTransform); - } } return () => { diff --git a/src/store/index.ts b/src/store/index.ts index 5d843a83..2f9953c8 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,6 +1,7 @@ -import { createStore, Action, action } from 'easy-peasy'; +import { createStore, Action, action, Thunk, thunk } from 'easy-peasy'; import isEqual from 'fast-deep-equal'; import { Selection as D3Selection, ZoomBehavior } from 'd3'; +import { zoomIdentity } from 'd3-zoom'; import { getDimensions } from '../utils'; import { getHandleBounds } from '../components/Nodes/utils'; @@ -20,6 +21,7 @@ import { HandleType, SetConnectionId, NodePosUpdate, + FitViewParams, } from '../types'; type TransformXYK = { @@ -122,6 +124,11 @@ export interface StoreModel { setUserSelection: Action; updateUserSelection: Action; unsetUserSelection: Action; + + fitView: Action; + zoom: Action; + zoomIn: Thunk; + zoomOut: Thunk; } export const storeModel: StoreModel = { @@ -370,6 +377,50 @@ export const storeModel: StoreModel = { setInteractive: action((state, isInteractive) => { state.isInteractive = isInteractive; }), + + fitView: action((state, { padding = 0.1 }) => { + const { nodes, width, height, d3Selection, d3Zoom } = state; + + if (!d3Selection || !d3Zoom || !nodes.length) { + return; + } + + const bounds = getRectOfNodes(nodes); + const maxBoundsSize = Math.max(bounds.width, bounds.height); + const k = Math.min(width, height) / (maxBoundsSize + maxBoundsSize * padding); + const boundsCenterX = bounds.x + bounds.width / 2; + const boundsCenterY = bounds.y + bounds.height / 2; + const transform = [width / 2 - boundsCenterX * k, height / 2 - boundsCenterY * k]; + const fittedTransform = zoomIdentity.translate(transform[0], transform[1]).scale(k); + + d3Selection.call(d3Zoom.transform, fittedTransform); + + state.transform = [fittedTransform.x, fittedTransform.y, fittedTransform.k]; + }), + + zoom: action((state, amount) => { + const { d3Zoom, d3Selection, transform } = state; + const nextZoom = transform[2] + amount; + + if (d3Zoom && d3Selection) { + d3Zoom.scaleTo(d3Selection, nextZoom); + + const graphTransform = zoomIdentity.translate(transform[0], transform[1]).scale(nextZoom); + d3Selection.call(d3Zoom.transform, graphTransform); + + console.log(graphTransform); + + state.transform = [graphTransform.x, graphTransform.y, graphTransform.k]; + } + }), + + zoomIn: thunk((actions) => { + actions.zoom(0.2); + }), + + zoomOut: thunk((actions) => { + actions.zoom(-0.2); + }), }; const store = createStore(storeModel); diff --git a/src/types/index.ts b/src/types/index.ts index 859d2727..74697a9b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -134,7 +134,7 @@ export interface WrapNodeProps { } export type FitViewParams = { - padding: number; + padding?: number; }; export type FitViewFunc = (fitViewOptions: FitViewParams) => void; export type ProjectFunc = (position: XYPosition) => XYPosition; diff --git a/src/utils/graph.ts b/src/utils/graph.ts index 65c06be9..c20d3162 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -182,7 +182,7 @@ export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => { }); }; -export const fitView = ({ padding }: FitViewParams = { padding: 0.1 }): void => { +export const fitView = ({ padding = 0.1 }: FitViewParams = { padding: 0.1 }): void => { const { nodes, width, height, d3Selection, d3Zoom } = store.getState(); if (!d3Selection || !d3Zoom || !nodes.length) { From 5bfe72f2259d941a93061f99959d1b00fcd4a857 Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 5 Jun 2020 00:15:45 +0200 Subject: [PATCH 2/6] refactor(graph-utils): store handling --- src/store/index.ts | 11 +++-------- src/utils/graph.ts | 25 +++---------------------- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/src/store/index.ts b/src/store/index.ts index 2f9953c8..7588aa52 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,7 +1,7 @@ import { createStore, Action, action, Thunk, thunk } from 'easy-peasy'; import isEqual from 'fast-deep-equal'; import { Selection as D3Selection, ZoomBehavior } from 'd3'; -import { zoomIdentity } from 'd3-zoom'; +import { zoomIdentity, zoomTransform } from 'd3-zoom'; import { getDimensions } from '../utils'; import { getHandleBounds } from '../components/Nodes/utils'; @@ -404,13 +404,8 @@ export const storeModel: StoreModel = { if (d3Zoom && d3Selection) { d3Zoom.scaleTo(d3Selection, nextZoom); - - const graphTransform = zoomIdentity.translate(transform[0], transform[1]).scale(nextZoom); - d3Selection.call(d3Zoom.transform, graphTransform); - - console.log(graphTransform); - - state.transform = [graphTransform.x, graphTransform.y, graphTransform.k]; + const transforms = zoomTransform(d3Selection.node() as Element); + state.transform = [transforms.x, transforms.y, transforms.k]; } }), diff --git a/src/utils/graph.ts b/src/utils/graph.ts index c20d3162..d81affd3 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -1,5 +1,3 @@ -import { zoomIdentity } from 'd3-zoom'; - import store from '../store'; import { ElementId, Node, Edge, Elements, Transform, XYPosition, Rect, FitViewParams, Box, Connection } from '../types'; @@ -182,29 +180,12 @@ export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => { }); }; -export const fitView = ({ padding = 0.1 }: FitViewParams = { padding: 0.1 }): void => { - const { nodes, width, height, d3Selection, d3Zoom } = store.getState(); - - if (!d3Selection || !d3Zoom || !nodes.length) { - return; - } - - const bounds = getRectOfNodes(nodes); - const maxBoundsSize = Math.max(bounds.width, bounds.height); - const k = Math.min(width, height) / (maxBoundsSize + maxBoundsSize * padding); - const boundsCenterX = bounds.x + bounds.width / 2; - const boundsCenterY = bounds.y + bounds.height / 2; - const transform = [width / 2 - boundsCenterX * k, height / 2 - boundsCenterY * k]; - const fittedTransform = zoomIdentity.translate(transform[0], transform[1]).scale(k); - - d3Selection.call(d3Zoom.transform, fittedTransform); +export const fitView = (params: FitViewParams = { padding: 0.1 }): void => { + store.getActions().fitView(params); }; const zoom = (amount: number): void => { - const { d3Zoom, d3Selection, transform } = store.getState(); - if (d3Zoom && d3Selection) { - d3Zoom.scaleTo(d3Selection, transform[2] + amount); - } + store.getActions().zoom(amount); }; export const zoomIn = (): void => zoom(0.2); From 5a3adab3a0b21af2c0111d9fb16dfd059142d27c Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 5 Jun 2020 00:17:28 +0200 Subject: [PATCH 3/6] refactor(internals): rename __rg to __rf --- example/src/Provider/Sidebar.js | 2 +- .../MiniMap/MiniMapNode.tsx | 2 +- src/components/ConnectionLine/index.tsx | 12 ++++----- src/components/NodesSelection/index.tsx | 4 +-- src/container/EdgeRenderer/index.tsx | 26 +++++++++---------- src/container/NodeRenderer/index.tsx | 4 +-- src/hooks/useElementUpdater.ts | 4 +-- src/store/index.ts | 10 +++---- src/types/index.ts | 2 +- src/utils/graph.ts | 8 +++--- 10 files changed, 37 insertions(+), 37 deletions(-) diff --git a/example/src/Provider/Sidebar.js b/example/src/Provider/Sidebar.js index 41dc0891..ab8def0d 100644 --- a/example/src/Provider/Sidebar.js +++ b/example/src/Provider/Sidebar.js @@ -17,7 +17,7 @@ export default () => {
Nodes
{nodes.map((node) => (
- Node {node.id} - x: {node.__rg.position.x.toFixed(2)}, y: {node.__rg.position.y.toFixed(2)} + Node {node.id} - x: {node.__rf.position.x.toFixed(2)}, y: {node.__rf.position.y.toFixed(2)}
))} diff --git a/src/additional-components/MiniMap/MiniMapNode.tsx b/src/additional-components/MiniMap/MiniMapNode.tsx index 3f316df9..e85ba788 100644 --- a/src/additional-components/MiniMap/MiniMapNode.tsx +++ b/src/additional-components/MiniMap/MiniMapNode.tsx @@ -13,7 +13,7 @@ const MiniMapNode = ({ node, color, borderRadius }: MiniMapNodeProps) => { position: { x, y }, width, height, - } = node.__rg; + } = node.__rf; const { background, backgroundColor } = node.style || {}; const fill = (background || backgroundColor || color) as string; diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index 9297562c..10db3023 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -46,12 +46,12 @@ export default ({ const connectionLineClasses: string = cx('react-flow__connection', className); const sourceHandle = handleId - ? sourceNode.__rg.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId) - : sourceNode.__rg.handleBounds[connectionHandleType][0]; - const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNode.__rg.width / 2; - const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.__rg.height; - const sourceX = sourceNode.__rg.position.x + sourceHandleX; - const sourceY = sourceNode.__rg.position.y + sourceHandleY; + ? sourceNode.__rf.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId) + : sourceNode.__rf.handleBounds[connectionHandleType][0]; + const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNode.__rf.width / 2; + const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.__rf.height; + const sourceX = sourceNode.__rf.position.x + sourceHandleX; + const sourceY = sourceNode.__rf.position.y + sourceHandleY; const targetX = (connectionPositionX - transform[0]) * (1 / transform[2]); const targetY = (connectionPositionY - transform[1]) * (1 / transform[2]); diff --git a/src/components/NodesSelection/index.tsx b/src/components/NodesSelection/index.tsx index 8a707657..37ffbd5a 100644 --- a/src/components/NodesSelection/index.tsx +++ b/src/components/NodesSelection/index.tsx @@ -17,8 +17,8 @@ function getStartPositions(nodes: Node[]): StartPositions { return nodes.reduce((res, node) => { const startPosition = { - x: node.__rg.position.x || node.position.x, - y: node.__rg.position.y || node.position.y, + x: node.__rf.position.x || node.position.x, + y: node.__rf.position.y || node.position.y, }; res[node.id] = startPosition; diff --git a/src/container/EdgeRenderer/index.tsx b/src/container/EdgeRenderer/index.tsx index 23db059a..44275a60 100644 --- a/src/container/EdgeRenderer/index.tsx +++ b/src/container/EdgeRenderer/index.tsx @@ -26,23 +26,23 @@ function getHandlePosition(position: Position, node: Node, handle: any | null = switch (position) { case Position.Top: return { - x: node.__rg.width / 2, + x: node.__rf.width / 2, y: 0, }; case Position.Right: return { - x: node.__rg.width, - y: node.__rg.height / 2, + x: node.__rf.width, + y: node.__rf.height / 2, }; case Position.Bottom: return { - x: node.__rg.width / 2, - y: node.__rg.height, + x: node.__rf.width / 2, + y: node.__rf.height, }; case Position.Left: return { x: 0, - y: node.__rg.height / 2, + y: node.__rf.height / 2, }; } } @@ -98,12 +98,12 @@ function getEdgePositions( targetPosition: Position ): EdgePositions { const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle); - const sourceX = sourceNode.__rg.position.x + sourceHandlePos.x; - const sourceY = sourceNode.__rg.position.y + sourceHandlePos.y; + const sourceX = sourceNode.__rf.position.x + sourceHandlePos.x; + const sourceY = sourceNode.__rf.position.y + sourceHandlePos.y; const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle); - const targetX = targetNode.__rg.position.x + targetHandlePos.x; - const targetY = targetNode.__rg.position.y + targetHandlePos.y; + const targetX = targetNode.__rf.position.x + targetHandlePos.x; + const targetY = targetNode.__rf.position.y + targetHandlePos.y; return { sourceX, @@ -134,14 +134,14 @@ function renderEdge( throw new Error(`couldn't create edge for target id: ${targetId}`); } - if (!sourceNode.__rg.width || !sourceNode.__rg.height) { + if (!sourceNode.__rf.width || !sourceNode.__rf.height) { return null; } const edgeType = edge.type || 'default'; const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default; - const sourceHandle = getHandle(sourceNode.__rg.handleBounds.source, sourceHandleId); - const targetHandle = getHandle(targetNode.__rg.handleBounds.target, targetHandleId); + const sourceHandle = getHandle(sourceNode.__rf.handleBounds.source, sourceHandleId); + const targetHandle = getHandle(targetNode.__rf.handleBounds.target, targetHandleId); const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom; const targetPosition = targetHandle ? targetHandle.position : Position.Top; diff --git a/src/container/NodeRenderer/index.tsx b/src/container/NodeRenderer/index.tsx index 6c9d684b..df5e68e5 100644 --- a/src/container/NodeRenderer/index.tsx +++ b/src/container/NodeRenderer/index.tsx @@ -34,8 +34,8 @@ function renderNode( id={node.id} type={nodeType} data={node.data} - xPos={node.__rg.position.x} - yPos={node.__rg.position.y} + xPos={node.__rf.position.x} + yPos={node.__rf.position.y} onClick={props.onElementClick} onNodeDragStart={props.onNodeDragStart} onNodeDragStop={props.onNodeDragStop} diff --git a/src/hooks/useElementUpdater.ts b/src/hooks/useElementUpdater.ts index c8c6936d..51877d11 100644 --- a/src/hooks/useElementUpdater.ts +++ b/src/hooks/useElementUpdater.ts @@ -39,8 +39,8 @@ const useElementUpdater = (elements: Elements): void => { }; if (positionChanged) { - nodeProps.__rg = { - ...existingNode.__rg, + nodeProps.__rf = { + ...existingNode.__rf, position: propNode.position, }; nodeProps.position = propNode.position; diff --git a/src/store/index.ts b/src/store/index.ts index 7588aa52..68250901 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -192,7 +192,7 @@ export const storeModel: StoreModel = { // only update when size change if ( !matchingNode || - (matchingNode.__rg.width === dimensions.width && matchingNode.__rg.height === dimensions.height) + (matchingNode.__rf.width === dimensions.width && matchingNode.__rf.height === dimensions.height) ) { return; } @@ -204,8 +204,8 @@ export const storeModel: StoreModel = { state.nodes.forEach((n) => { if (n.id === id) { - n.__rg = { - ...n.__rg, + n.__rf = { + ...n.__rf, ...dimensions, handleBounds, }; @@ -227,8 +227,8 @@ export const storeModel: StoreModel = { state.nodes.forEach((n) => { if (n.id === id) { - n.__rg = { - ...n.__rg, + n.__rf = { + ...n.__rf, position, }; } diff --git a/src/types/index.ts b/src/types/index.ts index 74697a9b..050ad740 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -34,7 +34,7 @@ export interface Node { id: ElementId; position: XYPosition; type?: string; - __rg?: any; + __rf?: any; data?: any; style?: CSSProperties; className?: string; diff --git a/src/utils/graph.ts b/src/utils/graph.ts index d81affd3..5d6f7f4c 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -94,7 +94,7 @@ export const parseElement = (element: Node | Edge): Node | Edge => { ...element, id: element.id.toString(), type: element.type || 'default', - __rg: { + __rf: { position: element.position, width: null, height: null, @@ -129,7 +129,7 @@ export const getBoundsofRects = (rect1: Rect, rect2: Rect): Rect => export const getRectOfNodes = (nodes: Node[]): Rect => { const box = nodes.reduce( - (currBox, { __rg: { position, width, height } }) => + (currBox, { __rf: { position, width, height } }) => getBoundsOfBoxes(currBox, rectToBox({ ...position, width, height })), { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity } ); @@ -155,7 +155,7 @@ export const getNodesInside = ( height: rect.height / tScale, }); - return nodes.filter(({ __rg: { position, width, height } }) => { + return nodes.filter(({ __rf: { position, width, height } }) => { 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)); @@ -199,7 +199,7 @@ export const getElements = (): Elements => { ...nodes.map((node) => { const n = { ...node }; - delete n.__rg; + delete n.__rf; return n; }), ...edges.map((e) => ({ ...e })), From 4ae4f2e2135e45b163d20e247cf63641e6aa805f Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 5 Jun 2020 01:04:40 +0200 Subject: [PATCH 4/6] fix(userselection): handle shift key + mouse up handling --- src/components/UserSelection/index.tsx | 12 +++++------- src/container/GraphView/index.tsx | 5 +++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/components/UserSelection/index.tsx b/src/components/UserSelection/index.tsx index 1922e200..4be5cd7e 100644 --- a/src/components/UserSelection/index.tsx +++ b/src/components/UserSelection/index.tsx @@ -53,16 +53,16 @@ export default memo(({ isInteractive }: UserSelectionProps) => { return null; } - function onMouseDown(evt: React.MouseEvent): void { + const onMouseDown = (evt: React.MouseEvent): void => { const mousePos = getMousePosition(evt); if (!mousePos) { return; } setUserSelection(mousePos); - } + }; - function onMouseMove(evt: React.MouseEvent): void { + const onMouseMove = (evt: React.MouseEvent): void => { const mousePos = getMousePosition(evt); if (!mousePos) { @@ -70,11 +70,9 @@ export default memo(({ isInteractive }: UserSelectionProps) => { } updateUserSelection(mousePos); - } + }; - function onMouseUp() { - unsetUserSelection(); - } + const onMouseUp = () => unsetUserSelection(); return (
s.height); const d3Initialised = useStoreState((s) => s.d3Initialised); const nodesSelectionActive = useStoreState((s) => s.nodesSelectionActive); - + const selectionActive = useStoreState((s) => s.selectionActive); const updateSize = useStoreActions((actions) => actions.updateSize); const setNodesSelection = useStoreActions((actions) => actions.setNodesSelection); const setOnConnect = useStoreActions((a) => a.setOnConnect); @@ -91,6 +91,7 @@ const GraphView = memo( const selectionKeyPressed = useKeyPress(selectionKeyCode); const rendererClasses = classnames('react-flow__renderer', { 'is-interactive': isInteractive }); + const userSelectionActive = selectionKeyPressed || selectionActive; const onZoomPaneClick = () => setNodesSelection({ isActive: false }); @@ -188,7 +189,7 @@ const GraphView = memo( connectionLineType={connectionLineType} connectionLineStyle={connectionLineStyle} /> - {selectionKeyPressed && } + {userSelectionActive && } {nodesSelectionActive && }
From c2fad7e6808bb1ff8eb8b4832dcf239f97dbb46c Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 5 Jun 2020 01:04:56 +0200 Subject: [PATCH 5/6] refactor(useD3hook): init d3 inside store --- example/public/index.html | 2 +- src/hooks/useD3Zoom.ts | 45 ++++++++++++++++----------------------- src/store/index.ts | 18 ++++++---------- 3 files changed, 26 insertions(+), 39 deletions(-) diff --git a/example/public/index.html b/example/public/index.html index e4e256d1..6e06a78f 100644 --- a/example/public/index.html +++ b/example/public/index.html @@ -3,7 +3,7 @@ - + React Flow Examples diff --git a/src/hooks/useD3Zoom.ts b/src/hooks/useD3Zoom.ts index 20f66780..ec2028bd 100644 --- a/src/hooks/useD3Zoom.ts +++ b/src/hooks/useD3Zoom.ts @@ -1,6 +1,5 @@ import { useEffect, MutableRefObject } from 'react'; -import { zoom } from 'd3-zoom'; -import { select, event } from 'd3-selection'; +import { event } from 'd3-selection'; import { useStoreState, useStoreActions } from '../store/hooks'; @@ -18,35 +17,27 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi useEffect(() => { if (zoomPane.current) { - const nextD3ZoomInstance = zoom(); - const selection = select(zoomPane.current).call(nextD3ZoomInstance); - initD3({ zoom: nextD3ZoomInstance, selection }); + initD3(zoomPane.current); } }, []); useEffect(() => { - if (!d3Zoom) { - return; + if (d3Zoom) { + if (selectionKeyPressed) { + d3Zoom.on('zoom', null); + } else { + d3Zoom.on('zoom', function () { + if (!event.sourceEvent || (event.sourceEvent && event.sourceEvent.target !== zoomPane.current)) { + return; + } + + updateTransform(event.transform); + + if (onMove) { + onMove(); + } + }); + } } - - if (selectionKeyPressed) { - d3Zoom.on('zoom', null); - } else { - d3Zoom.on('zoom', () => { - if (!event.sourceEvent || (event.sourceEvent && event.sourceEvent.target !== zoomPane.current)) { - return; - } - - updateTransform(event.transform); - - if (onMove) { - onMove(); - } - }); - } - - return () => { - d3Zoom.on('zoom', null); - }; }, [selectionKeyPressed, d3Zoom]); }; diff --git a/src/store/index.ts b/src/store/index.ts index 68250901..be3e9a68 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,7 +1,8 @@ import { createStore, Action, action, Thunk, thunk } from 'easy-peasy'; import isEqual from 'fast-deep-equal'; import { Selection as D3Selection, ZoomBehavior } from 'd3'; -import { zoomIdentity, zoomTransform } from 'd3-zoom'; +import { zoom, zoomIdentity, zoomTransform } from 'd3-zoom'; +import { select } from 'd3-selection'; import { getDimensions } from '../utils'; import { getHandleBounds } from '../components/Nodes/utils'; @@ -40,11 +41,6 @@ type SelectionUpdate = { selection?: SelectionRect; }; -type D3Init = { - zoom: ZoomBehavior; - selection: D3Selection; -}; - type SetMinMaxZoom = { minZoom: number; maxZoom: number; @@ -109,7 +105,7 @@ export interface StoreModel { updateSize: Action; - initD3: Action; + initD3: Action; setMinMaxZoom: Action; @@ -342,11 +338,11 @@ export const storeModel: StoreModel = { state.height = size.height; }), - initD3: action((state, { zoom, selection }) => { - state.d3Zoom = zoom; - - state.d3Zoom.scaleExtent([state.minZoom, state.maxZoom]); + initD3: action((state, zoomPaneNode) => { + const d3ZoomInstance = zoom().scaleExtent([state.minZoom, state.maxZoom]); + const selection = select(zoomPaneNode).call(d3ZoomInstance); + state.d3Zoom = d3ZoomInstance; state.d3Selection = selection; state.d3Initialised = true; }), From 33d2770b239393dc919c5f2dfb96ee17a62819d4 Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 5 Jun 2020 12:37:54 +0200 Subject: [PATCH 6/6] fix(userselection): dont render user selection when its not active --- src/components/NodesSelection/index.tsx | 5 +++++ src/components/UserSelection/index.tsx | 19 ++++++++++++++++--- src/container/GraphView/index.tsx | 14 +++++++------- src/style.css | 4 ++-- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/components/NodesSelection/index.tsx b/src/components/NodesSelection/index.tsx index 37ffbd5a..c23eb40f 100644 --- a/src/components/NodesSelection/index.tsx +++ b/src/components/NodesSelection/index.tsx @@ -38,9 +38,14 @@ export default memo(() => { const nodes = useStoreState((s) => s.nodes); const updateNodePos = useStoreActions((a) => a.updateNodePos); + const position = selectedNodesBbox; const grid = (snapToGrid ? snapGrid : [1, 1])! as [number, number]; + if (!selectedElements) { + return null; + } + const onStart = (evt: MouseEvent) => { const scaledClient: XYPosition = { x: evt.clientX / tScale, diff --git a/src/components/UserSelection/index.tsx b/src/components/UserSelection/index.tsx index 4be5cd7e..6064d05e 100644 --- a/src/components/UserSelection/index.tsx +++ b/src/components/UserSelection/index.tsx @@ -2,13 +2,14 @@ * The user selection rectangle gets displayed when a user drags the mouse while pressing shift */ -import React, { memo } from 'react'; +import React, { memo, useEffect } from 'react'; import { useStoreActions, useStoreState } from '../../store/hooks'; import { XYPosition } from '../../types'; type UserSelectionProps = { isInteractive: boolean; + selectionKeyPressed: boolean; }; function getMousePosition(evt: React.MouseEvent): XYPosition | void { @@ -44,12 +45,21 @@ const SelectionRect = () => { ); }; -export default memo(({ isInteractive }: UserSelectionProps) => { +export default memo(({ isInteractive, selectionKeyPressed }: UserSelectionProps) => { + const selectionActive = useStoreState((s) => s.selectionActive); + const setUserSelection = useStoreActions((a) => a.setUserSelection); const updateUserSelection = useStoreActions((a) => a.updateUserSelection); const unsetUserSelection = useStoreActions((a) => a.unsetUserSelection); + const renderUserSelectionPane = selectionActive || selectionKeyPressed; - if (!isInteractive) { + useEffect(() => { + if (!selectionKeyPressed) { + unsetUserSelection(); + } + }, [selectionKeyPressed]); + + if (!isInteractive || !renderUserSelectionPane) { return null; } @@ -63,6 +73,9 @@ export default memo(({ isInteractive }: UserSelectionProps) => { }; const onMouseMove = (evt: React.MouseEvent): void => { + if (!selectionKeyPressed || !selectionActive) { + return; + } const mousePos = getMousePosition(evt); if (!mousePos) { diff --git a/src/container/GraphView/index.tsx b/src/container/GraphView/index.tsx index f890c1e3..8daae264 100644 --- a/src/container/GraphView/index.tsx +++ b/src/container/GraphView/index.tsx @@ -12,7 +12,7 @@ import useD3Zoom from '../../hooks/useD3Zoom'; import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler'; import useElementUpdater from '../../hooks/useElementUpdater'; import { getDimensions } from '../../utils'; -import { fitView, zoomIn, zoomOut, project, getElements } from '../../utils/graph'; +import { project, getElements } from '../../utils/graph'; import { Elements, NodeTypesType, @@ -80,7 +80,6 @@ const GraphView = memo( const height = useStoreState((s) => s.height); const d3Initialised = useStoreState((s) => s.d3Initialised); const nodesSelectionActive = useStoreState((s) => s.nodesSelectionActive); - const selectionActive = useStoreState((s) => s.selectionActive); const updateSize = useStoreActions((actions) => actions.updateSize); const setNodesSelection = useStoreActions((actions) => actions.setNodesSelection); const setOnConnect = useStoreActions((a) => a.setOnConnect); @@ -88,10 +87,11 @@ const GraphView = memo( const setInteractive = useStoreActions((actions) => actions.setInteractive); const updateTransform = useStoreActions((actions) => actions.updateTransform); const setMinMaxZoom = useStoreActions((actions) => actions.setMinMaxZoom); + const fitView = useStoreActions((actions) => actions.fitView); + const zoom = useStoreActions((actions) => actions.zoom); const selectionKeyPressed = useKeyPress(selectionKeyCode); const rendererClasses = classnames('react-flow__renderer', { 'is-interactive': isInteractive }); - const userSelectionActive = selectionKeyPressed || selectionActive; const onZoomPaneClick = () => setNodesSelection({ isActive: false }); @@ -147,9 +147,9 @@ const GraphView = memo( useEffect(() => { if (d3Initialised && onLoad) { onLoad({ - fitView, - zoomIn, - zoomOut, + fitView: (params = { padding: 0.1 }) => fitView(params), + zoomIn: () => zoom(0.2), + zoomOut: () => zoom(-0.2), project, getElements, }); @@ -189,7 +189,7 @@ const GraphView = memo( connectionLineType={connectionLineType} connectionLineStyle={connectionLineStyle} /> - {userSelectionActive && } + {nodesSelectionActive && }
diff --git a/src/style.css b/src/style.css index 1424190d..731a6191 100644 --- a/src/style.css +++ b/src/style.css @@ -69,6 +69,7 @@ .react-flow__edge-text { font-size: 12px; pointer-events: none; + user-select: none; } .react-flow__edge-textbg { @@ -132,7 +133,6 @@ .react-flow__node-output { background: #55dd99; - } .react-flow__nodesselection { @@ -184,4 +184,4 @@ right: 0; top: 50%; transform: translate(0, -50%); -} \ No newline at end of file +}