refactor(easy-peasy): return single values from useStoreState

This commit is contained in:
moklick
2020-05-25 17:12:11 +02:00
parent 17614bda6b
commit 56cfc8d731
10 changed files with 93 additions and 130 deletions
@@ -25,11 +25,9 @@ const defaultColors = {
const Background = memo( const Background = memo(
({ variant = BackgroundVariant.Dots, gap = 24, size = 0.5, color, style = {}, className = '' }: BackgroundProps) => { ({ variant = BackgroundVariant.Dots, gap = 24, size = 0.5, color, style = {}, className = '' }: BackgroundProps) => {
const { const width = useStoreState((s) => s.width);
width, const height = useStoreState((s) => s.height);
height, const [x, y, scale] = useStoreState((s) => s.transform);
transform: [x, y, scale],
} = useStoreState((s) => s);
const bgClasses = classnames('react-flow__background', className); const bgClasses = classnames('react-flow__background', className);
const bgColor = color ? color : defaultColors[variant]; const bgColor = color ? color : defaultColors[variant];
+1 -1
View File
@@ -25,7 +25,7 @@ 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 { isInteractive } = useStoreState(({ isInteractive }) => ({ isInteractive })); const isInteractive = useStoreState((s) => s.isInteractive);
const mapClasses: string = classnames('react-flow__controls', className); const mapClasses: string = classnames('react-flow__controls', className);
return ( return (
+11 -15
View File
@@ -30,27 +30,23 @@ const MiniMap = ({
nodeBorderRadius = 5, nodeBorderRadius = 5,
maskColor = 'rgba(10, 10, 10, .25)', maskColor = 'rgba(10, 10, 10, .25)',
}: MiniMapProps) => { }: MiniMapProps) => {
const state = useStoreState(({ width, height, nodes, transform: [tX, tY, tScale] }) => ({ const containerWidth = useStoreState((s) => s.width);
width, const containerHeight = useStoreState((s) => s.height);
height, const [tX, tY, tScale] = useStoreState((s) => s.transform);
nodes, const nodes = useStoreState((s) => s.nodes);
tX,
tY,
tScale,
}));
const mapClasses = classnames('react-flow__minimap', className); const mapClasses = classnames('react-flow__minimap', className);
const elementWidth = (style.width || baseStyle.width)! as number; const elementWidth = (style.width || baseStyle.width)! as number;
const elementHeight = (style.height || baseStyle.height)! as number; const elementHeight = (style.height || baseStyle.height)! as number;
const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc; const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc;
const hasNodes = state.nodes && state.nodes.length; const hasNodes = nodes && nodes.length;
const bb = getRectOfNodes(state.nodes); const bb = getRectOfNodes(nodes);
const viewBB: Rect = { const viewBB: Rect = {
x: -state.tX / state.tScale, x: -tX / tScale,
y: -state.tY / state.tScale, y: -tY / tScale,
width: state.width / state.tScale, width: containerWidth / tScale,
height: state.height / state.tScale, height: containerHeight / tScale,
}; };
const boundingRect = hasNodes ? getBoundsofRects(bb, viewBB) : viewBB; const boundingRect = hasNodes ? getBoundsofRects(bb, viewBB) : viewBB;
@@ -79,7 +75,7 @@ const MiniMap = ({
}} }}
className={mapClasses} className={mapClasses}
> >
{state.nodes.map((node) => ( {nodes.map((node) => (
<MiniMapNode key={node.id} node={node} color={nodeColorFunc(node)} borderRadius={nodeBorderRadius} /> <MiniMapNode key={node.id} node={node} color={nodeColorFunc(node)} borderRadius={nodeBorderRadius} />
))} ))}
<path <path
+24 -26
View File
@@ -30,30 +30,28 @@ function getStartPositions(nodes: Node[]): StartPositions {
export default memo(() => { export default memo(() => {
const [offset, setOffset] = useState<XYPosition>({ x: 0, y: 0 }); const [offset, setOffset] = useState<XYPosition>({ x: 0, y: 0 });
const [startPositions, setStartPositions] = useState<StartPositions>({}); const [startPositions, setStartPositions] = useState<StartPositions>({});
const state = useStoreState((s) => ({ const [tX, tY, tScale] = useStoreState((s) => s.transform);
transform: s.transform, const selectedNodesBbox = useStoreState((s) => s.selectedNodesBbox);
selectedNodesBbox: s.selectedNodesBbox, const selectedElements = useStoreState((s) => s.selectedElements);
selectedElements: s.selectedElements, const snapToGrid = useStoreState((s) => s.snapToGrid);
snapToGrid: s.snapToGrid, const snapGrid = useStoreState((s) => s.snapGrid);
snapGrid: s.snapGrid, const nodes = useStoreState((s) => s.nodes);
nodes: s.nodes,
}));
const updateNodePos = useStoreActions((a) => a.updateNodePos); const updateNodePos = useStoreActions((a) => a.updateNodePos);
const [tx, ty, tScale] = state.transform; const position = selectedNodesBbox;
const position = state.selectedNodesBbox; const grid = (snapToGrid ? snapGrid : [1, 1])! as [number, number];
const grid = (state.snapToGrid ? state.snapGrid : [1, 1])! as [number, number];
const onStart = (evt: MouseEvent) => { const onStart = (evt: MouseEvent) => {
const scaledClient: XYPosition = { const scaledClient: XYPosition = {
x: evt.clientX / tScale, x: evt.clientX / tScale,
y: evt.clientY / tScale, y: evt.clientY / tScale,
}; };
const offsetX: number = scaledClient.x - position.x - tx; const offsetX: number = scaledClient.x - position.x - tX;
const offsetY: number = scaledClient.y - position.y - ty; const offsetY: number = scaledClient.y - position.y - tY;
const selectedNodes = state.selectedElements const selectedNodes = selectedElements
? (state.selectedElements.filter(isNode) as Node[]).map( ? selectedElements
(selectedNode) => state.nodes.find((node) => node.id === selectedNode.id)! as Node .filter(isNode)
) .map((selectedNode) => nodes.find((node) => node.id === selectedNode.id)! as Node)
: []; : [];
const nextStartPositions = getStartPositions(selectedNodes); const nextStartPositions = getStartPositions(selectedNodes);
@@ -70,11 +68,11 @@ export default memo(() => {
y: evt.clientY / tScale, y: evt.clientY / tScale,
}; };
if (state.selectedElements) { if (selectedElements) {
(state.selectedElements.filter(isNode) as Node[]).forEach((node) => { selectedElements.filter(isNode).forEach((node) => {
const pos: XYPosition = { const pos: XYPosition = {
x: startPositions[node.id].x + scaledClient.x - position.x - offset.x - tx, x: startPositions[node.id].x + scaledClient.x - position.x - offset.x - tX,
y: startPositions[node.id].y + scaledClient.y - position.y - offset.y - ty, y: startPositions[node.id].y + scaledClient.y - position.y - offset.y - tY,
}; };
updateNodePos({ id: node.id, pos }); updateNodePos({ id: node.id, pos });
@@ -86,7 +84,7 @@ export default memo(() => {
<div <div
className="react-flow__nodesselection" className="react-flow__nodesselection"
style={{ style={{
transform: `translate(${tx}px,${ty}px) scale(${tScale})`, transform: `translate(${tX}px,${tY}px) scale(${tScale})`,
}} }}
> >
<ReactDraggable <ReactDraggable
@@ -98,10 +96,10 @@ export default memo(() => {
<div <div
className="react-flow__nodesselection-rect" className="react-flow__nodesselection-rect"
style={{ style={{
width: state.selectedNodesBbox.width, width: selectedNodesBbox.width,
height: state.selectedNodesBbox.height, height: selectedNodesBbox.height,
top: state.selectedNodesBbox.y, top: selectedNodesBbox.y,
left: state.selectedNodesBbox.x, left: selectedNodesBbox.x,
}} }}
/> />
</ReactDraggable> </ReactDraggable>
+11 -22
View File
@@ -187,23 +187,13 @@ function renderEdge(
} }
const EdgeRenderer = memo((props: EdgeRendererProps) => { const EdgeRenderer = memo((props: EdgeRendererProps) => {
const { const [tX, tY, tScale] = useStoreState((s) => s.transform);
transform, const edges = useStoreState((s) => s.edges);
edges, const nodes = useStoreState((s) => s.nodes);
nodes, const connectionSourceId = useStoreState((s) => s.connectionSourceId);
connectionSourceId, const connectionPosition = useStoreState((s) => s.connectionPosition);
connectionPosition: { x, y }, const selectedElements = useStoreState((s) => s.selectedElements);
selectedElements, const isInteractive = useStoreState((s) => s.isInteractive);
isInteractive,
} = useStoreState((s) => ({
transform: s.transform,
edges: s.edges,
nodes: s.nodes,
connectionSourceId: s.connectionSourceId,
connectionPosition: s.connectionPosition,
selectedElements: s.selectedElements,
isInteractive: s.isInteractive,
}));
const { width, height, connectionLineStyle, connectionLineType } = props; const { width, height, connectionLineStyle, connectionLineType } = props;
@@ -211,8 +201,7 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
return null; return null;
} }
const [tx, ty, tScale] = transform; const transformStyle = `translate(${tX},${tY}) scale(${tScale})`;
const transformStyle = `translate(${tx},${ty}) scale(${tScale})`;
return ( return (
<svg width={width} height={height} className="react-flow__edges"> <svg width={width} height={height} className="react-flow__edges">
@@ -222,9 +211,9 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
<ConnectionLine <ConnectionLine
nodes={nodes} nodes={nodes}
connectionSourceId={connectionSourceId} connectionSourceId={connectionSourceId}
connectionPositionX={x} connectionPositionX={connectionPosition.x}
connectionPositionY={y} connectionPositionY={connectionPosition.y}
transform={transform} transform={[tX, tY, tScale]}
connectionLineStyle={connectionLineStyle} connectionLineStyle={connectionLineStyle}
connectionLineType={connectionLineType} connectionLineType={connectionLineType}
isInteractive={isInteractive} isInteractive={isInteractive}
+10 -13
View File
@@ -58,14 +58,11 @@ const GraphView = memo(
}: GraphViewProps) => { }: GraphViewProps) => {
const zoomPane = useRef<HTMLDivElement>(null); const zoomPane = useRef<HTMLDivElement>(null);
const rendererNode = useRef<HTMLDivElement>(null); const rendererNode = useRef<HTMLDivElement>(null);
const state = useStoreState((s) => ({ const width = useStoreState((s) => s.width);
width: s.width, const height = useStoreState((s) => s.height);
height: s.height, const d3Initialised = useStoreState((s) => s.d3Initialised);
nodes: s.nodes, const nodesSelectionActive = useStoreState((s) => s.nodesSelectionActive);
edges: s.edges,
d3Initialised: s.d3Initialised,
nodesSelectionActive: s.nodesSelectionActive,
}));
const updateSize = useStoreActions((actions) => actions.updateSize); const updateSize = useStoreActions((actions) => actions.updateSize);
const setNodesSelection = useStoreActions((actions) => actions.setNodesSelection); const setNodesSelection = useStoreActions((actions) => actions.setNodesSelection);
const setOnConnect = useStoreActions((a) => a.setOnConnect); const setOnConnect = useStoreActions((a) => a.setOnConnect);
@@ -106,7 +103,7 @@ const GraphView = memo(
useD3Zoom({ zoomPane, onMove, selectionKeyPressed }); useD3Zoom({ zoomPane, onMove, selectionKeyPressed });
useEffect(() => { useEffect(() => {
if (state.d3Initialised && onLoad) { if (d3Initialised && onLoad) {
onLoad({ onLoad({
fitView, fitView,
zoomIn, zoomIn,
@@ -114,7 +111,7 @@ const GraphView = memo(
project, project,
}); });
} }
}, [state.d3Initialised, onLoad]); }, [d3Initialised, onLoad]);
useEffect(() => { useEffect(() => {
setSnapGrid({ snapToGrid, snapGrid }); setSnapGrid({ snapToGrid, snapGrid });
@@ -137,15 +134,15 @@ const GraphView = memo(
onlyRenderVisibleNodes={onlyRenderVisibleNodes} onlyRenderVisibleNodes={onlyRenderVisibleNodes}
/> />
<EdgeRenderer <EdgeRenderer
width={state.width} width={width}
height={state.height} height={height}
edgeTypes={edgeTypes} edgeTypes={edgeTypes}
onElementClick={onElementClick} onElementClick={onElementClick}
connectionLineType={connectionLineType} connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle} connectionLineStyle={connectionLineStyle}
/> />
{selectionKeyPressed && <UserSelection isInteractive={isInteractive} />} {selectionKeyPressed && <UserSelection isInteractive={isInteractive} />}
{state.nodesSelectionActive && <NodesSelection />} {nodesSelectionActive && <NodesSelection />}
<div className="react-flow__zoompane" onClick={onZoomPaneClick} ref={zoomPane} /> <div className="react-flow__zoompane" onClick={onZoomPaneClick} ref={zoomPane} />
</div> </div>
); );
+8 -11
View File
@@ -49,18 +49,15 @@ function renderNode(
} }
const NodeRenderer = memo(({ onlyRenderVisibleNodes = true, ...props }: NodeRendererProps) => { const NodeRenderer = memo(({ onlyRenderVisibleNodes = true, ...props }: NodeRendererProps) => {
const { nodes, transform, selectedElements, width, height, isInteractive } = useStoreState((s) => ({ const nodes = useStoreState((s) => s.nodes);
nodes: s.nodes, const transform = useStoreState((s) => s.transform);
transform: s.transform, const selectedElements = useStoreState((s) => s.selectedElements);
selectedElements: s.selectedElements, const width = useStoreState((s) => s.width);
width: s.width, const height = useStoreState((s) => s.height);
height: s.height, const isInteractive = useStoreState((s) => s.isInteractive);
isInteractive: s.isInteractive, const [tX, tY, tScale] = transform;
}));
const [tx, ty, tScale] = transform;
const transformStyle = { const transformStyle = {
transform: `translate(${tx}px,${ty}px) scale(${tScale})`, transform: `translate(${tX}px,${tY}px) scale(${tScale})`,
}; };
const renderNodes = onlyRenderVisibleNodes const renderNodes = onlyRenderVisibleNodes
+8 -14
View File
@@ -1,5 +1,5 @@
import { useEffect, MutableRefObject } from 'react'; import { useEffect, MutableRefObject } from 'react';
import * as d3Zoom from 'd3-zoom'; import { zoom, zoomIdentity } 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';
@@ -10,17 +10,14 @@ interface UseD3ZoomParams {
onMove?: () => void; onMove?: () => void;
} }
const d3ZoomInstance = d3Zoom const d3ZoomInstance = zoom()
.zoom()
.scaleExtent([0.5, 2]) .scaleExtent([0.5, 2])
.filter(() => !event.button); .filter(() => !event.button);
export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): void => { export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): void => {
const state = useStoreState((s) => ({ const transform = useStoreState((s) => s.transform);
transform: s.transform, const d3Selection = useStoreState((s) => s.d3Selection);
d3Selection: s.d3Selection, const d3Zoom = useStoreState((s) => s.d3Zoom);
d3Zoom: s.d3Zoom,
}));
const initD3 = useStoreActions((actions) => actions.initD3); const initD3 = useStoreActions((actions) => actions.initD3);
const updateTransform = useStoreActions((actions) => actions.updateTransform); const updateTransform = useStoreActions((actions) => actions.updateTransform);
@@ -48,13 +45,10 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi
} }
}); });
if (state.d3Selection && state.d3Zoom) { if (d3Selection && d3Zoom) {
// we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced // we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced
const graphTransform = d3Zoom.zoomIdentity const graphTransform = zoomIdentity.translate(transform[0], transform[1]).scale(transform[2]);
.translate(state.transform[0], state.transform[1]) d3Selection.call(d3Zoom.transform, graphTransform);
.scale(state.transform[2]);
state.d3Selection.call(state.d3Zoom.transform, graphTransform);
} }
} }
+6 -11
View File
@@ -6,13 +6,8 @@ import { parseElement, isNode, isEdge } from '../utils/graph';
import { Elements, Node, Edge } from '../types'; import { Elements, Node, Edge } from '../types';
const useElementUpdater = (elements: Elements): void => { const useElementUpdater = (elements: Elements): void => {
const state = useStoreState((s) => ({ const stateNodes = useStoreState((s) => s.nodes);
nodes: s.nodes, const stateEdges = useStoreState((s) => s.edges);
edges: s.edges,
transform: s.transform,
snapToGrid: s.snapToGrid,
snapGrid: s.snapGrid,
}));
const setNodes = useStoreActions((a) => a.setNodes); const setNodes = useStoreActions((a) => a.setNodes);
const setEdges = useStoreActions((a) => a.setEdges); const setEdges = useStoreActions((a) => a.setEdges);
@@ -22,7 +17,7 @@ const useElementUpdater = (elements: Elements): void => {
const edges: Edge[] = elements.filter(isEdge).map((e) => parseElement(e) as Edge); const edges: Edge[] = elements.filter(isEdge).map((e) => parseElement(e) as Edge);
const nextNodes: Node[] = nodes.map((propNode) => { const nextNodes: Node[] = nodes.map((propNode) => {
const existingNode = state.nodes.find((n) => n.id === propNode.id); const existingNode = stateNodes.find((n) => n.id === propNode.id);
if (existingNode) { if (existingNode) {
const data = !isEqual(existingNode.data, propNode.data) const data = !isEqual(existingNode.data, propNode.data)
@@ -59,8 +54,8 @@ const useElementUpdater = (elements: Elements): void => {
return parseElement(propNode) as Node; return parseElement(propNode) as Node;
}); });
const nodesChanged: boolean = !isEqual(state.nodes, nextNodes); const nodesChanged: boolean = !isEqual(stateNodes, nextNodes);
const edgesChanged: boolean = !isEqual(state.edges, edges); const edgesChanged: boolean = !isEqual(stateEdges, edges);
if (nodesChanged) { if (nodesChanged) {
setNodes(nextNodes); setNodes(nextNodes);
@@ -69,7 +64,7 @@ const useElementUpdater = (elements: Elements): void => {
if (edgesChanged) { if (edgesChanged) {
setEdges(edges); setEdges(edges);
} }
}, [elements, state.nodes, state.edges]); }, [elements, stateNodes, stateEdges]);
}; };
export default useElementUpdater; export default useElementUpdater;
+11 -12
View File
@@ -2,8 +2,8 @@ import { useEffect } from 'react';
import { useStoreState, useStoreActions } from '../store/hooks'; import { useStoreState, useStoreActions } from '../store/hooks';
import useKeyPress from './useKeyPress'; import useKeyPress from './useKeyPress';
import { isEdge, getConnectedEdges } from '../utils/graph'; import { isNode, getConnectedEdges } from '../utils/graph';
import { Elements, Node } from '../types'; import { Elements } from '../types';
interface HookParams { interface HookParams {
deleteKeyCode: number; deleteKeyCode: number;
@@ -11,22 +11,21 @@ interface HookParams {
} }
export default ({ deleteKeyCode, onElementsRemove }: HookParams): void => { export default ({ deleteKeyCode, onElementsRemove }: HookParams): void => {
const state = useStoreState((s) => ({ const selectedElements = useStoreState((s) => s.selectedElements);
selectedElements: s.selectedElements, const edges = useStoreState((s) => s.edges);
edges: s.edges,
}));
const setNodesSelection = useStoreActions((a) => a.setNodesSelection); const setNodesSelection = useStoreActions((a) => a.setNodesSelection);
const deleteKeyPressed = useKeyPress(deleteKeyCode); const deleteKeyPressed = useKeyPress(deleteKeyCode);
useEffect(() => { useEffect(() => {
if (onElementsRemove && deleteKeyPressed && state.selectedElements) { if (onElementsRemove && deleteKeyPressed && selectedElements) {
let elementsToRemove = state.selectedElements; let elementsToRemove = selectedElements;
// we also want to remove the edges if only one node is selected // we also want to remove the edges if only one node is selected
if (state.selectedElements.length === 1 && !isEdge(state.selectedElements[0])) { if (selectedElements.length === 1 && isNode(selectedElements[0])) {
const node = (state.selectedElements[0] as unknown) as Node; const node = selectedElements[0];
const connectedEdges = getConnectedEdges([node], state.edges); const connectedEdges = getConnectedEdges([node], edges);
elementsToRemove = [...state.selectedElements, ...connectedEdges]; elementsToRemove = [...selectedElements, ...connectedEdges];
} }
onElementsRemove(elementsToRemove); onElementsRemove(elementsToRemove);