2
0
Files
bot/apps/builder/src/features/graph/components/edges/Edge.tsx
Baptiste Arnaud 00dcb135f3 (editor) Actions on multiple groups
You can now select groups, move, copy, delete them easily

Closes #830, closes #1092
2024-01-29 09:37:19 +01:00

150 lines
4.4 KiB
TypeScript

import React, { useMemo, useState } from 'react'
import { Edge as EdgeProps } from '@typebot.io/schemas'
import { Portal, useColorMode, useDisclosure } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { colors } from '@/lib/theme'
import { useEndpoints } from '../../providers/EndpointsProvider'
import { computeEdgePath } from '../../helpers/computeEdgePath'
import { getAnchorsPosition } from '../../helpers/getAnchorsPosition'
import { useGraph } from '../../providers/GraphProvider'
import { EdgeMenu } from './EdgeMenu'
import { useEventsCoordinates } from '../../providers/EventsCoordinateProvider'
import { eventWidth, groupWidth } from '../../constants'
import { useGroupsStore } from '../../hooks/useGroupsStore'
import { useShallow } from 'zustand/react/shallow'
type Props = {
edge: EdgeProps
fromGroupId: string | undefined
}
export const Edge = ({ edge, fromGroupId }: Props) => {
const isDark = useColorMode().colorMode === 'dark'
const { deleteEdge } = useTypebot()
const { previewingEdge, graphPosition, isReadOnly, setPreviewingEdge } =
useGraph()
const { sourceEndpointYOffsets, targetEndpointYOffsets } = useEndpoints()
const fromGroupCoordinates = useGroupsStore(
useShallow((state) =>
fromGroupId && state.groupsCoordinates
? state.groupsCoordinates[fromGroupId]
: undefined
)
)
const toGroupCoordinates = useGroupsStore(
useShallow((state) =>
state.groupsCoordinates
? state.groupsCoordinates[edge.to.groupId]
: undefined
)
)
const { eventsCoordinates } = useEventsCoordinates()
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
const sourceElementCoordinates =
'eventId' in edge.from
? eventsCoordinates[edge.from.eventId]
: fromGroupCoordinates
const sourceTop = useMemo(() => {
const endpointId =
'eventId' in edge.from
? edge.from.eventId
: edge?.from.itemId ?? edge?.from.blockId
if (!endpointId) return
return sourceEndpointYOffsets.get(endpointId)?.y
}, [edge.from, sourceEndpointYOffsets])
const targetTop = useMemo(
() =>
edge?.to.blockId
? targetEndpointYOffsets.get(edge?.to.blockId)?.y
: undefined,
[edge?.to.blockId, targetEndpointYOffsets]
)
const path = useMemo(() => {
if (!sourceElementCoordinates || !toGroupCoordinates || !sourceTop)
return ``
const anchorsPosition = getAnchorsPosition({
sourceGroupCoordinates: sourceElementCoordinates,
targetGroupCoordinates: toGroupCoordinates,
elementWidth: 'eventId' in edge.from ? eventWidth : groupWidth,
sourceTop,
targetTop,
graphScale: graphPosition.scale,
})
return computeEdgePath(anchorsPosition)
}, [
sourceElementCoordinates,
toGroupCoordinates,
sourceTop,
edge.from,
targetTop,
graphPosition.scale,
])
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 (
<>
<path
data-testid="clickable-edge"
d={path}
strokeWidth="18px"
stroke="white"
fill="none"
pointerEvents="stroke"
style={{ cursor: 'pointer', visibility: 'hidden' }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleEdgeClick}
onContextMenu={handleContextMenuTrigger}
/>
<path
data-testid="edge"
d={path}
stroke={
isPreviewing
? colors.blue[400]
: isDark
? colors.gray[700]
: colors.gray[400]
}
strokeWidth="2px"
markerEnd={isPreviewing ? 'url(#blue-arrow)' : 'url(#arrow)'}
fill="none"
pointerEvents="none"
/>
<Portal>
<EdgeMenu
isOpen={isOpen}
position={edgeMenuPosition}
onDeleteEdge={handleDeleteEdge}
onClose={onClose}
/>
</Portal>
</>
)
}