import React, { memo, CSSProperties, useCallback } from 'react'; 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 { Position, Edge, Node, Elements, Connection, ConnectionLineType, ConnectionLineComponent, ConnectionMode, Transform, OnEdgeUpdateFunc, HandleType, } from '../../types'; interface EdgeRendererProps { edgeTypes: any; connectionLineType: ConnectionLineType; connectionLineStyle?: CSSProperties; connectionLineComponent?: ConnectionLineComponent; connectionMode?: ConnectionMode; onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void; onEdgeDoubleClick?: (event: React.MouseEvent, edge: Edge) => void; arrowHeadColor: string; markerEndId?: string; onlyRenderVisibleElements: boolean; onEdgeUpdate?: OnEdgeUpdateFunc; 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; onEdgeUpdateStart?: (event: React.MouseEvent, edge: Edge, handleType: HandleType) => void; onEdgeUpdateEnd?: (event: MouseEvent, edge: Edge, handleType: HandleType) => void; edgeUpdaterRadius?: number; } interface EdgeWrapperProps { edge: Edge; props: EdgeRendererProps; nodes: 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, 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 ( ); }; 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); const connectionHandleType = useStoreState((state) => state.connectionHandleType); const connectionPosition = useStoreState((state) => state.connectionPosition); const selectedElements = useStoreState((state) => state.selectedElements); const nodesConnectable = useStoreState((state) => state.nodesConnectable); const elementsSelectable = useStoreState((state) => state.elementsSelectable); const width = useStoreState((state) => state.width); const height = useStoreState((state) => state.height); if (!width) { return null; } const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent, onlyRenderVisibleElements, } = props; const transformStyle = `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})`; const renderConnectionLine = connectionNodeId && connectionHandleType; return ( {edges.map((edge: Edge) => ( ))} {renderConnectionLine && ( )} ); }; EdgeRenderer.displayName = 'EdgeRenderer'; export default memo(EdgeRenderer);