@@ -1,5 +1,5 @@
|
|||||||
import React, { memo, useCallback, Dispatch, FC } from 'react';
|
import React, { memo, useCallback, Dispatch, FC } from 'react';
|
||||||
import { useReactFlow, ReactFlowInstance, Edge, Node, ReactFlowJsonObject } from 'react-flow-renderer';
|
import { useReactFlow, Edge, Node, ReactFlowJsonObject } from 'react-flow-renderer';
|
||||||
import localforage from 'localforage';
|
import localforage from 'localforage';
|
||||||
|
|
||||||
localforage.config({
|
localforage.config({
|
||||||
@@ -12,20 +12,17 @@ const flowKey = 'example-flow';
|
|||||||
const getNodeId = () => `randomnode_${+new Date()}`;
|
const getNodeId = () => `randomnode_${+new Date()}`;
|
||||||
|
|
||||||
type ControlsProps = {
|
type ControlsProps = {
|
||||||
rfInstance?: ReactFlowInstance;
|
|
||||||
setNodes: Dispatch<React.SetStateAction<Node<any>[]>>;
|
setNodes: Dispatch<React.SetStateAction<Node<any>[]>>;
|
||||||
setEdges: Dispatch<React.SetStateAction<Edge<any>[]>>;
|
setEdges: Dispatch<React.SetStateAction<Edge<any>[]>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Controls: FC<ControlsProps> = ({ rfInstance, setNodes, setEdges }) => {
|
const Controls: FC<ControlsProps> = ({ setNodes, setEdges }) => {
|
||||||
const { setViewport } = useReactFlow();
|
const { setViewport, toObject } = useReactFlow();
|
||||||
|
|
||||||
const onSave = useCallback(() => {
|
const onSave = useCallback(() => {
|
||||||
if (rfInstance) {
|
const flow = toObject();
|
||||||
const flow = rfInstance.toObject();
|
localforage.setItem(flowKey, flow);
|
||||||
localforage.setItem(flowKey, flow);
|
}, [toObject]);
|
||||||
}
|
|
||||||
}, [rfInstance]);
|
|
||||||
|
|
||||||
const onRestore = useCallback(() => {
|
const onRestore = useCallback(() => {
|
||||||
const restoreFlow = async () => {
|
const restoreFlow = async () => {
|
||||||
@@ -33,6 +30,7 @@ const Controls: FC<ControlsProps> = ({ rfInstance, setNodes, setEdges }) => {
|
|||||||
|
|
||||||
if (flow) {
|
if (flow) {
|
||||||
const { x, y, zoom } = flow.viewport;
|
const { x, y, zoom } = flow.viewport;
|
||||||
|
|
||||||
setNodes(flow.nodes || []);
|
setNodes(flow.nodes || []);
|
||||||
setEdges(flow.edges || []);
|
setEdges(flow.edges || []);
|
||||||
setViewport({ x, y, zoom: zoom || 0 });
|
setViewport({ x, y, zoom: zoom || 0 });
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { useState } from 'react';
|
import { useCallback } from 'react';
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
Node,
|
Node,
|
||||||
addEdge,
|
addEdge,
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
ReactFlowInstance,
|
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
@@ -22,10 +21,9 @@ const initialNodes: Node[] = [
|
|||||||
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
|
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
|
||||||
|
|
||||||
const SaveRestore = () => {
|
const SaveRestore = () => {
|
||||||
const [rfInstance, setRfInstance] = useState<ReactFlowInstance>();
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds));
|
const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
@@ -35,9 +33,8 @@ const SaveRestore = () => {
|
|||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onInit={setRfInstance}
|
|
||||||
>
|
>
|
||||||
<Controls rfInstance={rfInstance} setNodes={setNodes} setEdges={setEdges} />
|
<Controls setNodes={setNodes} setEdges={setEdges} />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,21 +4,17 @@ import { GetState, SetState } from 'zustand';
|
|||||||
import { HandleElement, Node, Position, ReactFlowState } from '../../types';
|
import { HandleElement, Node, Position, ReactFlowState } from '../../types';
|
||||||
import { getDimensions } from '../../utils';
|
import { getDimensions } from '../../utils';
|
||||||
|
|
||||||
export const getHandleBounds = (nodeElement: HTMLDivElement, scale: number) => {
|
function getTranslateValues(domNode: HTMLDivElement): [number, number] {
|
||||||
const bounds = nodeElement.getBoundingClientRect();
|
if (typeof window === 'undefined' || !window.DOMMatrixReadOnly) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
const style = window.getComputedStyle(domNode);
|
||||||
source: getHandleBoundsByHandleType('.source', nodeElement, bounds, scale),
|
const { m41, m42 } = new window.DOMMatrixReadOnly(style.transform);
|
||||||
target: getHandleBoundsByHandleType('.target', nodeElement, bounds, scale),
|
return [m41, m42];
|
||||||
};
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export const getHandleBoundsByHandleType = (
|
export const getHandleBounds = (selector: string, nodeElement: HTMLDivElement): HandleElement[] | null => {
|
||||||
selector: string,
|
|
||||||
nodeElement: HTMLDivElement,
|
|
||||||
parentBounds: DOMRect,
|
|
||||||
k: number
|
|
||||||
): HandleElement[] | null => {
|
|
||||||
const handles = nodeElement.querySelectorAll(selector);
|
const handles = nodeElement.querySelectorAll(selector);
|
||||||
|
|
||||||
if (!handles || !handles.length) {
|
if (!handles || !handles.length) {
|
||||||
@@ -28,17 +24,16 @@ export const getHandleBoundsByHandleType = (
|
|||||||
const handlesArray = Array.from(handles) as HTMLDivElement[];
|
const handlesArray = Array.from(handles) as HTMLDivElement[];
|
||||||
|
|
||||||
return handlesArray.map((handle): HandleElement => {
|
return handlesArray.map((handle): HandleElement => {
|
||||||
const bounds = handle.getBoundingClientRect();
|
// we don't use getBoundingClientRect here, because it includes the transform of the parent (scaled viewport)
|
||||||
const dimensions = getDimensions(handle);
|
// that we would then need to calculate out again in order to get the correct position.
|
||||||
const handleId = handle.getAttribute('data-handleid');
|
const [translateX, translateY] = getTranslateValues(handle);
|
||||||
const handlePosition = handle.getAttribute('data-handlepos') as unknown as Position;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: handleId,
|
id: handle.getAttribute('data-handleid'),
|
||||||
position: handlePosition,
|
position: handle.getAttribute('data-handlepos') as unknown as Position,
|
||||||
x: (bounds.left - parentBounds.left) / k,
|
x: handle.offsetLeft + nodeElement.clientLeft + translateX,
|
||||||
y: (bounds.top - parentBounds.top) / k,
|
y: handle.offsetTop + nodeElement.clientTop + translateY,
|
||||||
...dimensions,
|
...getDimensions(handle),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
+5
-3
@@ -43,7 +43,7 @@ const createStore = () =>
|
|||||||
set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges });
|
set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges });
|
||||||
},
|
},
|
||||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
|
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => {
|
||||||
const { onNodesChange, transform, nodeInternals, fitViewOnInit, fitViewOnInitDone, fitViewOnInitOptions } = get();
|
const { onNodesChange, nodeInternals, fitViewOnInit, fitViewOnInitDone, fitViewOnInitOptions } = get();
|
||||||
|
|
||||||
const changes: NodeDimensionChange[] = updates.reduce<NodeDimensionChange[]>((res, update) => {
|
const changes: NodeDimensionChange[] = updates.reduce<NodeDimensionChange[]>((res, update) => {
|
||||||
const node = nodeInternals.get(update.id);
|
const node = nodeInternals.get(update.id);
|
||||||
@@ -57,12 +57,14 @@ const createStore = () =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (doUpdate) {
|
if (doUpdate) {
|
||||||
const handleBounds = getHandleBounds(update.nodeElement, transform[2]);
|
|
||||||
nodeInternals.set(node.id, {
|
nodeInternals.set(node.id, {
|
||||||
...node,
|
...node,
|
||||||
[internalsSymbol]: {
|
[internalsSymbol]: {
|
||||||
...node[internalsSymbol],
|
...node[internalsSymbol],
|
||||||
handleBounds,
|
handleBounds: {
|
||||||
|
source: getHandleBounds('.source', update.nodeElement),
|
||||||
|
target: getHandleBounds('.target', update.nodeElement),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
...dimensions,
|
...dimensions,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user