@@ -1,11 +1,4 @@
|
||||
import { Flex, HStack, Tooltip, useColorModeValue } from '@chakra-ui/react'
|
||||
import {
|
||||
BubbleBlockType,
|
||||
DraggableBlockType,
|
||||
InputBlockType,
|
||||
IntegrationBlockType,
|
||||
LogicBlockType,
|
||||
} from '@typebot.io/schemas'
|
||||
import { useBlockDnd } from '@/features/graph/providers/GraphDndProvider'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { BlockIcon } from './BlockIcon'
|
||||
@@ -15,13 +8,18 @@ import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { BlockLabel } from './BlockLabel'
|
||||
import { LockTag } from '@/features/billing/components/LockTag'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
import { BlockV6 } from '@typebot.io/schemas'
|
||||
|
||||
type Props = {
|
||||
type: DraggableBlockType
|
||||
type: BlockV6['type']
|
||||
tooltip?: string
|
||||
isDisabled?: boolean
|
||||
children: React.ReactNode
|
||||
onMouseDown: (e: React.MouseEvent, type: DraggableBlockType) => void
|
||||
onMouseDown: (e: React.MouseEvent, type: BlockV6['type']) => void
|
||||
}
|
||||
|
||||
export const BlockCard = (
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { StackProps, HStack, useColorModeValue } from '@chakra-ui/react'
|
||||
import { BlockType } from '@typebot.io/schemas'
|
||||
import { BlockIcon } from './BlockIcon'
|
||||
import { BlockLabel } from './BlockLabel'
|
||||
import { BlockV6 } from '@typebot.io/schemas'
|
||||
|
||||
export const BlockCardOverlay = ({
|
||||
type,
|
||||
...props
|
||||
}: StackProps & { type: BlockType }) => {
|
||||
}: StackProps & { type: BlockV6['type'] }) => {
|
||||
return (
|
||||
<HStack
|
||||
borderWidth="1px"
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import { IconProps, useColorModeValue } from '@chakra-ui/react'
|
||||
import {
|
||||
BubbleBlockType,
|
||||
InputBlockType,
|
||||
IntegrationBlockType,
|
||||
LogicBlockType,
|
||||
BlockType,
|
||||
} from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { FlagIcon, SendEmailIcon, WebhookIcon } from '@/components/icons'
|
||||
import { WaitIcon } from '@/features/blocks/logic/wait/components/WaitIcon'
|
||||
@@ -41,8 +34,13 @@ import { AbTestIcon } from '@/features/blocks/logic/abTest/components/AbTestIcon
|
||||
import { PictureChoiceIcon } from '@/features/blocks/inputs/pictureChoice/components/PictureChoiceIcon'
|
||||
import { PixelLogo } from '@/features/blocks/integrations/pixel/components/PixelLogo'
|
||||
import { ZemanticAiLogo } from '@/features/blocks/integrations/zemanticAi/ZemanticAiLogo'
|
||||
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
import { Block } from '@typebot.io/schemas'
|
||||
|
||||
type BlockIconProps = { type: BlockType } & IconProps
|
||||
type BlockIconProps = { type: Block['type'] } & IconProps
|
||||
|
||||
export const BlockIcon = ({ type, ...props }: BlockIconProps): JSX.Element => {
|
||||
const blue = useColorModeValue('blue.500', 'blue.300')
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import {
|
||||
BubbleBlockType,
|
||||
InputBlockType,
|
||||
IntegrationBlockType,
|
||||
LogicBlockType,
|
||||
BlockType,
|
||||
} from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
import { Block } from '@typebot.io/schemas'
|
||||
|
||||
type Props = { type: BlockType }
|
||||
type Props = { type: Block['type'] }
|
||||
|
||||
export const BlockLabel = ({ type }: Props): JSX.Element => {
|
||||
const { t } = useTranslate()
|
||||
|
||||
@@ -10,13 +10,6 @@ import {
|
||||
Fade,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react'
|
||||
import {
|
||||
BubbleBlockType,
|
||||
DraggableBlockType,
|
||||
InputBlockType,
|
||||
IntegrationBlockType,
|
||||
LogicBlockType,
|
||||
} from '@typebot.io/schemas'
|
||||
import { useBlockDnd } from '@/features/graph/providers/GraphDndProvider'
|
||||
import React, { useState } from 'react'
|
||||
import { BlockCard } from './BlockCard'
|
||||
@@ -24,6 +17,11 @@ import { LockedIcon, UnlockedIcon } from '@/components/icons'
|
||||
import { BlockCardOverlay } from './BlockCardOverlay'
|
||||
import { headerHeight } from '../constants'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
import { BlockV6 } from '@typebot.io/schemas'
|
||||
|
||||
export const BlocksSideBar = () => {
|
||||
const { t } = useTranslate()
|
||||
@@ -47,7 +45,7 @@ export const BlocksSideBar = () => {
|
||||
}
|
||||
useEventListener('mousemove', handleMouseMove)
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent, type: DraggableBlockType) => {
|
||||
const handleMouseDown = (e: React.MouseEvent, type: BlockV6['type']) => {
|
||||
const element = e.currentTarget as HTMLDivElement
|
||||
const rect = element.getBoundingClientRect()
|
||||
setPosition({ x: rect.left, y: rect.top })
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Graph } from '@/features/graph/components/Graph'
|
||||
import { GraphDndProvider } from '@/features/graph/providers/GraphDndProvider'
|
||||
import { GraphProvider } from '@/features/graph/providers/GraphProvider'
|
||||
import { GroupsCoordinatesProvider } from '@/features/graph/providers/GroupsCoordinateProvider'
|
||||
import { EventsCoordinatesProvider } from '@/features/graph/providers/EventsCoordinateProvider'
|
||||
|
||||
export const EditorPage = () => {
|
||||
const { typebot, isReadOnly } = useTypebot()
|
||||
@@ -42,9 +43,11 @@ export const EditorPage = () => {
|
||||
{!isReadOnly && <BlocksSideBar />}
|
||||
<GraphProvider isReadOnly={isReadOnly}>
|
||||
<GroupsCoordinatesProvider groups={typebot.groups}>
|
||||
<Graph flex="1" typebot={typebot} key={typebot.id} />
|
||||
<BoardMenuButton pos="absolute" right="40px" top="20px" />
|
||||
<RightPanel />
|
||||
<EventsCoordinatesProvider events={typebot.events}>
|
||||
<Graph flex="1" typebot={typebot} key={typebot.id} />
|
||||
<BoardMenuButton pos="absolute" right="40px" top="20px" />
|
||||
<RightPanel />
|
||||
</EventsCoordinatesProvider>
|
||||
</GroupsCoordinatesProvider>
|
||||
</GraphProvider>
|
||||
</GraphDndProvider>
|
||||
|
||||
@@ -46,7 +46,12 @@ export const TypebotHeader = () => {
|
||||
canRedo,
|
||||
isSavingLoading,
|
||||
} = useTypebot()
|
||||
const { setRightPanel, rightPanel, setStartPreviewAtGroup } = useEditor()
|
||||
const {
|
||||
setRightPanel,
|
||||
rightPanel,
|
||||
setStartPreviewAtGroup,
|
||||
setStartPreviewAtEvent,
|
||||
} = useEditor()
|
||||
const [isUndoShortcutTooltipOpen, setUndoShortcutTooltipOpen] =
|
||||
useState(false)
|
||||
const hideUndoShortcutTooltipLater = useDebouncedCallback(() => {
|
||||
@@ -62,6 +67,7 @@ export const TypebotHeader = () => {
|
||||
|
||||
const handlePreviewClick = async () => {
|
||||
setStartPreviewAtGroup(undefined)
|
||||
setStartPreviewAtEvent(undefined)
|
||||
save().then()
|
||||
setRightPanel(RightPanel.PREVIEW)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import test, { expect } from '@playwright/test'
|
||||
import { defaultTextInputOptions, InputBlockType } from '@typebot.io/schemas'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import {
|
||||
createTypebots,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
} from '@typebot.io/lib/playwright/databaseActions'
|
||||
import { parseDefaultGroupWithBlock } from '@typebot.io/lib/playwright/databaseHelpers'
|
||||
import { getTestAsset } from '@/test/utils/playwright'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
|
||||
test.describe.configure({ mode: 'parallel' })
|
||||
|
||||
@@ -24,20 +24,20 @@ test('Edges connection should work', async ({ page }) => {
|
||||
})
|
||||
await page.dragAndDrop(
|
||||
'text=Text >> nth=0',
|
||||
'[data-testid="group"] >> nth=1',
|
||||
'[data-testid="group"] >> nth=0',
|
||||
{
|
||||
targetPosition: { x: 100, y: 50 },
|
||||
}
|
||||
)
|
||||
await page.dragAndDrop(
|
||||
'[data-testid="endpoint"]',
|
||||
'[data-testid="group"] >> nth=1',
|
||||
'[data-testid="group"] >> nth=0',
|
||||
{ targetPosition: { x: 100, y: 10 } }
|
||||
)
|
||||
await expect(page.locator('[data-testid="edge"]')).toBeVisible()
|
||||
await page.dragAndDrop(
|
||||
'[data-testid="endpoint"]',
|
||||
'[data-testid="group"] >> nth=1'
|
||||
'[data-testid="group"] >> nth=0'
|
||||
)
|
||||
await expect(page.locator('[data-testid="edge"]')).toBeVisible()
|
||||
await page.dragAndDrop('text=Date', '#editor-container', {
|
||||
@@ -45,7 +45,7 @@ test('Edges connection should work', async ({ page }) => {
|
||||
})
|
||||
await page.dragAndDrop(
|
||||
'[data-testid="endpoint"] >> nth=2',
|
||||
'[data-testid="group"] >> nth=2',
|
||||
'[data-testid="group"] >> nth=1',
|
||||
{
|
||||
targetPosition: { x: 100, y: 10 },
|
||||
}
|
||||
@@ -72,17 +72,17 @@ test('Drag and drop blocks and items should work', async ({ page }) => {
|
||||
|
||||
// Blocks dnd
|
||||
await page.goto(`/typebots/${typebotId}/edit`)
|
||||
await expect(page.locator('[data-testid="block"] >> nth=0')).toHaveText(
|
||||
'Hello!'
|
||||
)
|
||||
await page.dragAndDrop('text=Hello', '[data-testid="block"] >> nth=2', {
|
||||
targetPosition: { x: 100, y: 0 },
|
||||
})
|
||||
await expect(page.locator('[data-testid="block"] >> nth=1')).toHaveText(
|
||||
'Hello!'
|
||||
)
|
||||
await page.dragAndDrop('text=Hello', '[data-testid="block"] >> nth=3', {
|
||||
targetPosition: { x: 100, y: 0 },
|
||||
})
|
||||
await expect(page.locator('[data-testid="block"] >> nth=2')).toHaveText(
|
||||
'Hello!'
|
||||
)
|
||||
await page.dragAndDrop('text=Hello', 'text=Group #2')
|
||||
await expect(page.locator('[data-testid="block"] >> nth=3')).toHaveText(
|
||||
await expect(page.locator('[data-testid="block"] >> nth=2')).toHaveText(
|
||||
'Hello!'
|
||||
)
|
||||
|
||||
@@ -120,7 +120,6 @@ test('Undo / Redo and Zoom buttons should work', async ({ page }) => {
|
||||
id: typebotId,
|
||||
...parseDefaultGroupWithBlock({
|
||||
type: InputBlockType.TEXT,
|
||||
options: defaultTextInputOptions,
|
||||
}),
|
||||
},
|
||||
])
|
||||
@@ -129,7 +128,7 @@ test('Undo / Redo and Zoom buttons should work', async ({ page }) => {
|
||||
await page.click('text=Group #1', { button: 'right' })
|
||||
await page.click('text=Duplicate')
|
||||
await expect(page.locator('text="Group #1"')).toBeVisible()
|
||||
await expect(page.locator('text="Group #1 copy"')).toBeVisible()
|
||||
await expect(page.locator('text="Group #1 (1)"')).toBeVisible()
|
||||
await page.click('text="Group #1"', { button: 'right' })
|
||||
await page.click('text=Delete')
|
||||
await expect(page.locator('text="Group #1"')).toBeHidden()
|
||||
@@ -140,18 +139,18 @@ test('Undo / Redo and Zoom buttons should work', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Zoom in' }).click()
|
||||
await expect(page.getByTestId('graph')).toHaveAttribute(
|
||||
'style',
|
||||
/scale\(1\.2\);$/
|
||||
/scale\(1\.2\)/
|
||||
)
|
||||
await page.getByRole('button', { name: 'Zoom in' }).click()
|
||||
await expect(page.getByTestId('graph')).toHaveAttribute(
|
||||
'style',
|
||||
/scale\(1\.4\);$/
|
||||
/scale\(1\.4\)/
|
||||
)
|
||||
await page.getByRole('button', { name: 'Zoom out' }).dblclick()
|
||||
await page.getByRole('button', { name: 'Zoom out' }).dblclick()
|
||||
await expect(page.getByTestId('graph')).toHaveAttribute(
|
||||
'style',
|
||||
/scale\(0\.6\);$/
|
||||
/scale\(0\.6\)/
|
||||
)
|
||||
})
|
||||
|
||||
@@ -163,7 +162,6 @@ test('Rename and icon change should work', async ({ page }) => {
|
||||
name: 'My awesome typebot',
|
||||
...parseDefaultGroupWithBlock({
|
||||
type: InputBlockType.TEXT,
|
||||
options: defaultTextInputOptions,
|
||||
}),
|
||||
},
|
||||
])
|
||||
@@ -194,7 +192,7 @@ test('Preview from group should work', async ({ page }) => {
|
||||
await page.goto(`/typebots/${typebotId}/edit`)
|
||||
await page
|
||||
.getByTestId('group')
|
||||
.nth(1)
|
||||
.nth(0)
|
||||
.click({ position: { x: 100, y: 10 } })
|
||||
await page.click('[aria-label="Preview bot from this group"]')
|
||||
await expect(
|
||||
@@ -202,7 +200,7 @@ test('Preview from group should work', async ({ page }) => {
|
||||
).toBeVisible()
|
||||
await page
|
||||
.getByTestId('group')
|
||||
.nth(2)
|
||||
.nth(1)
|
||||
.click({ position: { x: 100, y: 10 } })
|
||||
await page.click('[aria-label="Preview bot from this group"]')
|
||||
await expect(
|
||||
@@ -223,8 +221,8 @@ test('Published typebot menu should work', async ({ page }) => {
|
||||
name: 'My awesome typebot',
|
||||
...parseDefaultGroupWithBlock({
|
||||
type: InputBlockType.TEXT,
|
||||
options: defaultTextInputOptions,
|
||||
}),
|
||||
version: '6',
|
||||
},
|
||||
])
|
||||
await page.goto(`/typebots/${typebotId}/edit`)
|
||||
|
||||
@@ -16,6 +16,8 @@ const editorContext = createContext<{
|
||||
setRightPanel: Dispatch<SetStateAction<RightPanel | undefined>>
|
||||
startPreviewAtGroup: string | undefined
|
||||
setStartPreviewAtGroup: Dispatch<SetStateAction<string | undefined>>
|
||||
startPreviewAtEvent: string | undefined
|
||||
setStartPreviewAtEvent: Dispatch<SetStateAction<string | undefined>>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
@@ -23,6 +25,7 @@ const editorContext = createContext<{
|
||||
export const EditorProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [rightPanel, setRightPanel] = useState<RightPanel>()
|
||||
const [startPreviewAtGroup, setStartPreviewAtGroup] = useState<string>()
|
||||
const [startPreviewAtEvent, setStartPreviewAtEvent] = useState<string>()
|
||||
|
||||
return (
|
||||
<editorContext.Provider
|
||||
@@ -31,6 +34,8 @@ export const EditorProvider = ({ children }: { children: ReactNode }) => {
|
||||
setRightPanel,
|
||||
startPreviewAtGroup,
|
||||
setStartPreviewAtGroup,
|
||||
startPreviewAtEvent,
|
||||
setStartPreviewAtEvent,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PublicTypebot, Typebot } from '@typebot.io/schemas'
|
||||
import { PublicTypebot, PublicTypebotV6, TypebotV6 } from '@typebot.io/schemas'
|
||||
import { Router, useRouter } from 'next/router'
|
||||
import {
|
||||
createContext,
|
||||
@@ -23,12 +23,13 @@ import { areTypebotsEqual } from '@/features/publish/helpers/areTypebotsEqual'
|
||||
import { isPublished as isPublishedHelper } from '@/features/publish/helpers/isPublished'
|
||||
import { convertPublicTypebotToTypebot } from '@/features/publish/helpers/convertPublicTypebotToTypebot'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { EventsActions, eventsActions } from './typebotActions/events'
|
||||
|
||||
const autoSaveTimeout = 10000
|
||||
|
||||
type UpdateTypebotPayload = Partial<
|
||||
Pick<
|
||||
Typebot,
|
||||
TypebotV6,
|
||||
| 'theme'
|
||||
| 'selectedThemeTemplateId'
|
||||
| 'settings'
|
||||
@@ -43,17 +44,18 @@ type UpdateTypebotPayload = Partial<
|
||||
>
|
||||
|
||||
export type SetTypebot = (
|
||||
newPresent: Typebot | ((current: Typebot) => Typebot)
|
||||
newPresent: TypebotV6 | ((current: TypebotV6) => TypebotV6)
|
||||
) => void
|
||||
|
||||
const typebotContext = createContext<
|
||||
{
|
||||
typebot?: Typebot
|
||||
publishedTypebot?: PublicTypebot
|
||||
typebot?: TypebotV6
|
||||
publishedTypebot?: PublicTypebotV6
|
||||
publishedTypebotVersion?: PublicTypebot['version']
|
||||
isReadOnly?: boolean
|
||||
isPublished: boolean
|
||||
isSavingLoading: boolean
|
||||
save: () => Promise<Typebot | undefined>
|
||||
save: () => Promise<TypebotV6 | undefined>
|
||||
undo: () => void
|
||||
redo: () => void
|
||||
canRedo: boolean
|
||||
@@ -61,13 +63,14 @@ const typebotContext = createContext<
|
||||
updateTypebot: (props: {
|
||||
updates: UpdateTypebotPayload
|
||||
save?: boolean
|
||||
}) => Promise<Typebot | undefined>
|
||||
}) => Promise<TypebotV6 | undefined>
|
||||
restorePublishedTypebot: () => void
|
||||
} & GroupsActions &
|
||||
BlocksActions &
|
||||
ItemsActions &
|
||||
VariablesActions &
|
||||
EdgesActions
|
||||
EdgesActions &
|
||||
EventsActions
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
>({})
|
||||
@@ -87,27 +90,50 @@ export const TypebotProvider = ({
|
||||
isLoading: isFetchingTypebot,
|
||||
refetch: refetchTypebot,
|
||||
} = trpc.typebot.getTypebot.useQuery(
|
||||
{ typebotId: typebotId as string },
|
||||
{ typebotId: typebotId as string, migrateToLatestVersion: true },
|
||||
{
|
||||
enabled: isDefined(typebotId),
|
||||
retry: 0,
|
||||
onError: (error) => {
|
||||
if (error.data?.httpStatus === 404) {
|
||||
showToast({
|
||||
status: 'info',
|
||||
description: "Couldn't find typebot.",
|
||||
description: "Couldn't find typebot. Redirecting...",
|
||||
})
|
||||
push('/typebots')
|
||||
return
|
||||
}
|
||||
showToast({
|
||||
title: 'Could not fetch typebot',
|
||||
description: error.message,
|
||||
details: {
|
||||
content: JSON.stringify(error.data?.zodError?.fieldErrors, null, 2),
|
||||
lang: 'json',
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const { data: publishedTypebotData } =
|
||||
trpc.typebot.getPublishedTypebot.useQuery(
|
||||
{ typebotId: typebotId as string },
|
||||
{ typebotId: typebotId as string, migrateToLatestVersion: true },
|
||||
{
|
||||
enabled: isDefined(typebotId),
|
||||
onError: (error) => {
|
||||
showToast({
|
||||
title: 'Could not fetch published typebot',
|
||||
description: error.message,
|
||||
details: {
|
||||
content: JSON.stringify(
|
||||
error.data?.zodError?.fieldErrors,
|
||||
null,
|
||||
2
|
||||
),
|
||||
lang: 'json',
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -124,13 +150,14 @@ export const TypebotProvider = ({
|
||||
},
|
||||
})
|
||||
|
||||
const typebot = typebotData?.typebot
|
||||
const publishedTypebot = publishedTypebotData?.publishedTypebot ?? undefined
|
||||
const typebot = typebotData?.typebot as TypebotV6
|
||||
const publishedTypebot = (publishedTypebotData?.publishedTypebot ??
|
||||
undefined) as PublicTypebotV6 | undefined
|
||||
|
||||
const [
|
||||
localTypebot,
|
||||
{ redo, undo, flush, canRedo, canUndo, set: setLocalTypebot },
|
||||
] = useUndo<Typebot>(undefined)
|
||||
] = useUndo<TypebotV6>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
if (!typebot && isDefined(localTypebot)) setLocalTypebot(undefined)
|
||||
@@ -154,7 +181,7 @@ export const TypebotProvider = ({
|
||||
])
|
||||
|
||||
const saveTypebot = useCallback(
|
||||
async (updates?: Partial<Typebot>) => {
|
||||
async (updates?: Partial<TypebotV6>) => {
|
||||
if (!localTypebot || !typebot || typebotData?.isReadOnly) return
|
||||
const typebotToSave = { ...localTypebot, ...updates }
|
||||
if (dequal(omit(typebot, 'updatedAt'), omit(typebotToSave, 'updatedAt')))
|
||||
@@ -241,6 +268,7 @@ export const TypebotProvider = ({
|
||||
value={{
|
||||
typebot: localTypebot,
|
||||
publishedTypebot,
|
||||
publishedTypebotVersion: publishedTypebotData?.version,
|
||||
isReadOnly: typebotData?.isReadOnly,
|
||||
isSavingLoading: isSaving,
|
||||
save: saveTypebot,
|
||||
@@ -256,6 +284,7 @@ export const TypebotProvider = ({
|
||||
...variablesAction(setLocalTypebot as SetTypebot),
|
||||
...edgesAction(setLocalTypebot as SetTypebot),
|
||||
...itemsAction(setLocalTypebot as SetTypebot),
|
||||
...eventsActions(setLocalTypebot as SetTypebot),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
import {
|
||||
Block,
|
||||
Typebot,
|
||||
DraggableBlock,
|
||||
DraggableBlockType,
|
||||
BlockIndices,
|
||||
Webhook,
|
||||
BlockV6,
|
||||
TypebotV6,
|
||||
} from '@typebot.io/schemas'
|
||||
import { SetTypebot } from '../TypebotProvider'
|
||||
import { produce, Draft } from 'immer'
|
||||
import { cleanUpEdgeDraft, deleteEdgeDraft } from './edges'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { byId, isWebhookBlock, blockHasItems } from '@typebot.io/lib'
|
||||
import { byId, blockHasItems } from '@typebot.io/lib'
|
||||
import { duplicateItemDraft } from './items'
|
||||
import { parseNewBlock } from '@/features/typebot/helpers/parseNewBlock'
|
||||
|
||||
export type BlocksActions = {
|
||||
createBlock: (
|
||||
groupId: string,
|
||||
block: DraggableBlock | DraggableBlockType,
|
||||
indices: BlockIndices
|
||||
) => void
|
||||
createBlock: (block: BlockV6 | BlockV6['type'], indices: BlockIndices) => void
|
||||
updateBlock: (
|
||||
indices: BlockIndices,
|
||||
updates: Partial<Omit<Block, 'id' | 'type'>>
|
||||
updates: Partial<Omit<BlockV6, 'id' | 'type'>>
|
||||
) => void
|
||||
duplicateBlock: (indices: BlockIndices) => void
|
||||
detachBlockFromGroup: (indices: BlockIndices) => void
|
||||
@@ -38,14 +34,10 @@ export type WebhookCallBacks = {
|
||||
}
|
||||
|
||||
export const blocksAction = (setTypebot: SetTypebot): BlocksActions => ({
|
||||
createBlock: (
|
||||
groupId: string,
|
||||
block: DraggableBlock | DraggableBlockType,
|
||||
indices: BlockIndices
|
||||
) =>
|
||||
createBlock: (block: BlockV6 | BlockV6['type'], indices: BlockIndices) =>
|
||||
setTypebot((typebot) =>
|
||||
produce(typebot, (typebot) => {
|
||||
createBlockDraft(typebot, block, groupId, indices)
|
||||
createBlockDraft(typebot, block, indices)
|
||||
})
|
||||
),
|
||||
updateBlock: (
|
||||
@@ -64,8 +56,8 @@ export const blocksAction = (setTypebot: SetTypebot): BlocksActions => ({
|
||||
const block = { ...typebot.groups[groupIndex].blocks[blockIndex] }
|
||||
const blocks = typebot.groups[groupIndex].blocks
|
||||
if (blockIndex === blocks.length - 1 && block.outgoingEdgeId)
|
||||
deleteEdgeDraft(typebot, block.outgoingEdgeId as string)
|
||||
const newBlock = duplicateBlockDraft(block.groupId)(block)
|
||||
deleteEdgeDraft({ typebot, edgeId: block.outgoingEdgeId })
|
||||
const newBlock = duplicateBlockDraft(block)
|
||||
typebot.groups[groupIndex].blocks.splice(blockIndex + 1, 0, newBlock)
|
||||
})
|
||||
),
|
||||
@@ -84,15 +76,13 @@ export const blocksAction = (setTypebot: SetTypebot): BlocksActions => ({
|
||||
|
||||
const removeBlockFromGroup =
|
||||
({ groupIndex, blockIndex }: BlockIndices) =>
|
||||
(typebot: Draft<Typebot>) => {
|
||||
if (typebot.groups[groupIndex].blocks[blockIndex].type === 'start') return
|
||||
(typebot: Draft<TypebotV6>) => {
|
||||
typebot.groups[groupIndex].blocks.splice(blockIndex, 1)
|
||||
}
|
||||
|
||||
export const createBlockDraft = (
|
||||
typebot: Draft<Typebot>,
|
||||
block: DraggableBlock | DraggableBlockType,
|
||||
groupId: string,
|
||||
typebot: Draft<TypebotV6>,
|
||||
block: BlockV6 | BlockV6['type'],
|
||||
{ groupIndex, blockIndex }: BlockIndices
|
||||
) => {
|
||||
const blocks = typebot.groups[groupIndex].blocks
|
||||
@@ -101,50 +91,42 @@ export const createBlockDraft = (
|
||||
blockIndex > 0 &&
|
||||
blocks[blockIndex - 1].outgoingEdgeId
|
||||
)
|
||||
deleteEdgeDraft(typebot, blocks[blockIndex - 1].outgoingEdgeId as string)
|
||||
deleteEdgeDraft({
|
||||
typebot,
|
||||
edgeId: blocks[blockIndex - 1].outgoingEdgeId as string,
|
||||
groupIndex,
|
||||
})
|
||||
typeof block === 'string'
|
||||
? createNewBlock(typebot, block, groupId, { groupIndex, blockIndex })
|
||||
: moveBlockToGroup(typebot, block, groupId, { groupIndex, blockIndex })
|
||||
? createNewBlock(typebot, block, { groupIndex, blockIndex })
|
||||
: moveBlockToGroup(typebot, block, { groupIndex, blockIndex })
|
||||
removeEmptyGroups(typebot)
|
||||
}
|
||||
|
||||
const createNewBlock = async (
|
||||
typebot: Draft<Typebot>,
|
||||
type: DraggableBlockType,
|
||||
groupId: string,
|
||||
{ groupIndex, blockIndex }: BlockIndices,
|
||||
onWebhookBlockCreated?: (data: Partial<Webhook>) => void
|
||||
type: BlockV6['type'],
|
||||
{ groupIndex, blockIndex }: BlockIndices
|
||||
) => {
|
||||
const newBlock = parseNewBlock(type, groupId)
|
||||
const newBlock = parseNewBlock(type)
|
||||
typebot.groups[groupIndex].blocks.splice(blockIndex ?? 0, 0, newBlock)
|
||||
if (onWebhookBlockCreated && 'webhookId' in newBlock && newBlock.webhookId)
|
||||
onWebhookBlockCreated({ id: newBlock.webhookId })
|
||||
}
|
||||
|
||||
const moveBlockToGroup = (
|
||||
typebot: Draft<Typebot>,
|
||||
block: DraggableBlock,
|
||||
groupId: string,
|
||||
typebot: Draft<TypebotV6>,
|
||||
block: BlockV6,
|
||||
{ groupIndex, blockIndex }: BlockIndices
|
||||
) => {
|
||||
const newBlock = { ...block, groupId }
|
||||
const items = blockHasItems(block) ? block.items : []
|
||||
items.forEach((item) => {
|
||||
const edgeIndex = typebot.edges.findIndex(byId(item.outgoingEdgeId))
|
||||
if (edgeIndex === -1) return
|
||||
typebot.edges[edgeIndex].from.groupId = groupId
|
||||
})
|
||||
const newBlock = { ...block }
|
||||
if (block.outgoingEdgeId) {
|
||||
if (typebot.groups[groupIndex].blocks.length > blockIndex ?? 0) {
|
||||
deleteEdgeDraft(typebot, block.outgoingEdgeId)
|
||||
deleteEdgeDraft({ typebot, edgeId: block.outgoingEdgeId })
|
||||
newBlock.outgoingEdgeId = undefined
|
||||
} else {
|
||||
const edgeIndex = typebot.edges.findIndex(byId(block.outgoingEdgeId))
|
||||
edgeIndex !== -1
|
||||
? (typebot.edges[edgeIndex].from.groupId = groupId)
|
||||
: (newBlock.outgoingEdgeId = undefined)
|
||||
if (edgeIndex === -1) newBlock.outgoingEdgeId = undefined
|
||||
}
|
||||
}
|
||||
const groupId = typebot.groups[groupIndex].id
|
||||
typebot.edges.forEach((edge) => {
|
||||
if (edge.to.blockId === block.id) {
|
||||
edge.to.groupId = groupId
|
||||
@@ -153,44 +135,29 @@ const moveBlockToGroup = (
|
||||
typebot.groups[groupIndex].blocks.splice(blockIndex ?? 0, 0, newBlock)
|
||||
}
|
||||
|
||||
export const duplicateBlockDraft =
|
||||
(groupId: string) =>
|
||||
(block: Block): Block => {
|
||||
const blockId = createId()
|
||||
if (blockHasItems(block))
|
||||
return {
|
||||
...block,
|
||||
groupId,
|
||||
id: blockId,
|
||||
items: block.items.map(duplicateItemDraft(blockId)),
|
||||
outgoingEdgeId: undefined,
|
||||
} as Block
|
||||
if (isWebhookBlock(block)) {
|
||||
const newWebhookId = createId()
|
||||
return {
|
||||
...block,
|
||||
groupId,
|
||||
id: blockId,
|
||||
webhookId: newWebhookId,
|
||||
outgoingEdgeId: undefined,
|
||||
}
|
||||
}
|
||||
export const duplicateBlockDraft = (block: BlockV6): BlockV6 => {
|
||||
const blockId = createId()
|
||||
if (blockHasItems(block))
|
||||
return {
|
||||
...block,
|
||||
groupId,
|
||||
id: blockId,
|
||||
items: block.items?.map(duplicateItemDraft(blockId)),
|
||||
outgoingEdgeId: undefined,
|
||||
}
|
||||
} as BlockV6
|
||||
return {
|
||||
...block,
|
||||
id: blockId,
|
||||
outgoingEdgeId: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteGroupDraft =
|
||||
(typebot: Draft<Typebot>) => (groupIndex: number) => {
|
||||
if (typebot.groups[groupIndex].blocks.at(0)?.type === 'start') return
|
||||
(typebot: Draft<TypebotV6>) => (groupIndex: number) => {
|
||||
cleanUpEdgeDraft(typebot, typebot.groups[groupIndex].id)
|
||||
typebot.groups.splice(groupIndex, 1)
|
||||
}
|
||||
|
||||
export const removeEmptyGroups = (typebot: Draft<Typebot>) => {
|
||||
export const removeEmptyGroups = (typebot: Draft<TypebotV6>) => {
|
||||
const emptyGroupsIndices = typebot.groups.reduce<number[]>(
|
||||
(arr, group, idx) => {
|
||||
group.blocks.length === 0 && arr.push(idx)
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
BlockIndices,
|
||||
ItemIndices,
|
||||
Block,
|
||||
TypebotV6,
|
||||
} from '@typebot.io/schemas'
|
||||
import { SetTypebot } from '../TypebotProvider'
|
||||
import { Draft, produce } from 'immer'
|
||||
@@ -27,28 +28,53 @@ export const edgesAction = (setTypebot: SetTypebot): EdgesActions => ({
|
||||
}
|
||||
removeExistingEdge(typebot, edge)
|
||||
typebot.edges.push(newEdge)
|
||||
const groupIndex = typebot.groups.findIndex(byId(edge.from.groupId))
|
||||
const blockIndex = typebot.groups[groupIndex].blocks.findIndex(
|
||||
byId(edge.from.blockId)
|
||||
)
|
||||
const itemIndex = edge.from.itemId
|
||||
? (
|
||||
typebot.groups[groupIndex].blocks[blockIndex] as
|
||||
| BlockWithItems
|
||||
| undefined
|
||||
)?.items.findIndex(byId(edge.from.itemId))
|
||||
: null
|
||||
if ('eventId' in edge.from) {
|
||||
const eventIndex = typebot.events.findIndex(byId(edge.from.eventId))
|
||||
addEdgeIdToEvent(typebot, newEdge.id, {
|
||||
eventIndex,
|
||||
})
|
||||
} else {
|
||||
const groupIndex = typebot.groups.findIndex((g) =>
|
||||
g.blocks.some(
|
||||
(b) => 'blockId' in edge.from && b.id === edge.from.blockId
|
||||
)
|
||||
)
|
||||
const blockIndex = typebot.groups[groupIndex].blocks.findIndex(
|
||||
byId(edge.from.blockId)
|
||||
)
|
||||
const itemIndex = edge.from.itemId
|
||||
? (
|
||||
typebot.groups[groupIndex].blocks[blockIndex] as
|
||||
| BlockWithItems
|
||||
| undefined
|
||||
)?.items.findIndex(byId(edge.from.itemId))
|
||||
: null
|
||||
|
||||
isDefined(itemIndex) && itemIndex !== -1
|
||||
? addEdgeIdToItem(typebot, newEdge.id, {
|
||||
groupIndex,
|
||||
blockIndex,
|
||||
itemIndex,
|
||||
})
|
||||
: addEdgeIdToBlock(typebot, newEdge.id, {
|
||||
groupIndex,
|
||||
blockIndex,
|
||||
})
|
||||
isDefined(itemIndex) && itemIndex !== -1
|
||||
? addEdgeIdToItem(typebot, newEdge.id, {
|
||||
groupIndex,
|
||||
blockIndex,
|
||||
itemIndex,
|
||||
})
|
||||
: addEdgeIdToBlock(typebot, newEdge.id, {
|
||||
groupIndex,
|
||||
blockIndex,
|
||||
})
|
||||
|
||||
const block = typebot.groups[groupIndex].blocks[blockIndex]
|
||||
if (isDefined(itemIndex) && isDefined(block.outgoingEdgeId)) {
|
||||
const areAllItemsConnected = (block as BlockWithItems).items.every(
|
||||
(item) => isDefined(item.outgoingEdgeId)
|
||||
)
|
||||
if (areAllItemsConnected) {
|
||||
deleteEdgeDraft({
|
||||
typebot,
|
||||
edgeId: block.outgoingEdgeId,
|
||||
groupIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
),
|
||||
updateEdge: (edgeIndex: number, updates: Partial<Omit<Edge, 'id'>>) =>
|
||||
@@ -64,11 +90,17 @@ export const edgesAction = (setTypebot: SetTypebot): EdgesActions => ({
|
||||
deleteEdge: (edgeId: string) =>
|
||||
setTypebot((typebot) =>
|
||||
produce(typebot, (typebot) => {
|
||||
deleteEdgeDraft(typebot, edgeId)
|
||||
deleteEdgeDraft({ typebot, edgeId })
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
const addEdgeIdToEvent = (
|
||||
typebot: Draft<TypebotV6>,
|
||||
edgeId: string,
|
||||
{ eventIndex }: { eventIndex: number }
|
||||
) => (typebot.events[eventIndex].outgoingEdgeId = edgeId)
|
||||
|
||||
const addEdgeIdToBlock = (
|
||||
typebot: Draft<Typebot>,
|
||||
edgeId: string,
|
||||
@@ -86,17 +118,43 @@ const addEdgeIdToItem = (
|
||||
itemIndex
|
||||
].outgoingEdgeId = edgeId)
|
||||
|
||||
export const deleteEdgeDraft = (typebot: Draft<Typebot>, edgeId: string) => {
|
||||
export const deleteEdgeDraft = ({
|
||||
typebot,
|
||||
edgeId,
|
||||
groupIndex,
|
||||
}: {
|
||||
typebot: Draft<TypebotV6>
|
||||
edgeId: string
|
||||
groupIndex?: number
|
||||
}) => {
|
||||
const edgeIndex = typebot.edges.findIndex(byId(edgeId))
|
||||
if (edgeIndex === -1) return
|
||||
deleteOutgoingEdgeIdProps(typebot, edgeId)
|
||||
deleteOutgoingEdgeIdProps({ typebot, edgeId, groupIndex })
|
||||
typebot.edges.splice(edgeIndex, 1)
|
||||
}
|
||||
|
||||
const deleteOutgoingEdgeIdProps = (typebot: Draft<Typebot>, edgeId: string) => {
|
||||
const deleteOutgoingEdgeIdProps = ({
|
||||
typebot,
|
||||
edgeId,
|
||||
groupIndex,
|
||||
}: {
|
||||
typebot: Draft<TypebotV6>
|
||||
edgeId: string
|
||||
groupIndex?: number
|
||||
}) => {
|
||||
const edge = typebot.edges.find(byId(edgeId))
|
||||
if (!edge) return
|
||||
const fromGroupIndex = typebot.groups.findIndex(byId(edge.from.groupId))
|
||||
if ('eventId' in edge.from) {
|
||||
const eventIndex = typebot.events.findIndex(byId(edge.from.eventId))
|
||||
if (eventIndex === -1) return
|
||||
typebot.events[eventIndex].outgoingEdgeId = undefined
|
||||
return
|
||||
}
|
||||
const fromGroupIndex =
|
||||
groupIndex ??
|
||||
typebot.groups.findIndex((g) =>
|
||||
g.blocks.some((b) => 'blockId' in edge.from && b.id === edge.from.blockId)
|
||||
)
|
||||
const fromBlockIndex = typebot.groups[fromGroupIndex].blocks.findIndex(
|
||||
byId(edge.from.blockId)
|
||||
)
|
||||
@@ -106,40 +164,52 @@ const deleteOutgoingEdgeIdProps = (typebot: Draft<Typebot>, edgeId: string) => {
|
||||
if (!block) return
|
||||
const fromItemIndex =
|
||||
edge.from.itemId && blockHasItems(block)
|
||||
? block.items.findIndex(byId(edge.from.itemId))
|
||||
? block.items?.findIndex(byId(edge.from.itemId))
|
||||
: -1
|
||||
if (fromItemIndex !== -1) {
|
||||
;(
|
||||
typebot.groups[fromGroupIndex].blocks[fromBlockIndex] as BlockWithItems
|
||||
).items[fromItemIndex].outgoingEdgeId = undefined
|
||||
).items[fromItemIndex ?? 0].outgoingEdgeId = undefined
|
||||
} else if (fromBlockIndex !== -1)
|
||||
typebot.groups[fromGroupIndex].blocks[fromBlockIndex].outgoingEdgeId =
|
||||
undefined
|
||||
}
|
||||
|
||||
export const cleanUpEdgeDraft = (
|
||||
typebot: Draft<Typebot>,
|
||||
typebot: Draft<TypebotV6>,
|
||||
deletedNodeId: string
|
||||
) => {
|
||||
const edgesToDelete = typebot.edges.filter((edge) =>
|
||||
[
|
||||
edge.from.groupId,
|
||||
const edgesToDelete = typebot.edges.filter((edge) => {
|
||||
if ('eventId' in edge.from)
|
||||
return [edge.from.eventId, edge.to.groupId, edge.to.blockId].includes(
|
||||
deletedNodeId
|
||||
)
|
||||
|
||||
return [
|
||||
edge.from.blockId,
|
||||
edge.from.itemId,
|
||||
edge.to.groupId,
|
||||
edge.to.blockId,
|
||||
].includes(deletedNodeId)
|
||||
)
|
||||
edgesToDelete.forEach((edge) => deleteEdgeDraft(typebot, edge.id))
|
||||
})
|
||||
|
||||
edgesToDelete.forEach((edge) => deleteEdgeDraft({ typebot, edgeId: edge.id }))
|
||||
}
|
||||
|
||||
const removeExistingEdge = (
|
||||
typebot: Draft<Typebot>,
|
||||
edge: Omit<Edge, 'id'>
|
||||
) => {
|
||||
typebot.edges = typebot.edges.filter((e) =>
|
||||
edge.from.itemId
|
||||
? e.from.itemId !== edge.from.itemId
|
||||
typebot.edges = typebot.edges.filter((e) => {
|
||||
if ('eventId' in edge.from) {
|
||||
if ('eventId' in e.from) return e.from.eventId !== edge.from.eventId
|
||||
return true
|
||||
}
|
||||
|
||||
if ('eventId' in e.from) return true
|
||||
|
||||
return edge.from.itemId
|
||||
? e.from && e.from.itemId !== edge.from.itemId
|
||||
: isDefined(e.from.itemId) || e.from.blockId !== edge.from.blockId
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { produce } from 'immer'
|
||||
import { TEvent } from '@typebot.io/schemas'
|
||||
import { SetTypebot } from '../TypebotProvider'
|
||||
|
||||
export type EventsActions = {
|
||||
updateEvent: (
|
||||
eventIndex: number,
|
||||
updates: Partial<Omit<TEvent, 'id'>>
|
||||
) => void
|
||||
}
|
||||
|
||||
const eventsActions = (setTypebot: SetTypebot): EventsActions => ({
|
||||
updateEvent: (eventIndex: number, updates: Partial<Omit<TEvent, 'id'>>) =>
|
||||
setTypebot((typebot) =>
|
||||
produce(typebot, (typebot) => {
|
||||
const event = typebot.events[eventIndex]
|
||||
typebot.events[eventIndex] = { ...event, ...updates }
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
export { eventsActions }
|
||||
@@ -1,11 +1,6 @@
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { produce } from 'immer'
|
||||
import {
|
||||
Group,
|
||||
DraggableBlock,
|
||||
DraggableBlockType,
|
||||
BlockIndices,
|
||||
} from '@typebot.io/schemas'
|
||||
import { BlockIndices, BlockV6, GroupV6 } from '@typebot.io/schemas'
|
||||
import { SetTypebot } from '../TypebotProvider'
|
||||
import {
|
||||
deleteGroupDraft,
|
||||
@@ -19,11 +14,14 @@ export type GroupsActions = {
|
||||
createGroup: (
|
||||
props: Coordinates & {
|
||||
id: string
|
||||
block: DraggableBlock | DraggableBlockType
|
||||
block: BlockV6 | BlockV6['type']
|
||||
indices: BlockIndices
|
||||
}
|
||||
) => void
|
||||
updateGroup: (groupIndex: number, updates: Partial<Omit<Group, 'id'>>) => void
|
||||
updateGroup: (
|
||||
groupIndex: number,
|
||||
updates: Partial<Omit<GroupV6, 'id'>>
|
||||
) => void
|
||||
duplicateGroup: (groupIndex: number) => void
|
||||
deleteGroup: (groupIndex: number) => void
|
||||
}
|
||||
@@ -36,22 +34,22 @@ const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
|
||||
...graphCoordinates
|
||||
}: Coordinates & {
|
||||
id: string
|
||||
block: DraggableBlock | DraggableBlockType
|
||||
block: BlockV6 | BlockV6['type']
|
||||
indices: BlockIndices
|
||||
}) =>
|
||||
setTypebot((typebot) =>
|
||||
produce(typebot, (typebot) => {
|
||||
const newGroup: Group = {
|
||||
const newGroup: GroupV6 = {
|
||||
id,
|
||||
graphCoordinates,
|
||||
title: `Group #${typebot.groups.length}`,
|
||||
title: `Group #${typebot.groups.length + 1}`,
|
||||
blocks: [],
|
||||
}
|
||||
typebot.groups.push(newGroup)
|
||||
createBlockDraft(typebot, block, newGroup.id, indices)
|
||||
createBlockDraft(typebot, block, indices)
|
||||
})
|
||||
),
|
||||
updateGroup: (groupIndex: number, updates: Partial<Omit<Group, 'id'>>) =>
|
||||
updateGroup: (groupIndex: number, updates: Partial<Omit<GroupV6, 'id'>>) =>
|
||||
setTypebot((typebot) =>
|
||||
produce(typebot, (typebot) => {
|
||||
const block = typebot.groups[groupIndex]
|
||||
@@ -68,7 +66,7 @@ const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
|
||||
(g) => g.title === group.title
|
||||
).length
|
||||
|
||||
const newGroup: Group = {
|
||||
const newGroup: GroupV6 = {
|
||||
...group,
|
||||
title: isEmpty(group.title)
|
||||
? ''
|
||||
@@ -78,7 +76,7 @@ const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
|
||||
: ''
|
||||
}`,
|
||||
id,
|
||||
blocks: group.blocks.map((block) => duplicateBlockDraft(id)(block)),
|
||||
blocks: group.blocks.map((block) => duplicateBlockDraft(block)),
|
||||
graphCoordinates: {
|
||||
x: group.graphCoordinates.x + 200,
|
||||
y: group.graphCoordinates.y + 100,
|
||||
|
||||
@@ -2,10 +2,6 @@ import {
|
||||
ItemIndices,
|
||||
Item,
|
||||
BlockWithItems,
|
||||
defaultConditionContent,
|
||||
Block,
|
||||
LogicBlockType,
|
||||
InputBlockType,
|
||||
ConditionItem,
|
||||
ButtonItem,
|
||||
PictureChoiceItem,
|
||||
@@ -15,12 +11,14 @@ import { Draft, produce } from 'immer'
|
||||
import { cleanUpEdgeDraft } from './edges'
|
||||
import { byId, blockHasItems } from '@typebot.io/lib'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { DraggabbleItem } from '@/features/graph/providers/GraphDndProvider'
|
||||
import {
|
||||
BlockWithCreatableItems,
|
||||
DraggableItem,
|
||||
} from '@/features/graph/providers/GraphDndProvider'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
|
||||
type NewItem = Pick<DraggabbleItem, 'blockId' | 'outgoingEdgeId' | 'type'> &
|
||||
Partial<DraggabbleItem>
|
||||
|
||||
type BlockWithCreatableItems = Extract<Block, { items: DraggabbleItem[] }>
|
||||
type NewItem = Pick<DraggableItem, 'outgoingEdgeId'> & Partial<DraggableItem>
|
||||
|
||||
export type ItemsActions = {
|
||||
createItem: (item: NewItem, indices: ItemIndices) => void
|
||||
@@ -41,7 +39,7 @@ const createItem = (
|
||||
const newItem = {
|
||||
...baseItem,
|
||||
id: 'id' in item && item.id ? item.id : createId(),
|
||||
content: baseItem.content ?? defaultConditionContent,
|
||||
content: baseItem.content,
|
||||
}
|
||||
block.items.splice(itemIndex, 0, newItem)
|
||||
return newItem
|
||||
@@ -79,7 +77,7 @@ const duplicateItem = (
|
||||
const newItem = {
|
||||
...baseItem,
|
||||
id: createId(),
|
||||
content: baseItem.content ?? defaultConditionContent,
|
||||
content: baseItem.content,
|
||||
}
|
||||
block.items.splice(itemIndex + 1, 0, newItem)
|
||||
return newItem
|
||||
@@ -125,7 +123,6 @@ const itemsAction = (setTypebot: SetTypebot): ItemsActions => ({
|
||||
const edgeIndex = typebot.edges.findIndex(byId(item.outgoingEdgeId))
|
||||
edgeIndex !== -1
|
||||
? (typebot.edges[edgeIndex].from = {
|
||||
groupId: block.groupId,
|
||||
blockId: block.id,
|
||||
itemId: newItem.id,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user