♻️ (builder) Remove barrel export and flatten folder arch

This commit is contained in:
Baptiste Arnaud
2023-03-15 11:51:30 +01:00
parent cbc8194f19
commit 44d7a0bcb8
498 changed files with 1542 additions and 1786 deletions

View File

@@ -1,16 +1,15 @@
import { useEventListener } from '@chakra-ui/react'
import assert from 'assert'
import {
useGraph,
ConnectingIds,
Coordinates,
useGroupsCoordinates,
} from '../../providers'
import { useTypebot } from '@/features/editor'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { colors } from '@/lib/theme'
import React, { useMemo, useState } from 'react'
import { computeConnectingEdgePath, computeEdgePathToMouse } from '../../utils'
import { useEndpoints } from '../../providers/EndpointsProvider'
import { Coordinates } from '@dnd-kit/utilities'
import { computeConnectingEdgePath } from '../../helpers/computeConnectingEdgePath'
import { computeEdgePathToMouse } from '../../helpers/computeEdgePathToMouth'
import { useGraph } from '../../providers/GraphProvider'
import { useGroupsCoordinates } from '../../providers/GroupsCoordinateProvider'
import { ConnectingIds } from '../../types'
export const DrawingEdge = () => {
const { graphPosition, setConnectingIds, connectingIds } = useGraph()

View File

@@ -6,15 +6,16 @@ import {
useColorModeValue,
theme,
} from '@chakra-ui/react'
import { useGroupsCoordinates } from '../../providers'
import { useTypebot } from '@/features/editor'
import { useWorkspace } from '@/features/workspace'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import React, { useMemo } from 'react'
import { byId, isDefined } from '@typebot.io/lib'
import { isProPlan } from '@/features/billing'
import { AnswersCount } from '@/features/analytics'
import { computeSourceCoordinates, computeDropOffPath } from '../../utils'
import { useEndpoints } from '../../providers/EndpointsProvider'
import { useGroupsCoordinates } from '../../providers/GroupsCoordinateProvider'
import { AnswersCount } from '@/features/analytics/types'
import { isProPlan } from '@/features/billing/helpers/isProPlan'
import { computeDropOffPath } from '../../helpers/computeDropOffPath'
import { computeSourceCoordinates } from '../../helpers/computeSourceCoordinates'
type Props = {
groupId: string

View File

@@ -1,19 +1,14 @@
import { Coordinates, useGraph, useGroupsCoordinates } from '../../providers'
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'
import { EdgeMenu } from './EdgeMenu'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { colors } from '@/lib/theme'
import { getAnchorsPosition, computeEdgePath } from '../../utils'
import { useEndpoints } from '../../providers/EndpointsProvider'
export type AnchorsPositionProps = {
sourcePosition: Coordinates
targetPosition: Coordinates
sourceType: 'right' | 'left'
totalSegments: number
}
import { computeEdgePath } from '../../helpers/computeEdgePath'
import { getAnchorsPosition } from '../../helpers/getAnchorsPosition'
import { useGraph } from '../../providers/GraphProvider'
import { useGroupsCoordinates } from '../../providers/GroupsCoordinateProvider'
import { EdgeMenu } from './EdgeMenu'
type Props = {
edge: EdgeProps

View File

@@ -2,10 +2,10 @@ import { chakra, useColorMode } from '@chakra-ui/react'
import { colors } from '@/lib/theme'
import { Edge as EdgeProps } from '@typebot.io/schemas'
import React from 'react'
import { AnswersCount } from '@/features/analytics/types'
import { DrawingEdge } from './DrawingEdge'
import { DropOffEdge } from './DropOffEdge'
import { Edge } from './Edge'
import { AnswersCount } from '@/features/analytics'
type Props = {
edges: EdgeProps[]

View File

@@ -1 +0,0 @@
export { Edges } from './Edges'

View File

@@ -4,7 +4,6 @@ import {
useColorModeValue,
useEventListener,
} from '@chakra-ui/react'
import { useGraph, useGroupsCoordinates } from '../../providers'
import { Source } from '@typebot.io/schemas'
import React, {
useEffect,
@@ -14,6 +13,8 @@ import React, {
useState,
} from 'react'
import { useEndpoints } from '../../providers/EndpointsProvider'
import { useGraph } from '../../providers/GraphProvider'
import { useGroupsCoordinates } from '../../providers/GroupsCoordinateProvider'
const endpointHeight = 32

View File

@@ -6,8 +6,8 @@ import React, {
useRef,
useState,
} from 'react'
import { useGraph } from '../../providers'
import { useEndpoints } from '../../providers/EndpointsProvider'
import { useGraph } from '../../providers/GraphProvider'
const endpointHeight = 20

View File

@@ -1,2 +0,0 @@
export { SourceEndpoint } from './SourceEndpoint'
export { TargetEndpoint } from './TargetEndpoint'

View File

@@ -1,24 +1,21 @@
import { Flex, FlexProps, useEventListener } from '@chakra-ui/react'
import React, { useRef, useMemo, useEffect, useState } from 'react'
import {
blockWidth,
Coordinates,
graphPositionDefaultValue,
useGraph,
useGroupsCoordinates,
useBlockDnd,
} from '../providers'
import { useTypebot } from '@/features/editor'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { DraggableBlockType, PublicTypebot, Typebot } from '@typebot.io/schemas'
import { useDebounce } from 'use-debounce'
import GraphElements from './GraphElements'
import { createId } from '@paralleldrive/cuid2'
import { useUser } from '@/features/account'
import { useUser } from '@/features/account/hooks/useUser'
import { ZoomButtons } from './ZoomButtons'
import { AnswersCount } from '@/features/analytics'
import { headerHeight } from '@/features/editor'
import { useGesture } from '@use-gesture/react'
import { GraphNavigation } from '@typebot.io/prisma'
import { AnswersCount } from '@/features/analytics/types'
import { headerHeight } from '@/features/editor/constants'
import { graphPositionDefaultValue, blockWidth } from '../constants'
import { useBlockDnd } from '../providers/GraphDndProvider'
import { useGraph } from '../providers/GraphProvider'
import { useGroupsCoordinates } from '../providers/GroupsCoordinateProvider'
import { Coordinates } from '../types'
const maxScale = 2
const minScale = 0.3

View File

@@ -1,9 +1,9 @@
import { AnswersCount } from '@/features/analytics'
import { AnswersCount } from '@/features/analytics/types'
import { Edge, Group } from '@typebot.io/schemas'
import React, { memo } from 'react'
import { EndpointsProvider } from '../providers/EndpointsProvider'
import { Edges } from './Edges'
import { GroupNode } from './Nodes/GroupNode'
import { Edges } from './edges/Edges'
import { GroupNode } from './nodes/group'
type Props = {
edges: Edge[]

View File

@@ -1 +0,0 @@
export { BlockNodeContent } from './BlockNodeContent'

View File

@@ -1 +0,0 @@
export { SettingsPopoverContent } from './SettingsPopoverContent'

View File

@@ -1 +0,0 @@
export { BlockNodesList } from './BlockNodesList'

View File

@@ -1 +0,0 @@
export { ItemNodeContent } from './ItemNodeContent'

View File

@@ -1 +0,0 @@
export { ItemNodesList } from './ItemNodesList'

View File

@@ -18,27 +18,27 @@ import {
LogicBlockType,
} from '@typebot.io/schemas'
import { isBubbleBlock, isDefined, isTextBubbleBlock } from '@typebot.io/lib'
import { BlockNodeContent } from './BlockNodeContent/BlockNodeContent'
import { BlockIcon, useTypebot } from '@/features/editor'
import { SettingsPopoverContent } from './SettingsPopoverContent'
import { BlockNodeContent } from './BlockNodeContent'
import { BlockSettings, SettingsPopoverContent } from './SettingsPopoverContent'
import { BlockNodeContextMenu } from './BlockNodeContextMenu'
import { SourceEndpoint } from '../../Endpoints/SourceEndpoint'
import { SourceEndpoint } from '../../endpoints/SourceEndpoint'
import { useRouter } from 'next/router'
import { SettingsModal } from './SettingsPopoverContent/SettingsModal'
import { BlockSettings } from './SettingsPopoverContent/SettingsPopoverContent'
import { TargetEndpoint } from '../../Endpoints'
import { MediaBubblePopoverContent } from './MediaBubblePopoverContent'
import { ContextMenu } from '@/components/ContextMenu'
import { TextBubbleEditor } from '@/features/blocks/bubbles/textBubble'
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,
useGraph,
useBlockDnd,
useDragDistance,
ParentModalProvider,
} from '../../../providers'
import { hasDefaultConnector } from '../../../utils'
import { setMultipleRefs } from '@/utils/helpers'
} 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'
export const BlockNode = ({
block,

View File

@@ -8,37 +8,37 @@ import {
IntegrationBlockType,
BlockIndices,
} from '@typebot.io/schemas'
import { ItemNodesList } from '../../ItemNode'
import { TextBubbleContent } from '@/features/blocks/bubbles/textBubble'
import { ImageBubbleContent } from '@/features/blocks/bubbles/image'
import { VideoBubbleContent } from '@/features/blocks/bubbles/video'
import { EmbedBubbleContent } from '@/features/blocks/bubbles/embed'
import { TextInputNodeContent } from '@/features/blocks/inputs/textInput'
import { NumberNodeContent } from '@/features/blocks/inputs/number'
import { EmailInputNodeContent } from '@/features/blocks/inputs/emailInput'
import { UrlNodeContent } from '@/features/blocks/inputs/url'
import { PhoneNodeContent } from '@/features/blocks/inputs/phone'
import { DateNodeContent } from '@/features/blocks/inputs/date'
import { SetVariableContent } from '@/features/blocks/logic/setVariable'
import { WebhookContent } from '@/features/blocks/integrations/webhook'
import { ChatwootBlockNodeLabel } from '@/features/blocks/integrations/chatwoot'
import { RedirectNodeContent } from '@/features/blocks/logic/redirect'
import { PabblyConnectContent } from '@/features/blocks/integrations/pabbly'
import { PaymentInputContent } from '@/features/blocks/inputs/payment'
import { RatingInputContent } from '@/features/blocks/inputs/rating'
import { FileInputContent } from '@/features/blocks/inputs/fileUpload'
import { TypebotLinkNode } from '@/features/blocks/logic/typebotLink'
import { GoogleSheetsNodeContent } from '@/features/blocks/integrations/googleSheets'
import { GoogleAnalyticsNodeContent } from '@/features/blocks/integrations/googleAnalytics/components/GoogleAnalyticsNodeContent'
import { ZapierContent } from '@/features/blocks/integrations/zapier'
import { SendEmailContent } from '@/features/blocks/integrations/sendEmail'
import { MakeComContent } from '@/features/blocks/integrations/makeCom'
import { AudioBubbleNode } from '@/features/blocks/bubbles/audio'
import { WaitNodeContent } from '@/features/blocks/logic/wait/components/WaitNodeContent'
import { ScriptNodeContent } from '@/features/blocks/logic/script/components/ScriptNodeContent'
import { ButtonsBlockNode } from '@/features/blocks/inputs/buttons/components/ButtonsBlockNode'
import { JumpNodeBody } from '@/features/blocks/logic/jump/components/JumpNodeBody'
import { OpenAINodeBody } from '@/features/blocks/integrations/openai/components/OpenAINodeBody'
import { AudioBubbleNode } from '@/features/blocks/bubbles/audio/components/AudioBubbleNode'
import { EmbedBubbleContent } from '@/features/blocks/bubbles/embed/components/EmbedBubbleContent'
import { ImageBubbleContent } from '@/features/blocks/bubbles/image/components/ImageBubbleContent'
import { TextBubbleContent } from '@/features/blocks/bubbles/textBubble/components/TextBubbleContent'
import { VideoBubbleContent } from '@/features/blocks/bubbles/video/components/VideoBubbleContent'
import { DateNodeContent } from '@/features/blocks/inputs/date/components/DateNodeContent'
import { EmailInputNodeContent } from '@/features/blocks/inputs/emailInput/components/EmailInputNodeContent'
import { FileInputContent } from '@/features/blocks/inputs/fileUpload/components/FileInputContent'
import { NumberNodeContent } from '@/features/blocks/inputs/number/components/NumberNodeContent'
import { PaymentInputContent } from '@/features/blocks/inputs/payment/components/PaymentInputContent'
import { PhoneNodeContent } from '@/features/blocks/inputs/phone/components/PhoneNodeContent'
import { RatingInputContent } from '@/features/blocks/inputs/rating/components/RatingInputContent'
import { TextInputNodeContent } from '@/features/blocks/inputs/textInput/components/TextInputNodeContent'
import { UrlNodeContent } from '@/features/blocks/inputs/url/components/UrlNodeContent'
import { GoogleSheetsNodeContent } from '@/features/blocks/integrations/googleSheets/components/GoogleSheetsNodeContent'
import { MakeComContent } from '@/features/blocks/integrations/makeCom/components/MakeComContent'
import { PabblyConnectContent } from '@/features/blocks/integrations/pabbly/components/PabblyConnectContent'
import { SendEmailContent } from '@/features/blocks/integrations/sendEmail/components/SendEmailContent'
import { WebhookContent } from '@/features/blocks/integrations/webhook/components/WebhookContent'
import { ZapierContent } from '@/features/blocks/integrations/zapier/components/ZapierContent'
import { RedirectNodeContent } from '@/features/blocks/logic/redirect/components/RedirectNodeContent'
import { SetVariableContent } from '@/features/blocks/logic/setVariable/components/SetVariableContent'
import { TypebotLinkNode } from '@/features/blocks/logic/typebotLink/components/TypebotLinkNode'
import { ItemNodesList } from '../item/ItemNodesList'
import { GoogleAnalyticsNodeBody } from '@/features/blocks/integrations/googleAnalytics/components/GoogleAnalyticsNodeBody'
import { ChatwootNodeBody } from '@/features/blocks/integrations/chatwoot/components/ChatwootNodeBody'
type Props = {
block: Block | StartBlock
@@ -155,7 +155,7 @@ export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
}
case IntegrationBlockType.GOOGLE_ANALYTICS: {
return (
<GoogleAnalyticsNodeContent
<GoogleAnalyticsNodeBody
action={
block.options?.action
? `Track "${block.options?.action}" `
@@ -180,7 +180,7 @@ export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
return <SendEmailContent block={block} />
}
case IntegrationBlockType.CHATWOOT: {
return <ChatwootBlockNodeLabel block={block} />
return <ChatwootNodeBody block={block} />
}
case IntegrationBlockType.OPEN_AI: {
return (

View File

@@ -1,6 +1,6 @@
import { MenuList, MenuItem } from '@chakra-ui/react'
import { CopyIcon, TrashIcon } from '@/components/icons'
import { useTypebot } from '@/features/editor'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { BlockIndices } from '@typebot.io/schemas'
type Props = { indices: BlockIndices }

View File

@@ -1,7 +1,7 @@
import { BlockIcon } from '@/features/editor'
import { BlockIcon } from '@/features/editor/components/BlockIcon'
import { StackProps, HStack, useColorModeValue } from '@chakra-ui/react'
import { StartBlock, Block, BlockIndices } from '@typebot.io/schemas'
import { BlockNodeContent } from './BlockNodeContent/BlockNodeContent'
import { BlockNodeContent } from './BlockNodeContent'
export const BlockNodeOverlay = ({
block,

View File

@@ -1,17 +1,17 @@
import { useEventListener, Stack, Portal } from '@chakra-ui/react'
import { DraggableBlock, DraggableBlockType, Block } from '@typebot.io/schemas'
import {
computeNearestPlaceholderIndex,
useBlockDnd,
Coordinates,
useGraph,
} from '../../../providers'
import { useEffect, useRef, useState } from 'react'
import { useTypebot } from '@/features/editor'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { BlockNode } from './BlockNode'
import { BlockNodeOverlay } from './BlockNodeOverlay'
import { PlaceholderNode } from '../PlaceholderNode'
import { isDefined } from '@typebot.io/lib'
import {
useBlockDnd,
computeNearestPlaceholderIndex,
} from '@/features/graph/providers/GraphDndProvider'
import { useGraph } from '@/features/graph/providers/GraphProvider'
import { Coordinates } from '@dnd-kit/utilities'
type Props = {
groupId: string

View File

@@ -1,7 +1,7 @@
import { ImageUploadContent } from '@/components/ImageUploadContent'
import { AudioBubbleForm } from '@/features/blocks/bubbles/audio/components/AudioBubbleForm'
import { EmbedUploadContent } from '@/features/blocks/bubbles/embed'
import { VideoUploadContent } from '@/features/blocks/bubbles/video'
import { EmbedUploadContent } from '@/features/blocks/bubbles/embed/components/EmbedUploadContent'
import { VideoUploadContent } from '@/features/blocks/bubbles/video/components/VideoUploadContent'
import {
Portal,
PopoverContent,

View File

@@ -1,4 +1,4 @@
import { useParentModal } from '@/features/graph'
import { useParentModal } from '@/features/graph/providers/ParentModalProvider'
import {
Modal,
ModalOverlay,

View File

@@ -19,25 +19,6 @@ import {
BlockWithOptions,
} from '@typebot.io/schemas'
import { useRef } from 'react'
import { DateInputSettingsBody } from '@/features/blocks/inputs/date'
import { EmailInputSettingsBody } from '@/features/blocks/inputs/emailInput'
import { FileInputSettings } from '@/features/blocks/inputs/fileUpload'
import { NumberInputSettingsBody } from '@/features/blocks/inputs/number'
import { PaymentSettings } from '@/features/blocks/inputs/payment'
import { PhoneNumberSettingsBody } from '@/features/blocks/inputs/phone'
import { RatingInputSettings } from '@/features/blocks/inputs/rating'
import { TextInputSettingsBody } from '@/features/blocks/inputs/textInput'
import { UrlInputSettingsBody } from '@/features/blocks/inputs/url'
import { GoogleAnalyticsSettings } from '@/features/blocks/integrations/googleAnalytics'
import { GoogleSheetsSettingsBody } from '@/features/blocks/integrations/googleSheets'
import { SendEmailSettings } from '@/features/blocks/integrations/sendEmail'
import { WebhookSettings } from '@/features/blocks/integrations/webhook'
import { ZapierSettings } from '@/features/blocks/integrations/zapier'
import { RedirectSettings } from '@/features/blocks/logic/redirect'
import { SetVariableSettings } from '@/features/blocks/logic/setVariable'
import { TypebotLinkForm } from '@/features/blocks/logic/typebotLink'
import { ButtonsBlockSettings } from '@/features/blocks/inputs/buttons'
import { ChatwootSettingsForm } from '@/features/blocks/integrations/chatwoot'
import { HelpDocButton } from './HelpDocButton'
import { WaitSettings } from '@/features/blocks/logic/wait/components/WaitSettings'
import { ScriptSettings } from '@/features/blocks/logic/script/components/ScriptSettings'
@@ -45,6 +26,25 @@ import { JumpSettings } from '@/features/blocks/logic/jump/components/JumpSettin
import { MakeComSettings } from '@/features/blocks/integrations/makeCom/components/MakeComSettings'
import { PabblyConnectSettings } from '@/features/blocks/integrations/pabbly/components/PabblyConnectSettings'
import { OpenAISettings } from '@/features/blocks/integrations/openai/components/OpenAISettings'
import { ButtonsBlockSettings } from '@/features/blocks/inputs/buttons/components/ButtonsBlockSettings'
import { FileInputSettings } from '@/features/blocks/inputs/fileUpload/components/FileInputSettings'
import { PaymentSettings } from '@/features/blocks/inputs/payment/components/PaymentSettings'
import { RatingInputSettings } from '@/features/blocks/inputs/rating/components/RatingInputSettings'
import { TextInputSettings } from '@/features/blocks/inputs/textInput/components/TextInputSettings'
import { GoogleAnalyticsSettings } from '@/features/blocks/integrations/googleAnalytics/components/GoogleAnalyticsSettings'
import { SendEmailSettings } from '@/features/blocks/integrations/sendEmail/components/SendEmailSettings'
import { WebhookSettings } from '@/features/blocks/integrations/webhook/components/WebhookSettings'
import { ZapierSettings } from '@/features/blocks/integrations/zapier/components/ZapierSettings'
import { RedirectSettings } from '@/features/blocks/logic/redirect/components/RedirectSettings'
import { SetVariableSettings } from '@/features/blocks/logic/setVariable/components/SetVariableSettings'
import { TypebotLinkForm } from '@/features/blocks/logic/typebotLink/components/TypebotLinkForm'
import { NumberInputSettings } from '@/features/blocks/inputs/number/components/NumberInputSettings'
import { EmailInputSettings } from '@/features/blocks/inputs/emailInput/components/EmailInputSettings'
import { UrlInputSettings } from '@/features/blocks/inputs/url/components/UrlInputSettings'
import { DateInputSettings } from '@/features/blocks/inputs/date/components/DateInputSettings'
import { PhoneInputSettings } from '@/features/blocks/inputs/phone/components/PhoneInputSettings'
import { GoogleSheetsSettings } from '@/features/blocks/integrations/googleSheets/components/GoogleSheetsSettings'
import { ChatwootSettings } from '@/features/blocks/integrations/chatwoot/components/ChatwootSettings'
type Props = {
block: BlockWithOptions
@@ -105,7 +105,7 @@ export const BlockSettings = ({
switch (block.type) {
case InputBlockType.TEXT: {
return (
<TextInputSettingsBody
<TextInputSettings
options={block.options}
onOptionsChange={updateOptions}
/>
@@ -113,7 +113,7 @@ export const BlockSettings = ({
}
case InputBlockType.NUMBER: {
return (
<NumberInputSettingsBody
<NumberInputSettings
options={block.options}
onOptionsChange={updateOptions}
/>
@@ -121,7 +121,7 @@ export const BlockSettings = ({
}
case InputBlockType.EMAIL: {
return (
<EmailInputSettingsBody
<EmailInputSettings
options={block.options}
onOptionsChange={updateOptions}
/>
@@ -129,7 +129,7 @@ export const BlockSettings = ({
}
case InputBlockType.URL: {
return (
<UrlInputSettingsBody
<UrlInputSettings
options={block.options}
onOptionsChange={updateOptions}
/>
@@ -137,7 +137,7 @@ export const BlockSettings = ({
}
case InputBlockType.DATE: {
return (
<DateInputSettingsBody
<DateInputSettings
options={block.options}
onOptionsChange={updateOptions}
/>
@@ -145,7 +145,7 @@ export const BlockSettings = ({
}
case InputBlockType.PHONE: {
return (
<PhoneNumberSettingsBody
<PhoneInputSettings
options={block.options}
onOptionsChange={updateOptions}
/>
@@ -231,7 +231,7 @@ export const BlockSettings = ({
}
case IntegrationBlockType.GOOGLE_SHEETS: {
return (
<GoogleSheetsSettingsBody
<GoogleSheetsSettings
options={block.options}
onOptionsChange={updateOptions}
blockId={block.id}
@@ -270,7 +270,7 @@ export const BlockSettings = ({
}
case IntegrationBlockType.CHATWOOT: {
return (
<ChatwootSettingsForm
<ChatwootSettings
options={block.options}
onOptionsChange={updateOptions}
/>

View File

@@ -1,6 +1,6 @@
import { chakra, Text, TextProps } from '@chakra-ui/react'
import React from 'react'
import { useTypebot } from '@/features/editor'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { byId } from '@typebot.io/lib'
type Props = {

View File

@@ -8,22 +8,24 @@ import {
} from '@chakra-ui/react'
import React, { memo, useCallback, useEffect, useRef, useState } from 'react'
import { Group } from '@typebot.io/schemas'
import {
Coordinates,
useGraph,
useGroupsCoordinates,
useBlockDnd,
} from '../../../providers'
import { BlockNodesList } from '../BlockNode/BlockNodesList'
import { BlockNodesList } from '../block/BlockNodesList'
import { isDefined, isEmpty, isNotDefined } from '@typebot.io/lib'
import { useTypebot, RightPanel, useEditor } from '@/features/editor'
import { GroupNodeContextMenu } from './GroupNodeContextMenu'
import { useDebounce } from 'use-debounce'
import { ContextMenu } from '@/components/ContextMenu'
import { setMultipleRefs } from '@/utils/helpers'
import { useDrag } from '@use-gesture/react'
import { GroupFocusToolbar } from './GroupFocusToolbar'
import { useOutsideClick } from '@/hooks/useOutsideClick'
import {
RightPanel,
useEditor,
} from '@/features/editor/providers/EditorProvider'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { useBlockDnd } from '@/features/graph/providers/GraphDndProvider'
import { useGraph } from '@/features/graph/providers/GraphProvider'
import { useGroupsCoordinates } from '@/features/graph/providers/GroupsCoordinateProvider'
import { setMultipleRefs } from '@/helpers/setMultipleRefs'
import { Coordinates } from '@/features/graph/types'
type Props = {
group: Group

View File

@@ -1,6 +1,6 @@
import { MenuList, MenuItem } from '@chakra-ui/react'
import { CopyIcon, TrashIcon } from '@/components/icons'
import { useTypebot } from '@/features/editor'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
export const GroupNodeContextMenu = ({
groupIndex,

View File

@@ -1,19 +1,19 @@
import { Flex, useColorModeValue } from '@chakra-ui/react'
import {
Coordinates,
useGraph,
NodePosition,
useDragDistance,
} from '../../../providers'
import { useTypebot } from '@/features/editor'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { ChoiceInputBlock, Item, ItemIndices } from '@typebot.io/schemas'
import React, { useRef, useState } from 'react'
import { SourceEndpoint } from '../../Endpoints/SourceEndpoint'
import { SourceEndpoint } from '../../endpoints/SourceEndpoint'
import { ItemNodeContent } from './ItemNodeContent'
import { ItemNodeContextMenu } from './ItemNodeContextMenu'
import { ContextMenu } from '@/components/ContextMenu'
import { setMultipleRefs } from '@/utils/helpers'
import { isDefined } from '@typebot.io/lib'
import { Coordinates } from '@/features/graph/types'
import {
NodePosition,
useDragDistance,
} from '@/features/graph/providers/GraphDndProvider'
import { useGraph } from '@/features/graph/providers/GraphProvider'
import { setMultipleRefs } from '@/helpers/setMultipleRefs'
type Props = {
item: Item

View File

@@ -1,5 +1,5 @@
import { ButtonsItemNode } from '@/features/blocks/inputs/buttons'
import { ConditionItemNode } from '@/features/blocks/logic/condition'
import { ButtonsItemNode } from '@/features/blocks/inputs/buttons/components/ButtonsItemNode'
import { ConditionItemNode } from '@/features/blocks/logic/condition/components/ConditionItemNode'
import { Item, ItemIndices, ItemType } from '@typebot.io/schemas'
import React from 'react'

View File

@@ -1,6 +1,6 @@
import { MenuList, MenuItem } from '@chakra-ui/react'
import { TrashIcon } from '@/components/icons'
import { useTypebot } from '@/features/editor'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { ItemIndices } from '@typebot.io/schemas'
type Props = {

View File

@@ -6,13 +6,7 @@ import {
useColorModeValue,
useEventListener,
} from '@chakra-ui/react'
import {
computeNearestPlaceholderIndex,
useBlockDnd,
Coordinates,
useGraph,
} from '../../../providers'
import { useTypebot } from '@/features/editor'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import {
BlockIndices,
BlockWithItems,
@@ -21,9 +15,15 @@ import {
} from '@typebot.io/schemas'
import React, { useEffect, useRef, useState } from 'react'
import { ItemNode } from './ItemNode'
import { SourceEndpoint } from '../../Endpoints'
import { PlaceholderNode } from '../PlaceholderNode'
import { isDefined } from '@typebot.io/lib'
import {
useBlockDnd,
computeNearestPlaceholderIndex,
} from '@/features/graph/providers/GraphDndProvider'
import { useGraph } from '@/features/graph/providers/GraphProvider'
import { Coordinates } from '@dnd-kit/utilities'
import { SourceEndpoint } from '../../endpoints/SourceEndpoint'
type Props = {
block: BlockWithItems

View File

@@ -1,6 +1,6 @@
import { Stack, IconButton, useColorModeValue } from '@chakra-ui/react'
import { PlusIcon, MinusIcon } from '@/components/icons'
import { headerHeight } from '@/features/editor'
import { headerHeight } from '@/features/editor/constants'
type Props = {
onZoomInClick: () => void

View File

@@ -1 +0,0 @@
export { Graph } from './Graph'

View File

@@ -0,0 +1,28 @@
import { Coordinates } from './types'
export const stubLength = 20
export const blockWidth = 300
export const blockAnchorsOffset = {
left: {
x: 0,
y: 20,
},
top: {
x: blockWidth / 2,
y: 0,
},
right: {
x: blockWidth,
y: 20,
},
}
export const graphPositionDefaultValue = (
firstGroupCoordinates: Coordinates
) => ({
x: 400 - firstGroupCoordinates.x,
y: 100 - firstGroupCoordinates.y,
scale: 1,
})
export const pathRadius = 20

View File

@@ -0,0 +1,22 @@
import { computeEdgePath } from './computeEdgePath'
import {
getAnchorsPosition,
GetAnchorsPositionProps,
} from './getAnchorsPosition'
export const computeConnectingEdgePath = ({
sourceGroupCoordinates,
targetGroupCoordinates,
sourceTop,
targetTop,
graphScale,
}: GetAnchorsPositionProps) => {
const anchorsPosition = getAnchorsPosition({
sourceGroupCoordinates,
targetGroupCoordinates,
sourceTop,
targetTop,
graphScale,
})
return computeEdgePath(anchorsPosition)
}

View File

@@ -0,0 +1,20 @@
import { roundCorners } from 'svg-round-corners'
import { pathRadius } from '../constants'
import { Coordinates } from '../types'
import { computeSourceCoordinates } from './computeSourceCoordinates'
import { computeTwoSegments } from './segments'
export const computeDropOffPath = (
sourcePosition: Coordinates,
sourceTop: number
) => {
const sourceCoord = computeSourceCoordinates(sourcePosition, sourceTop)
const segments = computeTwoSegments(sourceCoord, {
x: sourceCoord.x + 20,
y: sourceCoord.y + 80,
})
return roundCorners(
`M${sourceCoord.x},${sourceCoord.y} ${segments}`,
pathRadius
).path
}

View File

@@ -0,0 +1,22 @@
import { roundCorners } from 'svg-round-corners'
import { pathRadius } from '../constants'
import { AnchorsPositionProps } from '../types'
import { getSegments } from './segments'
export const computeEdgePath = ({
sourcePosition,
targetPosition,
sourceType,
totalSegments,
}: AnchorsPositionProps) => {
const segments = getSegments({
sourcePosition,
targetPosition,
sourceType,
totalSegments,
})
return roundCorners(
`M${sourcePosition.x},${sourcePosition.y} ${segments}`,
pathRadius
).path
}

View File

@@ -0,0 +1,35 @@
import { roundCorners } from 'svg-round-corners'
import { blockWidth, pathRadius } from '../constants'
import { Coordinates } from '../types'
import { computeThreeSegments } from './segments'
export const computeEdgePathToMouse = ({
sourceGroupCoordinates,
mousePosition,
sourceTop,
}: {
sourceGroupCoordinates: Coordinates
mousePosition: Coordinates
sourceTop: number
}): string => {
const sourcePosition = {
x:
mousePosition.x - sourceGroupCoordinates.x > blockWidth / 2
? sourceGroupCoordinates.x + blockWidth
: sourceGroupCoordinates.x,
y: sourceTop,
}
const sourceType =
mousePosition.x - sourceGroupCoordinates.x > blockWidth / 2
? 'right'
: 'left'
const segments = computeThreeSegments(
sourcePosition,
mousePosition,
sourceType
)
return roundCorners(
`M${sourcePosition.x},${sourcePosition.y} ${segments}`,
pathRadius
).path
}

View File

@@ -0,0 +1,10 @@
import { blockWidth } from '../constants'
import { Coordinates } from '../types'
export const computeSourceCoordinates = (
sourcePosition: Coordinates,
sourceTop: number
) => ({
x: sourcePosition.x + blockWidth,
y: sourceTop,
})

View File

@@ -0,0 +1,94 @@
import { blockAnchorsOffset, blockWidth, stubLength } from '../constants'
import { AnchorsPositionProps, Coordinates } from '../types'
import { computeSourceCoordinates } from './computeSourceCoordinates'
export type GetAnchorsPositionProps = {
sourceGroupCoordinates: Coordinates
targetGroupCoordinates: Coordinates
sourceTop: number
targetTop?: number
graphScale: number
}
export const getAnchorsPosition = ({
sourceGroupCoordinates,
targetGroupCoordinates,
sourceTop,
targetTop,
}: GetAnchorsPositionProps): AnchorsPositionProps => {
const sourcePosition = computeSourceCoordinates(
sourceGroupCoordinates,
sourceTop
)
let sourceType: 'right' | 'left' = 'right'
if (sourceGroupCoordinates.x > targetGroupCoordinates.x) {
sourcePosition.x = sourceGroupCoordinates.x
sourceType = 'left'
}
const { targetPosition, totalSegments } = computeGroupTargetPosition(
sourceGroupCoordinates,
targetGroupCoordinates,
sourcePosition.y,
targetTop
)
return { sourcePosition, targetPosition, sourceType, totalSegments }
}
const computeGroupTargetPosition = (
sourceGroupPosition: Coordinates,
targetGroupPosition: Coordinates,
sourceOffsetY: number,
targetOffsetY?: number
): { targetPosition: Coordinates; totalSegments: number } => {
const isTargetGroupBelow =
targetGroupPosition.y > sourceOffsetY &&
targetGroupPosition.x < sourceGroupPosition.x + blockWidth + stubLength &&
targetGroupPosition.x > sourceGroupPosition.x - blockWidth - stubLength
const isTargetGroupToTheRight = targetGroupPosition.x < sourceGroupPosition.x
const isTargettingGroup = !targetOffsetY
if (isTargetGroupBelow && isTargettingGroup) {
const isExterior =
targetGroupPosition.x <
sourceGroupPosition.x - blockWidth / 2 - stubLength ||
targetGroupPosition.x >
sourceGroupPosition.x + blockWidth / 2 + stubLength
const targetPosition = parseGroupAnchorPosition(targetGroupPosition, 'top')
return { totalSegments: isExterior ? 2 : 4, targetPosition }
} else {
const isExterior =
targetGroupPosition.x < sourceGroupPosition.x - blockWidth ||
targetGroupPosition.x > sourceGroupPosition.x + blockWidth
const targetPosition = parseGroupAnchorPosition(
targetGroupPosition,
isTargetGroupToTheRight ? 'right' : 'left',
targetOffsetY
)
return { totalSegments: isExterior ? 3 : 5, targetPosition }
}
}
const parseGroupAnchorPosition = (
blockPosition: Coordinates,
anchor: 'left' | 'top' | 'right',
targetOffsetY?: number
): Coordinates => {
switch (anchor) {
case 'left':
return {
x: blockPosition.x + blockAnchorsOffset.left.x,
y: targetOffsetY ?? blockPosition.y + blockAnchorsOffset.left.y,
}
case 'top':
return {
x: blockPosition.x + blockAnchorsOffset.top.x,
y: blockPosition.y + blockAnchorsOffset.top.y,
}
case 'right':
return {
x: blockPosition.x + blockAnchorsOffset.right.x,
y: targetOffsetY ?? blockPosition.y + blockAnchorsOffset.right.y,
}
}
}

View File

@@ -0,0 +1,92 @@
import { stubLength } from '../constants'
import { AnchorsPositionProps, Coordinates } from '../types'
export const getSegments = ({
sourcePosition,
targetPosition,
sourceType,
totalSegments,
}: AnchorsPositionProps) => {
switch (totalSegments) {
case 2:
return computeTwoSegments(sourcePosition, targetPosition)
case 3:
return computeThreeSegments(sourcePosition, targetPosition, sourceType)
case 4:
return computeFourSegments(sourcePosition, targetPosition, sourceType)
default:
return computeFiveSegments(sourcePosition, targetPosition, sourceType)
}
}
export const computeTwoSegments = (
sourcePosition: Coordinates,
targetPosition: Coordinates
) => {
const segments = []
segments.push(`L${targetPosition.x},${sourcePosition.y}`)
segments.push(`L${targetPosition.x},${targetPosition.y}`)
return segments.join(' ')
}
export const computeThreeSegments = (
sourcePosition: Coordinates,
targetPosition: Coordinates,
sourceType: 'right' | 'left'
) => {
const segments = []
const firstSegmentX =
sourceType === 'right'
? sourcePosition.x + (targetPosition.x - sourcePosition.x) / 2
: sourcePosition.x - (sourcePosition.x - targetPosition.x) / 2
segments.push(`L${firstSegmentX},${sourcePosition.y}`)
segments.push(`L${firstSegmentX},${targetPosition.y}`)
segments.push(`L${targetPosition.x},${targetPosition.y}`)
return segments.join(' ')
}
export const computeFourSegments = (
sourcePosition: Coordinates,
targetPosition: Coordinates,
sourceType: 'right' | 'left'
) => {
const segments = []
const firstSegmentX =
sourcePosition.x + (sourceType === 'right' ? stubLength : -stubLength)
segments.push(`L${firstSegmentX},${sourcePosition.y}`)
const secondSegmentY =
sourcePosition.y + (targetPosition.y - sourcePosition.y) - stubLength
segments.push(`L${firstSegmentX},${secondSegmentY}`)
segments.push(`L${targetPosition.x},${secondSegmentY}`)
segments.push(`L${targetPosition.x},${targetPosition.y}`)
return segments.join(' ')
}
export const computeFiveSegments = (
sourcePosition: Coordinates,
targetPosition: Coordinates,
sourceType: 'right' | 'left'
) => {
const segments = []
const firstSegmentX =
sourcePosition.x + (sourceType === 'right' ? stubLength : -stubLength)
segments.push(`L${firstSegmentX},${sourcePosition.y}`)
const firstSegmentY =
sourcePosition.y + (targetPosition.y - sourcePosition.y) / 2
segments.push(
`L${
sourcePosition.x + (sourceType === 'right' ? stubLength : -stubLength)
},${firstSegmentY}`
)
const secondSegmentX =
targetPosition.x - (sourceType === 'right' ? stubLength : -stubLength)
segments.push(`L${secondSegmentX},${firstSegmentY}`)
segments.push(`L${secondSegmentX},${targetPosition.y}`)
segments.push(`L${targetPosition.x},${targetPosition.y}`)
return segments.join(' ')
}

View File

@@ -1,12 +0,0 @@
export { Graph } from './components/Graph'
export {
ParentModalProvider,
GraphProvider,
GroupsCoordinatesProvider,
GraphDndProvider,
type Coordinates,
useGraph,
useBlockDnd,
useParentModal,
} from './providers'
export { parseNewBlock } from './utils'

View File

@@ -5,11 +5,7 @@ import {
useContext,
useState,
} from 'react'
export type Endpoint = {
id: string
y: number
}
import { Endpoint } from '../types'
export const endpointsContext = createContext<{
sourceEndpointYOffsets: Map<string, Endpoint>

View File

@@ -11,7 +11,7 @@ import {
useRef,
useState,
} from 'react'
import { Coordinates } from './GraphProvider'
import { Coordinates } from '../types'
type NodeElement = {
id: string

View File

@@ -1,4 +1,5 @@
import { Group, Edge, IdMap, Source, Block, Target } from '@typebot.io/schemas'
import { Coordinates } from '@dnd-kit/utilities'
import { Edge } from '@typebot.io/schemas'
import {
createContext,
Dispatch,
@@ -7,53 +8,11 @@ import {
useContext,
useState,
} from 'react'
export const stubLength = 20
export const blockWidth = 300
export const blockAnchorsOffset = {
left: {
x: 0,
y: 20,
},
top: {
x: blockWidth / 2,
y: 0,
},
right: {
x: blockWidth,
y: 20,
},
}
export type Coordinates = { x: number; y: number }
import { graphPositionDefaultValue } from '../constants'
import { ConnectingIds } from '../types'
type Position = Coordinates & { scale: number }
export type Anchor = {
coordinates: Coordinates
}
export type Node = Omit<Group, 'blocks'> & {
blocks: (Block & {
sourceAnchorsPosition: { left: Coordinates; right: Coordinates }
})[]
}
export const graphPositionDefaultValue = (
firstGroupCoordinates: Coordinates
) => ({
x: 400 - firstGroupCoordinates.x,
y: 100 - firstGroupCoordinates.y,
scale: 1,
})
export type ConnectingIds = {
source: Source
target?: Target
}
export type GroupsCoordinates = IdMap<Coordinates>
type PreviewingBlock = {
id: string
groupId: string

View File

@@ -7,7 +7,7 @@ import {
createContext,
useCallback,
} from 'react'
import { GroupsCoordinates, Coordinates } from './GraphProvider'
import { Coordinates, GroupsCoordinates } from '../types'
const groupsCoordinatesContext = createContext<{
groupsCoordinates: GroupsCoordinates

View File

@@ -1,4 +0,0 @@
export * from './GraphDndProvider'
export * from './GraphProvider'
export * from './GroupsCoordinateProvider'
export * from './ParentModalProvider'

View File

@@ -0,0 +1,32 @@
import { Group, Block, Source, IdMap, Target } from '@typebot.io/schemas'
export type Coordinates = { x: number; y: number }
export type Anchor = {
coordinates: Coordinates
}
export type Node = Omit<Group, 'blocks'> & {
blocks: (Block & {
sourceAnchorsPosition: { left: Coordinates; right: Coordinates }
})[]
}
export type ConnectingIds = {
source: Source
target?: Target
}
export type GroupsCoordinates = IdMap<Coordinates>
export type AnchorsPositionProps = {
sourcePosition: Coordinates
targetPosition: Coordinates
sourceType: 'right' | 'left'
totalSegments: number
}
export type Endpoint = {
id: string
y: number
}

View File

@@ -1,437 +0,0 @@
import {
Block,
BlockOptions,
BlockWithOptionsType,
BubbleBlockContent,
BubbleBlockType,
defaultChatwootOptions,
defaultChoiceInputOptions,
defaultConditionContent,
defaultDateInputOptions,
defaultEmailInputOptions,
defaultEmbedBubbleContent,
defaultFileInputOptions,
defaultGoogleAnalyticsOptions,
defaultGoogleSheetsOptions,
defaultImageBubbleContent,
defaultAudioBubbleContent,
defaultNumberInputOptions,
defaultPaymentInputOptions,
defaultPhoneInputOptions,
defaultRatingInputOptions,
defaultRedirectOptions,
defaultSendEmailOptions,
defaultSetVariablesOptions,
defaultTextBubbleContent,
defaultTextInputOptions,
defaultUrlInputOptions,
defaultVideoBubbleContent,
defaultWebhookOptions,
DraggableBlock,
DraggableBlockType,
InputBlockType,
IntegrationBlockType,
Item,
ItemType,
LogicBlockType,
defaultWaitOptions,
defaultScriptOptions,
} from '@typebot.io/schemas'
import {
stubLength,
blockWidth,
blockAnchorsOffset,
Coordinates,
} from './providers'
import { roundCorners } from 'svg-round-corners'
import { AnchorsPositionProps } from './components/Edges/Edge'
import { createId } from '@paralleldrive/cuid2'
import {
isBubbleBlockType,
blockTypeHasOption,
blockTypeHasWebhook,
blockTypeHasItems,
isChoiceInput,
isConditionBlock,
isDefined,
} from '@typebot.io/lib'
const roundSize = 20
export const computeDropOffPath = (
sourcePosition: Coordinates,
sourceTop: number
) => {
const sourceCoord = computeSourceCoordinates(sourcePosition, sourceTop)
const segments = computeTwoSegments(sourceCoord, {
x: sourceCoord.x + 20,
y: sourceCoord.y + 80,
})
return roundCorners(
`M${sourceCoord.x},${sourceCoord.y} ${segments}`,
roundSize
).path
}
export const computeSourceCoordinates = (
sourcePosition: Coordinates,
sourceTop: number
) => ({
x: sourcePosition.x + blockWidth,
y: sourceTop,
})
const getSegments = ({
sourcePosition,
targetPosition,
sourceType,
totalSegments,
}: AnchorsPositionProps) => {
switch (totalSegments) {
case 2:
return computeTwoSegments(sourcePosition, targetPosition)
case 3:
return computeThreeSegments(sourcePosition, targetPosition, sourceType)
case 4:
return computeFourSegments(sourcePosition, targetPosition, sourceType)
default:
return computeFiveSegments(sourcePosition, targetPosition, sourceType)
}
}
const computeTwoSegments = (
sourcePosition: Coordinates,
targetPosition: Coordinates
) => {
const segments = []
segments.push(`L${targetPosition.x},${sourcePosition.y}`)
segments.push(`L${targetPosition.x},${targetPosition.y}`)
return segments.join(' ')
}
const computeThreeSegments = (
sourcePosition: Coordinates,
targetPosition: Coordinates,
sourceType: 'right' | 'left'
) => {
const segments = []
const firstSegmentX =
sourceType === 'right'
? sourcePosition.x + (targetPosition.x - sourcePosition.x) / 2
: sourcePosition.x - (sourcePosition.x - targetPosition.x) / 2
segments.push(`L${firstSegmentX},${sourcePosition.y}`)
segments.push(`L${firstSegmentX},${targetPosition.y}`)
segments.push(`L${targetPosition.x},${targetPosition.y}`)
return segments.join(' ')
}
const computeFourSegments = (
sourcePosition: Coordinates,
targetPosition: Coordinates,
sourceType: 'right' | 'left'
) => {
const segments = []
const firstSegmentX =
sourcePosition.x + (sourceType === 'right' ? stubLength : -stubLength)
segments.push(`L${firstSegmentX},${sourcePosition.y}`)
const secondSegmentY =
sourcePosition.y + (targetPosition.y - sourcePosition.y) - stubLength
segments.push(`L${firstSegmentX},${secondSegmentY}`)
segments.push(`L${targetPosition.x},${secondSegmentY}`)
segments.push(`L${targetPosition.x},${targetPosition.y}`)
return segments.join(' ')
}
const computeFiveSegments = (
sourcePosition: Coordinates,
targetPosition: Coordinates,
sourceType: 'right' | 'left'
) => {
const segments = []
const firstSegmentX =
sourcePosition.x + (sourceType === 'right' ? stubLength : -stubLength)
segments.push(`L${firstSegmentX},${sourcePosition.y}`)
const firstSegmentY =
sourcePosition.y + (targetPosition.y - sourcePosition.y) / 2
segments.push(
`L${
sourcePosition.x + (sourceType === 'right' ? stubLength : -stubLength)
},${firstSegmentY}`
)
const secondSegmentX =
targetPosition.x - (sourceType === 'right' ? stubLength : -stubLength)
segments.push(`L${secondSegmentX},${firstSegmentY}`)
segments.push(`L${secondSegmentX},${targetPosition.y}`)
segments.push(`L${targetPosition.x},${targetPosition.y}`)
return segments.join(' ')
}
type GetAnchorsPositionParams = {
sourceGroupCoordinates: Coordinates
targetGroupCoordinates: Coordinates
sourceTop: number
targetTop?: number
graphScale: number
}
export const getAnchorsPosition = ({
sourceGroupCoordinates,
targetGroupCoordinates,
sourceTop,
targetTop,
}: GetAnchorsPositionParams): AnchorsPositionProps => {
const sourcePosition = computeSourceCoordinates(
sourceGroupCoordinates,
sourceTop
)
let sourceType: 'right' | 'left' = 'right'
if (sourceGroupCoordinates.x > targetGroupCoordinates.x) {
sourcePosition.x = sourceGroupCoordinates.x
sourceType = 'left'
}
const { targetPosition, totalSegments } = computeGroupTargetPosition(
sourceGroupCoordinates,
targetGroupCoordinates,
sourcePosition.y,
targetTop
)
return { sourcePosition, targetPosition, sourceType, totalSegments }
}
const computeGroupTargetPosition = (
sourceGroupPosition: Coordinates,
targetGroupPosition: Coordinates,
sourceOffsetY: number,
targetOffsetY?: number
): { targetPosition: Coordinates; totalSegments: number } => {
const isTargetGroupBelow =
targetGroupPosition.y > sourceOffsetY &&
targetGroupPosition.x < sourceGroupPosition.x + blockWidth + stubLength &&
targetGroupPosition.x > sourceGroupPosition.x - blockWidth - stubLength
const isTargetGroupToTheRight = targetGroupPosition.x < sourceGroupPosition.x
const isTargettingGroup = !targetOffsetY
if (isTargetGroupBelow && isTargettingGroup) {
const isExterior =
targetGroupPosition.x <
sourceGroupPosition.x - blockWidth / 2 - stubLength ||
targetGroupPosition.x >
sourceGroupPosition.x + blockWidth / 2 + stubLength
const targetPosition = parseGroupAnchorPosition(targetGroupPosition, 'top')
return { totalSegments: isExterior ? 2 : 4, targetPosition }
} else {
const isExterior =
targetGroupPosition.x < sourceGroupPosition.x - blockWidth ||
targetGroupPosition.x > sourceGroupPosition.x + blockWidth
const targetPosition = parseGroupAnchorPosition(
targetGroupPosition,
isTargetGroupToTheRight ? 'right' : 'left',
targetOffsetY
)
return { totalSegments: isExterior ? 3 : 5, targetPosition }
}
}
const parseGroupAnchorPosition = (
blockPosition: Coordinates,
anchor: 'left' | 'top' | 'right',
targetOffsetY?: number
): Coordinates => {
switch (anchor) {
case 'left':
return {
x: blockPosition.x + blockAnchorsOffset.left.x,
y: targetOffsetY ?? blockPosition.y + blockAnchorsOffset.left.y,
}
case 'top':
return {
x: blockPosition.x + blockAnchorsOffset.top.x,
y: blockPosition.y + blockAnchorsOffset.top.y,
}
case 'right':
return {
x: blockPosition.x + blockAnchorsOffset.right.x,
y: targetOffsetY ?? blockPosition.y + blockAnchorsOffset.right.y,
}
}
}
export const computeEdgePath = ({
sourcePosition,
targetPosition,
sourceType,
totalSegments,
}: AnchorsPositionProps) => {
const segments = getSegments({
sourcePosition,
targetPosition,
sourceType,
totalSegments,
})
return roundCorners(
`M${sourcePosition.x},${sourcePosition.y} ${segments}`,
roundSize
).path
}
export const computeConnectingEdgePath = ({
sourceGroupCoordinates,
targetGroupCoordinates,
sourceTop,
targetTop,
graphScale,
}: GetAnchorsPositionParams) => {
const anchorsPosition = getAnchorsPosition({
sourceGroupCoordinates,
targetGroupCoordinates,
sourceTop,
targetTop,
graphScale,
})
return computeEdgePath(anchorsPosition)
}
export const computeEdgePathToMouse = ({
sourceGroupCoordinates,
mousePosition,
sourceTop,
}: {
sourceGroupCoordinates: Coordinates
mousePosition: Coordinates
sourceTop: number
}): string => {
const sourcePosition = {
x:
mousePosition.x - sourceGroupCoordinates.x > blockWidth / 2
? sourceGroupCoordinates.x + blockWidth
: sourceGroupCoordinates.x,
y: sourceTop,
}
const sourceType =
mousePosition.x - sourceGroupCoordinates.x > blockWidth / 2
? 'right'
: 'left'
const segments = computeThreeSegments(
sourcePosition,
mousePosition,
sourceType
)
return roundCorners(
`M${sourcePosition.x},${sourcePosition.y} ${segments}`,
roundSize
).path
}
export const parseNewBlock = (
type: DraggableBlockType,
groupId: string
): DraggableBlock => {
const id = createId()
return {
id,
groupId,
type,
content: isBubbleBlockType(type) ? parseDefaultContent(type) : undefined,
options: blockTypeHasOption(type)
? parseDefaultBlockOptions(type)
: undefined,
webhookId: blockTypeHasWebhook(type) ? createId() : undefined,
items: blockTypeHasItems(type) ? parseDefaultItems(type, id) : undefined,
} as DraggableBlock
}
const parseDefaultItems = (
type: LogicBlockType.CONDITION | InputBlockType.CHOICE,
blockId: string
): Item[] => {
switch (type) {
case InputBlockType.CHOICE:
return [{ id: createId(), blockId, type: ItemType.BUTTON }]
case LogicBlockType.CONDITION:
return [
{
id: createId(),
blockId,
type: ItemType.CONDITION,
content: defaultConditionContent,
},
]
}
}
const parseDefaultContent = (type: BubbleBlockType): BubbleBlockContent => {
switch (type) {
case BubbleBlockType.TEXT:
return defaultTextBubbleContent
case BubbleBlockType.IMAGE:
return defaultImageBubbleContent
case BubbleBlockType.VIDEO:
return defaultVideoBubbleContent
case BubbleBlockType.EMBED:
return defaultEmbedBubbleContent
case BubbleBlockType.AUDIO:
return defaultAudioBubbleContent
}
}
const parseDefaultBlockOptions = (type: BlockWithOptionsType): BlockOptions => {
switch (type) {
case InputBlockType.TEXT:
return defaultTextInputOptions
case InputBlockType.NUMBER:
return defaultNumberInputOptions
case InputBlockType.EMAIL:
return defaultEmailInputOptions
case InputBlockType.DATE:
return defaultDateInputOptions
case InputBlockType.PHONE:
return defaultPhoneInputOptions
case InputBlockType.URL:
return defaultUrlInputOptions
case InputBlockType.CHOICE:
return defaultChoiceInputOptions
case InputBlockType.PAYMENT:
return defaultPaymentInputOptions
case InputBlockType.RATING:
return defaultRatingInputOptions
case InputBlockType.FILE:
return defaultFileInputOptions
case LogicBlockType.SET_VARIABLE:
return defaultSetVariablesOptions
case LogicBlockType.REDIRECT:
return defaultRedirectOptions
case LogicBlockType.SCRIPT:
return defaultScriptOptions
case LogicBlockType.WAIT:
return defaultWaitOptions
case LogicBlockType.JUMP:
return {}
case LogicBlockType.TYPEBOT_LINK:
return {}
case IntegrationBlockType.GOOGLE_SHEETS:
return defaultGoogleSheetsOptions
case IntegrationBlockType.GOOGLE_ANALYTICS:
return defaultGoogleAnalyticsOptions
case IntegrationBlockType.ZAPIER:
case IntegrationBlockType.PABBLY_CONNECT:
case IntegrationBlockType.MAKE_COM:
case IntegrationBlockType.WEBHOOK:
return defaultWebhookOptions
case IntegrationBlockType.EMAIL:
return defaultSendEmailOptions
case IntegrationBlockType.CHATWOOT:
return defaultChatwootOptions
case IntegrationBlockType.OPEN_AI:
return {}
}
}
export const hasDefaultConnector = (block: Block) =>
(!isChoiceInput(block) && !isConditionBlock(block)) ||
(block.type === InputBlockType.CHOICE &&
isDefined(block.options.dynamicVariableId))