import { Coordinates, useGraph } from 'contexts/GraphContext' import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react' import { getAnchorsPosition, computeEdgePath, getEndpointTopOffset, getSourceEndpointId, } from 'services/graph' import { Edge as EdgeProps } from 'models' import { Portal, useDisclosure } from '@chakra-ui/react' import { useTypebot } from 'contexts/TypebotContext' import { EdgeMenu } from './EdgeMenu' import { colors } from 'libs/theme' export type AnchorsPositionProps = { sourcePosition: Coordinates targetPosition: Coordinates sourceType: 'right' | 'left' totalSegments: number } export const Edge = ({ edge }: { edge: EdgeProps }) => { const { deleteEdge } = useTypebot() const { previewingEdge, sourceEndpoints, targetEndpoints, blocksCoordinates, graphPosition, isReadOnly, setPreviewingEdge, } = useGraph() const [isMouseOver, setIsMouseOver] = useState(false) const { isOpen, onOpen, onClose } = useDisclosure() const [edgeMenuPosition, setEdgeMenuPosition] = useState({ x: 0, y: 0 }) const [refreshEdge, setRefreshEdge] = useState(false) const isPreviewing = isMouseOver || previewingEdge?.id === edge.id const sourceBlockCoordinates = blocksCoordinates && blocksCoordinates[edge.from.blockId] const targetBlockCoordinates = blocksCoordinates && blocksCoordinates[edge.to.blockId] const sourceTop = useMemo( () => getEndpointTopOffset( sourceEndpoints, graphPosition.y, getSourceEndpointId(edge) ), // eslint-disable-next-line react-hooks/exhaustive-deps [sourceBlockCoordinates?.y, edge, sourceEndpoints, refreshEdge] ) useEffect(() => { setTimeout(() => setRefreshEdge(true), 50) }, []) const [targetTop, setTargetTop] = useState( getEndpointTopOffset(targetEndpoints, graphPosition.y, edge?.to.stepId) ) useLayoutEffect(() => { setTargetTop( getEndpointTopOffset(targetEndpoints, graphPosition.y, edge?.to.stepId) ) }, [ targetBlockCoordinates?.y, targetEndpoints, graphPosition.y, edge?.to.stepId, ]) const path = useMemo(() => { if (!sourceBlockCoordinates || !targetBlockCoordinates || !sourceTop) return `` const anchorsPosition = getAnchorsPosition({ sourceBlockCoordinates, targetBlockCoordinates, sourceTop, targetTop, }) return computeEdgePath(anchorsPosition) // eslint-disable-next-line react-hooks/exhaustive-deps }, [ sourceBlockCoordinates?.x, sourceBlockCoordinates?.y, targetBlockCoordinates?.x, targetBlockCoordinates?.y, sourceTop, ]) const handleMouseEnter = () => setIsMouseOver(true) const handleMouseLeave = () => setIsMouseOver(false) const handleEdgeClick = () => { setPreviewingEdge(edge) } const handleContextMenuTrigger = (e: React.MouseEvent) => { if (isReadOnly) return e.preventDefault() setEdgeMenuPosition({ x: e.clientX, y: e.clientY }) onOpen() } const handleDeleteEdge = () => deleteEdge(edge.id) return ( <> ) }