refactor(controls): dont import store but use actions

This commit is contained in:
moklick
2020-06-04 23:46:37 +02:00
parent a73c448edc
commit 407e34b1cb
7 changed files with 73 additions and 21 deletions
+4 -2
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react'; 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'; import Sidebar from './Sidebar';
@@ -31,7 +31,9 @@ const ProviderFlow = () => {
onElementClick={onElementClick} onElementClick={onElementClick}
onConnect={onConnect} onConnect={onConnect}
onElementsRemove={onElementsRemove} onElementsRemove={onElementsRemove}
/> >
<Controls />
</ReactFlow>
</div> </div>
</ReactFlowProvider> </ReactFlowProvider>
</div> </div>
+10 -4
View File
@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { fitView, zoomIn, zoomOut } from '../../utils/graph';
import { useStoreState, useStoreActions } from '../../store/hooks'; import { useStoreState, useStoreActions } from '../../store/hooks';
import PlusIcon from '../../../assets/icons/plus.svg'; import PlusIcon from '../../../assets/icons/plus.svg';
@@ -20,6 +19,10 @@ interface ControlProps extends React.HTMLAttributes<HTMLDivElement> {
const Controls = ({ style, showZoom = true, showFitView = true, showInteractive = true, className }: ControlProps) => { const Controls = ({ style, showZoom = true, showFitView = true, showInteractive = true, className }: ControlProps) => {
const setInteractive = useStoreActions((actions) => actions.setInteractive); 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 isInteractive = useStoreState((s) => s.isInteractive);
const mapClasses = classnames('react-flow__controls', className); const mapClasses = classnames('react-flow__controls', className);
@@ -27,16 +30,19 @@ const Controls = ({ style, showZoom = true, showFitView = true, showInteractive
<div className={mapClasses} style={style}> <div className={mapClasses} style={style}>
{showZoom && ( {showZoom && (
<> <>
<div className="react-flow__controls-button react-flow__controls-zoomin" onClick={zoomIn}> <div className="react-flow__controls-button react-flow__controls-zoomin" onClick={() => zoomIn()}>
<PlusIcon /> <PlusIcon />
</div> </div>
<div className="react-flow__controls-button react-flow__controls-zoomout" onClick={zoomOut}> <div className="react-flow__controls-button react-flow__controls-zoomout" onClick={() => zoomOut()}>
<MinusIcon /> <MinusIcon />
</div> </div>
</> </>
)} )}
{showFitView && ( {showFitView && (
<div className="react-flow__controls-button react-flow__controls-fitview" onClick={() => fitView()}> <div
className="react-flow__controls-button react-flow__controls-fitview"
onClick={() => fitView({ padding: 0.1 })}
>
<FitviewIcon /> <FitviewIcon />
</div> </div>
)} )}
+3 -2
View File
@@ -1,7 +1,7 @@
import React, { memo, ComponentType, CSSProperties } from 'react'; import React, { memo, ComponentType, CSSProperties } from 'react';
import cx from 'classnames'; import cx from 'classnames';
import store from '../../store'; import { useStoreActions } from '../../store/hooks';
import { ElementId, Edge, EdgeCompProps } from '../../types'; import { ElementId, Edge, EdgeCompProps } from '../../types';
interface EdgeWrapperProps { interface EdgeWrapperProps {
@@ -38,6 +38,7 @@ export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
className, className,
...rest ...rest
}: EdgeWrapperProps) => { }: EdgeWrapperProps) => {
const setSelectedElements = useStoreActions((a) => a.setSelectedElements);
const edgeClasses = cx('react-flow__edge', `react-flow__edge-${type}`, className, { selected, animated }); const edgeClasses = cx('react-flow__edge', `react-flow__edge-${type}`, className, { selected, animated });
const edgeGroupStyle: CSSProperties = { const edgeGroupStyle: CSSProperties = {
pointerEvents: isInteractive ? 'all' : 'none', pointerEvents: isInteractive ? 'all' : 'none',
@@ -47,7 +48,7 @@ export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
return; return;
} }
store.dispatch.setSelectedElements({ id, source, target }); setSelectedElements({ id, source, target });
if (onClick) { if (onClick) {
onClick({ id, source, target, type }); onClick({ id, source, target, type });
+2 -10
View File
@@ -1,5 +1,5 @@
import { useEffect, MutableRefObject } from 'react'; import { useEffect, MutableRefObject } from 'react';
import { zoom, zoomIdentity } from 'd3-zoom'; import { zoom } from 'd3-zoom';
import { select, event } from 'd3-selection'; import { select, event } from 'd3-selection';
import { useStoreState, useStoreActions } from '../store/hooks'; import { useStoreState, useStoreActions } from '../store/hooks';
@@ -11,8 +11,6 @@ interface UseD3ZoomParams {
} }
export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): void => { 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 d3Zoom = useStoreState((s) => s.d3Zoom);
const initD3 = useStoreActions((actions) => actions.initD3); const initD3 = useStoreActions((actions) => actions.initD3);
@@ -35,7 +33,7 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi
d3Zoom.on('zoom', null); d3Zoom.on('zoom', null);
} else { } else {
d3Zoom.on('zoom', () => { d3Zoom.on('zoom', () => {
if (event.sourceEvent && event.sourceEvent.target !== zoomPane.current) { if (!event.sourceEvent || (event.sourceEvent && event.sourceEvent.target !== zoomPane.current)) {
return; return;
} }
@@ -45,12 +43,6 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi
onMove(); 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 () => { return () => {
+52 -1
View File
@@ -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 isEqual from 'fast-deep-equal';
import { Selection as D3Selection, ZoomBehavior } from 'd3'; import { Selection as D3Selection, ZoomBehavior } from 'd3';
import { zoomIdentity } from 'd3-zoom';
import { getDimensions } from '../utils'; import { getDimensions } from '../utils';
import { getHandleBounds } from '../components/Nodes/utils'; import { getHandleBounds } from '../components/Nodes/utils';
@@ -20,6 +21,7 @@ import {
HandleType, HandleType,
SetConnectionId, SetConnectionId,
NodePosUpdate, NodePosUpdate,
FitViewParams,
} from '../types'; } from '../types';
type TransformXYK = { type TransformXYK = {
@@ -122,6 +124,11 @@ export interface StoreModel {
setUserSelection: Action<StoreModel, XYPosition>; setUserSelection: Action<StoreModel, XYPosition>;
updateUserSelection: Action<StoreModel, XYPosition>; updateUserSelection: Action<StoreModel, XYPosition>;
unsetUserSelection: Action<StoreModel>; unsetUserSelection: Action<StoreModel>;
fitView: Action<StoreModel, FitViewParams>;
zoom: Action<StoreModel, number>;
zoomIn: Thunk<StoreModel>;
zoomOut: Thunk<StoreModel>;
} }
export const storeModel: StoreModel = { export const storeModel: StoreModel = {
@@ -370,6 +377,50 @@ export const storeModel: StoreModel = {
setInteractive: action((state, isInteractive) => { setInteractive: action((state, isInteractive) => {
state.isInteractive = 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); const store = createStore(storeModel);
+1 -1
View File
@@ -134,7 +134,7 @@ export interface WrapNodeProps {
} }
export type FitViewParams = { export type FitViewParams = {
padding: number; padding?: number;
}; };
export type FitViewFunc = (fitViewOptions: FitViewParams) => void; export type FitViewFunc = (fitViewOptions: FitViewParams) => void;
export type ProjectFunc = (position: XYPosition) => XYPosition; export type ProjectFunc = (position: XYPosition) => XYPosition;
+1 -1
View File
@@ -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(); const { nodes, width, height, d3Selection, d3Zoom } = store.getState();
if (!d3Selection || !d3Zoom || !nodes.length) { if (!d3Selection || !d3Zoom || !nodes.length) {