refactor(elements): split into nodes and edges
This commit is contained in:
@@ -26,7 +26,7 @@ const MiniMapNode = ({
|
|||||||
strokeWidth,
|
strokeWidth,
|
||||||
className,
|
className,
|
||||||
borderRadius,
|
borderRadius,
|
||||||
shapeRendering
|
shapeRendering,
|
||||||
}: MiniMapNodeProps) => {
|
}: MiniMapNodeProps) => {
|
||||||
const { background, backgroundColor } = style || {};
|
const { background, backgroundColor } = style || {};
|
||||||
const fill = (color || background || backgroundColor) as string;
|
const fill = (color || background || backgroundColor) as string;
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ const MiniMap = ({
|
|||||||
const elementWidth = (style?.width || defaultWidth)! as number;
|
const elementWidth = (style?.width || defaultWidth)! as number;
|
||||||
const elementHeight = (style?.height || defaultHeight)! as number;
|
const elementHeight = (style?.height || defaultHeight)! as number;
|
||||||
const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc;
|
const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc;
|
||||||
const nodeStrokeColorFunc = (nodeStrokeColor instanceof Function
|
const nodeStrokeColorFunc = (
|
||||||
? nodeStrokeColor
|
nodeStrokeColor instanceof Function ? nodeStrokeColor : () => nodeStrokeColor
|
||||||
: () => nodeStrokeColor) as StringFunc;
|
) as StringFunc;
|
||||||
const nodeClassNameFunc = (nodeClassName instanceof Function ? nodeClassName : () => nodeClassName) as StringFunc;
|
const nodeClassNameFunc = (nodeClassName instanceof Function ? nodeClassName : () => nodeClassName) as StringFunc;
|
||||||
const hasNodes = nodes && nodes.length;
|
const hasNodes = nodes && nodes.length;
|
||||||
const bb = getRectOfNodes(nodes);
|
const bb = getRectOfNodes(nodes);
|
||||||
@@ -64,7 +64,7 @@ const MiniMap = ({
|
|||||||
const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset;
|
const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset;
|
||||||
const width = viewWidth + offset * 2;
|
const width = viewWidth + offset * 2;
|
||||||
const height = viewHeight + offset * 2;
|
const height = viewHeight + offset * 2;
|
||||||
const shapeRendering = (typeof window === "undefined" || !!window.chrome) ? "crispEdges" : "geometricPrecision";
|
const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
@@ -75,14 +75,14 @@ const MiniMap = ({
|
|||||||
className={mapClasses}
|
className={mapClasses}
|
||||||
>
|
>
|
||||||
{nodes
|
{nodes
|
||||||
.filter((node) => !node.isHidden)
|
.filter((node) => !node.isHidden && node.width && node.height)
|
||||||
.map((node) => (
|
.map((node) => (
|
||||||
<MiniMapNode
|
<MiniMapNode
|
||||||
key={node.id}
|
key={node.id}
|
||||||
x={node.__rf.position.x}
|
x={node.position.x}
|
||||||
y={node.__rf.position.y}
|
y={node.position.y}
|
||||||
width={node.__rf.width}
|
width={node.width!}
|
||||||
height={node.__rf.height}
|
height={node.height!}
|
||||||
style={node.style}
|
style={node.style}
|
||||||
className={nodeClassNameFunc(node)}
|
className={nodeClassNameFunc(node)}
|
||||||
color={nodeColorFunc(node)}
|
color={nodeColorFunc(node)}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState, CSSProperties } from 'react';
|
import React, { useEffect, useState, CSSProperties } from 'react';
|
||||||
|
|
||||||
|
import { useStoreState } from '../../store/hooks';
|
||||||
import { getBezierPath } from '../Edges/BezierEdge';
|
import { getBezierPath } from '../Edges/BezierEdge';
|
||||||
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
|
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
|
||||||
import {
|
import {
|
||||||
@@ -20,7 +21,6 @@ interface ConnectionLineProps {
|
|||||||
connectionPositionX: number;
|
connectionPositionX: number;
|
||||||
connectionPositionY: number;
|
connectionPositionY: number;
|
||||||
connectionLineType: ConnectionLineType;
|
connectionLineType: ConnectionLineType;
|
||||||
nodes: Node[];
|
|
||||||
transform: Transform;
|
transform: Transform;
|
||||||
isConnectable: boolean;
|
isConnectable: boolean;
|
||||||
connectionLineStyle?: CSSProperties;
|
connectionLineStyle?: CSSProperties;
|
||||||
@@ -35,11 +35,11 @@ export default ({
|
|||||||
connectionPositionX,
|
connectionPositionX,
|
||||||
connectionPositionY,
|
connectionPositionY,
|
||||||
connectionLineType = ConnectionLineType.Bezier,
|
connectionLineType = ConnectionLineType.Bezier,
|
||||||
nodes = [],
|
|
||||||
transform,
|
transform,
|
||||||
isConnectable,
|
isConnectable,
|
||||||
CustomConnectionLineComponent,
|
CustomConnectionLineComponent,
|
||||||
}: ConnectionLineProps) => {
|
}: ConnectionLineProps) => {
|
||||||
|
const nodes = useStoreState((state) => state.nodes);
|
||||||
const [sourceNode, setSourceNode] = useState<Node | null>(null);
|
const [sourceNode, setSourceNode] = useState<Node | null>(null);
|
||||||
const nodeId = connectionNodeId;
|
const nodeId = connectionNodeId;
|
||||||
const handleId = connectionHandleId;
|
const handleId = connectionHandleId;
|
||||||
@@ -54,12 +54,12 @@ export default ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sourceHandle = handleId
|
const sourceHandle = handleId
|
||||||
? sourceNode.__rf.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId)
|
? sourceNode.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId)
|
||||||
: sourceNode.__rf.handleBounds[connectionHandleType][0];
|
: sourceNode.handleBounds[connectionHandleType][0];
|
||||||
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNode.__rf.width / 2;
|
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNode.width! / 2;
|
||||||
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.__rf.height;
|
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.height;
|
||||||
const sourceX = sourceNode.__rf.position.x + sourceHandleX;
|
const sourceX = sourceNode.position.x + sourceHandleX;
|
||||||
const sourceY = sourceNode.__rf.position.y + sourceHandleY;
|
const sourceY = sourceNode.position.y + sourceHandleY;
|
||||||
|
|
||||||
const targetX = (connectionPositionX - transform[0]) / transform[2];
|
const targetX = (connectionPositionX - transform[0]) / transform[2];
|
||||||
const targetY = (connectionPositionY - transform[1]) / transform[2];
|
const targetY = (connectionPositionY - transform[1]) / transform[2];
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { useStoreActions } from '../../store/hooks';
|
import { useStoreActions } from '../../store/hooks';
|
||||||
import { Elements } from '../../types';
|
import { Node, Edge } from '../../types';
|
||||||
|
|
||||||
interface ElementUpdaterProps {
|
interface ElementUpdaterProps {
|
||||||
elements: Elements;
|
nodes: Node[];
|
||||||
|
edges: Edge[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const ElementUpdater = ({ elements }: ElementUpdaterProps) => {
|
const ElementUpdater = ({ nodes, edges }: ElementUpdaterProps) => {
|
||||||
const setElements = useStoreActions((actions) => actions.setElements);
|
const setNodes = useStoreActions((actions) => actions.setNodes);
|
||||||
|
const setEdges = useStoreActions((actions) => actions.setEdges);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setElements(elements);
|
setNodes(nodes);
|
||||||
}, [elements]);
|
}, [nodes]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setEdges(edges);
|
||||||
|
}, [edges]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { MouseEvent as ReactMouseEvent } from 'react';
|
import { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
|
import { Store } from 'redux';
|
||||||
|
|
||||||
import { getHostForElement } from '../../utils';
|
import { getHostForElement } from '../../utils';
|
||||||
|
import { ReactFlowState } from '../../types';
|
||||||
|
import { ReactFlowAction } from '../../store/actions';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ElementId,
|
ElementId,
|
||||||
@@ -103,7 +106,8 @@ export function onMouseDown(
|
|||||||
onEdgeUpdateEnd?: (evt: MouseEvent) => void,
|
onEdgeUpdateEnd?: (evt: MouseEvent) => void,
|
||||||
onConnectStart?: OnConnectStartFunc,
|
onConnectStart?: OnConnectStartFunc,
|
||||||
onConnectStop?: OnConnectStopFunc,
|
onConnectStop?: OnConnectStopFunc,
|
||||||
onConnectEnd?: OnConnectEndFunc
|
onConnectEnd?: OnConnectEndFunc,
|
||||||
|
store?: Store<ReactFlowState, ReactFlowAction>
|
||||||
): void {
|
): void {
|
||||||
const reactFlowNode = (event.target as Element).closest('.react-flow');
|
const reactFlowNode = (event.target as Element).closest('.react-flow');
|
||||||
// when react-flow is used inside a shadow root we can't use document
|
// when react-flow is used inside a shadow root we can't use document
|
||||||
@@ -177,7 +181,7 @@ export function onMouseDown(
|
|||||||
onConnectStop?.(event);
|
onConnectStop?.(event);
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
onConnect?.(connection);
|
onConnect?.(connection, store?.getState().nodes || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnectEnd?.(event);
|
onConnectEnd?.(event);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { memo, useContext, useCallback, HTMLAttributes, forwardRef } from 'react';
|
import React, { memo, useContext, useCallback, HTMLAttributes, forwardRef } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
|
|
||||||
import { useStoreActions, useStoreState } from '../../store/hooks';
|
import { useStoreActions, useStoreState, useStore } from '../../store/hooks';
|
||||||
import NodeIdContext from '../../contexts/NodeIdContext';
|
import NodeIdContext from '../../contexts/NodeIdContext';
|
||||||
import { HandleProps, Connection, ElementId, Position } from '../../types';
|
import { HandleProps, Connection, ElementId, Position, Node } from '../../types';
|
||||||
|
|
||||||
import { onMouseDown, SetSourceIdFunc, SetPosition } from './handler';
|
import { onMouseDown, SetSourceIdFunc, SetPosition } from './handler';
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
|
const store = useStore();
|
||||||
const nodeId = useContext(NodeIdContext) as ElementId;
|
const nodeId = useContext(NodeIdContext) as ElementId;
|
||||||
const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
|
const setPosition = useStoreActions((actions) => actions.setConnectionPosition);
|
||||||
const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
|
const setConnectionNodeId = useStoreActions((actions) => actions.setConnectionNodeId);
|
||||||
@@ -38,9 +39,9 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
const isTarget = type === 'target';
|
const isTarget = type === 'target';
|
||||||
|
|
||||||
const onConnectExtended = useCallback(
|
const onConnectExtended = useCallback(
|
||||||
(params: Connection) => {
|
(params: Connection, nodes: Node[]) => {
|
||||||
onConnectAction?.(params);
|
onConnectAction?.(params, nodes);
|
||||||
onConnect?.(params);
|
onConnect?.(params, nodes);
|
||||||
},
|
},
|
||||||
[onConnectAction, onConnect]
|
[onConnectAction, onConnect]
|
||||||
);
|
);
|
||||||
@@ -61,7 +62,8 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
undefined,
|
undefined,
|
||||||
onConnectStart,
|
onConnectStart,
|
||||||
onConnectStop,
|
onConnectStop,
|
||||||
onConnectEnd
|
onConnectEnd,
|
||||||
|
store
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -1,18 +1,8 @@
|
|||||||
import React, {
|
import React, { useEffect, useRef, memo, ComponentType, CSSProperties, useMemo, MouseEvent, useCallback } from 'react';
|
||||||
useEffect,
|
|
||||||
useLayoutEffect,
|
|
||||||
useRef,
|
|
||||||
memo,
|
|
||||||
ComponentType,
|
|
||||||
CSSProperties,
|
|
||||||
useMemo,
|
|
||||||
MouseEvent,
|
|
||||||
useCallback,
|
|
||||||
} from 'react';
|
|
||||||
import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable';
|
import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
|
|
||||||
import { useStoreActions } from '../../store/hooks';
|
import { useStoreActions, useStoreState } from '../../store/hooks';
|
||||||
import { Provider } from '../../contexts/NodeIdContext';
|
import { Provider } from '../../contexts/NodeIdContext';
|
||||||
import { NodeComponentProps, WrapNodeProps } from '../../types';
|
import { NodeComponentProps, WrapNodeProps } from '../../types';
|
||||||
|
|
||||||
@@ -50,9 +40,9 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
resizeObserver,
|
resizeObserver,
|
||||||
dragHandle,
|
dragHandle,
|
||||||
}: WrapNodeProps) => {
|
}: WrapNodeProps) => {
|
||||||
const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
|
// const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
|
||||||
const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
|
const addSelectedElements = useStoreActions((actions) => actions.addSelectedElements);
|
||||||
const updateNodePosDiff = useStoreActions((actions) => actions.updateNodePosDiff);
|
const onNodesChange = useStoreState((state) => state.onNodesChange);
|
||||||
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
|
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
|
||||||
|
|
||||||
const nodeElement = useRef<HTMLDivElement>(null);
|
const nodeElement = useRef<HTMLDivElement>(null);
|
||||||
@@ -84,6 +74,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
onMouseLeave,
|
onMouseLeave,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onMouseEnterHandler = useMemo(() => {
|
const onMouseEnterHandler = useMemo(() => {
|
||||||
if (!onMouseEnter || isDragging) {
|
if (!onMouseEnter || isDragging) {
|
||||||
return;
|
return;
|
||||||
@@ -153,20 +144,25 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
|
|
||||||
const onDrag = useCallback(
|
const onDrag = useCallback(
|
||||||
(event: DraggableEvent, draggableData: DraggableData) => {
|
(event: DraggableEvent, draggableData: DraggableData) => {
|
||||||
|
node.position.x += draggableData.deltaX;
|
||||||
|
node.position.y += draggableData.deltaY;
|
||||||
|
|
||||||
if (onNodeDrag) {
|
if (onNodeDrag) {
|
||||||
node.position.x += draggableData.deltaX;
|
|
||||||
node.position.y += draggableData.deltaY;
|
|
||||||
onNodeDrag(event as MouseEvent, node);
|
onNodeDrag(event as MouseEvent, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNodePosDiff({
|
onNodesChange?.([
|
||||||
id,
|
{
|
||||||
diff: {
|
id,
|
||||||
x: draggableData.deltaX,
|
change: {
|
||||||
y: draggableData.deltaY,
|
position: {
|
||||||
|
x: node.position.x,
|
||||||
|
y: node.position.y,
|
||||||
|
},
|
||||||
|
isDragging: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
isDragging: true,
|
]);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[id, node, onNodeDrag]
|
[id, node, onNodeDrag]
|
||||||
);
|
);
|
||||||
@@ -185,10 +181,14 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNodePosDiff({
|
onNodesChange?.([
|
||||||
id: node.id,
|
{
|
||||||
isDragging: false,
|
id: node.id,
|
||||||
});
|
change: {
|
||||||
|
isDragging: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
onNodeDragStop?.(event as MouseEvent, node);
|
onNodeDragStop?.(event as MouseEvent, node);
|
||||||
},
|
},
|
||||||
@@ -202,11 +202,11 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
[node, onNodeDoubleClick]
|
[node, onNodeDoubleClick]
|
||||||
);
|
);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
// useEffect(() => {
|
||||||
if (nodeElement.current && !isHidden) {
|
// if (nodeElement.current && !isHidden) {
|
||||||
updateNodeDimensions([{ id, nodeElement: nodeElement.current, forceUpdate: true }]);
|
// updateNodeDimensions([{ id, nodeElement: nodeElement.current, forceUpdate: true }]);
|
||||||
}
|
// }
|
||||||
}, [id, isHidden, sourcePosition, targetPosition]);
|
// }, [id, isHidden, sourcePosition, targetPosition]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nodeElement.current) {
|
if (nodeElement.current) {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...matchingNode,
|
...matchingNode,
|
||||||
position: matchingNode?.__rf.position,
|
position: matchingNode?.position,
|
||||||
} as Node;
|
} as Node;
|
||||||
})
|
})
|
||||||
: [],
|
: [],
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useStoreState } from '../../store/hooks';
|
|||||||
import ConnectionLine from '../../components/ConnectionLine/index';
|
import ConnectionLine from '../../components/ConnectionLine/index';
|
||||||
import { isEdge } from '../../utils/graph';
|
import { isEdge } from '../../utils/graph';
|
||||||
import MarkerDefinitions from './MarkerDefinitions';
|
import MarkerDefinitions from './MarkerDefinitions';
|
||||||
import { getEdgePositions, getHandle, isEdgeVisible, getSourceTargetNodes } from './utils';
|
import { getEdgePositions, getHandle } from './utils';
|
||||||
import {
|
import {
|
||||||
Position,
|
Position,
|
||||||
Edge,
|
Edge,
|
||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
ConnectionLineComponent,
|
ConnectionLineComponent,
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
Transform,
|
|
||||||
OnEdgeUpdateFunc,
|
OnEdgeUpdateFunc,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
@@ -41,151 +40,167 @@ interface EdgeRendererProps {
|
|||||||
|
|
||||||
interface EdgeWrapperProps {
|
interface EdgeWrapperProps {
|
||||||
edge: Edge;
|
edge: Edge;
|
||||||
props: EdgeRendererProps;
|
edgeTypes: any;
|
||||||
nodes: Node[];
|
markerEndId?: string;
|
||||||
|
onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void;
|
||||||
|
onEdgeContextMenu?: (event: React.MouseEvent, edge: Edge) => void;
|
||||||
|
onEdgeMouseEnter?: (event: React.MouseEvent, edge: Edge) => void;
|
||||||
|
onEdgeMouseMove?: (event: React.MouseEvent, edge: Edge) => void;
|
||||||
|
onEdgeMouseLeave?: (event: React.MouseEvent, edge: Edge) => void;
|
||||||
|
edgeUpdaterRadius?: number;
|
||||||
|
onEdgeDoubleClick?: (event: React.MouseEvent, edge: Edge) => void;
|
||||||
|
onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge) => void;
|
||||||
|
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void;
|
||||||
|
onEdgeUpdate?: OnEdgeUpdateFunc;
|
||||||
|
targetNode?: Node;
|
||||||
|
sourceNode?: Node;
|
||||||
selectedElements: Elements | null;
|
selectedElements: Elements | null;
|
||||||
elementsSelectable: boolean;
|
elementsSelectable: boolean;
|
||||||
transform: Transform;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
onlyRenderVisibleElements: boolean;
|
|
||||||
connectionMode?: ConnectionMode;
|
connectionMode?: ConnectionMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Edge = ({
|
const Edge = memo(
|
||||||
edge,
|
({
|
||||||
props,
|
edge,
|
||||||
nodes,
|
edgeTypes,
|
||||||
selectedElements,
|
markerEndId,
|
||||||
elementsSelectable,
|
onElementClick,
|
||||||
transform,
|
onEdgeContextMenu,
|
||||||
width,
|
onEdgeMouseEnter,
|
||||||
height,
|
onEdgeMouseMove,
|
||||||
onlyRenderVisibleElements,
|
onEdgeMouseLeave,
|
||||||
connectionMode,
|
edgeUpdaterRadius,
|
||||||
}: EdgeWrapperProps) => {
|
onEdgeDoubleClick,
|
||||||
const sourceHandleId = edge.sourceHandle || null;
|
onEdgeUpdateStart,
|
||||||
const targetHandleId = edge.targetHandle || null;
|
onEdgeUpdateEnd,
|
||||||
const { sourceNode, targetNode } = getSourceTargetNodes(edge, nodes);
|
onEdgeUpdate,
|
||||||
|
|
||||||
const onConnectEdge = useCallback(
|
|
||||||
(connection: Connection) => {
|
|
||||||
props.onEdgeUpdate?.(edge, connection);
|
|
||||||
},
|
|
||||||
[edge, props.onEdgeUpdate]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!sourceNode) {
|
|
||||||
console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetNode) {
|
|
||||||
console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// source and target node need to be initialized
|
|
||||||
if (!sourceNode.__rf.width || !targetNode.__rf.width) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const edgeType = edge.type || 'default';
|
|
||||||
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
|
|
||||||
const targetNodeBounds = targetNode.__rf.handleBounds;
|
|
||||||
// when connection type is loose we can define all handles as sources
|
|
||||||
const targetNodeHandles =
|
|
||||||
connectionMode === ConnectionMode.Strict
|
|
||||||
? targetNodeBounds.target
|
|
||||||
: targetNodeBounds.target || targetNodeBounds.source;
|
|
||||||
const sourceHandle = getHandle(sourceNode.__rf.handleBounds.source, sourceHandleId);
|
|
||||||
const targetHandle = getHandle(targetNodeHandles, targetHandleId);
|
|
||||||
const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom;
|
|
||||||
const targetPosition = targetHandle ? targetHandle.position : Position.Top;
|
|
||||||
|
|
||||||
if (!sourceHandle) {
|
|
||||||
console.warn(`couldn't create edge for source handle id: ${sourceHandleId}; edge id: ${edge.id}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetHandle) {
|
|
||||||
console.warn(`couldn't create edge for target handle id: ${targetHandleId}; edge id: ${edge.id}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
|
|
||||||
sourceNode,
|
|
||||||
sourceHandle,
|
|
||||||
sourcePosition,
|
|
||||||
targetNode,
|
targetNode,
|
||||||
targetHandle,
|
sourceNode,
|
||||||
targetPosition
|
selectedElements,
|
||||||
);
|
elementsSelectable,
|
||||||
|
connectionMode,
|
||||||
|
}: EdgeWrapperProps) => {
|
||||||
|
const sourceHandleId = edge.sourceHandle || null;
|
||||||
|
const targetHandleId = edge.targetHandle || null;
|
||||||
|
|
||||||
const isVisible = onlyRenderVisibleElements
|
const onConnectEdge = useCallback(
|
||||||
? isEdgeVisible({
|
(connection: Connection) => {
|
||||||
sourcePos: { x: sourceX, y: sourceY },
|
onEdgeUpdate?.(edge, connection);
|
||||||
targetPos: { x: targetX, y: targetY },
|
},
|
||||||
width,
|
[edge, onEdgeUpdate]
|
||||||
height,
|
);
|
||||||
transform,
|
|
||||||
})
|
|
||||||
: true;
|
|
||||||
|
|
||||||
if (!isVisible) {
|
if (!sourceNode) {
|
||||||
return null;
|
console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetNode) {
|
||||||
|
console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// source and target node need to be initialized
|
||||||
|
if (!sourceNode.width || !targetNode.width) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const edgeType = edge.type || 'default';
|
||||||
|
const EdgeComponent = edgeTypes[edgeType] || edgeTypes.default;
|
||||||
|
const targetNodeBounds = targetNode.handleBounds;
|
||||||
|
// when connection type is loose we can define all handles as sources
|
||||||
|
const targetNodeHandles =
|
||||||
|
connectionMode === ConnectionMode.Strict
|
||||||
|
? targetNodeBounds.target
|
||||||
|
: targetNodeBounds.target || targetNodeBounds.source;
|
||||||
|
const sourceHandle = getHandle(sourceNode.handleBounds.source, sourceHandleId);
|
||||||
|
const targetHandle = getHandle(targetNodeHandles, targetHandleId);
|
||||||
|
const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom;
|
||||||
|
const targetPosition = targetHandle ? targetHandle.position : Position.Top;
|
||||||
|
|
||||||
|
if (!sourceHandle) {
|
||||||
|
console.warn(`couldn't create edge for source handle id: ${sourceHandleId}; edge id: ${edge.id}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetHandle) {
|
||||||
|
console.warn(`couldn't create edge for target handle id: ${targetHandleId}; edge id: ${edge.id}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
|
||||||
|
sourceNode,
|
||||||
|
sourceHandle,
|
||||||
|
sourcePosition,
|
||||||
|
targetNode,
|
||||||
|
targetHandle,
|
||||||
|
targetPosition
|
||||||
|
);
|
||||||
|
|
||||||
|
// const isVisible = onlyRenderVisibleElements
|
||||||
|
// ? isEdgeVisible({
|
||||||
|
// sourcePos: { x: sourceX, y: sourceY },
|
||||||
|
// targetPos: { x: targetX, y: targetY },
|
||||||
|
// width,
|
||||||
|
// height,
|
||||||
|
// transform,
|
||||||
|
// })
|
||||||
|
// : true;
|
||||||
|
|
||||||
|
// if (!isVisible) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
const isSelected = selectedElements?.some((elm) => isEdge(elm) && elm.id === edge.id) || false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EdgeComponent
|
||||||
|
key={edge.id}
|
||||||
|
id={edge.id}
|
||||||
|
className={edge.className}
|
||||||
|
type={edge.type}
|
||||||
|
data={edge.data}
|
||||||
|
onClick={onElementClick}
|
||||||
|
selected={isSelected}
|
||||||
|
animated={edge.animated}
|
||||||
|
label={edge.label}
|
||||||
|
labelStyle={edge.labelStyle}
|
||||||
|
labelShowBg={edge.labelShowBg}
|
||||||
|
labelBgStyle={edge.labelBgStyle}
|
||||||
|
labelBgPadding={edge.labelBgPadding}
|
||||||
|
labelBgBorderRadius={edge.labelBgBorderRadius}
|
||||||
|
style={edge.style}
|
||||||
|
arrowHeadType={edge.arrowHeadType}
|
||||||
|
source={edge.source}
|
||||||
|
target={edge.target}
|
||||||
|
sourceHandleId={sourceHandleId}
|
||||||
|
targetHandleId={targetHandleId}
|
||||||
|
sourceX={sourceX}
|
||||||
|
sourceY={sourceY}
|
||||||
|
targetX={targetX}
|
||||||
|
targetY={targetY}
|
||||||
|
sourcePosition={sourcePosition}
|
||||||
|
targetPosition={targetPosition}
|
||||||
|
elementsSelectable={elementsSelectable}
|
||||||
|
markerEndId={markerEndId}
|
||||||
|
isHidden={edge.isHidden}
|
||||||
|
onConnectEdge={onConnectEdge}
|
||||||
|
handleEdgeUpdate={typeof onEdgeUpdate !== 'undefined'}
|
||||||
|
onContextMenu={onEdgeContextMenu}
|
||||||
|
onMouseEnter={onEdgeMouseEnter}
|
||||||
|
onMouseMove={onEdgeMouseMove}
|
||||||
|
onMouseLeave={onEdgeMouseLeave}
|
||||||
|
edgeUpdaterRadius={edgeUpdaterRadius}
|
||||||
|
onEdgeDoubleClick={onEdgeDoubleClick}
|
||||||
|
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||||
|
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
const isSelected = selectedElements?.some((elm) => isEdge(elm) && elm.id === edge.id) || false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EdgeComponent
|
|
||||||
key={edge.id}
|
|
||||||
id={edge.id}
|
|
||||||
className={edge.className}
|
|
||||||
type={edge.type}
|
|
||||||
data={edge.data}
|
|
||||||
onClick={props.onElementClick}
|
|
||||||
selected={isSelected}
|
|
||||||
animated={edge.animated}
|
|
||||||
label={edge.label}
|
|
||||||
labelStyle={edge.labelStyle}
|
|
||||||
labelShowBg={edge.labelShowBg}
|
|
||||||
labelBgStyle={edge.labelBgStyle}
|
|
||||||
labelBgPadding={edge.labelBgPadding}
|
|
||||||
labelBgBorderRadius={edge.labelBgBorderRadius}
|
|
||||||
style={edge.style}
|
|
||||||
arrowHeadType={edge.arrowHeadType}
|
|
||||||
source={edge.source}
|
|
||||||
target={edge.target}
|
|
||||||
sourceHandleId={sourceHandleId}
|
|
||||||
targetHandleId={targetHandleId}
|
|
||||||
sourceX={sourceX}
|
|
||||||
sourceY={sourceY}
|
|
||||||
targetX={targetX}
|
|
||||||
targetY={targetY}
|
|
||||||
sourcePosition={sourcePosition}
|
|
||||||
targetPosition={targetPosition}
|
|
||||||
elementsSelectable={elementsSelectable}
|
|
||||||
markerEndId={props.markerEndId}
|
|
||||||
isHidden={edge.isHidden}
|
|
||||||
onConnectEdge={onConnectEdge}
|
|
||||||
handleEdgeUpdate={typeof props.onEdgeUpdate !== 'undefined'}
|
|
||||||
onContextMenu={props.onEdgeContextMenu}
|
|
||||||
onMouseEnter={props.onEdgeMouseEnter}
|
|
||||||
onMouseMove={props.onEdgeMouseMove}
|
|
||||||
onMouseLeave={props.onEdgeMouseLeave}
|
|
||||||
edgeUpdaterRadius={props.edgeUpdaterRadius}
|
|
||||||
onEdgeDoubleClick={props.onEdgeDoubleClick}
|
|
||||||
onEdgeUpdateStart={props.onEdgeUpdateStart}
|
|
||||||
onEdgeUpdateEnd={props.onEdgeUpdateEnd}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const EdgeRenderer = (props: EdgeRendererProps) => {
|
const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||||
const transform = useStoreState((state) => state.transform);
|
const transform = useStoreState((state) => state.transform);
|
||||||
const nodes = useStoreState((state) => state.nodes);
|
|
||||||
const edges = useStoreState((state) => state.edges);
|
const edges = useStoreState((state) => state.edges);
|
||||||
const connectionNodeId = useStoreState((state) => state.connectionNodeId);
|
const connectionNodeId = useStoreState((state) => state.connectionNodeId);
|
||||||
const connectionHandleId = useStoreState((state) => state.connectionHandleId);
|
const connectionHandleId = useStoreState((state) => state.connectionHandleId);
|
||||||
@@ -201,13 +216,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = props;
|
||||||
connectionLineType,
|
|
||||||
arrowHeadColor,
|
|
||||||
connectionLineStyle,
|
|
||||||
connectionLineComponent,
|
|
||||||
onlyRenderVisibleElements,
|
|
||||||
} = props;
|
|
||||||
const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`;
|
const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`;
|
||||||
const renderConnectionLine = connectionNodeId && connectionHandleType;
|
const renderConnectionLine = connectionNodeId && connectionHandleType;
|
||||||
|
|
||||||
@@ -219,19 +228,25 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
|||||||
<Edge
|
<Edge
|
||||||
key={edge.id}
|
key={edge.id}
|
||||||
edge={edge}
|
edge={edge}
|
||||||
props={props}
|
sourceNode={edge.sourceNode}
|
||||||
nodes={nodes}
|
targetNode={edge.targetNode}
|
||||||
selectedElements={selectedElements}
|
selectedElements={selectedElements}
|
||||||
elementsSelectable={elementsSelectable}
|
elementsSelectable={elementsSelectable}
|
||||||
transform={transform}
|
markerEndId={props.markerEndId}
|
||||||
width={width}
|
onEdgeContextMenu={props.onEdgeContextMenu}
|
||||||
height={height}
|
onEdgeMouseEnter={props.onEdgeMouseEnter}
|
||||||
onlyRenderVisibleElements={onlyRenderVisibleElements}
|
onEdgeMouseMove={props.onEdgeMouseMove}
|
||||||
|
onEdgeMouseLeave={props.onEdgeMouseLeave}
|
||||||
|
edgeUpdaterRadius={props.edgeUpdaterRadius}
|
||||||
|
onEdgeDoubleClick={props.onEdgeDoubleClick}
|
||||||
|
onEdgeUpdateStart={props.onEdgeUpdateStart}
|
||||||
|
onEdgeUpdateEnd={props.onEdgeUpdateEnd}
|
||||||
|
onEdgeUpdate={props.onEdgeUpdate}
|
||||||
|
edgeTypes={props.edgeTypes}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{renderConnectionLine && (
|
{renderConnectionLine && (
|
||||||
<ConnectionLine
|
<ConnectionLine
|
||||||
nodes={nodes}
|
|
||||||
connectionNodeId={connectionNodeId!}
|
connectionNodeId={connectionNodeId!}
|
||||||
connectionHandleId={connectionHandleId}
|
connectionHandleId={connectionHandleId}
|
||||||
connectionHandleType={connectionHandleType!}
|
connectionHandleType={connectionHandleType!}
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition {
|
export function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition {
|
||||||
const x = (handle?.x || 0) + node.__rf.position.x;
|
const x = (handle?.x || 0) + node.position.x;
|
||||||
const y = (handle?.y || 0) + node.__rf.position.y;
|
const y = (handle?.y || 0) + node.position.y;
|
||||||
const width = handle?.width || node.__rf.width;
|
const width = handle?.width || node.__rf.width;
|
||||||
const height = handle?.height || node.__rf.height;
|
const height = handle?.height || node.__rf.height;
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ const FlowRenderer = ({
|
|||||||
onPaneClick,
|
onPaneClick,
|
||||||
onPaneContextMenu,
|
onPaneContextMenu,
|
||||||
onPaneScroll,
|
onPaneScroll,
|
||||||
onElementsRemove,
|
|
||||||
deleteKeyCode,
|
deleteKeyCode,
|
||||||
onMove,
|
onMove,
|
||||||
onMoveStart,
|
onMoveStart,
|
||||||
@@ -61,7 +60,7 @@ const FlowRenderer = ({
|
|||||||
|
|
||||||
const selectionKeyPressed = useKeyPress(selectionKeyCode);
|
const selectionKeyPressed = useKeyPress(selectionKeyCode);
|
||||||
|
|
||||||
useGlobalKeyHandler({ onElementsRemove, deleteKeyCode, multiSelectionKeyCode });
|
useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode });
|
||||||
|
|
||||||
const onClick = useCallback(
|
const onClick = useCallback(
|
||||||
(event: MouseEvent) => {
|
(event: MouseEvent) => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { ReactFlowProps } from '../ReactFlow';
|
|||||||
|
|
||||||
import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode } from '../../types';
|
import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode } from '../../types';
|
||||||
|
|
||||||
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange' | 'elements'> {
|
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges'> {
|
||||||
nodeTypes: NodeTypesType;
|
nodeTypes: NodeTypesType;
|
||||||
edgeTypes: EdgeTypesType;
|
edgeTypes: EdgeTypesType;
|
||||||
selectionKeyCode: KeyCode;
|
selectionKeyCode: KeyCode;
|
||||||
@@ -55,7 +55,6 @@ const GraphView = ({
|
|||||||
selectionKeyCode,
|
selectionKeyCode,
|
||||||
multiSelectionKeyCode,
|
multiSelectionKeyCode,
|
||||||
zoomActivationKeyCode,
|
zoomActivationKeyCode,
|
||||||
onElementsRemove,
|
|
||||||
deleteKeyCode,
|
deleteKeyCode,
|
||||||
onConnect,
|
onConnect,
|
||||||
onConnectStart,
|
onConnectStart,
|
||||||
@@ -95,6 +94,8 @@ const GraphView = ({
|
|||||||
edgeUpdaterRadius,
|
edgeUpdaterRadius,
|
||||||
onEdgeUpdateStart,
|
onEdgeUpdateStart,
|
||||||
onEdgeUpdateEnd,
|
onEdgeUpdateEnd,
|
||||||
|
onNodesChange,
|
||||||
|
onEdgesChange,
|
||||||
}: GraphViewProps) => {
|
}: GraphViewProps) => {
|
||||||
const isInitialized = useRef<boolean>(false);
|
const isInitialized = useRef<boolean>(false);
|
||||||
const setOnConnect = useStoreActions((actions) => actions.setOnConnect);
|
const setOnConnect = useStoreActions((actions) => actions.setOnConnect);
|
||||||
@@ -111,6 +112,9 @@ const GraphView = ({
|
|||||||
const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent);
|
const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent);
|
||||||
const setNodeExtent = useStoreActions((actions) => actions.setNodeExtent);
|
const setNodeExtent = useStoreActions((actions) => actions.setNodeExtent);
|
||||||
const setConnectionMode = useStoreActions((actions) => actions.setConnectionMode);
|
const setConnectionMode = useStoreActions((actions) => actions.setConnectionMode);
|
||||||
|
const setOnNodesChange = useStoreActions((actions) => actions.setOnNodesChange);
|
||||||
|
const setOnEdgesChange = useStoreActions((actions) => actions.setOnEdgesChange);
|
||||||
|
|
||||||
const currentStore = useStore();
|
const currentStore = useStore();
|
||||||
const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper();
|
const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper();
|
||||||
|
|
||||||
@@ -217,12 +221,23 @@ const GraphView = ({
|
|||||||
}
|
}
|
||||||
}, [connectionMode]);
|
}, [connectionMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof onNodesChange !== 'undefined') {
|
||||||
|
setOnNodesChange(onNodesChange);
|
||||||
|
}
|
||||||
|
}, [onNodesChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof onEdgesChange !== 'undefined') {
|
||||||
|
setOnEdgesChange(onEdgesChange);
|
||||||
|
}
|
||||||
|
}, [onEdgesChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlowRenderer
|
<FlowRenderer
|
||||||
onPaneClick={onPaneClick}
|
onPaneClick={onPaneClick}
|
||||||
onPaneContextMenu={onPaneContextMenu}
|
onPaneContextMenu={onPaneContextMenu}
|
||||||
onPaneScroll={onPaneScroll}
|
onPaneScroll={onPaneScroll}
|
||||||
onElementsRemove={onElementsRemove}
|
|
||||||
deleteKeyCode={deleteKeyCode}
|
deleteKeyCode={deleteKeyCode}
|
||||||
selectionKeyCode={selectionKeyCode}
|
selectionKeyCode={selectionKeyCode}
|
||||||
multiSelectionKeyCode={multiSelectionKeyCode}
|
multiSelectionKeyCode={multiSelectionKeyCode}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { memo, useMemo, ComponentType, MouseEvent } from 'react';
|
import React, { memo, useMemo, ComponentType, MouseEvent } from 'react';
|
||||||
|
|
||||||
import { getNodesInside } from '../../utils/graph';
|
|
||||||
import { useStoreState, useStoreActions } from '../../store/hooks';
|
import { useStoreState, useStoreActions } from '../../store/hooks';
|
||||||
import { Node, NodeTypesType, WrapNodeProps, Edge } from '../../types';
|
import { Node, NodeTypesType, WrapNodeProps, Edge } from '../../types';
|
||||||
interface NodeRendererProps {
|
interface NodeRendererProps {
|
||||||
@@ -26,14 +25,12 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
|||||||
const nodesDraggable = useStoreState((state) => state.nodesDraggable);
|
const nodesDraggable = useStoreState((state) => state.nodesDraggable);
|
||||||
const nodesConnectable = useStoreState((state) => state.nodesConnectable);
|
const nodesConnectable = useStoreState((state) => state.nodesConnectable);
|
||||||
const elementsSelectable = useStoreState((state) => state.elementsSelectable);
|
const elementsSelectable = useStoreState((state) => state.elementsSelectable);
|
||||||
const width = useStoreState((state) => state.width);
|
|
||||||
const height = useStoreState((state) => state.height);
|
|
||||||
const nodes = useStoreState((state) => state.nodes);
|
const nodes = useStoreState((state) => state.nodes);
|
||||||
const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
|
const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
|
||||||
|
|
||||||
const visibleNodes = props.onlyRenderVisibleElements
|
// const visibleNodes = props.onlyRenderVisibleElements
|
||||||
? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true)
|
// ? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true)
|
||||||
: nodes;
|
// : nodes;
|
||||||
|
|
||||||
const transformStyle = useMemo(
|
const transformStyle = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -59,7 +56,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="react-flow__nodes" style={transformStyle}>
|
<div className="react-flow__nodes" style={transformStyle}>
|
||||||
{visibleNodes.map((node) => {
|
{nodes.map((node) => {
|
||||||
const nodeType = node.type || 'default';
|
const nodeType = node.type || 'default';
|
||||||
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>;
|
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>;
|
||||||
|
|
||||||
@@ -82,10 +79,10 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
|||||||
sourcePosition={node.sourcePosition}
|
sourcePosition={node.sourcePosition}
|
||||||
targetPosition={node.targetPosition}
|
targetPosition={node.targetPosition}
|
||||||
isHidden={node.isHidden}
|
isHidden={node.isHidden}
|
||||||
xPos={node.__rf.position.x}
|
xPos={node.position.x}
|
||||||
yPos={node.__rf.position.y}
|
yPos={node.position.y}
|
||||||
isDragging={node.__rf.isDragging}
|
isDragging={node.isDragging}
|
||||||
isInitialized={node.__rf.width !== null && node.__rf.height !== null}
|
isInitialized={node.width !== null && node.height !== null}
|
||||||
snapGrid={props.snapGrid}
|
snapGrid={props.snapGrid}
|
||||||
snapToGrid={props.snapToGrid}
|
snapToGrid={props.snapToGrid}
|
||||||
selectNodesOnDrag={props.selectNodesOnDrag}
|
selectNodesOnDrag={props.selectNodesOnDrag}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
OnLoadFunc,
|
OnLoadFunc,
|
||||||
Node,
|
Node,
|
||||||
Edge,
|
Edge,
|
||||||
Connection,
|
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
ConnectionLineComponent,
|
ConnectionLineComponent,
|
||||||
@@ -33,11 +32,13 @@ import {
|
|||||||
OnConnectStartFunc,
|
OnConnectStartFunc,
|
||||||
OnConnectStopFunc,
|
OnConnectStopFunc,
|
||||||
OnConnectEndFunc,
|
OnConnectEndFunc,
|
||||||
|
OnConnectFunc,
|
||||||
TranslateExtent,
|
TranslateExtent,
|
||||||
KeyCode,
|
KeyCode,
|
||||||
PanOnScrollMode,
|
PanOnScrollMode,
|
||||||
OnEdgeUpdateFunc,
|
OnEdgeUpdateFunc,
|
||||||
NodeExtent,
|
NodeExtent,
|
||||||
|
ElementChange,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import '../../style.css';
|
import '../../style.css';
|
||||||
@@ -57,9 +58,11 @@ const defaultEdgeTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
|
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
|
||||||
elements: Elements;
|
nodes: Node[];
|
||||||
|
edges: Edge[];
|
||||||
|
onNodesChange?: (nodeChanges: ElementChange[]) => void;
|
||||||
|
onEdgesChange?: (edgeChanges: ElementChange[]) => void;
|
||||||
onElementClick?: (event: ReactMouseEvent, element: Node | Edge) => void;
|
onElementClick?: (event: ReactMouseEvent, element: Node | Edge) => void;
|
||||||
onElementsRemove?: (elements: Elements) => void;
|
|
||||||
onNodeDoubleClick?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeDoubleClick?: (event: ReactMouseEvent, node: Node) => void;
|
||||||
onNodeMouseEnter?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeMouseEnter?: (event: ReactMouseEvent, node: Node) => void;
|
||||||
onNodeMouseMove?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeMouseMove?: (event: ReactMouseEvent, node: Node) => void;
|
||||||
@@ -68,7 +71,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
|||||||
onNodeDragStart?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeDragStart?: (event: ReactMouseEvent, node: Node) => void;
|
||||||
onNodeDrag?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeDrag?: (event: ReactMouseEvent, node: Node) => void;
|
||||||
onNodeDragStop?: (event: ReactMouseEvent, node: Node) => void;
|
onNodeDragStop?: (event: ReactMouseEvent, node: Node) => void;
|
||||||
onConnect?: (connection: Edge | Connection) => void;
|
onConnect?: OnConnectFunc;
|
||||||
onConnectStart?: OnConnectStartFunc;
|
onConnectStart?: OnConnectStartFunc;
|
||||||
onConnectStop?: OnConnectStopFunc;
|
onConnectStop?: OnConnectStopFunc;
|
||||||
onConnectEnd?: OnConnectEndFunc;
|
onConnectEnd?: OnConnectEndFunc;
|
||||||
@@ -132,10 +135,14 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
|||||||
|
|
||||||
export type ReactFlowRefType = HTMLDivElement;
|
export type ReactFlowRefType = HTMLDivElement;
|
||||||
|
|
||||||
|
const initSnapGrid: [number, number] = [15, 15];
|
||||||
|
const initDefaultPosition: [number, number] = [0, 0];
|
||||||
|
|
||||||
const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
elements = [],
|
nodes = [],
|
||||||
|
edges = [],
|
||||||
className,
|
className,
|
||||||
nodeTypes = defaultNodeTypes,
|
nodeTypes = defaultNodeTypes,
|
||||||
edgeTypes = defaultEdgeTypes,
|
edgeTypes = defaultEdgeTypes,
|
||||||
@@ -144,7 +151,6 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
onMove,
|
onMove,
|
||||||
onMoveStart,
|
onMoveStart,
|
||||||
onMoveEnd,
|
onMoveEnd,
|
||||||
onElementsRemove,
|
|
||||||
onConnect,
|
onConnect,
|
||||||
onConnectStart,
|
onConnectStart,
|
||||||
onConnectStop,
|
onConnectStop,
|
||||||
@@ -171,7 +177,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
multiSelectionKeyCode = 'Meta',
|
multiSelectionKeyCode = 'Meta',
|
||||||
zoomActivationKeyCode = 'Meta',
|
zoomActivationKeyCode = 'Meta',
|
||||||
snapToGrid = false,
|
snapToGrid = false,
|
||||||
snapGrid = [15, 15],
|
snapGrid = initSnapGrid,
|
||||||
onlyRenderVisibleElements = false,
|
onlyRenderVisibleElements = false,
|
||||||
selectNodesOnDrag = true,
|
selectNodesOnDrag = true,
|
||||||
nodesDraggable,
|
nodesDraggable,
|
||||||
@@ -180,7 +186,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
minZoom,
|
minZoom,
|
||||||
maxZoom,
|
maxZoom,
|
||||||
defaultZoom = 1,
|
defaultZoom = 1,
|
||||||
defaultPosition = [0, 0],
|
defaultPosition = initDefaultPosition,
|
||||||
translateExtent,
|
translateExtent,
|
||||||
preventScrolling = true,
|
preventScrolling = true,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
@@ -208,6 +214,8 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
edgeUpdaterRadius = 10,
|
edgeUpdaterRadius = 10,
|
||||||
nodeTypesId = '1',
|
nodeTypesId = '1',
|
||||||
edgeTypesId = '1',
|
edgeTypesId = '1',
|
||||||
|
onNodesChange,
|
||||||
|
onEdgesChange,
|
||||||
...rest
|
...rest
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
@@ -240,7 +248,6 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
connectionLineStyle={connectionLineStyle}
|
connectionLineStyle={connectionLineStyle}
|
||||||
connectionLineComponent={connectionLineComponent}
|
connectionLineComponent={connectionLineComponent}
|
||||||
selectionKeyCode={selectionKeyCode}
|
selectionKeyCode={selectionKeyCode}
|
||||||
onElementsRemove={onElementsRemove}
|
|
||||||
deleteKeyCode={deleteKeyCode}
|
deleteKeyCode={deleteKeyCode}
|
||||||
multiSelectionKeyCode={multiSelectionKeyCode}
|
multiSelectionKeyCode={multiSelectionKeyCode}
|
||||||
zoomActivationKeyCode={zoomActivationKeyCode}
|
zoomActivationKeyCode={zoomActivationKeyCode}
|
||||||
@@ -287,8 +294,10 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||||
edgeUpdaterRadius={edgeUpdaterRadius}
|
edgeUpdaterRadius={edgeUpdaterRadius}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
/>
|
/>
|
||||||
<ElementUpdater elements={elements} />
|
<ElementUpdater nodes={nodes} edges={edges} />
|
||||||
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
|
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
|
||||||
{children}
|
{children}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { useStore, useStoreActions } from '../store/hooks';
|
import { useStore, useStoreActions, useStoreState } from '../store/hooks';
|
||||||
import useKeyPress from './useKeyPress';
|
import useKeyPress from './useKeyPress';
|
||||||
import { isNode, getConnectedEdges } from '../utils/graph';
|
import { isNode, isEdge, getConnectedEdges } from '../utils/graph';
|
||||||
import { Elements, KeyCode, ElementId, FlowElement } from '../types';
|
import { KeyCode } from '../types';
|
||||||
|
|
||||||
interface HookParams {
|
interface HookParams {
|
||||||
deleteKeyCode: KeyCode;
|
deleteKeyCode: KeyCode;
|
||||||
multiSelectionKeyCode: KeyCode;
|
multiSelectionKeyCode: KeyCode;
|
||||||
onElementsRemove?: (elements: Elements) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ deleteKeyCode, multiSelectionKeyCode, onElementsRemove }: HookParams): void => {
|
export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
|
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
|
||||||
const setMultiSelectionActive = useStoreActions((actions) => actions.setMultiSelectionActive);
|
const setMultiSelectionActive = useStoreActions((actions) => actions.setMultiSelectionActive);
|
||||||
const resetSelectedElements = useStoreActions((actions) => actions.resetSelectedElements);
|
const resetSelectedElements = useStoreActions((actions) => actions.resetSelectedElements);
|
||||||
|
const onNodesChange = useStoreState((state) => state.onNodesChange);
|
||||||
|
const onEdgesChange = useStoreState((state) => state.onEdgesChange);
|
||||||
|
|
||||||
const deleteKeyPressed = useKeyPress(deleteKeyCode);
|
const deleteKeyPressed = useKeyPress(deleteKeyCode);
|
||||||
const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
|
const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode);
|
||||||
@@ -24,19 +25,21 @@ export default ({ deleteKeyCode, multiSelectionKeyCode, onElementsRemove }: Hook
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { edges, selectedElements } = store.getState();
|
const { edges, selectedElements } = store.getState();
|
||||||
|
|
||||||
if (onElementsRemove && deleteKeyPressed && selectedElements) {
|
if (deleteKeyPressed && selectedElements) {
|
||||||
const selectedNodes = selectedElements.filter(isNode);
|
const selectedNodes = selectedElements.filter(isNode);
|
||||||
|
const selectedEdges = selectedElements.filter(isEdge);
|
||||||
const connectedEdges = getConnectedEdges(selectedNodes, edges);
|
const connectedEdges = getConnectedEdges(selectedNodes, edges);
|
||||||
const elementsToRemove = [...selectedElements, ...connectedEdges].reduce(
|
|
||||||
(res, item) => res.set(item.id, item),
|
|
||||||
new Map<ElementId, FlowElement>()
|
|
||||||
);
|
|
||||||
|
|
||||||
onElementsRemove(Array.from(elementsToRemove.values()));
|
const nodeChanges = selectedNodes.map((n) => ({ id: n.id, delete: true }));
|
||||||
|
const edgeChanges = [...selectedEdges, ...connectedEdges].map((e) => ({ id: e.id, delete: true }));
|
||||||
|
|
||||||
|
onNodesChange?.(nodeChanges);
|
||||||
|
onEdgesChange?.(edgeChanges);
|
||||||
|
|
||||||
unsetNodesSelection();
|
unsetNodesSelection();
|
||||||
resetSelectedElements();
|
resetSelectedElements();
|
||||||
}
|
}
|
||||||
}, [deleteKeyPressed, onElementsRemove]);
|
}, [deleteKeyPressed, onNodesChange, onEdgesChange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMultiSelectionActive(multiSelectionKeyPressed);
|
setMultiSelectionActive(multiSelectionKeyPressed);
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export {
|
|||||||
updateEdge,
|
updateEdge,
|
||||||
getTransformForBounds,
|
getTransformForBounds,
|
||||||
getRectOfNodes,
|
getRectOfNodes,
|
||||||
|
applyNodeChanges,
|
||||||
|
applyEdgeChanges,
|
||||||
} from './utils/graph';
|
} from './utils/graph';
|
||||||
export { default as useZoomPanHelper } from './hooks/useZoomPanHelper';
|
export { default as useZoomPanHelper } from './hooks/useZoomPanHelper';
|
||||||
export { default as useUpdateNodeInternals } from './hooks/useUpdateNodeInternals';
|
export { default as useUpdateNodeInternals } from './hooks/useUpdateNodeInternals';
|
||||||
|
|||||||
+15
-2
@@ -1,6 +1,8 @@
|
|||||||
import { createAction } from './utils';
|
import { createAction } from './utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Node,
|
||||||
|
Edge,
|
||||||
Elements,
|
Elements,
|
||||||
OnConnectEndFunc,
|
OnConnectEndFunc,
|
||||||
OnConnectFunc,
|
OnConnectFunc,
|
||||||
@@ -18,6 +20,7 @@ import {
|
|||||||
SnapGrid,
|
SnapGrid,
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
NodeExtent,
|
NodeExtent,
|
||||||
|
OnElementsChange,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
import * as constants from './contants';
|
import * as constants from './contants';
|
||||||
@@ -42,7 +45,8 @@ export const setOnConnectEnd = (onConnectEnd: OnConnectEndFunc) =>
|
|||||||
onConnectEnd,
|
onConnectEnd,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setElements = (elements: Elements) => createAction(constants.SET_ELEMENTS, elements);
|
export const setNodes = (nodes: Node[]) => createAction(constants.SET_NODES, nodes);
|
||||||
|
export const setEdges = (edges: Edge[]) => createAction(constants.SET_EDGES, edges);
|
||||||
|
|
||||||
export const updateNodeDimensions = (updates: NodeDimensionUpdate[]) =>
|
export const updateNodeDimensions = (updates: NodeDimensionUpdate[]) =>
|
||||||
createAction(constants.UPDATE_NODE_DIMENSIONS, updates);
|
createAction(constants.UPDATE_NODE_DIMENSIONS, updates);
|
||||||
@@ -126,12 +130,19 @@ export const setConnectionMode = (connectionMode: ConnectionMode) =>
|
|||||||
|
|
||||||
export const setNodeExtent = (nodeExtent: NodeExtent) => createAction(constants.SET_NODE_EXTENT, nodeExtent);
|
export const setNodeExtent = (nodeExtent: NodeExtent) => createAction(constants.SET_NODE_EXTENT, nodeExtent);
|
||||||
|
|
||||||
|
export const setOnNodesChange = (onNodesChange: OnElementsChange) =>
|
||||||
|
createAction(constants.SET_ON_NODES_CHANGE, { onNodesChange });
|
||||||
|
|
||||||
|
export const setOnEdgesChange = (onEdgesChange: OnElementsChange) =>
|
||||||
|
createAction(constants.SET_ON_EDGES_CHANGE, { onEdgesChange });
|
||||||
|
|
||||||
export type ReactFlowAction = ReturnType<
|
export type ReactFlowAction = ReturnType<
|
||||||
| typeof setOnConnect
|
| typeof setOnConnect
|
||||||
| typeof setOnConnectStart
|
| typeof setOnConnectStart
|
||||||
| typeof setOnConnectStop
|
| typeof setOnConnectStop
|
||||||
| typeof setOnConnectEnd
|
| typeof setOnConnectEnd
|
||||||
| typeof setElements
|
| typeof setNodes
|
||||||
|
| typeof setEdges
|
||||||
| typeof updateNodeDimensions
|
| typeof updateNodeDimensions
|
||||||
| typeof updateNodePos
|
| typeof updateNodePos
|
||||||
| typeof updateNodePosDiff
|
| typeof updateNodePosDiff
|
||||||
@@ -160,4 +171,6 @@ export type ReactFlowAction = ReturnType<
|
|||||||
| typeof setMultiSelectionActive
|
| typeof setMultiSelectionActive
|
||||||
| typeof setConnectionMode
|
| typeof setConnectionMode
|
||||||
| typeof setNodeExtent
|
| typeof setNodeExtent
|
||||||
|
| typeof setOnNodesChange
|
||||||
|
| typeof setOnEdgesChange
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { createStore, Store } from 'redux';
|
import { createStore, applyMiddleware, Store } from 'redux';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
|
||||||
import { ReactFlowState } from '../types';
|
import { ReactFlowState } from '../types';
|
||||||
import { ReactFlowAction } from './actions';
|
import { ReactFlowAction } from './actions';
|
||||||
import reactFlowReducer from './reducer';
|
import reactFlowReducer from './reducer';
|
||||||
|
|
||||||
export default function configureStore(preloadedState: ReactFlowState): Store<ReactFlowState, ReactFlowAction> {
|
export default function configureStore(preloadedState: ReactFlowState): Store<ReactFlowState, ReactFlowAction> {
|
||||||
const store = createStore(reactFlowReducer, preloadedState);
|
const store = createStore(reactFlowReducer, preloadedState, applyMiddleware(thunk));
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ export const SET_ON_CONNECT = 'SET_ON_CONNECT';
|
|||||||
export const SET_ON_CONNECT_START = 'SET_ON_CONNECT_START';
|
export const SET_ON_CONNECT_START = 'SET_ON_CONNECT_START';
|
||||||
export const SET_ON_CONNECT_STOP = 'SET_ON_CONNECT_STOP';
|
export const SET_ON_CONNECT_STOP = 'SET_ON_CONNECT_STOP';
|
||||||
export const SET_ON_CONNECT_END = 'SET_ON_CONNECT_END';
|
export const SET_ON_CONNECT_END = 'SET_ON_CONNECT_END';
|
||||||
export const SET_ELEMENTS = 'SET_ELEMENTS';
|
export const SET_NODES = 'SET_NODES';
|
||||||
|
export const SET_EDGES = 'SET_EDGES';
|
||||||
export const UPDATE_NODE_DIMENSIONS = 'UPDATE_NODE_DIMENSIONS';
|
export const UPDATE_NODE_DIMENSIONS = 'UPDATE_NODE_DIMENSIONS';
|
||||||
export const UPDATE_NODE_POS = 'UPDATE_NODE_POS';
|
export const UPDATE_NODE_POS = 'UPDATE_NODE_POS';
|
||||||
export const UPDATE_NODE_POS_DIFF = 'UPDATE_NODE_POS_DIFF';
|
export const UPDATE_NODE_POS_DIFF = 'UPDATE_NODE_POS_DIFF';
|
||||||
@@ -31,3 +32,5 @@ export const SET_ELEMENTS_SELECTABLE = 'SET_ELEMENTS_SELECTABLE';
|
|||||||
export const SET_MULTI_SELECTION_ACTIVE = 'SET_MULTI_SELECTION_ACTIVE';
|
export const SET_MULTI_SELECTION_ACTIVE = 'SET_MULTI_SELECTION_ACTIVE';
|
||||||
export const SET_CONNECTION_MODE = 'SET_CONNECTION_MODE';
|
export const SET_CONNECTION_MODE = 'SET_CONNECTION_MODE';
|
||||||
export const SET_NODE_EXTENT = 'SET_NODE_EXTENT';
|
export const SET_NODE_EXTENT = 'SET_NODE_EXTENT';
|
||||||
|
export const SET_ON_NODES_CHANGE = 'SET_ON_NODES_CHANGE';
|
||||||
|
export const SET_ON_EDGES_CHANGE = 'SET_ON_EDGES_CHANGE';
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ export const initialState: ReactFlowState = {
|
|||||||
transform: [0, 0, 1],
|
transform: [0, 0, 1],
|
||||||
nodes: [],
|
nodes: [],
|
||||||
edges: [],
|
edges: [],
|
||||||
|
onNodesChange: null,
|
||||||
|
onEdgesChange: null,
|
||||||
|
|
||||||
selectedElements: null,
|
selectedElements: null,
|
||||||
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
||||||
|
|
||||||
|
|||||||
+100
-91
@@ -1,109 +1,111 @@
|
|||||||
import isEqual from 'fast-deep-equal';
|
import isEqual from 'fast-deep-equal';
|
||||||
|
|
||||||
import { clampPosition, getDimensions } from '../utils';
|
import { clampPosition, getDimensions } from '../utils';
|
||||||
import {
|
import { getNodesInside, getConnectedEdges, getRectOfNodes, isNode, parseNode, parseEdge } from '../utils/graph';
|
||||||
getNodesInside,
|
|
||||||
getConnectedEdges,
|
|
||||||
getRectOfNodes,
|
|
||||||
isNode,
|
|
||||||
isEdge,
|
|
||||||
parseNode,
|
|
||||||
parseEdge,
|
|
||||||
} from '../utils/graph';
|
|
||||||
import { getHandleBounds } from '../components/Nodes/utils';
|
import { getHandleBounds } from '../components/Nodes/utils';
|
||||||
|
import { getSourceTargetNodes } from '../container/EdgeRenderer/utils';
|
||||||
|
|
||||||
import { ReactFlowState, Node, XYPosition, Edge } from '../types';
|
import { ReactFlowState, Node, XYPosition, Edge, ElementChange } from '../types';
|
||||||
import * as constants from './contants';
|
import * as constants from './contants';
|
||||||
import { ReactFlowAction } from './actions';
|
import { ReactFlowAction } from './actions';
|
||||||
|
|
||||||
import { initialState } from './index';
|
import { initialState } from './index';
|
||||||
|
|
||||||
type NextElements = {
|
|
||||||
nextNodes: Node[];
|
|
||||||
nextEdges: Edge[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function reactFlowReducer(state = initialState, action: ReactFlowAction): ReactFlowState {
|
export default function reactFlowReducer(state = initialState, action: ReactFlowAction): ReactFlowState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case constants.SET_ELEMENTS: {
|
case constants.SET_NODES: {
|
||||||
const propElements = action.payload;
|
const propNodes = action.payload;
|
||||||
const nextElements: NextElements = {
|
const nextNodes = propNodes.map((propNode: Node) => {
|
||||||
nextNodes: [],
|
const storeNode = state.nodes.find((node) => node.id === propNode.id);
|
||||||
nextEdges: [],
|
|
||||||
};
|
|
||||||
const { nextNodes, nextEdges } = propElements.reduce((res, propElement): NextElements => {
|
|
||||||
if (isNode(propElement)) {
|
|
||||||
const storeNode = state.nodes.find((node) => node.id === propElement.id);
|
|
||||||
|
|
||||||
if (storeNode) {
|
if (storeNode) {
|
||||||
|
if (typeof propNode.type !== 'undefined' && propNode.type !== storeNode.type) {
|
||||||
const updatedNode: Node = {
|
const updatedNode: Node = {
|
||||||
...storeNode,
|
...storeNode,
|
||||||
...propElement,
|
...propNode,
|
||||||
};
|
};
|
||||||
|
// we reset the elements dimensions here in order to force a re-calculation of the bounds.
|
||||||
if (storeNode.position.x !== propElement.position.x || storeNode.position.y !== propElement.position.y) {
|
// When the type of a node changes it is possible that the number or positions of handles changes too.
|
||||||
updatedNode.__rf.position = propElement.position;
|
updatedNode.width = null;
|
||||||
}
|
return updatedNode;
|
||||||
|
|
||||||
if (typeof propElement.type !== 'undefined' && propElement.type !== storeNode.type) {
|
|
||||||
// we reset the elements dimensions here in order to force a re-calculation of the bounds.
|
|
||||||
// When the type of a node changes it is possible that the number or positions of handles changes too.
|
|
||||||
updatedNode.__rf.width = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.nextNodes.push(updatedNode);
|
|
||||||
} else {
|
|
||||||
res.nextNodes.push(parseNode(propElement, state.nodeExtent));
|
|
||||||
}
|
|
||||||
} else if (isEdge(propElement)) {
|
|
||||||
const storeEdge = state.edges.find((se) => se.id === propElement.id);
|
|
||||||
|
|
||||||
if (storeEdge) {
|
|
||||||
res.nextEdges.push({
|
|
||||||
...storeEdge,
|
|
||||||
...propElement,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.nextEdges.push(parseEdge(propElement));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return parseNode(propNode, state.nodeExtent);
|
||||||
}, nextElements);
|
});
|
||||||
|
|
||||||
return { ...state, nodes: nextNodes, edges: nextEdges };
|
const updatedEdges = state.edges.map((edge) => {
|
||||||
|
const { sourceNode, targetNode } = getSourceTargetNodes(edge, nextNodes);
|
||||||
|
|
||||||
|
if (sourceNode) {
|
||||||
|
edge.sourceNode = sourceNode;
|
||||||
|
}
|
||||||
|
if (targetNode) {
|
||||||
|
edge.targetNode = targetNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return edge;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...state, nodes: nextNodes, edges: updatedEdges };
|
||||||
|
}
|
||||||
|
case constants.SET_EDGES: {
|
||||||
|
const propElements = action.payload;
|
||||||
|
const nextEdges = propElements.map((propEdge: Edge) => {
|
||||||
|
const storeEdge = state.edges.find((se) => se.id === propEdge.id);
|
||||||
|
|
||||||
|
if (storeEdge) {
|
||||||
|
return parseEdge(propEdge);
|
||||||
|
} else {
|
||||||
|
const parsedEdge = parseEdge(propEdge);
|
||||||
|
const { sourceNode, targetNode } = getSourceTargetNodes(parsedEdge, state.nodes);
|
||||||
|
|
||||||
|
if (sourceNode) {
|
||||||
|
parsedEdge.sourceNode = sourceNode;
|
||||||
|
}
|
||||||
|
if (targetNode) {
|
||||||
|
parsedEdge.targetNode = targetNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedEdge;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...state, edges: nextEdges };
|
||||||
}
|
}
|
||||||
case constants.UPDATE_NODE_DIMENSIONS: {
|
case constants.UPDATE_NODE_DIMENSIONS: {
|
||||||
const updatedNodes = state.nodes.map((node) => {
|
const initialChanges: ElementChange[] = [];
|
||||||
|
const nodesToChange: ElementChange[] = state.nodes.reduce((res, node) => {
|
||||||
const update = action.payload.find((u) => u.id === node.id);
|
const update = action.payload.find((u) => u.id === node.id);
|
||||||
if (update) {
|
if (update) {
|
||||||
const dimensions = getDimensions(update.nodeElement);
|
const dimensions = getDimensions(update.nodeElement);
|
||||||
const doUpdate =
|
const doUpdate =
|
||||||
dimensions.width &&
|
dimensions.width &&
|
||||||
dimensions.height &&
|
dimensions.height &&
|
||||||
(node.__rf.width !== dimensions.width || node.__rf.height !== dimensions.height || update.forceUpdate);
|
(node.width !== dimensions.width || node.height !== dimensions.height || update.forceUpdate);
|
||||||
|
|
||||||
if (doUpdate) {
|
if (doUpdate) {
|
||||||
const handleBounds = getHandleBounds(update.nodeElement, state.transform[2]);
|
const handleBounds = getHandleBounds(update.nodeElement, state.transform[2]);
|
||||||
|
const change = {
|
||||||
return {
|
id: node.id,
|
||||||
...node,
|
change: {
|
||||||
__rf: {
|
|
||||||
...node.__rf,
|
|
||||||
...dimensions,
|
...dimensions,
|
||||||
handleBounds,
|
handleBounds,
|
||||||
},
|
},
|
||||||
};
|
} as ElementChange;
|
||||||
|
|
||||||
|
res.push(change);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return res;
|
||||||
});
|
}, initialChanges);
|
||||||
|
|
||||||
return {
|
if (state.onNodesChange) {
|
||||||
...state,
|
requestAnimationFrame(() => state.onNodesChange?.(nodesToChange));
|
||||||
nodes: updatedNodes,
|
}
|
||||||
};
|
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
case constants.UPDATE_NODE_POS: {
|
case constants.UPDATE_NODE_POS: {
|
||||||
const { id, pos } = action.payload;
|
const { id, pos } = action.payload;
|
||||||
@@ -117,13 +119,20 @@ export default function reactFlowReducer(state = initialState, action: ReactFlow
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.onNodesChange) {
|
||||||
|
state.onNodesChange([{ id, change: { position } }]);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
const nextNodes = state.nodes.map((node) => {
|
const nextNodes = state.nodes.map((node) => {
|
||||||
if (node.id === id) {
|
if (node.id === id) {
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
|
position,
|
||||||
|
|
||||||
__rf: {
|
__rf: {
|
||||||
...node.__rf,
|
...node.__rf,
|
||||||
position,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -136,30 +145,28 @@ export default function reactFlowReducer(state = initialState, action: ReactFlow
|
|||||||
case constants.UPDATE_NODE_POS_DIFF: {
|
case constants.UPDATE_NODE_POS_DIFF: {
|
||||||
const { id, diff, isDragging } = action.payload;
|
const { id, diff, isDragging } = action.payload;
|
||||||
|
|
||||||
const nextNodes = state.nodes.map((node) => {
|
if (state.onNodesChange && id && diff) {
|
||||||
if (id === node.id || state.selectedElements?.find((sNode) => sNode.id === node.id)) {
|
const matchingNode = state.nodes.find((n) => n.id === id);
|
||||||
const updatedNode = {
|
|
||||||
...node,
|
|
||||||
__rf: {
|
|
||||||
...node.__rf,
|
|
||||||
isDragging,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (diff) {
|
if (matchingNode) {
|
||||||
updatedNode.__rf.position = {
|
requestAnimationFrame(() =>
|
||||||
x: node.__rf.position.x + diff.x,
|
state.onNodesChange?.([
|
||||||
y: node.__rf.position.y + diff.y,
|
{
|
||||||
};
|
id,
|
||||||
}
|
change: {
|
||||||
|
position: {
|
||||||
return updatedNode;
|
x: matchingNode.position.x + diff.x,
|
||||||
|
y: matchingNode.position.y + diff.y,
|
||||||
|
isDragging,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
return state;
|
||||||
});
|
|
||||||
|
|
||||||
return { ...state, nodes: nextNodes };
|
|
||||||
}
|
}
|
||||||
case constants.SET_USER_SELECTION: {
|
case constants.SET_USER_SELECTION: {
|
||||||
const mousePos = action.payload;
|
const mousePos = action.payload;
|
||||||
@@ -308,9 +315,9 @@ export default function reactFlowReducer(state = initialState, action: ReactFlow
|
|||||||
nodes: state.nodes.map((node) => {
|
nodes: state.nodes.map((node) => {
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
|
position: clampPosition(node.position, nodeExtent),
|
||||||
__rf: {
|
__rf: {
|
||||||
...node.__rf,
|
...node.__rf,
|
||||||
position: clampPosition(node.__rf.position, nodeExtent),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -334,6 +341,8 @@ export default function reactFlowReducer(state = initialState, action: ReactFlow
|
|||||||
case constants.SET_ELEMENTS_SELECTABLE:
|
case constants.SET_ELEMENTS_SELECTABLE:
|
||||||
case constants.SET_MULTI_SELECTION_ACTIVE:
|
case constants.SET_MULTI_SELECTION_ACTIVE:
|
||||||
case constants.SET_CONNECTION_MODE:
|
case constants.SET_CONNECTION_MODE:
|
||||||
|
case constants.SET_ON_NODES_CHANGE:
|
||||||
|
case constants.SET_ON_EDGES_CHANGE:
|
||||||
return { ...state, ...action.payload };
|
return { ...state, ...action.payload };
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
+17
-1
@@ -9,6 +9,12 @@ export type Elements<T = any> = Array<FlowElement<T>>;
|
|||||||
|
|
||||||
export type Transform = [number, number, number];
|
export type Transform = [number, number, number];
|
||||||
|
|
||||||
|
export type ElementChange = {
|
||||||
|
id: string;
|
||||||
|
change?: any;
|
||||||
|
delete?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export enum Position {
|
export enum Position {
|
||||||
Left = 'left',
|
Left = 'left',
|
||||||
Top = 'top',
|
Top = 'top',
|
||||||
@@ -50,6 +56,10 @@ export interface Node<T = any> {
|
|||||||
selectable?: boolean;
|
selectable?: boolean;
|
||||||
connectable?: boolean;
|
connectable?: boolean;
|
||||||
dragHandle?: string;
|
dragHandle?: string;
|
||||||
|
isDragging?: boolean;
|
||||||
|
width?: number | null;
|
||||||
|
height?: number | null;
|
||||||
|
handleBounds?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ArrowHeadType {
|
export enum ArrowHeadType {
|
||||||
@@ -76,6 +86,8 @@ export interface Edge<T = any> {
|
|||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
data?: T;
|
data?: T;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
sourceNode?: Node;
|
||||||
|
targetNode?: Node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BackgroundVariant {
|
export enum BackgroundVariant {
|
||||||
@@ -313,7 +325,7 @@ export type ConnectionLineComponentProps = {
|
|||||||
|
|
||||||
export type ConnectionLineComponent = React.ComponentType<ConnectionLineComponentProps>;
|
export type ConnectionLineComponent = React.ComponentType<ConnectionLineComponentProps>;
|
||||||
|
|
||||||
export type OnConnectFunc = (connection: Connection) => void;
|
export type OnConnectFunc = (connection: Connection, nodes: Node[]) => void;
|
||||||
export type OnConnectStartParams = {
|
export type OnConnectStartParams = {
|
||||||
nodeId: ElementId | null;
|
nodeId: ElementId | null;
|
||||||
handleId: ElementId | null;
|
handleId: ElementId | null;
|
||||||
@@ -398,6 +410,8 @@ export type InitD3ZoomPayload = {
|
|||||||
transform: Transform;
|
transform: Transform;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type OnElementsChange = (nodes: ElementChange[]) => void;
|
||||||
|
|
||||||
export interface ReactFlowState {
|
export interface ReactFlowState {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@@ -406,6 +420,8 @@ export interface ReactFlowState {
|
|||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
selectedElements: Elements | null;
|
selectedElements: Elements | null;
|
||||||
selectedNodesBbox: Rect;
|
selectedNodesBbox: Rect;
|
||||||
|
onNodesChange: OnElementsChange | null;
|
||||||
|
onEdgesChange: OnElementsChange | null;
|
||||||
|
|
||||||
d3Zoom: ZoomBehavior<Element, unknown> | null;
|
d3Zoom: ZoomBehavior<Element, unknown> | null;
|
||||||
d3Selection: D3Selection<Element, unknown, null, undefined> | null;
|
d3Selection: D3Selection<Element, unknown, null, undefined> | null;
|
||||||
|
|||||||
+54
-42
@@ -15,6 +15,7 @@ import {
|
|||||||
FlowExportObject,
|
FlowExportObject,
|
||||||
ReactFlowState,
|
ReactFlowState,
|
||||||
NodeExtent,
|
NodeExtent,
|
||||||
|
ElementChange,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
export const isEdge = (element: Node | Connection | Edge): element is Edge =>
|
export const isEdge = (element: Node | Connection | Edge): element is Edge =>
|
||||||
@@ -68,10 +69,10 @@ const connectionExists = (edge: Edge, elements: Elements) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elements => {
|
export const addEdge = (edgeParams: Edge | Connection, nodes: Node[], edges: Edge[]): Edge[] => {
|
||||||
if (!edgeParams.source || !edgeParams.target) {
|
if (!edgeParams.source || !edgeParams.target) {
|
||||||
console.warn("Can't create edge. An edge needs a source and a target.");
|
console.warn("Can't create edge. An edge needs a source and a target.");
|
||||||
return elements;
|
return edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
let edge: Edge;
|
let edge: Edge;
|
||||||
@@ -84,11 +85,11 @@ export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elem
|
|||||||
} as Edge;
|
} as Edge;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionExists(edge, elements)) {
|
if (connectionExists(edge, nodes)) {
|
||||||
return elements;
|
return edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
return elements.concat(edge);
|
return edges.concat(edge);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: Elements): Elements => {
|
export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: Elements): Elements => {
|
||||||
@@ -147,30 +148,23 @@ export const onLoadProject = (currentStore: Store<ReactFlowState>) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const parseNode = (node: Node, nodeExtent: NodeExtent): Node => {
|
export const parseNode = (node: Node, nodeExtent: NodeExtent): Node => {
|
||||||
return {
|
if (!node.type) {
|
||||||
...node,
|
node.type = 'default';
|
||||||
id: node.id.toString(),
|
}
|
||||||
type: node.type || 'default',
|
|
||||||
__rf: {
|
if (nodeExtent) {
|
||||||
position: clampPosition(node.position, nodeExtent),
|
node.position = clampPosition(node.position, nodeExtent);
|
||||||
width: null,
|
}
|
||||||
height: null,
|
|
||||||
handleBounds: {},
|
return node;
|
||||||
isDragging: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parseEdge = (edge: Edge): Edge => {
|
export const parseEdge = (edge: Edge): Edge => {
|
||||||
return {
|
if (!edge.type) {
|
||||||
...edge,
|
edge.type = 'default';
|
||||||
source: edge.source.toString(),
|
}
|
||||||
target: edge.target.toString(),
|
|
||||||
sourceHandle: edge.sourceHandle ? edge.sourceHandle.toString() : null,
|
return edge;
|
||||||
targetHandle: edge.targetHandle ? edge.targetHandle.toString() : null,
|
|
||||||
id: edge.id.toString(),
|
|
||||||
type: edge.type || 'default',
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBoundsOfBoxes = (box1: Box, box2: Box): Box => ({
|
const getBoundsOfBoxes = (box1: Box, box2: Box): Box => ({
|
||||||
@@ -199,8 +193,8 @@ export const getBoundsofRects = (rect1: Rect, rect2: Rect): Rect =>
|
|||||||
|
|
||||||
export const getRectOfNodes = (nodes: Node[]): Rect => {
|
export const getRectOfNodes = (nodes: Node[]): Rect => {
|
||||||
const box = nodes.reduce(
|
const box = nodes.reduce(
|
||||||
(currBox, { __rf: { position, width, height } = {} }) =>
|
(currBox, { position, width, height }) =>
|
||||||
getBoundsOfBoxes(currBox, rectToBox({ ...position, width, height })),
|
getBoundsOfBoxes(currBox, rectToBox({ ...position, width: width || 0, height: height || 0 })),
|
||||||
{ x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity }
|
{ x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity }
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -227,12 +221,12 @@ export const getNodesInside = (
|
|||||||
height: rect.height / tScale,
|
height: rect.height / tScale,
|
||||||
});
|
});
|
||||||
|
|
||||||
return nodes.filter(({ selectable = true, __rf: { position, width, height, isDragging } }) => {
|
return nodes.filter(({ selectable = true, position, width, height, isDragging }) => {
|
||||||
if (excludeNonSelectableNodes && !selectable) {
|
if (excludeNonSelectableNodes && !selectable) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nBox = rectToBox({ ...position, width, height });
|
const nBox = rectToBox({ ...position, width: width || 0, height: height || 0 });
|
||||||
const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x));
|
const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x));
|
||||||
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y));
|
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y));
|
||||||
const overlappingArea = Math.ceil(xOverlap * yOverlap);
|
const overlappingArea = Math.ceil(xOverlap * yOverlap);
|
||||||
@@ -246,7 +240,7 @@ export const getNodesInside = (
|
|||||||
return overlappingArea > 0;
|
return overlappingArea > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const area = width * height;
|
const area = (width || 0) * (height || 0);
|
||||||
|
|
||||||
return overlappingArea >= area;
|
return overlappingArea >= area;
|
||||||
});
|
});
|
||||||
@@ -259,17 +253,7 @@ export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const parseElements = (nodes: Node[], edges: Edge[]): Elements => {
|
const parseElements = (nodes: Node[], edges: Edge[]): Elements => {
|
||||||
return [
|
return [...nodes.map((n) => ({ ...n })), ...edges.map((e) => ({ ...e }))];
|
||||||
...nodes.map((node) => {
|
|
||||||
const n = { ...node };
|
|
||||||
|
|
||||||
n.position = n.__rf.position;
|
|
||||||
|
|
||||||
delete n.__rf;
|
|
||||||
return n;
|
|
||||||
}),
|
|
||||||
...edges.map((e) => ({ ...e })),
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onLoadGetElements = (currentStore: Store<ReactFlowState>) => {
|
export const onLoadGetElements = (currentStore: Store<ReactFlowState>) => {
|
||||||
@@ -311,3 +295,31 @@ export const getTransformForBounds = (
|
|||||||
|
|
||||||
return [x, y, clampedZoom];
|
return [x, y, clampedZoom];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function applyChanges(changes: ElementChange[], elements: any[]): any[] {
|
||||||
|
const initElements: any[] = [];
|
||||||
|
|
||||||
|
return elements.reduce((res: any[], node: any) => {
|
||||||
|
const hasChange = changes.find((c) => c.id === node.id);
|
||||||
|
|
||||||
|
if (hasChange?.delete) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChange?.change) {
|
||||||
|
res.push({ ...node, ...hasChange.change });
|
||||||
|
} else {
|
||||||
|
res.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}, initElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyNodeChanges(changes: ElementChange[], nodes: Node[]): Node[] {
|
||||||
|
return applyChanges(changes, nodes) as Node[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyEdgeChanges(changes: ElementChange[], edges: Edge[]): Edge[] {
|
||||||
|
return applyChanges(changes, edges) as Edge[];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user