import { Box, Flex, HStack, Popover, PopoverTrigger, useEventListener, } from '@chakra-ui/react' import React, { useEffect, useMemo, useState } from 'react' import { Block, Step } from 'models' import { useGraph } from 'contexts/GraphContext' import { StepIcon } from 'components/board/StepTypesList/StepIcon' import { isChoiceInput, isDefined, isInputStep, isLogicStep, isTextBubbleStep, } from 'utils' import { Coordinates } from '@dnd-kit/core/dist/types' import { TextEditor } from './TextEditor/TextEditor' import { StepNodeContent } from './StepNodeContent' import { useTypebot } from 'contexts/TypebotContext' import { ContextMenu } from 'components/shared/ContextMenu' import { SettingsPopoverContent } from './SettingsPopoverContent' import { DraggableStep } from 'contexts/DndContext' import { StepNodeContextMenu } from './StepNodeContextMenu' import { SourceEndpoint } from './SourceEndpoint' export const StepNode = ({ step, isConnectable, onMouseMoveBottomOfElement, onMouseMoveTopOfElement, onMouseDown, }: { step: Step isConnectable: boolean onMouseMoveBottomOfElement?: () => void onMouseMoveTopOfElement?: () => void onMouseDown?: ( stepNodePosition: { absolute: Coordinates; relative: Coordinates }, step: DraggableStep ) => void }) => { const { setConnectingIds, connectingIds } = useGraph() const { moveStep, typebot } = useTypebot() const [isConnecting, setIsConnecting] = useState(false) const [mouseDownEvent, setMouseDownEvent] = useState<{ absolute: Coordinates; relative: Coordinates }>() const [isEditing, setIsEditing] = useState( isTextBubbleStep(step) && step.content.plainText === '' ) useEffect(() => { setIsConnecting( connectingIds?.target?.blockId === step.blockId && connectingIds?.target?.stepId === step.id ) }, [connectingIds, step.blockId, step.id]) const handleMouseEnter = () => { if (connectingIds?.target) setConnectingIds({ ...connectingIds, target: { ...connectingIds.target, stepId: step.id }, }) } const handleMouseLeave = () => { if (connectingIds?.target) setConnectingIds({ ...connectingIds, target: { ...connectingIds.target, stepId: undefined }, }) } const handleMouseDown = (e: React.MouseEvent) => { if (!onMouseDown) return e.stopPropagation() const element = e.currentTarget as HTMLDivElement const rect = element.getBoundingClientRect() const relativeX = e.clientX - rect.left const relativeY = e.clientY - rect.top setMouseDownEvent({ absolute: { x: e.clientX + relativeX, y: e.clientY + relativeY }, relative: { x: relativeX, y: relativeY }, }) } const handleGlobalMouseUp = () => { setMouseDownEvent(undefined) } useEventListener('mouseup', handleGlobalMouseUp) const handleMouseUp = () => { if (mouseDownEvent) { setIsEditing(true) } } const handleMouseMove = (event: React.MouseEvent) => { if (!onMouseMoveBottomOfElement || !onMouseMoveTopOfElement) return const isMovingAndIsMouseDown = mouseDownEvent && onMouseDown && (event.movementX > 0 || event.movementY > 0) if (isMovingAndIsMouseDown && step.type !== 'start') { onMouseDown(mouseDownEvent, step) moveStep(step.id) setMouseDownEvent(undefined) } const element = event.currentTarget as HTMLDivElement const rect = element.getBoundingClientRect() const y = event.clientY - rect.top if (y > rect.height / 2) onMouseMoveBottomOfElement() else onMouseMoveTopOfElement() } const handleCloseEditor = () => { setIsEditing(false) } const connectedStubPosition: 'right' | 'left' | undefined = useMemo(() => { if (!typebot) return const currentBlock = typebot.blocks?.byId[step.blockId] const isDragginConnectorFromCurrentBlock = connectingIds?.source.blockId === currentBlock?.id && connectingIds?.target?.blockId const targetBlockId = isDragginConnectorFromCurrentBlock ? connectingIds.target?.blockId : step.target?.blockId const targetedBlock = targetBlockId && typebot.blocks.byId[targetBlockId] return targetedBlock ? targetedBlock.graphCoordinates.x < (currentBlock as Block).graphCoordinates.x ? 'left' : 'right' : undefined }, [ typebot, step.blockId, step.target?.blockId, connectingIds?.source.blockId, connectingIds?.target?.blockId, ]) return isEditing && isTextBubbleStep(step) ? ( ) : ( renderMenu={() => } > {(ref, isOpened) => ( {connectedStubPosition === 'left' && ( )} {isConnectable && !isChoiceInput(step) && ( )} {isDefined(connectedStubPosition) && !isChoiceInput(step) && ( )} {(isInputStep(step) || isLogicStep(step)) && ( )} )} ) }