diff --git a/src/container/FlowRenderer/index.tsx b/src/container/FlowRenderer/index.tsx index 83b9be95..b6ad9a3c 100644 --- a/src/container/FlowRenderer/index.tsx +++ b/src/container/FlowRenderer/index.tsx @@ -1,12 +1,11 @@ -import React, { useCallback, useRef, memo, ReactNode, WheelEvent, MouseEvent } from 'react'; +import React, { useCallback, memo, ReactNode, WheelEvent, MouseEvent } from 'react'; import { useStoreActions, useStoreState } from '../../store/hooks'; -import useResizeHandler from '../../hooks/useResizeHandler'; import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler'; -import useD3Zoom from '../../hooks/useD3Zoom'; import useKeyPress from '../../hooks/useKeyPress'; import { GraphViewProps } from '../GraphView'; +import ZoomPane from '../ZoomPane'; import UserSelection from '../../components/UserSelection'; import NodesSelection from '../../components/NodesSelection'; @@ -49,30 +48,12 @@ const FlowRenderer = ({ onSelectionDragStop, onSelectionContextMenu, }: FlowRendererProps) => { - const zoomPane = useRef(null); const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection); const nodesSelectionActive = useStoreState((state) => state.nodesSelectionActive); const selectionKeyPressed = useKeyPress(selectionKeyCode); - useResizeHandler(zoomPane); useGlobalKeyHandler({ onElementsRemove, deleteKeyCode }); - useD3Zoom({ - zoomPane, - onMove, - onMoveStart, - onMoveEnd, - selectionKeyPressed, - elementsSelectable, - zoomOnScroll, - panOnScroll, - zoomOnDoubleClick, - paneMoveable, - defaultPosition, - defaultZoom, - translateExtent, - }); - const onClick = useCallback( (event: MouseEvent) => { onPaneClick?.(event); @@ -96,7 +77,20 @@ const FlowRenderer = ({ ); return ( -
+ {children} {nodesSelectionActive && ( @@ -108,7 +102,7 @@ const FlowRenderer = ({ /> )}
-
+
); }; diff --git a/src/hooks/useD3Zoom.ts b/src/container/ZoomPane/index.tsx similarity index 76% rename from src/hooks/useD3Zoom.ts rename to src/container/ZoomPane/index.tsx index 07d6a1e6..45095301 100644 --- a/src/hooks/useD3Zoom.ts +++ b/src/container/ZoomPane/index.tsx @@ -1,10 +1,10 @@ -import { useEffect, useRef, MutableRefObject } from 'react'; +import React, { useEffect, useRef, ReactNode } from 'react'; -import { useStoreState, useStoreActions } from '../store/hooks'; -import { FlowTransform, TranslateExtent } from '../types'; +import useResizeHandler from '../../hooks/useResizeHandler'; +import { useStoreState, useStoreActions } from '../../store/hooks'; +import { FlowTransform, TranslateExtent } from '../../types'; -interface UseD3ZoomParams { - zoomPane: MutableRefObject; +interface ZoomPaneProps { selectionKeyPressed: boolean; elementsSelectable?: boolean; zoomOnScroll?: boolean; @@ -17,6 +17,7 @@ interface UseD3ZoomParams { onMove?: (flowTransform?: FlowTransform) => void; onMoveStart?: (flowTransform?: FlowTransform) => void; onMoveEnd?: (flowTransform?: FlowTransform) => void; + children: ReactNode; } const viewChanged = (prevTransform: FlowTransform, eventTransform: any): boolean => @@ -30,8 +31,7 @@ const eventToFlowTransform = (eventTransform: any): FlowTransform => ({ zoom: eventTransform.k, }); -export default ({ - zoomPane, +const ZoomPane = ({ onMove, onMoveStart, onMoveEnd, @@ -44,34 +44,51 @@ export default ({ defaultPosition = [0, 0], defaultZoom = 1, translateExtent, -}: UseD3ZoomParams): void => { + children, +}: ZoomPaneProps) => { + const zoomPane = useRef(null); const prevTransform = useRef({ x: 0, y: 0, zoom: 0 }); + const d3Zoom = useStoreState((s) => s.d3Zoom); + const d3Selection = useStoreState((s) => s.d3Selection); + const d3ZoomHandler = useStoreState((s) => s.d3ZoomHandler); const initD3 = useStoreActions((actions) => actions.initD3); const updateTransform = useStoreActions((actions) => actions.updateTransform); const updateTransformDelta = useStoreActions((actions) => actions.updateTransformDelta); + useResizeHandler(zoomPane); + useEffect(() => { if (zoomPane.current) { initD3({ zoomPane: zoomPane.current, defaultPosition, defaultZoom, translateExtent }); } }, []); + useEffect(() => { + if (d3Selection && d3Zoom) { + const pan = (evt: any) => { + d3Zoom.translateBy(d3Selection, evt.wheelDeltaX, evt.wheelDeltaY); + }; + + if (panOnScroll) { + d3Selection.on('wheel', pan).on('wheel.zoom', null); + } else { + if (typeof d3ZoomHandler !== 'undefined') { + d3Selection.on('wheel', null).on('wheel.zoom', d3ZoomHandler); + } + } + } + }, [panOnScroll, d3Selection, d3Zoom]); + useEffect(() => { if (d3Zoom) { d3Zoom.on('zoom', (event: any) => { - if (panOnScroll && event.sourceEvent.type === 'wheel') { - const { deltaX, deltaY } = event.sourceEvent; - updateTransformDelta({ x: deltaX, y: deltaY }); - } else { - updateTransform(event.transform); + updateTransform(event.transform); - // todo: should we move this into the store or into a wrapper component with useEffect? - if (onMove) { - const flowTransform = eventToFlowTransform(event.transform); - onMove(flowTransform); - } + if (onMove) { + const flowTransform = eventToFlowTransform(event.transform); + onMove(flowTransform); } }); } @@ -154,4 +171,12 @@ export default ({ }); } }, [d3Zoom, zoomOnScroll, panOnScroll, zoomOnDoubleClick, paneMoveable, selectionKeyPressed, elementsSelectable]); + + return ( +
+ {children} +
+ ); }; + +export default ZoomPane; diff --git a/src/store/index.ts b/src/store/index.ts index d08c6c61..9220977e 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,6 +1,6 @@ import { createStore, Action, action, Thunk, thunk, computed, Computed } from 'easy-peasy'; import isEqual from 'fast-deep-equal'; -import { Selection as D3Selection, ZoomBehavior } from 'd3'; +import { Selection as D3Selection, ZoomBehavior, ValueFn } from 'd3'; import { zoom, zoomIdentity } from 'd3-zoom'; import { select } from 'd3-selection'; @@ -67,6 +67,7 @@ export interface StoreModel { d3Zoom: ZoomBehavior | null; d3Selection: D3Selection | null; + d3ZoomHandler: ValueFn | undefined; d3Initialised: boolean; minZoom: number; maxZoom: number; @@ -165,6 +166,7 @@ export const storeModel: StoreModel = { d3Zoom: null, d3Selection: null, d3Initialised: false, + d3ZoomHandler: () => {}, minZoom: 0.5, maxZoom: 2, translateExtent: [ @@ -403,11 +405,14 @@ export const storeModel: StoreModel = { const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom); selection.property('__zoom', updatedTransform); + const defaultHandler = selection.on('wheel.zoom'); + state.transform[0] = clampedX; state.transform[1] = clampedY; state.transform[2] = clampedZoom; state.d3Zoom = d3ZoomInstance; state.d3Selection = selection; + state.d3ZoomHandler = defaultHandler; state.d3Initialised = true; }),