feat(editor): ✨ Start preview from any block
This commit is contained in:
@@ -451,3 +451,9 @@ export const CreditCardIcon = (props: IconProps) => (
|
|||||||
<line x1="1" y1="10" x2="23" y2="10"></line>
|
<line x1="1" y1="10" x2="23" y2="10"></line>
|
||||||
</Icon>
|
</Icon>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const PlayIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { parseTypebotToPublicTypebot } from 'services/publicTypebot'
|
|||||||
|
|
||||||
export const PreviewDrawer = () => {
|
export const PreviewDrawer = () => {
|
||||||
const { typebot } = useTypebot()
|
const { typebot } = useTypebot()
|
||||||
const { setRightPanel } = useEditor()
|
const { setRightPanel, startPreviewAtBlock } = useEditor()
|
||||||
const { setPreviewingEdge } = useGraph()
|
const { setPreviewingEdge } = useGraph()
|
||||||
const [isResizing, setIsResizing] = useState(false)
|
const [isResizing, setIsResizing] = useState(false)
|
||||||
const [width, setWidth] = useState(500)
|
const [width, setWidth] = useState(500)
|
||||||
@@ -96,13 +96,14 @@ export const PreviewDrawer = () => {
|
|||||||
borderRadius={'lg'}
|
borderRadius={'lg'}
|
||||||
h="full"
|
h="full"
|
||||||
w="full"
|
w="full"
|
||||||
key={restartKey}
|
key={restartKey + (startPreviewAtBlock ?? '')}
|
||||||
pointerEvents={isResizing ? 'none' : 'auto'}
|
pointerEvents={isResizing ? 'none' : 'auto'}
|
||||||
>
|
>
|
||||||
<TypebotViewer
|
<TypebotViewer
|
||||||
typebot={publicTypebot}
|
typebot={publicTypebot}
|
||||||
onNewBlockVisible={setPreviewingEdge}
|
onNewBlockVisible={setPreviewingEdge}
|
||||||
onNewLog={handleNewLog}
|
onNewLog={handleNewLog}
|
||||||
|
startBlockId={startPreviewAtBlock}
|
||||||
isPreview
|
isPreview
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
Editable,
|
Editable,
|
||||||
EditableInput,
|
EditableInput,
|
||||||
EditablePreview,
|
EditablePreview,
|
||||||
|
IconButton,
|
||||||
Stack,
|
Stack,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
@@ -16,6 +17,8 @@ import { BlockNodeContextMenu } from './BlockNodeContextMenu'
|
|||||||
import { useDebounce } from 'use-debounce'
|
import { useDebounce } from 'use-debounce'
|
||||||
import { setMultipleRefs } from 'services/utils'
|
import { setMultipleRefs } from 'services/utils'
|
||||||
import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable'
|
import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable'
|
||||||
|
import { PlayIcon } from 'assets/icons'
|
||||||
|
import { RightPanel, useEditor } from 'contexts/EditorContext'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
block: Block
|
block: Block
|
||||||
@@ -38,6 +41,7 @@ export const BlockNode = ({ block, blockIndex }: Props) => {
|
|||||||
const { setMouseOverBlock, mouseOverBlock } = useStepDnd()
|
const { setMouseOverBlock, mouseOverBlock } = useStepDnd()
|
||||||
const [isMouseDown, setIsMouseDown] = useState(false)
|
const [isMouseDown, setIsMouseDown] = useState(false)
|
||||||
const [isConnecting, setIsConnecting] = useState(false)
|
const [isConnecting, setIsConnecting] = useState(false)
|
||||||
|
const { setRightPanel, setStartPreviewAtBlock } = useEditor()
|
||||||
const isPreviewing =
|
const isPreviewing =
|
||||||
previewingEdge?.from.blockId === block.id ||
|
previewingEdge?.from.blockId === block.id ||
|
||||||
(previewingEdge?.to.blockId === block.id &&
|
(previewingEdge?.to.blockId === block.id &&
|
||||||
@@ -99,6 +103,12 @@ export const BlockNode = ({ block, blockIndex }: Props) => {
|
|||||||
setFocusedBlockId(block.id)
|
setFocusedBlockId(block.id)
|
||||||
setIsMouseDown(true)
|
setIsMouseDown(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startPreviewAtThisBlock = () => {
|
||||||
|
setStartPreviewAtBlock(block.id)
|
||||||
|
setRightPanel(RightPanel.PREVIEW)
|
||||||
|
}
|
||||||
|
|
||||||
const onDragStop = () => setIsMouseDown(false)
|
const onDragStop = () => setIsMouseDown(false)
|
||||||
return (
|
return (
|
||||||
<ContextMenu<HTMLDivElement>
|
<ContextMenu<HTMLDivElement>
|
||||||
@@ -165,6 +175,16 @@ export const BlockNode = ({ block, blockIndex }: Props) => {
|
|||||||
isStartBlock={isStartBlock}
|
isStartBlock={isStartBlock}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<IconButton
|
||||||
|
icon={<PlayIcon />}
|
||||||
|
aria-label={'Preview bot from this group'}
|
||||||
|
pos="absolute"
|
||||||
|
right={2}
|
||||||
|
top={0}
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={startPreviewAtThisBlock}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</DraggableCore>
|
</DraggableCore>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export const headerHeight = 56
|
|||||||
|
|
||||||
export const TypebotHeader = () => {
|
export const TypebotHeader = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { rightPanel } = useEditor()
|
|
||||||
const {
|
const {
|
||||||
typebot,
|
typebot,
|
||||||
updateOnBothTypebots,
|
updateOnBothTypebots,
|
||||||
@@ -35,13 +34,14 @@ export const TypebotHeader = () => {
|
|||||||
canRedo,
|
canRedo,
|
||||||
isSavingLoading,
|
isSavingLoading,
|
||||||
} = useTypebot()
|
} = useTypebot()
|
||||||
const { setRightPanel } = useEditor()
|
const { setRightPanel, rightPanel, setStartPreviewAtBlock } = useEditor()
|
||||||
|
|
||||||
const handleNameSubmit = (name: string) => updateOnBothTypebots({ name })
|
const handleNameSubmit = (name: string) => updateOnBothTypebots({ name })
|
||||||
|
|
||||||
const handleChangeIcon = (icon: string) => updateTypebot({ icon })
|
const handleChangeIcon = (icon: string) => updateTypebot({ icon })
|
||||||
|
|
||||||
const handlePreviewClick = async () => {
|
const handlePreviewClick = async () => {
|
||||||
|
setStartPreviewAtBlock(undefined)
|
||||||
save().then()
|
save().then()
|
||||||
setRightPanel(RightPanel.PREVIEW)
|
setRightPanel(RightPanel.PREVIEW)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,18 +13,23 @@ export enum RightPanel {
|
|||||||
const editorContext = createContext<{
|
const editorContext = createContext<{
|
||||||
rightPanel?: RightPanel
|
rightPanel?: RightPanel
|
||||||
setRightPanel: Dispatch<SetStateAction<RightPanel | undefined>>
|
setRightPanel: Dispatch<SetStateAction<RightPanel | undefined>>
|
||||||
|
startPreviewAtBlock: string | undefined
|
||||||
|
setStartPreviewAtBlock: Dispatch<SetStateAction<string | undefined>>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
export const EditorContext = ({ children }: { children: ReactNode }) => {
|
export const EditorContext = ({ children }: { children: ReactNode }) => {
|
||||||
const [rightPanel, setRightPanel] = useState<RightPanel>()
|
const [rightPanel, setRightPanel] = useState<RightPanel>()
|
||||||
|
const [startPreviewAtBlock, setStartPreviewAtBlock] = useState<string>()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<editorContext.Provider
|
<editorContext.Provider
|
||||||
value={{
|
value={{
|
||||||
rightPanel,
|
rightPanel,
|
||||||
setRightPanel,
|
setRightPanel,
|
||||||
|
startPreviewAtBlock,
|
||||||
|
setStartPreviewAtBlock,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -440,9 +440,11 @@ const useLinkedTypebots = ({
|
|||||||
},
|
},
|
||||||
Error
|
Error
|
||||||
>(
|
>(
|
||||||
typebotIds?.every((id) => typebotId === id)
|
workspaceId
|
||||||
? undefined
|
? typebotIds?.every((id) => typebotId === id)
|
||||||
: `/api/typebots?${params}`,
|
? undefined
|
||||||
|
: `/api/typebots?${params}`
|
||||||
|
: null,
|
||||||
fetcher
|
fetcher
|
||||||
)
|
)
|
||||||
if (error) onError(error)
|
if (error) onError(error)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { byId } from 'utils'
|
import { byId, isNotEmpty } from 'utils'
|
||||||
import { MemberInWorkspace, Plan, Workspace, WorkspaceRole } from 'db'
|
import { MemberInWorkspace, Plan, Workspace, WorkspaceRole } from 'db'
|
||||||
import {
|
import {
|
||||||
createNewWorkspace,
|
createNewWorkspace,
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"id":"cl3wo63la1004801amwsqzbof","createdAt":"2022-06-02T07:01:46.030Z","updatedAt":"2022-06-02T07:34:02.336Z","icon":null,"name":"My typebot","publishedTypebotId":null,"folderId":null,"blocks":[{"id":"cl3wo63l80000801ae4lxgvad","steps":[{"id":"cl3wo63l80001801a8u9g96sp","type":"start","label":"Start","blockId":"cl3wo63l80000801ae4lxgvad","outgoingEdgeId":"cl3wo83ha000j2e6gdrk1crro"}],"title":"Start","graphCoordinates":{"x":0,"y":0}},{"id":"cl3wo7ucc000g2e6gdus80qeb","graphCoordinates":{"x":355,"y":-13},"title":"Group #1","steps":[{"id":"cl3wo7uce000h2e6gr9r3b11k","blockId":"cl3wo7ucc000g2e6gdus80qeb","type":"text","content":{"html":"<div>Hello this is group 1</div>","richText":[{"type":"p","children":[{"text":"Hello this is group 1"}]}],"plainText":"Hello this is group 1"}},{"id":"cl3wo8047000i2e6glma69ddz","blockId":"cl3wo7ucc000g2e6gdus80qeb","type":"text","content":{"html":"<div>What's your name?</div>","richText":[{"type":"p","children":[{"text":"What's your name?"}]}],"plainText":"What's your name?"}},{"id":"cl3wo85e8000k2e6gdb8qk860","blockId":"cl3wo7ucc000g2e6gdus80qeb","type":"text input","options":{"isLong":false,"labels":{"button":"Send","placeholder":"Type your answer..."}}}]},{"id":"cl3wo87et000l2e6ga64ipat6","graphCoordinates":{"x":22,"y":260},"title":"Group #1 copy","steps":[{"id":"cl3wo87eu000m2e6g5h90qs9u","blockId":"cl3wo87et000l2e6ga64ipat6","type":"text","content":{"html":"<div>Hello this is group 2</div>","richText":[{"type":"p","children":[{"text":"Hello this is group 2"}]}],"plainText":"Hello this is group 2"}},{"id":"cl3wo87ev000n2e6gp7vn2z62","blockId":"cl3wo87et000l2e6ga64ipat6","type":"text","content":{"html":"<div>What's your name?</div>","richText":[{"type":"p","children":[{"text":"What's your name?"}]}],"plainText":"What's your name?"}},{"id":"cl3wo87ev000o2e6g71r3hvor","blockId":"cl3wo87et000l2e6ga64ipat6","type":"text input","options":{"isLong":false,"labels":{"button":"Send","placeholder":"Type your answer..."}}}]},{"id":"cl3wo8kfl000p2e6gszlvkub0","graphCoordinates":{"x":367,"y":294},"title":"Group #1 copy copy","steps":[{"id":"cl3wo8kfl000q2e6gci1itvj3","blockId":"cl3wo8kfl000p2e6gszlvkub0","type":"text","content":{"html":"<div>Hello this is group 3</div>","richText":[{"type":"p","children":[{"text":"Hello this is group 3"}]}],"plainText":"Hello this is group 3"}},{"id":"cl3wo8kfl000r2e6gx0lxwitf","blockId":"cl3wo8kfl000p2e6gszlvkub0","type":"text","content":{"html":"<div>What's your name?</div>","richText":[{"type":"p","children":[{"text":"What's your name?"}]}],"plainText":"What's your name?"}},{"id":"cl3wo8kfl000s2e6g6ckc9om4","blockId":"cl3wo8kfl000p2e6gszlvkub0","type":"text input","options":{"isLong":false,"labels":{"button":"Send","placeholder":"Type your answer..."}}}]}],"variables":[],"edges":[{"from":{"blockId":"cl3wo63l80000801ae4lxgvad","stepId":"cl3wo63l80001801a8u9g96sp"},"to":{"blockId":"cl3wo7ucc000g2e6gdus80qeb"},"id":"cl3wo83ha000j2e6gdrk1crro"}],"theme":{"chat":{"inputs":{"color":"#303235","backgroundColor":"#FFFFFF","placeholderColor":"#9095A0"},"buttons":{"color":"#FFFFFF","backgroundColor":"#0042DA"},"hostAvatar":{"url":"https://avatars.githubusercontent.com/u/16015833?v=4","isEnabled":true},"hostBubbles":{"color":"#303235","backgroundColor":"#F7F8FF"},"guestBubbles":{"color":"#FFFFFF","backgroundColor":"#FF8E21"}},"general":{"font":"Open Sans","background":{"type":"None"}}},"settings":{"general":{"isBrandingEnabled":true,"isInputPrefillEnabled":true,"isHideQueryParamsEnabled":true,"isNewResultOnRefreshEnabled":false},"metadata":{"description":"Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."},"typingEmulation":{"speed":300,"enabled":true,"maxDelay":1.5}},"publicId":null,"customDomain":null,"workspaceId":"cl3ncues300081a1as58wmkxz"}
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
{
|
{
|
||||||
"name": "typebot-20-modal",
|
"name": "typebot-20-modal",
|
||||||
"value": "hide"
|
"value": "hide"
|
||||||
}
|
},
|
||||||
|
{ "name": "workspaceId", "value": "freeWorkspace" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const createUsers = async () => {
|
|||||||
role: WorkspaceRole.ADMIN,
|
role: WorkspaceRole.ADMIN,
|
||||||
workspace: {
|
workspace: {
|
||||||
create: {
|
create: {
|
||||||
id: freeWorkspaceId,
|
id: 'free',
|
||||||
name: "Free user's workspace",
|
name: "Free user's workspace",
|
||||||
plan: Plan.FREE,
|
plan: Plan.FREE,
|
||||||
},
|
},
|
||||||
@@ -98,12 +98,15 @@ export const createUsers = async () => {
|
|||||||
})
|
})
|
||||||
await prisma.workspace.create({
|
await prisma.workspace.create({
|
||||||
data: {
|
data: {
|
||||||
id: 'free',
|
id: freeWorkspaceId,
|
||||||
name: 'Free workspace',
|
name: 'Free Shared Workspace',
|
||||||
plan: Plan.FREE,
|
plan: Plan.FREE,
|
||||||
members: {
|
members: {
|
||||||
createMany: {
|
createMany: {
|
||||||
data: [{ role: WorkspaceRole.ADMIN, userId: 'proUser' }],
|
data: [
|
||||||
|
{ role: WorkspaceRole.MEMBER, userId: 'proUser' },
|
||||||
|
{ role: WorkspaceRole.ADMIN, userId: 'freeUser' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
import { defaultTextInputOptions, InputStepType } from 'models'
|
import { defaultTextInputOptions, InputStepType } from 'models'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
|
import { typebotViewer } from '../services/selectorUtils'
|
||||||
|
|
||||||
test.describe.parallel('Editor', () => {
|
test.describe.parallel('Editor', () => {
|
||||||
test('Edges connection should work', async ({ page }) => {
|
test('Edges connection should work', async ({ page }) => {
|
||||||
@@ -151,4 +152,29 @@ test.describe.parallel('Editor', () => {
|
|||||||
await expect(page.locator('text="😍"')).toBeVisible()
|
await expect(page.locator('text="😍"')).toBeVisible()
|
||||||
await expect(page.locator('text="My superb typebot"')).toBeVisible()
|
await expect(page.locator('text="My superb typebot"')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Preview from group should work', async ({ page }) => {
|
||||||
|
const typebotId = cuid()
|
||||||
|
await importTypebotInDatabase(
|
||||||
|
path.join(__dirname, '../fixtures/typebots/editor/previewFromGroup.json'),
|
||||||
|
{
|
||||||
|
id: typebotId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
|
await page.click('[aria-label="Preview bot from this group"] >> nth=1')
|
||||||
|
await expect(
|
||||||
|
typebotViewer(page).locator('text="Hello this is group 1"')
|
||||||
|
).toBeVisible()
|
||||||
|
await page.click('[aria-label="Preview bot from this group"] >> nth=2')
|
||||||
|
await expect(
|
||||||
|
typebotViewer(page).locator('text="Hello this is group 2"')
|
||||||
|
).toBeVisible()
|
||||||
|
await page.click('[aria-label="Close"]')
|
||||||
|
await page.click('text="Preview"')
|
||||||
|
await expect(
|
||||||
|
typebotViewer(page).locator('text="Hello this is group 1"')
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ test('can switch between workspaces and access typebot', async ({ page }) => {
|
|||||||
await page.goto('/typebots')
|
await page.goto('/typebots')
|
||||||
await expect(page.locator('text="Pro typebot"')).toBeVisible()
|
await expect(page.locator('text="Pro typebot"')).toBeVisible()
|
||||||
await page.click("text=Pro user's workspace")
|
await page.click("text=Pro user's workspace")
|
||||||
await page.click('text=Shared workspace')
|
await page.click('text="Shared workspace"')
|
||||||
await expect(page.locator('text="Pro typebot"')).toBeHidden()
|
await expect(page.locator('text="Pro typebot"')).toBeHidden()
|
||||||
await page.click('text="Shared typebot"')
|
await page.click('text="Shared typebot"')
|
||||||
await expect(page.locator('text="Hey there"')).toBeVisible()
|
await expect(page.locator('text="Hey there"')).toBeVisible()
|
||||||
@@ -135,7 +135,7 @@ test('can manage members', async ({ page }) => {
|
|||||||
test("can't edit workspace as a member", async ({ page }) => {
|
test("can't edit workspace as a member", async ({ page }) => {
|
||||||
await page.goto('/typebots')
|
await page.goto('/typebots')
|
||||||
await page.click("text=Pro user's workspace")
|
await page.click("text=Pro user's workspace")
|
||||||
await page.click('text=Shared workspace')
|
await page.click('text="Shared workspace"')
|
||||||
await page.click('text=Settings & Members')
|
await page.click('text=Settings & Members')
|
||||||
await expect(page.locator('text="Settings"')).toBeHidden()
|
await expect(page.locator('text="Settings"')).toBeHidden()
|
||||||
await page.click('text="Members"')
|
await page.click('text="Members"')
|
||||||
|
|||||||
@@ -33,10 +33,13 @@ type ChatBlockProps = {
|
|||||||
startStepIndex: number
|
startStepIndex: number
|
||||||
blockTitle: string
|
blockTitle: string
|
||||||
keepShowingHostAvatar: boolean
|
keepShowingHostAvatar: boolean
|
||||||
onBlockEnd: (
|
onBlockEnd: ({
|
||||||
edgeId?: string,
|
edgeId,
|
||||||
|
updatedTypebot,
|
||||||
|
}: {
|
||||||
|
edgeId?: string
|
||||||
updatedTypebot?: PublicTypebot | LinkedTypebot
|
updatedTypebot?: PublicTypebot | LinkedTypebot
|
||||||
) => void
|
}) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatDisplayChunk = { bubbles: BubbleStep[]; input?: InputStep }
|
type ChatDisplayChunk = { bubbles: BubbleStep[]; input?: InputStep }
|
||||||
@@ -129,7 +132,9 @@ export const ChatBlock = ({
|
|||||||
currentStep.type === LogicStepType.REDIRECT &&
|
currentStep.type === LogicStepType.REDIRECT &&
|
||||||
currentStep.options.isNewTab === false
|
currentStep.options.isNewTab === false
|
||||||
if (isRedirecting) return
|
if (isRedirecting) return
|
||||||
nextEdgeId ? onBlockEnd(nextEdgeId, linkedTypebot) : displayNextStep()
|
nextEdgeId
|
||||||
|
? onBlockEnd({ edgeId: nextEdgeId, updatedTypebot: linkedTypebot })
|
||||||
|
: displayNextStep()
|
||||||
}
|
}
|
||||||
if (isIntegrationStep(currentStep)) {
|
if (isIntegrationStep(currentStep)) {
|
||||||
const nextEdgeId = await executeIntegration({
|
const nextEdgeId = await executeIntegration({
|
||||||
@@ -149,9 +154,10 @@ export const ChatBlock = ({
|
|||||||
resultId,
|
resultId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
nextEdgeId ? onBlockEnd(nextEdgeId) : displayNextStep()
|
nextEdgeId ? onBlockEnd({ edgeId: nextEdgeId }) : displayNextStep()
|
||||||
}
|
}
|
||||||
if (currentStep.type === 'start') onBlockEnd(currentStep.outgoingEdgeId)
|
if (currentStep.type === 'start')
|
||||||
|
onBlockEnd({ edgeId: currentStep.outgoingEdgeId })
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayNextStep = (answerContent?: string, isRetry?: boolean) => {
|
const displayNextStep = (answerContent?: string, isRetry?: boolean) => {
|
||||||
@@ -175,14 +181,14 @@ export const ChatBlock = ({
|
|||||||
const nextEdgeId = currentStep.items.find(
|
const nextEdgeId = currentStep.items.find(
|
||||||
(i) => i.content === answerContent
|
(i) => i.content === answerContent
|
||||||
)?.outgoingEdgeId
|
)?.outgoingEdgeId
|
||||||
if (nextEdgeId) return onBlockEnd(nextEdgeId)
|
if (nextEdgeId) return onBlockEnd({ edgeId: nextEdgeId })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStep?.outgoingEdgeId || processedSteps.length === steps.length)
|
if (currentStep?.outgoingEdgeId || processedSteps.length === steps.length)
|
||||||
return onBlockEnd(currentStep.outgoingEdgeId)
|
return onBlockEnd({ edgeId: currentStep.outgoingEdgeId })
|
||||||
}
|
}
|
||||||
const nextStep = steps[processedSteps.length + startStepIndex]
|
const nextStep = steps[processedSteps.length + startStepIndex]
|
||||||
nextStep ? insertStepInStack(nextStep) : onBlockEnd()
|
nextStep ? insertStepInStack(nextStep) : onBlockEnd({})
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatarSrc = typebot.theme.chat.hostAvatar?.url
|
const avatarSrc = typebot.theme.chat.hostAvatar?.url
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ import { ChatContext } from 'contexts/ChatContext'
|
|||||||
type Props = {
|
type Props = {
|
||||||
theme: Theme
|
theme: Theme
|
||||||
predefinedVariables?: { [key: string]: string | undefined }
|
predefinedVariables?: { [key: string]: string | undefined }
|
||||||
|
startBlockId?: string
|
||||||
onNewBlockVisible: (edge: Edge) => void
|
onNewBlockVisible: (edge: Edge) => void
|
||||||
onCompleted: () => void
|
onCompleted: () => void
|
||||||
}
|
}
|
||||||
export const ConversationContainer = ({
|
export const ConversationContainer = ({
|
||||||
theme,
|
theme,
|
||||||
predefinedVariables,
|
predefinedVariables,
|
||||||
|
startBlockId,
|
||||||
onNewBlockVisible,
|
onNewBlockVisible,
|
||||||
onCompleted,
|
onCompleted,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
@@ -36,17 +38,35 @@ export const ConversationContainer = ({
|
|||||||
const bottomAnchor = useRef<HTMLDivElement | null>(null)
|
const bottomAnchor = useRef<HTMLDivElement | null>(null)
|
||||||
const scrollableContainer = useRef<HTMLDivElement | null>(null)
|
const scrollableContainer = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
const displayNextBlock = (
|
const displayNextBlock = ({
|
||||||
edgeId?: string,
|
edgeId,
|
||||||
|
updatedTypebot,
|
||||||
|
blockId,
|
||||||
|
}: {
|
||||||
|
edgeId?: string
|
||||||
|
blockId?: string
|
||||||
updatedTypebot?: PublicTypebot | LinkedTypebot
|
updatedTypebot?: PublicTypebot | LinkedTypebot
|
||||||
) => {
|
}) => {
|
||||||
const currentTypebot = updatedTypebot ?? typebot
|
const currentTypebot = updatedTypebot ?? typebot
|
||||||
|
if (blockId) {
|
||||||
|
const nextBlock = currentTypebot.blocks.find(byId(blockId))
|
||||||
|
if (!nextBlock) return
|
||||||
|
onNewBlockVisible({
|
||||||
|
id: 'edgeId',
|
||||||
|
from: { blockId: 'block', stepId: 'step' },
|
||||||
|
to: { blockId },
|
||||||
|
})
|
||||||
|
return setDisplayedBlocks([
|
||||||
|
...displayedBlocks,
|
||||||
|
{ block: nextBlock, startStepIndex: 0 },
|
||||||
|
])
|
||||||
|
}
|
||||||
const nextEdge = currentTypebot.edges.find(byId(edgeId))
|
const nextEdge = currentTypebot.edges.find(byId(edgeId))
|
||||||
if (!nextEdge) {
|
if (!nextEdge) {
|
||||||
if (linkedBotQueue.length > 0) {
|
if (linkedBotQueue.length > 0) {
|
||||||
const nextEdgeId = linkedBotQueue[0].edgeId
|
const nextEdgeId = linkedBotQueue[0].edgeId
|
||||||
popEdgeIdFromLinkedTypebotQueue()
|
popEdgeIdFromLinkedTypebotQueue()
|
||||||
displayNextBlock(nextEdgeId)
|
displayNextBlock({ edgeId: nextEdgeId })
|
||||||
}
|
}
|
||||||
return onCompleted()
|
return onCompleted()
|
||||||
}
|
}
|
||||||
@@ -65,7 +85,12 @@ export const ConversationContainer = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const prefilledVariables = injectPredefinedVariables(predefinedVariables)
|
const prefilledVariables = injectPredefinedVariables(predefinedVariables)
|
||||||
updateVariables(prefilledVariables)
|
updateVariables(prefilledVariables)
|
||||||
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId)
|
displayNextBlock({
|
||||||
|
edgeId: startBlockId
|
||||||
|
? undefined
|
||||||
|
: typebot.blocks[0].steps[0].outgoingEdgeId,
|
||||||
|
blockId: startBlockId,
|
||||||
|
})
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export type TypebotViewerProps = {
|
|||||||
style?: CSSProperties
|
style?: CSSProperties
|
||||||
predefinedVariables?: { [key: string]: string | undefined }
|
predefinedVariables?: { [key: string]: string | undefined }
|
||||||
resultId?: string
|
resultId?: string
|
||||||
|
startBlockId?: string
|
||||||
onNewBlockVisible?: (edge: Edge) => void
|
onNewBlockVisible?: (edge: Edge) => void
|
||||||
onNewAnswer?: (answer: Answer) => Promise<void>
|
onNewAnswer?: (answer: Answer) => Promise<void>
|
||||||
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
||||||
@@ -43,6 +44,7 @@ export const TypebotViewer = ({
|
|||||||
isPreview = false,
|
isPreview = false,
|
||||||
style,
|
style,
|
||||||
resultId,
|
resultId,
|
||||||
|
startBlockId,
|
||||||
predefinedVariables,
|
predefinedVariables,
|
||||||
onNewLog,
|
onNewLog,
|
||||||
onNewBlockVisible,
|
onNewBlockVisible,
|
||||||
@@ -116,6 +118,7 @@ export const TypebotViewer = ({
|
|||||||
onNewBlockVisible={handleNewBlockVisible}
|
onNewBlockVisible={handleNewBlockVisible}
|
||||||
onCompleted={handleCompleted}
|
onCompleted={handleCompleted}
|
||||||
predefinedVariables={predefinedVariables}
|
predefinedVariables={predefinedVariables}
|
||||||
|
startBlockId={startBlockId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{typebot.settings.general.isBrandingEnabled && <LiteBadge />}
|
{typebot.settings.general.isBrandingEnabled && <LiteBadge />}
|
||||||
|
|||||||
Reference in New Issue
Block a user