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