From 407e34b1cb18d39b417fda797d317115dfe198b8 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 4 Jun 2020 23:46:37 +0200 Subject: [PATCH] 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) {