2023-02-28 15:06:43 +01:00
|
|
|
import React, { useMemo, useState } from 'react'
|
2023-03-15 08:35:16 +01:00
|
|
|
import { Edge as EdgeProps } from '@typebot.io/schemas'
|
2022-12-22 17:02:34 +01:00
|
|
|
import { Portal, useColorMode, useDisclosure } from '@chakra-ui/react'
|
2023-03-15 11:51:30 +01:00
|
|
|
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
2022-11-15 09:35:48 +01:00
|
|
|
import { colors } from '@/lib/theme'
|
2023-02-28 15:06:43 +01:00
|
|
|
import { useEndpoints } from '../../providers/EndpointsProvider'
|
2023-03-15 11:51:30 +01:00
|
|
|
import { computeEdgePath } from '../../helpers/computeEdgePath'
|
|
|
|
|
import { getAnchorsPosition } from '../../helpers/getAnchorsPosition'
|
|
|
|
|
import { useGraph } from '../../providers/GraphProvider'
|
|
|
|
|
import { useGroupsCoordinates } from '../../providers/GroupsCoordinateProvider'
|
|
|
|
|
import { EdgeMenu } from './EdgeMenu'
|
2022-02-02 08:05:02 +01:00
|
|
|
|
2022-06-26 16:12:28 +02:00
|
|
|
type Props = {
|
|
|
|
|
edge: EdgeProps
|
|
|
|
|
}
|
2023-02-28 15:06:43 +01:00
|
|
|
|
2022-06-26 16:12:28 +02:00
|
|
|
export const Edge = ({ edge }: Props) => {
|
2022-12-20 16:55:43 +01:00
|
|
|
const isDark = useColorMode().colorMode === 'dark'
|
2022-03-02 09:21:09 +01:00
|
|
|
const { deleteEdge } = useTypebot()
|
2023-02-28 15:06:43 +01:00
|
|
|
const { previewingEdge, graphPosition, isReadOnly, setPreviewingEdge } =
|
|
|
|
|
useGraph()
|
|
|
|
|
const { sourceEndpointYOffsets, targetEndpointYOffsets } = useEndpoints()
|
2022-06-26 16:12:28 +02:00
|
|
|
const { groupsCoordinates } = useGroupsCoordinates()
|
2022-03-02 09:21:09 +01:00
|
|
|
const [isMouseOver, setIsMouseOver] = useState(false)
|
|
|
|
|
const { isOpen, onOpen, onClose } = useDisclosure()
|
|
|
|
|
const [edgeMenuPosition, setEdgeMenuPosition] = useState({ x: 0, y: 0 })
|
|
|
|
|
|
|
|
|
|
const isPreviewing = isMouseOver || previewingEdge?.id === edge.id
|
2022-02-02 08:05:02 +01:00
|
|
|
|
2022-06-11 07:27:38 +02:00
|
|
|
const sourceGroupCoordinates =
|
|
|
|
|
groupsCoordinates && groupsCoordinates[edge.from.groupId]
|
|
|
|
|
const targetGroupCoordinates =
|
|
|
|
|
groupsCoordinates && groupsCoordinates[edge.to.groupId]
|
2022-02-02 08:05:02 +01:00
|
|
|
|
2023-02-28 15:06:43 +01:00
|
|
|
const sourceTop = useMemo(() => {
|
|
|
|
|
const endpointId = edge?.from.itemId ?? edge?.from.blockId
|
|
|
|
|
if (!endpointId) return
|
|
|
|
|
return sourceEndpointYOffsets.get(endpointId)?.y
|
|
|
|
|
}, [edge?.from.itemId, edge?.from.blockId, sourceEndpointYOffsets])
|
2022-03-09 15:12:00 +01:00
|
|
|
|
2023-02-28 15:06:43 +01:00
|
|
|
const targetTop = useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
edge?.to.blockId
|
|
|
|
|
? targetEndpointYOffsets.get(edge?.to.blockId)?.y
|
|
|
|
|
: undefined,
|
|
|
|
|
[edge?.to.blockId, targetEndpointYOffsets]
|
2022-02-02 08:05:02 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const path = useMemo(() => {
|
2022-06-11 07:27:38 +02:00
|
|
|
if (!sourceGroupCoordinates || !targetGroupCoordinates || !sourceTop)
|
2022-02-02 08:05:02 +01:00
|
|
|
return ``
|
|
|
|
|
const anchorsPosition = getAnchorsPosition({
|
2022-06-11 07:27:38 +02:00
|
|
|
sourceGroupCoordinates,
|
|
|
|
|
targetGroupCoordinates,
|
2022-02-02 08:05:02 +01:00
|
|
|
sourceTop,
|
|
|
|
|
targetTop,
|
2022-04-08 14:30:46 -05:00
|
|
|
graphScale: graphPosition.scale,
|
2022-02-02 08:05:02 +01:00
|
|
|
})
|
|
|
|
|
return computeEdgePath(anchorsPosition)
|
|
|
|
|
}, [
|
2023-02-28 15:06:43 +01:00
|
|
|
sourceGroupCoordinates,
|
|
|
|
|
targetGroupCoordinates,
|
2022-02-02 08:05:02 +01:00
|
|
|
sourceTop,
|
2023-02-28 15:06:43 +01:00
|
|
|
targetTop,
|
|
|
|
|
graphPosition.scale,
|
2022-02-02 08:05:02 +01:00
|
|
|
])
|
|
|
|
|
|
2022-03-02 09:21:09 +01:00
|
|
|
const handleMouseEnter = () => setIsMouseOver(true)
|
|
|
|
|
|
|
|
|
|
const handleMouseLeave = () => setIsMouseOver(false)
|
|
|
|
|
|
2022-03-24 11:44:34 +01:00
|
|
|
const handleEdgeClick = () => {
|
|
|
|
|
setPreviewingEdge(edge)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleContextMenuTrigger = (e: React.MouseEvent) => {
|
2022-03-03 11:09:01 +01:00
|
|
|
if (isReadOnly) return
|
2022-03-24 11:44:34 +01:00
|
|
|
e.preventDefault()
|
2022-03-02 09:21:09 +01:00
|
|
|
setEdgeMenuPosition({ x: e.clientX, y: e.clientY })
|
|
|
|
|
onOpen()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleDeleteEdge = () => deleteEdge(edge.id)
|
|
|
|
|
|
2022-02-02 08:05:02 +01:00
|
|
|
return (
|
2022-03-02 09:21:09 +01:00
|
|
|
<>
|
|
|
|
|
<path
|
|
|
|
|
data-testid="clickable-edge"
|
|
|
|
|
d={path}
|
2022-10-02 10:34:13 +02:00
|
|
|
strokeWidth="18px"
|
2022-03-02 09:21:09 +01:00
|
|
|
stroke="white"
|
|
|
|
|
fill="none"
|
|
|
|
|
pointerEvents="stroke"
|
|
|
|
|
style={{ cursor: 'pointer', visibility: 'hidden' }}
|
|
|
|
|
onMouseEnter={handleMouseEnter}
|
|
|
|
|
onMouseLeave={handleMouseLeave}
|
|
|
|
|
onClick={handleEdgeClick}
|
2022-03-24 11:44:34 +01:00
|
|
|
onContextMenu={handleContextMenuTrigger}
|
2022-03-02 09:21:09 +01:00
|
|
|
/>
|
|
|
|
|
<path
|
|
|
|
|
data-testid="edge"
|
|
|
|
|
d={path}
|
2022-12-20 16:55:43 +01:00
|
|
|
stroke={
|
|
|
|
|
isPreviewing
|
|
|
|
|
? colors.blue[400]
|
|
|
|
|
: isDark
|
|
|
|
|
? colors.gray[700]
|
|
|
|
|
: colors.gray[400]
|
|
|
|
|
}
|
2022-03-02 09:21:09 +01:00
|
|
|
strokeWidth="2px"
|
|
|
|
|
markerEnd={isPreviewing ? 'url(#blue-arrow)' : 'url(#arrow)'}
|
|
|
|
|
fill="none"
|
2022-10-02 10:34:13 +02:00
|
|
|
pointerEvents="none"
|
2022-03-02 09:21:09 +01:00
|
|
|
/>
|
|
|
|
|
<Portal>
|
|
|
|
|
<EdgeMenu
|
|
|
|
|
isOpen={isOpen}
|
|
|
|
|
position={edgeMenuPosition}
|
|
|
|
|
onDeleteEdge={handleDeleteEdge}
|
|
|
|
|
onClose={onClose}
|
|
|
|
|
/>
|
|
|
|
|
</Portal>
|
|
|
|
|
</>
|
2022-02-02 08:05:02 +01:00
|
|
|
)
|
|
|
|
|
}
|