import { Flex, HStack, Popover, PopoverTrigger, useDisclosure, } from '@chakra-ui/react' import React, { useEffect, useRef, useState } from 'react' import { BubbleBlock, BubbleBlockContent, ConditionBlock, DraggableBlock, Block, BlockWithOptions, TextBubbleContent, TextBubbleBlock, } from 'models' import { useGraph } from 'contexts/GraphContext' import { BlockIcon } from 'components/editor/BlocksSideBar/BlockIcon' import { isBubbleBlock, isTextBubbleBlock } from 'utils' import { BlockNodeContent } from './BlockNodeContent/BlockNodeContent' import { useTypebot } from 'contexts/TypebotContext' import { ContextMenu } from 'components/shared/ContextMenu' import { SettingsPopoverContent } from './SettingsPopoverContent' import { BlockNodeContextMenu } from './BlockNodeContextMenu' import { SourceEndpoint } from '../../Endpoints/SourceEndpoint' import { hasDefaultConnector } from 'services/typebots' import { useRouter } from 'next/router' import { SettingsModal } from './SettingsPopoverContent/SettingsModal' import { BlockSettings } from './SettingsPopoverContent/SettingsPopoverContent' import { TextBubbleEditor } from './TextBubbleEditor' import { TargetEndpoint } from '../../Endpoints' import { MediaBubblePopoverContent } from './MediaBubblePopoverContent' import { NodePosition, useDragDistance } from 'contexts/GraphDndContext' import { setMultipleRefs } from 'services/utils' export const BlockNode = ({ block, isConnectable, indices, onMouseDown, }: { block: Block isConnectable: boolean indices: { blockIndex: number; groupIndex: number } onMouseDown?: (blockNodePosition: NodePosition, block: DraggableBlock) => void }) => { const { query } = useRouter() const { setConnectingIds, connectingIds, openedBlockId, setOpenedBlockId, setFocusedGroupId, previewingEdge, } = useGraph() const { updateBlock } = useTypebot() const [isConnecting, setIsConnecting] = useState(false) const [isPopoverOpened, setIsPopoverOpened] = useState( openedBlockId === block.id ) const [isEditing, setIsEditing] = useState( isTextBubbleBlock(block) && block.content.plainText === '' ) const blockRef = useRef(null) const isPreviewing = isConnecting || previewingEdge?.to.blockId === block.id const onDrag = (position: NodePosition) => { if (block.type === 'start' || !onMouseDown) return onMouseDown(position, block) } useDragDistance({ ref: blockRef, onDrag, isDisabled: !onMouseDown || block.type === 'start', }) const { isOpen: isModalOpen, onOpen: onModalOpen, onClose: onModalClose, } = useDisclosure() useEffect(() => { if (query.blockId?.toString() === block.id) setOpenedBlockId(block.id) // eslint-disable-next-line react-hooks/exhaustive-deps }, [query]) useEffect(() => { setIsConnecting( connectingIds?.target?.groupId === block.groupId && connectingIds?.target?.blockId === block.id ) }, [connectingIds, block.groupId, block.id]) const handleModalClose = () => { updateBlock(indices, { ...block }) onModalClose() } const handleMouseEnter = () => { if (connectingIds) setConnectingIds({ ...connectingIds, target: { groupId: block.groupId, blockId: block.id }, }) } const handleMouseLeave = () => { if (connectingIds?.target) setConnectingIds({ ...connectingIds, target: { ...connectingIds.target, blockId: undefined }, }) } const handleCloseEditor = (content: TextBubbleContent) => { const updatedBlock = { ...block, content } as Block updateBlock(indices, updatedBlock) setIsEditing(false) } const handleClick = (e: React.MouseEvent) => { setFocusedGroupId(block.groupId) e.stopPropagation() if (isTextBubbleBlock(block)) 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) // eslint-disable-next-line react-hooks/exhaustive-deps }, [openedBlockId]) return isEditing && isTextBubbleBlock(block) ? ( ) : ( renderMenu={() => } > {(ref, isOpened) => ( {isConnectable && hasDefaultConnector(block) && ( )} {hasSettingsPopover(block) && ( <> )} {isMediaBubbleBlock(block) && ( )} )} ) } const hasSettingsPopover = ( block: Block ): block is BlockWithOptions | ConditionBlock => !isBubbleBlock(block) const isMediaBubbleBlock = ( block: Block ): block is Exclude => isBubbleBlock(block) && !isTextBubbleBlock(block)