import { Coordinates, useGraph, useGroupsCoordinates, } 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 } type Props = { edge: EdgeProps } export const Edge = ({ edge }: Props) => { const { deleteEdge } = useTypebot() const { previewingEdge, sourceEndpoints, targetEndpoints, graphPosition, isReadOnly, setPreviewingEdge, } = useGraph() const { groupsCoordinates } = useGroupsCoordinates() 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 sourceGroupCoordinates = groupsCoordinates && groupsCoordinates[edge.from.groupId] const targetGroupCoordinates = groupsCoordinates && groupsCoordinates[edge.to.groupId] const sourceTop = useMemo( () => getEndpointTopOffset({ endpoints: sourceEndpoints, graphOffsetY: graphPosition.y, endpointId: getSourceEndpointId(edge), graphScale: graphPosition.scale, }), // eslint-disable-next-line react-hooks/exhaustive-deps [sourceGroupCoordinates?.y, edge, sourceEndpoints, refreshEdge] ) useEffect(() => { setTimeout(() => setRefreshEdge(true), 50) }, []) const [targetTop, setTargetTop] = useState( getEndpointTopOffset({ endpoints: targetEndpoints, graphOffsetY: graphPosition.y, endpointId: edge?.to.blockId, graphScale: graphPosition.scale, }) ) useLayoutEffect(() => { setTargetTop( getEndpointTopOffset({ endpoints: targetEndpoints, graphOffsetY: graphPosition.y, endpointId: edge?.to.blockId, graphScale: graphPosition.scale, }) ) }, [ targetGroupCoordinates?.y, targetEndpoints, graphPosition.y, edge?.to.blockId, graphPosition.scale, ]) const path = useMemo(() => { if (!sourceGroupCoordinates || !targetGroupCoordinates || !sourceTop) return `` const anchorsPosition = getAnchorsPosition({ sourceGroupCoordinates, targetGroupCoordinates, sourceTop, targetTop, graphScale: graphPosition.scale, }) return computeEdgePath(anchorsPosition) // eslint-disable-next-line react-hooks/exhaustive-deps }, [ sourceGroupCoordinates?.x, sourceGroupCoordinates?.y, targetGroupCoordinates?.x, targetGroupCoordinates?.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 ( <> ) }