import React, { memo, CSSProperties } from 'react'; import { useStoreState } from '../../store/hooks'; import ConnectionLine from '../../components/ConnectionLine/index'; import { isEdge } from '../../utils/graph'; import MarkerDefinitions from './MarkerDefinitions'; import { getHandlePosition, getHandle, isEdgeVisible } from './utils'; import { Position, Edge, Node, HandleElement, Elements, ConnectionLineType, ConnectionLineComponent, Transform, } from '../../types'; interface EdgeRendererProps { edgeTypes: any; connectionLineType: ConnectionLineType; connectionLineStyle?: CSSProperties; onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void; arrowHeadColor: string; markerEndId?: string; connectionLineComponent?: ConnectionLineComponent; } interface EdgePositions { sourceX: number; sourceY: number; targetX: number; targetY: number; } function getEdgePositions( sourceNode: Node, sourceHandle: HandleElement | unknown, sourcePosition: Position, targetNode: Node, targetHandle: HandleElement | unknown, targetPosition: Position ): EdgePositions { const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle); const sourceX = sourceNode.__rf.position.x + sourceHandlePos.x; const sourceY = sourceNode.__rf.position.y + sourceHandlePos.y; const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle); const targetX = targetNode.__rf.position.x + targetHandlePos.x; const targetY = targetNode.__rf.position.y + targetHandlePos.y; return { sourceX, sourceY, targetX, targetY, }; } type SourceTargetNode = { sourceNode: Node | null; targetNode: Node | null; }; const initialSourceTarget: SourceTargetNode = { sourceNode: null, targetNode: null }; function renderEdge( edge: Edge, props: EdgeRendererProps, nodes: Node[], selectedElements: Elements | null, elementsSelectable: boolean, transform: Transform, width: number, height: number ) { const sourceHandleId = edge.sourceHandle || null; const targetHandleId = edge.targetHandle || null; const { sourceNode, targetNode } = nodes.reduce((res, node) => { if (node.id === edge.source) { res.sourceNode = node; } else if (node.id === edge.target) { res.targetNode = node; } return res; }, initialSourceTarget); if (!sourceNode) { console.warn(`couldn't create edge for source id: ${edge.source}`); return null; } if (!targetNode) { console.warn(`couldn't create edge for target id: ${edge.target}`); return null; } if (!sourceNode.__rf.width || !sourceNode.__rf.height) { return null; } const renderEdge = isEdgeVisible({ sourcePos: sourceNode.__rf.position, targetPos: targetNode.__rf.position, width, height, transform, }); if (!renderEdge) { return null; } const edgeType = edge.type || 'default'; const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default; const sourceHandle = getHandle(sourceNode.__rf.handleBounds.source, sourceHandleId); const targetHandle = getHandle(targetNode.__rf.handleBounds.target, 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}`); return null; } if (!targetHandle) { console.warn(`couldn't create edge for source handle id: ${targetHandleId}`); return null; } const { sourceX, sourceY, targetX, targetY } = getEdgePositions( sourceNode, sourceHandle, sourcePosition, targetNode, targetHandle, targetPosition ); const isSelected = selectedElements?.some((elm) => isEdge(elm) && elm.id === edge.id) || false; return ( ); } const EdgeRenderer = (props: EdgeRendererProps) => { const transform = useStoreState((state) => state.transform); 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); const nodes = useStoreState((state) => state.nodes); if (!width) { return null; } const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = props; const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`; const renderConnectionLine = connectionNodeId && connectionHandleType; return ( {edges.map((edge: Edge) => renderEdge(edge, props, nodes, selectedElements, elementsSelectable, transform, width, height) )} {renderConnectionLine && ( )} ); }; EdgeRenderer.displayName = 'EdgeRenderer'; export default memo(EdgeRenderer);