2
0
Files
bot/apps/builder/components/shared/Graph/Edges/Edge.tsx

165 lines
4.3 KiB
TypeScript
Raw Normal View History

import {
Coordinates,
useGraph,
useGroupsCoordinates,
} from 'contexts/GraphContext'
2022-03-09 15:12:00 +01:00
import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react'
import {
getAnchorsPosition,
computeEdgePath,
getEndpointTopOffset,
getSourceEndpointId,
} from 'services/graph'
import { Edge as EdgeProps } from 'models'
2022-03-02 09:21:09 +01:00
import { Portal, useDisclosure } from '@chakra-ui/react'
import { useTypebot } from 'contexts/TypebotContext'
import { EdgeMenu } from './EdgeMenu'
2022-03-31 09:49:23 +02:00
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) => {
2022-03-02 09:21:09 +01:00
const { deleteEdge } = useTypebot()
const {
previewingEdge,
sourceEndpoints,
targetEndpoints,
graphPosition,
isReadOnly,
setPreviewingEdge,
} = useGraph()
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 })
2022-03-09 15:12:00 +01:00
const [refreshEdge, setRefreshEdge] = useState(false)
2022-03-02 09:21:09 +01:00
const isPreviewing = isMouseOver || previewingEdge?.id === edge.id
2022-06-11 07:27:38 +02:00
const sourceGroupCoordinates =
groupsCoordinates && groupsCoordinates[edge.from.groupId]
const targetGroupCoordinates =
groupsCoordinates && groupsCoordinates[edge.to.groupId]
const sourceTop = useMemo(
() =>
2022-04-08 14:30:46 -05:00
getEndpointTopOffset({
endpoints: sourceEndpoints,
graphOffsetY: graphPosition.y,
endpointId: getSourceEndpointId(edge),
graphScale: graphPosition.scale,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
2022-06-11 07:27:38 +02:00
[sourceGroupCoordinates?.y, edge, sourceEndpoints, refreshEdge]
)
2022-03-09 15:12:00 +01:00
useEffect(() => {
setTimeout(() => setRefreshEdge(true), 50)
}, [])
const [targetTop, setTargetTop] = useState(
2022-04-08 14:30:46 -05:00
getEndpointTopOffset({
endpoints: targetEndpoints,
graphOffsetY: graphPosition.y,
2022-06-11 07:27:38 +02:00
endpointId: edge?.to.blockId,
2022-04-08 14:30:46 -05:00
graphScale: graphPosition.scale,
})
)
useLayoutEffect(() => {
setTargetTop(
2022-04-08 14:30:46 -05:00
getEndpointTopOffset({
endpoints: targetEndpoints,
graphOffsetY: graphPosition.y,
2022-06-11 07:27:38 +02:00
endpointId: edge?.to.blockId,
2022-04-08 14:30:46 -05:00
graphScale: graphPosition.scale,
})
)
}, [
2022-06-11 07:27:38 +02:00
targetGroupCoordinates?.y,
targetEndpoints,
graphPosition.y,
2022-06-11 07:27:38 +02:00
edge?.to.blockId,
2022-04-08 14:30:46 -05:00
graphPosition.scale,
])
const path = useMemo(() => {
2022-06-11 07:27:38 +02:00
if (!sourceGroupCoordinates || !targetGroupCoordinates || !sourceTop)
return ``
const anchorsPosition = getAnchorsPosition({
2022-06-11 07:27:38 +02:00
sourceGroupCoordinates,
targetGroupCoordinates,
sourceTop,
targetTop,
2022-04-08 14:30:46 -05:00
graphScale: graphPosition.scale,
})
return computeEdgePath(anchorsPosition)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
2022-06-11 07:27:38 +02:00
sourceGroupCoordinates?.x,
sourceGroupCoordinates?.y,
targetGroupCoordinates?.x,
targetGroupCoordinates?.y,
sourceTop,
])
2022-03-02 09:21:09 +01:00
const handleMouseEnter = () => setIsMouseOver(true)
const handleMouseLeave = () => setIsMouseOver(false)
const handleEdgeClick = () => {
setPreviewingEdge(edge)
}
const handleContextMenuTrigger = (e: React.MouseEvent) => {
if (isReadOnly) return
e.preventDefault()
2022-03-02 09:21:09 +01:00
setEdgeMenuPosition({ x: e.clientX, y: e.clientY })
onOpen()
}
const handleDeleteEdge = () => deleteEdge(edge.id)
return (
2022-03-02 09:21:09 +01:00
<>
<path
data-testid="clickable-edge"
d={path}
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}
onContextMenu={handleContextMenuTrigger}
2022-03-02 09:21:09 +01:00
/>
<path
data-testid="edge"
d={path}
2022-03-31 09:49:23 +02:00
stroke={isPreviewing ? colors.blue[400] : colors.gray[400]}
2022-03-02 09:21:09 +01:00
strokeWidth="2px"
markerEnd={isPreviewing ? 'url(#blue-arrow)' : 'url(#arrow)'}
fill="none"
pointerEvents="none"
2022-03-02 09:21:09 +01:00
/>
<Portal>
<EdgeMenu
isOpen={isOpen}
position={edgeMenuPosition}
onDeleteEdge={handleDeleteEdge}
onClose={onClose}
/>
</Portal>
</>
)
}