import React, { memo, SVGAttributes } from 'react'; import { useStoreState } from '../../store/hooks'; import ConnectionLine from '../../components/ConnectionLine/index'; import { isEdge } from '../../utils/graph'; import { XYPosition, Position, Edge, Node, ElementId, HandleElement, Elements } from '../../types'; interface EdgeRendererProps { width: number; height: number; edgeTypes: any; connectionLineStyle?: SVGAttributes<{}>; connectionLineType?: string; onElementClick?: () => void; } interface EdgePositions { sourceX: number; sourceY: number; targetX: number; targetY: number; } function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition { if (!handle) { switch (position) { case Position.Top: return { x: node.__rg.width / 2, y: 0, }; case Position.Right: return { x: node.__rg.width, y: node.__rg.height / 2, }; case Position.Bottom: return { x: node.__rg.width / 2, y: node.__rg.height, }; case Position.Left: return { x: 0, y: node.__rg.height / 2, }; } } switch (position) { case Position.Top: return { x: handle.x + handle.width / 2, y: handle.y, }; case Position.Right: return { x: handle.x + handle.width, y: handle.y + handle.height / 2, }; case Position.Bottom: return { x: handle.x + handle.width / 2, y: handle.y + handle.height, }; case Position.Left: return { x: handle.x, y: handle.y + handle.height / 2, }; } } function getHandle(bounds: HandleElement[], handleId: ElementId | null): HandleElement | null | undefined { let handle = null; if (!bounds) { return null; } // there is no handleId when there are no multiple handles/ handles with ids // so we just pick the first one if (bounds.length === 1 || !handleId) { handle = bounds[0]; } else if (handleId) { handle = bounds.find(d => d.id === handleId); } return handle; } 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.__rg.position.x + sourceHandlePos.x; const sourceY = sourceNode.__rg.position.y + sourceHandlePos.y; const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle); const targetX = targetNode.__rg.position.x + targetHandlePos.x; const targetY = targetNode.__rg.position.y + targetHandlePos.y; return { sourceX, sourceY, targetX, targetY, }; } function renderEdge(edge: Edge, props: EdgeRendererProps, nodes: Node[], selectedElements: Elements) { const [sourceId, sourceHandleId] = edge.source.split('__'); const [targetId, targetHandleId] = edge.target.split('__'); const sourceNode = nodes.find(n => n.id === sourceId); const targetNode = nodes.find(n => n.id === targetId); if (!sourceNode) { throw new Error(`couldn't create edge for source id: ${sourceId}`); } if (!targetNode) { throw new Error(`couldn't create edge for target id: ${targetId}`); } if (!sourceNode.__rg.width || !sourceNode.__rg.height) { return null; } const edgeType = edge.type || 'default'; const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default; const sourceHandle = getHandle(sourceNode.__rg.handleBounds.source, sourceHandleId); const targetHandle = getHandle(targetNode.__rg.handleBounds.target, targetHandleId); const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom; const targetPosition = targetHandle ? targetHandle.position : Position.Top; const { sourceX, sourceY, targetX, targetY } = getEdgePositions( sourceNode, sourceHandle, sourcePosition, targetNode, targetHandle, targetPosition ); const isSelected = (selectedElements as Edge[]).some( elm => isEdge(elm) && elm.source === sourceId && elm.target === targetId ); return ( ); } const EdgeRenderer = memo((props: EdgeRendererProps) => { const { transform, edges, nodes, connectionSourceId, connectionPosition: { x, y }, selectedElements, } = useStoreState(s => s); const { width, height, connectionLineStyle, connectionLineType } = props; if (!width) { return null; } const [tx, ty, tScale] = transform; const transformStyle = `translate(${tx},${ty}) scale(${tScale})`; return ( {edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements))} {connectionSourceId && ( )} ); }); EdgeRenderer.displayName = 'EdgeRenderer'; export default EdgeRenderer;