import { Flex, HStack, Popover, PopoverTrigger, useColorModeValue, useDisclosure, } from '@chakra-ui/react' import React, { useEffect, useRef, useState } from 'react' import { BubbleBlock, BubbleBlockContent, Block, BlockWithOptions, TextBubbleBlock, BlockV6, } from '@typebot.io/schemas' import { isBubbleBlock, isDefined, isInputBlock, isTextBubbleBlock, } from '@typebot.io/lib' import { BlockNodeContent } from './BlockNodeContent' import { BlockSettings, SettingsPopoverContent } from './SettingsPopoverContent' import { BlockNodeContextMenu } from './BlockNodeContextMenu' import { BlockSourceEndpoint } from '../../endpoints/BlockSourceEndpoint' import { useRouter } from 'next/router' import { MediaBubblePopoverContent } from './MediaBubblePopoverContent' import { ContextMenu } from '@/components/ContextMenu' import { TextBubbleEditor } from '@/features/blocks/bubbles/textBubble/components/TextBubbleEditor' import { BlockIcon } from '@/features/editor/components/BlockIcon' import { useTypebot } from '@/features/editor/providers/TypebotProvider' import { NodePosition, useBlockDnd, useDragDistance, } from '@/features/graph/providers/GraphDndProvider' import { useGraph } from '@/features/graph/providers/GraphProvider' import { ParentModalProvider } from '@/features/graph/providers/ParentModalProvider' import { hasDefaultConnector } from '@/features/typebot/helpers/hasDefaultConnector' import { setMultipleRefs } from '@/helpers/setMultipleRefs' import { TargetEndpoint } from '../../endpoints/TargetEndpoint' import { SettingsModal } from './SettingsModal' import { TElement } from '@udecode/plate-common' import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants' import { useGroupsStore } from '@/features/graph/hooks/useGroupsStore' export const BlockNode = ({ block, isConnectable, indices, onMouseDown, }: { block: BlockV6 isConnectable: boolean indices: { blockIndex: number; groupIndex: number } onMouseDown?: (blockNodePosition: NodePosition, block: BlockV6) => void }) => { const bg = useColorModeValue('gray.50', 'gray.850') const previewingBorderColor = useColorModeValue('blue.400', 'blue.300') const borderColor = useColorModeValue('gray.200', 'gray.800') const { pathname, query } = useRouter() const { setConnectingIds, connectingIds, openedBlockId, setOpenedBlockId, setFocusedGroupId, previewingEdge, isReadOnly, isAnalytics, previewingBlock, } = useGraph() const { mouseOverBlock, setMouseOverBlock } = useBlockDnd() const { typebot, updateBlock } = useTypebot() const [isConnecting, setIsConnecting] = useState(false) const [isPopoverOpened, setIsPopoverOpened] = useState( openedBlockId === block.id ) const [isEditing, setIsEditing] = useState( isTextBubbleBlock(block) && (block.content?.richText?.length ?? 0) === 0 ) const blockRef = useRef(null) const isPreviewing = isConnecting || previewingEdge?.to.blockId === block.id || previewingBlock?.id === block.id const groupId = typebot?.groups[indices.groupIndex].id const isDraggingGraph = useGroupsStore((state) => state.isDraggingGraph) const onDrag = (position: NodePosition) => { if (!onMouseDown) return onMouseDown(position, block) } useDragDistance({ ref: blockRef, onDrag, isDisabled: !onMouseDown, }) const { isOpen: isModalOpen, onOpen: onModalOpen, onClose: onModalClose, } = useDisclosure() useEffect(() => { if (query.blockId?.toString() === block.id) setOpenedBlockId(block.id) }, [block.id, query, setOpenedBlockId]) useEffect(() => { setIsConnecting( connectingIds?.target?.groupId === groupId && connectingIds?.target?.blockId === block.id ) }, [connectingIds, block.id, groupId]) const handleModalClose = () => { updateBlock(indices, { ...block }) onModalClose() } const handleMouseEnter = () => { if (isReadOnly) return if (mouseOverBlock?.id !== block.id && blockRef.current) setMouseOverBlock({ id: block.id, element: blockRef.current }) if (connectingIds && groupId) setConnectingIds({ ...connectingIds, target: { groupId, blockId: block.id }, }) } const handleMouseLeave = () => { if (mouseOverBlock) setMouseOverBlock(undefined) if (connectingIds?.target) setConnectingIds({ ...connectingIds, target: { ...connectingIds.target, blockId: undefined }, }) } const handleCloseEditor = (content: TElement[]) => { const updatedBlock = { ...block, content: { richText: content } } updateBlock(indices, updatedBlock) setIsEditing(false) } const handleClick = (e: React.MouseEvent) => { setFocusedGroupId(groupId) e.stopPropagation() if (isTextBubbleBlock(block) && !isReadOnly) setIsEditing(true) setOpenedBlockId(block.id) } const handleExpandClick = () => { setOpenedBlockId(undefined) onModalOpen() } const handleBlockUpdate = (updates: Partial) => updateBlock(indices, { ...block, ...updates }) const handleContentChange = (content: BubbleBlockContent) => updateBlock(indices, { ...block, content } as Block) useEffect(() => { setIsPopoverOpened(openedBlockId === block.id) }, [block.id, openedBlockId]) useEffect(() => { if (!blockRef.current) return const blockElement = blockRef.current blockElement.addEventListener('pointerdown', (e) => e.stopPropagation()) return () => { blockElement.removeEventListener('pointerdown', (e) => e.stopPropagation() ) } }, []) const hasIcomingEdge = typebot?.edges.some((edge) => { return edge.to.blockId === block.id }) return isEditing && isTextBubbleBlock(block) ? ( ) : ( renderMenu={() => } > {(ref, isContextMenuOpened) => ( {typebot?.groups[indices.groupIndex].id && ( )} {(hasIcomingEdge || isDefined(connectingIds)) && ( )} {(isConnectable || (pathname.endsWith('analytics') && isInputBlock(block))) && hasDefaultConnector(block) && groupId && block.type !== LogicBlockType.JUMP && ( )} {hasSettingsPopover(block) && ( <> )} {typebot && isMediaBubbleBlock(block) && ( )} )} ) } const hasSettingsPopover = (block: BlockV6): block is BlockWithOptions => !isBubbleBlock(block) && block.type !== LogicBlockType.CONDITION const isMediaBubbleBlock = ( block: BlockV6 ): block is Exclude => isBubbleBlock(block) && !isTextBubbleBlock(block)