refactor(state): separate nodes and edges
This commit is contained in:
@@ -4,7 +4,7 @@ import shallow from 'zustand/shallow';
|
||||
import { useStore } from '../../store';
|
||||
import ConnectionLine from '../../components/ConnectionLine/index';
|
||||
import MarkerDefinitions from './MarkerDefinitions';
|
||||
import { getEdgePositions, getHandle } from './utils';
|
||||
import { getEdgePositions, getHandle, getSourceTargetNodes } from './utils';
|
||||
import {
|
||||
Position,
|
||||
Edge,
|
||||
@@ -15,15 +15,17 @@ import {
|
||||
ConnectionMode,
|
||||
OnEdgeUpdateFunc,
|
||||
ReactFlowState,
|
||||
NodeHandleBounds,
|
||||
} from '../../types';
|
||||
|
||||
interface EdgeRendererProps {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
edgeTypes: any;
|
||||
connectionLineType: ConnectionLineType;
|
||||
connectionLineStyle?: CSSProperties;
|
||||
connectionLineComponent?: ConnectionLineComponent;
|
||||
connectionMode?: ConnectionMode;
|
||||
onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void;
|
||||
onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
|
||||
onEdgeDoubleClick?: (event: React.MouseEvent, edge: Edge) => void;
|
||||
arrowHeadColor: string;
|
||||
markerEndId?: string;
|
||||
@@ -42,7 +44,7 @@ interface EdgeWrapperProps {
|
||||
edge: Edge;
|
||||
edgeTypes: any;
|
||||
markerEndId?: string;
|
||||
onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void;
|
||||
onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
|
||||
onEdgeContextMenu?: (event: React.MouseEvent, edge: Edge) => void;
|
||||
onEdgeMouseEnter?: (event: React.MouseEvent, edge: Edge) => void;
|
||||
onEdgeMouseMove?: (event: React.MouseEvent, edge: Edge) => void;
|
||||
@@ -52,10 +54,18 @@ interface EdgeWrapperProps {
|
||||
onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge) => void;
|
||||
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void;
|
||||
onEdgeUpdate?: OnEdgeUpdateFunc;
|
||||
targetNode?: Node;
|
||||
sourceNode?: Node;
|
||||
elementsSelectable: boolean;
|
||||
connectionMode?: ConnectionMode;
|
||||
sourceNodeWidth?: number | null;
|
||||
sourceNodeHeight?: number | null;
|
||||
sourceNodeX?: number;
|
||||
sourceNodeY?: number;
|
||||
sourceNodeHandleBounds?: NodeHandleBounds;
|
||||
targetNodeWidth?: number | null;
|
||||
targetNodeHeight?: number | null;
|
||||
targetNodeX?: number;
|
||||
targetNodeY?: number;
|
||||
targetNodeHandleBounds?: NodeHandleBounds;
|
||||
}
|
||||
|
||||
const Edge = memo(
|
||||
@@ -63,7 +73,7 @@ const Edge = memo(
|
||||
edge,
|
||||
edgeTypes,
|
||||
markerEndId,
|
||||
onElementClick,
|
||||
onEdgeClick,
|
||||
onEdgeContextMenu,
|
||||
onEdgeMouseEnter,
|
||||
onEdgeMouseMove,
|
||||
@@ -73,10 +83,18 @@ const Edge = memo(
|
||||
onEdgeUpdateStart,
|
||||
onEdgeUpdateEnd,
|
||||
onEdgeUpdate,
|
||||
targetNode,
|
||||
sourceNode,
|
||||
elementsSelectable,
|
||||
connectionMode,
|
||||
elementsSelectable,
|
||||
sourceNodeWidth,
|
||||
sourceNodeHeight,
|
||||
sourceNodeX,
|
||||
sourceNodeY,
|
||||
sourceNodeHandleBounds,
|
||||
targetNodeWidth,
|
||||
targetNodeHeight,
|
||||
targetNodeX,
|
||||
targetNodeY,
|
||||
targetNodeHandleBounds,
|
||||
}: EdgeWrapperProps) => {
|
||||
const sourceHandleId = edge.sourceHandle || null;
|
||||
const targetHandleId = edge.targetHandle || null;
|
||||
@@ -88,31 +106,40 @@ const Edge = memo(
|
||||
[edge, onEdgeUpdate]
|
||||
);
|
||||
|
||||
if (!sourceNode) {
|
||||
// source and target node need to be initialized
|
||||
if (!sourceNodeHandleBounds || !targetNodeHandleBounds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
!sourceNodeWidth ||
|
||||
!sourceNodeHeight ||
|
||||
typeof sourceNodeX === 'undefined' ||
|
||||
typeof sourceNodeY === 'undefined'
|
||||
) {
|
||||
console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!targetNode) {
|
||||
if (
|
||||
!targetNodeWidth ||
|
||||
!targetNodeHeight ||
|
||||
typeof targetNodeX === 'undefined' ||
|
||||
typeof targetNodeY === 'undefined'
|
||||
) {
|
||||
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);
|
||||
? targetNodeHandleBounds.target
|
||||
: targetNodeHandleBounds.target || targetNodeHandleBounds.source;
|
||||
const sourceHandle = getHandle(sourceNodeHandleBounds.source!, sourceHandleId);
|
||||
const targetHandle = getHandle(targetNodeHandles!, targetHandleId);
|
||||
const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom;
|
||||
const targetPosition = targetHandle ? targetHandle.position : Position.Top;
|
||||
|
||||
@@ -127,10 +154,10 @@ const Edge = memo(
|
||||
}
|
||||
|
||||
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
|
||||
sourceNode,
|
||||
{ x: sourceNodeX, y: sourceNodeY, width: sourceNodeWidth, height: sourceNodeHeight },
|
||||
sourceHandle,
|
||||
sourcePosition,
|
||||
targetNode,
|
||||
{ x: targetNodeX, y: targetNodeY, width: targetNodeWidth, height: targetNodeHeight },
|
||||
targetHandle,
|
||||
targetPosition
|
||||
);
|
||||
@@ -156,8 +183,8 @@ const Edge = memo(
|
||||
className={edge.className}
|
||||
type={edge.type}
|
||||
data={edge.data}
|
||||
onClick={onElementClick}
|
||||
selected={!!edge.selected}
|
||||
onClick={onEdgeClick}
|
||||
isSelected={!!edge.isSelected}
|
||||
animated={edge.animated}
|
||||
label={edge.label}
|
||||
labelStyle={edge.labelStyle}
|
||||
@@ -197,7 +224,6 @@ const Edge = memo(
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
transform: s.transform,
|
||||
edges: s.edges,
|
||||
connectionNodeId: s.connectionNodeId,
|
||||
connectionHandleId: s.connectionHandleId,
|
||||
connectionHandleType: s.connectionHandleType,
|
||||
@@ -206,12 +232,12 @@ const selector = (s: ReactFlowState) => ({
|
||||
elementsSelectable: s.elementsSelectable,
|
||||
width: s.width,
|
||||
height: s.height,
|
||||
connectionMode: s.connectionMode,
|
||||
});
|
||||
|
||||
const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
const {
|
||||
transform,
|
||||
edges,
|
||||
connectionNodeId,
|
||||
connectionHandleId,
|
||||
connectionHandleType,
|
||||
@@ -220,6 +246,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
elementsSelectable,
|
||||
width,
|
||||
height,
|
||||
connectionMode,
|
||||
} = useStore(selector, shallow);
|
||||
|
||||
if (!width) {
|
||||
@@ -227,33 +254,44 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
|
||||
}
|
||||
|
||||
const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = props;
|
||||
const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`;
|
||||
const renderConnectionLine = connectionNodeId && connectionHandleType;
|
||||
|
||||
return (
|
||||
<svg width={width} height={height} className="react-flow__edges">
|
||||
<MarkerDefinitions color={arrowHeadColor} />
|
||||
<g transform={transformStyle}>
|
||||
{edges.map((edge: Edge) => (
|
||||
<Edge
|
||||
key={edge.id}
|
||||
edge={edge}
|
||||
sourceNode={edge.sourceNode}
|
||||
targetNode={edge.targetNode}
|
||||
elementsSelectable={elementsSelectable}
|
||||
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}
|
||||
/>
|
||||
))}
|
||||
<g transform={`translate(${transform[0]},${transform[1]}) scale(${transform[2]})`}>
|
||||
{props.edges.map((edge: Edge) => {
|
||||
const { sourceNode, targetNode } = getSourceTargetNodes(edge, props.nodes);
|
||||
return (
|
||||
<Edge
|
||||
key={edge.id}
|
||||
edge={edge}
|
||||
sourceNodeWidth={sourceNode?.width}
|
||||
sourceNodeHeight={sourceNode?.height}
|
||||
sourceNodeX={sourceNode?.position.x}
|
||||
sourceNodeY={sourceNode?.position.y}
|
||||
sourceNodeHandleBounds={sourceNode?.handleBounds}
|
||||
targetNodeWidth={targetNode?.width}
|
||||
targetNodeHeight={targetNode?.height}
|
||||
targetNodeX={targetNode?.position.x}
|
||||
targetNodeY={targetNode?.position.y}
|
||||
targetNodeHandleBounds={targetNode?.handleBounds}
|
||||
elementsSelectable={elementsSelectable}
|
||||
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}
|
||||
connectionMode={connectionMode}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{renderConnectionLine && (
|
||||
<ConnectionLine
|
||||
connectionNodeId={connectionNodeId!}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
HandleElement,
|
||||
Transform,
|
||||
Edge,
|
||||
Rect,
|
||||
} from '../../types';
|
||||
|
||||
export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
|
||||
@@ -39,11 +40,11 @@ export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
|
||||
};
|
||||
}
|
||||
|
||||
export function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition {
|
||||
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;
|
||||
export function getHandlePosition(position: Position, nodeRect: Rect, handle: any | null = null): XYPosition {
|
||||
const x = (handle?.x || 0) + nodeRect.x;
|
||||
const y = (handle?.y || 0) + nodeRect.y;
|
||||
const width = handle?.width || nodeRect.width;
|
||||
const height = handle?.height || nodeRect.height;
|
||||
|
||||
switch (position) {
|
||||
case Position.Top:
|
||||
@@ -94,15 +95,15 @@ interface EdgePositions {
|
||||
}
|
||||
|
||||
export const getEdgePositions = (
|
||||
sourceNode: Node,
|
||||
sourceNodeRect: Rect,
|
||||
sourceHandle: HandleElement | unknown,
|
||||
sourcePosition: Position,
|
||||
targetNode: Node,
|
||||
targetNodeRect: Rect,
|
||||
targetHandle: HandleElement | unknown,
|
||||
targetPosition: Position
|
||||
): EdgePositions => {
|
||||
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle);
|
||||
const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle);
|
||||
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
|
||||
const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
|
||||
|
||||
return {
|
||||
sourceX: sourceHandlePos.x,
|
||||
@@ -169,16 +170,3 @@ export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNod
|
||||
{ sourceNode: null, targetNode: null } as SourceTargetNode
|
||||
);
|
||||
};
|
||||
|
||||
export const extendEdgeWithSourceAndTarget = (edge: Edge, nodes: Node[]): Edge => {
|
||||
const { sourceNode, targetNode } = getSourceTargetNodes(edge, nodes);
|
||||
|
||||
if (sourceNode) {
|
||||
edge.sourceNode = sourceNode;
|
||||
}
|
||||
if (targetNode) {
|
||||
edge.targetNode = targetNode;
|
||||
}
|
||||
|
||||
return edge;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,8 @@ interface FlowRendererProps
|
||||
| 'arrowHeadColor'
|
||||
| 'onlyRenderVisibleElements'
|
||||
| 'selectNodesOnDrag'
|
||||
| 'nodes'
|
||||
| 'edges'
|
||||
> {
|
||||
children: ReactNode;
|
||||
}
|
||||
@@ -54,7 +56,6 @@ const FlowRenderer = ({
|
||||
paneMoveable,
|
||||
defaultPosition,
|
||||
defaultZoom,
|
||||
translateExtent,
|
||||
preventScrolling,
|
||||
onSelectionDragStart,
|
||||
onSelectionDrag,
|
||||
@@ -106,7 +107,6 @@ const FlowRenderer = ({
|
||||
paneMoveable={paneMoveable}
|
||||
defaultPosition={defaultPosition}
|
||||
defaultZoom={defaultZoom}
|
||||
translateExtent={translateExtent}
|
||||
zoomActivationKeyCode={zoomActivationKeyCode}
|
||||
preventScrolling={preventScrolling}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useRef, memo } from 'react';
|
||||
import shallow from 'zustand/shallow';
|
||||
|
||||
import { useStore, useStoreApi } from '../../store';
|
||||
import { useStoreApi } from '../../store';
|
||||
import FlowRenderer from '../FlowRenderer';
|
||||
import NodeRenderer from '../NodeRenderer';
|
||||
import EdgeRenderer from '../EdgeRenderer';
|
||||
@@ -10,17 +9,15 @@ import useZoomPanHelper from '../../hooks/useZoomPanHelper';
|
||||
|
||||
import { ReactFlowProps } from '../ReactFlow';
|
||||
|
||||
import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode, ReactFlowState } from '../../types';
|
||||
import { NodeTypesType, EdgeTypesType, ConnectionLineType, KeyCode } from '../../types';
|
||||
|
||||
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange' | 'nodes' | 'edges'> {
|
||||
export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange'> {
|
||||
nodeTypes: NodeTypesType;
|
||||
edgeTypes: EdgeTypesType;
|
||||
selectionKeyCode: KeyCode;
|
||||
deleteKeyCode: KeyCode;
|
||||
multiSelectionKeyCode: KeyCode;
|
||||
connectionLineType: ConnectionLineType;
|
||||
snapToGrid: boolean;
|
||||
snapGrid: [number, number];
|
||||
onlyRenderVisibleElements: boolean;
|
||||
defaultZoom: number;
|
||||
defaultPosition: [number, number];
|
||||
@@ -28,33 +25,17 @@ export interface GraphViewProps extends Omit<ReactFlowProps, 'onSelectionChange'
|
||||
selectNodesOnDrag: boolean;
|
||||
}
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
setOnConnect: s.setOnConnect,
|
||||
setOnConnectStart: s.setOnConnectStart,
|
||||
setOnConnectStop: s.setOnConnectStop,
|
||||
setOnConnectEnd: s.setOnConnectEnd,
|
||||
setSnapGrid: s.setSnapGrid,
|
||||
setSnapToGrid: s.setSnapToGrid,
|
||||
setNodesDraggable: s.setNodesDraggable,
|
||||
setNodesConnectable: s.setNodesConnectable,
|
||||
setElementsSelectable: s.setElementsSelectable,
|
||||
setMinZoom: s.setMinZoom,
|
||||
setMaxZoom: s.setMaxZoom,
|
||||
setTranslateExtent: s.setTranslateExtent,
|
||||
setNodeExtent: s.setNodeExtent,
|
||||
setConnectionMode: s.setConnectionMode,
|
||||
setOnNodesChange: s.setOnNodesChange,
|
||||
setOnEdgesChange: s.setOnEdgesChange,
|
||||
});
|
||||
|
||||
const GraphView = ({
|
||||
nodes,
|
||||
edges,
|
||||
nodeTypes,
|
||||
edgeTypes,
|
||||
onMove,
|
||||
onMoveStart,
|
||||
onMoveEnd,
|
||||
onLoad,
|
||||
onElementClick,
|
||||
onNodeClick,
|
||||
onEdgeClick,
|
||||
onNodeDoubleClick,
|
||||
onEdgeDoubleClick,
|
||||
onNodeMouseEnter,
|
||||
@@ -68,7 +49,6 @@ const GraphView = ({
|
||||
onSelectionDrag,
|
||||
onSelectionDragStop,
|
||||
onSelectionContextMenu,
|
||||
connectionMode,
|
||||
connectionLineType,
|
||||
connectionLineStyle,
|
||||
connectionLineComponent,
|
||||
@@ -76,24 +56,12 @@ const GraphView = ({
|
||||
multiSelectionKeyCode,
|
||||
zoomActivationKeyCode,
|
||||
deleteKeyCode,
|
||||
onConnect,
|
||||
onConnectStart,
|
||||
onConnectStop,
|
||||
onConnectEnd,
|
||||
snapToGrid,
|
||||
snapGrid,
|
||||
onlyRenderVisibleElements,
|
||||
nodesDraggable,
|
||||
nodesConnectable,
|
||||
elementsSelectable,
|
||||
selectNodesOnDrag,
|
||||
minZoom,
|
||||
maxZoom,
|
||||
defaultZoom,
|
||||
defaultPosition,
|
||||
translateExtent,
|
||||
preventScrolling,
|
||||
nodeExtent,
|
||||
arrowHeadColor,
|
||||
markerEndId,
|
||||
zoomOnScroll,
|
||||
@@ -114,31 +82,9 @@ const GraphView = ({
|
||||
edgeUpdaterRadius,
|
||||
onEdgeUpdateStart,
|
||||
onEdgeUpdateEnd,
|
||||
onNodesChange,
|
||||
onEdgesChange,
|
||||
}: GraphViewProps) => {
|
||||
const isInitialized = useRef<boolean>(false);
|
||||
const store = useStoreApi();
|
||||
|
||||
const {
|
||||
setOnConnect,
|
||||
setOnConnectStart,
|
||||
setOnConnectStop,
|
||||
setOnConnectEnd,
|
||||
setSnapGrid,
|
||||
setSnapToGrid,
|
||||
setNodesDraggable,
|
||||
setNodesConnectable,
|
||||
setElementsSelectable,
|
||||
setMinZoom,
|
||||
setMaxZoom,
|
||||
setTranslateExtent,
|
||||
setNodeExtent,
|
||||
setConnectionMode,
|
||||
setOnNodesChange,
|
||||
setOnEdgesChange,
|
||||
} = useStore(selector, shallow);
|
||||
|
||||
const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -160,102 +106,6 @@ const GraphView = ({
|
||||
}
|
||||
}, [onLoad, zoomIn, zoomOut, zoomTo, transform, fitView, initialized]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onConnect) {
|
||||
setOnConnect(onConnect);
|
||||
}
|
||||
}, [onConnect]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onConnectStart) {
|
||||
setOnConnectStart(onConnectStart);
|
||||
}
|
||||
}, [onConnectStart]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onConnectStop) {
|
||||
setOnConnectStop(onConnectStop);
|
||||
}
|
||||
}, [onConnectStop]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onConnectEnd) {
|
||||
setOnConnectEnd(onConnectEnd);
|
||||
}
|
||||
}, [onConnectEnd]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof snapToGrid !== 'undefined') {
|
||||
setSnapToGrid(snapToGrid);
|
||||
}
|
||||
}, [snapToGrid]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof snapGrid !== 'undefined') {
|
||||
setSnapGrid(snapGrid);
|
||||
}
|
||||
}, [snapGrid]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof nodesDraggable !== 'undefined') {
|
||||
setNodesDraggable(nodesDraggable);
|
||||
}
|
||||
}, [nodesDraggable]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof nodesConnectable !== 'undefined') {
|
||||
setNodesConnectable(nodesConnectable);
|
||||
}
|
||||
}, [nodesConnectable]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof elementsSelectable !== 'undefined') {
|
||||
setElementsSelectable(elementsSelectable);
|
||||
}
|
||||
}, [elementsSelectable]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof minZoom !== 'undefined') {
|
||||
setMinZoom(minZoom);
|
||||
}
|
||||
}, [minZoom]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof maxZoom !== 'undefined') {
|
||||
setMaxZoom(maxZoom);
|
||||
}
|
||||
}, [maxZoom]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof translateExtent !== 'undefined') {
|
||||
setTranslateExtent(translateExtent);
|
||||
}
|
||||
}, [translateExtent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof nodeExtent !== 'undefined') {
|
||||
setNodeExtent(nodeExtent);
|
||||
}
|
||||
}, [nodeExtent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof connectionMode !== 'undefined') {
|
||||
setConnectionMode(connectionMode);
|
||||
}
|
||||
}, [connectionMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof onNodesChange !== 'undefined') {
|
||||
setOnNodesChange(onNodesChange);
|
||||
}
|
||||
}, [onNodesChange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof onEdgesChange !== 'undefined') {
|
||||
setOnEdgesChange(onEdgesChange);
|
||||
}
|
||||
}, [onEdgesChange]);
|
||||
|
||||
return (
|
||||
<FlowRenderer
|
||||
onPaneClick={onPaneClick}
|
||||
@@ -278,7 +128,6 @@ const GraphView = ({
|
||||
paneMoveable={paneMoveable}
|
||||
defaultPosition={defaultPosition}
|
||||
defaultZoom={defaultZoom}
|
||||
translateExtent={translateExtent}
|
||||
onSelectionDragStart={onSelectionDragStart}
|
||||
onSelectionDrag={onSelectionDrag}
|
||||
onSelectionDragStop={onSelectionDragStop}
|
||||
@@ -287,7 +136,7 @@ const GraphView = ({
|
||||
>
|
||||
<NodeRenderer
|
||||
nodeTypes={nodeTypes}
|
||||
onElementClick={onElementClick}
|
||||
onNodeClick={onNodeClick}
|
||||
onNodeDoubleClick={onNodeDoubleClick}
|
||||
onNodeMouseEnter={onNodeMouseEnter}
|
||||
onNodeMouseMove={onNodeMouseMove}
|
||||
@@ -297,18 +146,16 @@ const GraphView = ({
|
||||
onNodeDrag={onNodeDrag}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
selectNodesOnDrag={selectNodesOnDrag}
|
||||
snapToGrid={snapToGrid}
|
||||
snapGrid={snapGrid}
|
||||
onlyRenderVisibleElements={onlyRenderVisibleElements}
|
||||
nodes={nodes}
|
||||
/>
|
||||
<EdgeRenderer
|
||||
edgeTypes={edgeTypes}
|
||||
onElementClick={onElementClick}
|
||||
onEdgeClick={onEdgeClick}
|
||||
onEdgeDoubleClick={onEdgeDoubleClick}
|
||||
connectionLineType={connectionLineType}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
connectionLineComponent={connectionLineComponent}
|
||||
connectionMode={connectionMode}
|
||||
arrowHeadColor={arrowHeadColor}
|
||||
markerEndId={markerEndId}
|
||||
onEdgeUpdate={onEdgeUpdate}
|
||||
@@ -320,6 +167,8 @@ const GraphView = ({
|
||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||
edgeUpdaterRadius={edgeUpdaterRadius}
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
/>
|
||||
</FlowRenderer>
|
||||
);
|
||||
|
||||
@@ -2,11 +2,11 @@ import React, { memo, useMemo, ComponentType, MouseEvent } from 'react';
|
||||
import shallow from 'zustand/shallow';
|
||||
|
||||
import { useStore } from '../../store';
|
||||
import { Node, NodeTypesType, ReactFlowState, Edge, WrapNodeProps } from '../../types';
|
||||
import { Node, NodeTypesType, ReactFlowState, WrapNodeProps } from '../../types';
|
||||
interface NodeRendererProps {
|
||||
nodeTypes: NodeTypesType;
|
||||
selectNodesOnDrag: boolean;
|
||||
onElementClick?: (event: MouseEvent, element: Node | Edge) => void;
|
||||
onNodeClick?: (event: MouseEvent, element: Node) => void;
|
||||
onNodeDoubleClick?: (event: MouseEvent, element: Node) => void;
|
||||
onNodeMouseEnter?: (event: MouseEvent, node: Node) => void;
|
||||
onNodeMouseMove?: (event: MouseEvent, node: Node) => void;
|
||||
@@ -15,9 +15,8 @@ interface NodeRendererProps {
|
||||
onNodeDragStart?: (event: MouseEvent, node: Node) => void;
|
||||
onNodeDrag?: (event: MouseEvent, node: Node) => void;
|
||||
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
|
||||
snapToGrid: boolean;
|
||||
snapGrid: [number, number];
|
||||
onlyRenderVisibleElements: boolean;
|
||||
nodes: Node[];
|
||||
}
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
@@ -25,15 +24,21 @@ const selector = (s: ReactFlowState) => ({
|
||||
nodesDraggable: s.nodesDraggable,
|
||||
nodesConnectable: s.nodesConnectable,
|
||||
elementsSelectable: s.elementsSelectable,
|
||||
nodes: s.nodes,
|
||||
updateNodeDimensions: s.updateNodeDimensions,
|
||||
snapGrid: s.snapGrid,
|
||||
snapToGrid: s.snapToGrid,
|
||||
});
|
||||
|
||||
const NodeRenderer = (props: NodeRendererProps) => {
|
||||
const { transform, nodesDraggable, nodesConnectable, elementsSelectable, nodes, updateNodeDimensions } = useStore(
|
||||
selector,
|
||||
shallow
|
||||
);
|
||||
const {
|
||||
transform,
|
||||
nodesDraggable,
|
||||
nodesConnectable,
|
||||
elementsSelectable,
|
||||
updateNodeDimensions,
|
||||
snapGrid,
|
||||
snapToGrid,
|
||||
} = useStore(selector, shallow);
|
||||
|
||||
// const visibleNodes = props.onlyRenderVisibleElements
|
||||
// ? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true)
|
||||
@@ -63,7 +68,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
||||
|
||||
return (
|
||||
<div className="react-flow__nodes" style={transformStyle}>
|
||||
{nodes.map((node) => {
|
||||
{props.nodes.map((node) => {
|
||||
const nodeType = node.type || 'default';
|
||||
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>;
|
||||
|
||||
@@ -74,6 +79,11 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
||||
const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined'));
|
||||
const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined'));
|
||||
const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined'));
|
||||
const isInitialized =
|
||||
node.width !== null &&
|
||||
node.height !== null &&
|
||||
typeof node.width !== 'undefined' &&
|
||||
typeof node.height !== 'undefined';
|
||||
|
||||
return (
|
||||
<NodeComponent
|
||||
@@ -89,11 +99,11 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
||||
xPos={node.position.x}
|
||||
yPos={node.position.y}
|
||||
isDragging={node.isDragging}
|
||||
isInitialized={node.width !== null && node.height !== null}
|
||||
snapGrid={props.snapGrid}
|
||||
snapToGrid={props.snapToGrid}
|
||||
isInitialized={isInitialized}
|
||||
snapGrid={snapGrid}
|
||||
snapToGrid={snapToGrid}
|
||||
selectNodesOnDrag={props.selectNodesOnDrag}
|
||||
onClick={props.onElementClick}
|
||||
onClick={props.onNodeClick}
|
||||
onMouseEnter={props.onNodeMouseEnter}
|
||||
onMouseMove={props.onNodeMouseMove}
|
||||
onMouseLeave={props.onNodeMouseLeave}
|
||||
@@ -103,7 +113,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
|
||||
onNodeDrag={props.onNodeDrag}
|
||||
onNodeDragStop={props.onNodeDragStop}
|
||||
scale={transform[2]}
|
||||
selected={!!node.selected}
|
||||
isSelected={!!node.isSelected}
|
||||
isDraggable={isDraggable}
|
||||
isSelectable={isSelectable}
|
||||
isConnectable={isConnectable}
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { Provider, createStore } from '../../store';
|
||||
// import { ReactFlowState } from '../../types';
|
||||
|
||||
// const reactFlowVersionSelector = (s: ReactFlowState) => s.reactFlowVersion;
|
||||
import { Provider, createStore, useStoreApi } from '../../store';
|
||||
|
||||
const Wrapper: FC = ({ children }) => {
|
||||
// let isWrapped = useRef<boolean>(true);
|
||||
let isWrapped = true;
|
||||
|
||||
// try {
|
||||
// useStoreApi();
|
||||
// } catch {
|
||||
// isWrapped.current = false;
|
||||
// }
|
||||
try {
|
||||
useStoreApi();
|
||||
} catch (e) {
|
||||
isWrapped = false;
|
||||
}
|
||||
|
||||
// if (isWrapped) {
|
||||
// // we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
|
||||
// // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
|
||||
// return <>{children}</>;
|
||||
// }
|
||||
if (isWrapped) {
|
||||
// we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return <Provider createStore={createStore}>{children}</Provider>;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import React, {
|
||||
import cc from 'classcat';
|
||||
|
||||
import GraphView from '../GraphView';
|
||||
import ElementUpdater from '../../components/ElementUpdater';
|
||||
import StoreUpdater from '../../components/StoreUpdater';
|
||||
import DefaultNode from '../../components/Nodes/DefaultNode';
|
||||
import InputNode from '../../components/Nodes/InputNode';
|
||||
import OutputNode from '../../components/Nodes/OutputNode';
|
||||
@@ -38,7 +38,8 @@ import {
|
||||
PanOnScrollMode,
|
||||
OnEdgeUpdateFunc,
|
||||
NodeExtent,
|
||||
ElementChange,
|
||||
NodeChange,
|
||||
EdgeChange,
|
||||
} from '../../types';
|
||||
|
||||
import '../../style.css';
|
||||
@@ -60,9 +61,10 @@ const defaultEdgeTypes = {
|
||||
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
onNodesChange?: (nodeChanges: ElementChange[]) => void;
|
||||
onEdgesChange?: (edgeChanges: ElementChange[]) => void;
|
||||
onElementClick?: (event: ReactMouseEvent, element: Node | Edge) => void;
|
||||
onNodesChange?: (nodeChanges: NodeChange[]) => void;
|
||||
onEdgesChange?: (edgeChanges: EdgeChange[]) => void;
|
||||
onNodeClick?: (event: React.MouseEvent, node: Node) => void;
|
||||
onEdgeClick?: (event: React.MouseEvent, node: Edge) => void;
|
||||
onNodeDoubleClick?: (event: ReactMouseEvent, node: Node) => void;
|
||||
onNodeMouseEnter?: (event: ReactMouseEvent, node: Node) => void;
|
||||
onNodeMouseMove?: (event: ReactMouseEvent, node: Node) => void;
|
||||
@@ -129,8 +131,6 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
||||
onEdgeUpdateStart?: (event: ReactMouseEvent, edge: Edge) => void;
|
||||
onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge) => void;
|
||||
edgeUpdaterRadius?: number;
|
||||
nodeTypesId?: string;
|
||||
edgeTypesId?: string;
|
||||
}
|
||||
|
||||
export type ReactFlowRefType = HTMLDivElement;
|
||||
@@ -146,7 +146,8 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
className,
|
||||
nodeTypes = defaultNodeTypes,
|
||||
edgeTypes = defaultEdgeTypes,
|
||||
onElementClick,
|
||||
onNodeClick,
|
||||
onEdgeClick,
|
||||
onLoad,
|
||||
onMove,
|
||||
onMoveStart,
|
||||
@@ -212,27 +213,28 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
onEdgeUpdateStart,
|
||||
onEdgeUpdateEnd,
|
||||
edgeUpdaterRadius = 10,
|
||||
nodeTypesId = '1',
|
||||
edgeTypesId = '1',
|
||||
onNodesChange,
|
||||
onEdgesChange,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), [nodeTypesId]);
|
||||
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), [edgeTypesId]);
|
||||
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), [nodeTypes]);
|
||||
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), [edgeTypes]);
|
||||
const reactFlowClasses = cc(['react-flow', className]);
|
||||
|
||||
return (
|
||||
<div {...rest} ref={ref} className={reactFlowClasses}>
|
||||
<Wrapper>
|
||||
<GraphView
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onLoad={onLoad}
|
||||
onMove={onMove}
|
||||
onMoveStart={onMoveStart}
|
||||
onMoveEnd={onMoveEnd}
|
||||
onElementClick={onElementClick}
|
||||
onNodeClick={onNodeClick}
|
||||
onEdgeClick={onEdgeClick}
|
||||
onNodeMouseEnter={onNodeMouseEnter}
|
||||
onNodeMouseMove={onNodeMouseMove}
|
||||
onNodeMouseLeave={onNodeMouseLeave}
|
||||
@@ -243,7 +245,6 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
nodeTypes={nodeTypesParsed}
|
||||
edgeTypes={edgeTypesParsed}
|
||||
connectionMode={connectionMode}
|
||||
connectionLineType={connectionLineType}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
connectionLineComponent={connectionLineComponent}
|
||||
@@ -251,24 +252,11 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
deleteKeyCode={deleteKeyCode}
|
||||
multiSelectionKeyCode={multiSelectionKeyCode}
|
||||
zoomActivationKeyCode={zoomActivationKeyCode}
|
||||
onConnect={onConnect}
|
||||
onConnectStart={onConnectStart}
|
||||
onConnectStop={onConnectStop}
|
||||
onConnectEnd={onConnectEnd}
|
||||
snapToGrid={snapToGrid}
|
||||
snapGrid={snapGrid}
|
||||
onlyRenderVisibleElements={onlyRenderVisibleElements}
|
||||
nodesDraggable={nodesDraggable}
|
||||
nodesConnectable={nodesConnectable}
|
||||
elementsSelectable={elementsSelectable}
|
||||
selectNodesOnDrag={selectNodesOnDrag}
|
||||
minZoom={minZoom}
|
||||
maxZoom={maxZoom}
|
||||
defaultZoom={defaultZoom}
|
||||
defaultPosition={defaultPosition}
|
||||
translateExtent={translateExtent}
|
||||
preventScrolling={preventScrolling}
|
||||
nodeExtent={nodeExtent}
|
||||
arrowHeadColor={arrowHeadColor}
|
||||
markerEndId={markerEndId}
|
||||
zoomOnScroll={zoomOnScroll}
|
||||
@@ -294,10 +282,28 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||
edgeUpdaterRadius={edgeUpdaterRadius}
|
||||
/>
|
||||
<StoreUpdater
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onConnect={onConnect}
|
||||
onConnectStart={onConnectStart}
|
||||
onConnectStop={onConnectStop}
|
||||
onConnectEnd={onConnectEnd}
|
||||
nodesDraggable={nodesDraggable}
|
||||
nodesConnectable={nodesConnectable}
|
||||
elementsSelectable={elementsSelectable}
|
||||
minZoom={minZoom}
|
||||
maxZoom={maxZoom}
|
||||
nodeExtent={nodeExtent}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
snapToGrid={snapToGrid}
|
||||
snapGrid={snapGrid}
|
||||
connectionMode={connectionMode}
|
||||
translateExtent={translateExtent}
|
||||
/>
|
||||
<ElementUpdater nodes={nodes} edges={edges} />
|
||||
|
||||
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
|
||||
{children}
|
||||
</Wrapper>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { clamp } from '../../utils';
|
||||
import useKeyPress from '../../hooks/useKeyPress';
|
||||
import useResizeHandler from '../../hooks/useResizeHandler';
|
||||
import { useStore, useStoreApi } from '../../store';
|
||||
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode, ReactFlowState } from '../../types';
|
||||
import { FlowTransform, PanOnScrollMode, KeyCode, ReactFlowState } from '../../types';
|
||||
|
||||
interface ZoomPaneProps {
|
||||
selectionKeyPressed: boolean;
|
||||
@@ -21,7 +21,6 @@ interface ZoomPaneProps {
|
||||
paneMoveable?: boolean;
|
||||
defaultPosition?: [number, number];
|
||||
defaultZoom?: number;
|
||||
translateExtent?: TranslateExtent;
|
||||
onMove?: (flowTransform?: FlowTransform) => void;
|
||||
onMoveStart?: (flowTransform?: FlowTransform) => void;
|
||||
onMoveEnd?: (flowTransform?: FlowTransform) => void;
|
||||
@@ -67,7 +66,6 @@ const ZoomPane = ({
|
||||
paneMoveable = true,
|
||||
defaultPosition = [0, 0],
|
||||
defaultZoom = 1,
|
||||
translateExtent,
|
||||
zoomActivationKeyCode,
|
||||
preventScrolling = true,
|
||||
children,
|
||||
@@ -82,14 +80,13 @@ const ZoomPane = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (zoomPane.current) {
|
||||
const state = store.getState();
|
||||
const currentTranslateExtent = typeof translateExtent !== 'undefined' ? translateExtent : state.translateExtent;
|
||||
const d3ZoomInstance = zoom().scaleExtent([state.minZoom, state.maxZoom]).translateExtent(currentTranslateExtent);
|
||||
const { minZoom, maxZoom, translateExtent } = store.getState();
|
||||
const d3ZoomInstance = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent);
|
||||
const selection = select(zoomPane.current as Element).call(d3ZoomInstance);
|
||||
|
||||
const clampedX = clamp(defaultPosition[0], currentTranslateExtent[0][0], currentTranslateExtent[1][0]);
|
||||
const clampedY = clamp(defaultPosition[1], currentTranslateExtent[0][1], currentTranslateExtent[1][1]);
|
||||
const clampedZoom = clamp(defaultZoom, state.minZoom, state.maxZoom);
|
||||
const clampedX = clamp(defaultPosition[0], translateExtent[0][0], translateExtent[1][0]);
|
||||
const clampedY = clamp(defaultPosition[1], translateExtent[0][1], translateExtent[1][1]);
|
||||
const clampedZoom = clamp(defaultZoom, minZoom, maxZoom);
|
||||
const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom);
|
||||
|
||||
d3ZoomInstance.transform(selection, updatedTransform);
|
||||
|
||||
Reference in New Issue
Block a user