2
0
Files
bot/packages/bot-engine/src/components/ConversationContainer.tsx

112 lines
3.9 KiB
TypeScript
Raw Normal View History

import React, { useEffect, useRef, useState } from 'react'
import { ChatBlock } from './ChatBlock/ChatBlock'
2021-12-23 09:37:42 +01:00
import { useFrame } from 'react-frame-component'
import { setCssVariablesValue } from '../services/theme'
import { useAnswers } from '../contexts/AnswersContext'
2022-03-09 15:12:00 +01:00
import { Block, Edge, PublicTypebot, Theme, VariableWithValue } from 'models'
import { byId, isNotDefined } from 'utils'
import { animateScroll as scroll } from 'react-scroll'
2022-03-09 15:12:00 +01:00
import { LinkedTypebot, useTypebot } from 'contexts/TypebotContext'
type Props = {
2022-02-10 10:25:38 +01:00
theme: Theme
2022-03-23 09:56:39 +01:00
predefinedVariables?: { [key: string]: string | undefined }
onNewBlockVisible: (edge: Edge) => void
onCompleted: () => void
onVariablesPrefilled?: (prefilledVariables: VariableWithValue[]) => void
}
export const ConversationContainer = ({
2022-02-10 10:25:38 +01:00
theme,
2022-03-23 09:56:39 +01:00
predefinedVariables,
onNewBlockVisible,
onCompleted,
onVariablesPrefilled,
}: Props) => {
const { typebot, updateVariableValue } = useTypebot()
2021-12-23 09:37:42 +01:00
const { document: frameDocument } = useFrame()
2022-01-15 17:30:20 +01:00
const [displayedBlocks, setDisplayedBlocks] = useState<
{ block: Block; startStepIndex: number }[]
2022-01-15 17:30:20 +01:00
>([])
2022-03-01 11:40:22 +01:00
const { setPrefilledVariables } = useAnswers()
const bottomAnchor = useRef<HTMLDivElement | null>(null)
const scrollableContainer = useRef<HTMLDivElement | null>(null)
2022-03-09 15:12:00 +01:00
const displayNextBlock = (
edgeId?: string,
updatedTypebot?: PublicTypebot | LinkedTypebot
) => {
const currentTypebot = updatedTypebot ?? typebot
const nextEdge = currentTypebot.edges.find(byId(edgeId))
if (!nextEdge) return onCompleted()
2022-03-09 15:12:00 +01:00
const nextBlock = currentTypebot.blocks.find(byId(nextEdge.to.blockId))
if (!nextBlock) return onCompleted()
const startStepIndex = nextEdge.to.stepId
? nextBlock.steps.findIndex(byId(nextEdge.to.stepId))
: 0
onNewBlockVisible(nextEdge)
setDisplayedBlocks([
...displayedBlocks,
{ block: nextBlock, startStepIndex },
])
}
useEffect(() => {
const prefilledVariables = injectPredefinedVariables(predefinedVariables)
if (onVariablesPrefilled) onVariablesPrefilled(prefilledVariables)
setPrefilledVariables(prefilledVariables)
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const injectPredefinedVariables = (predefinedVariables?: {
2022-03-23 09:56:39 +01:00
[key: string]: string | undefined
}) => {
const prefilledVariables: VariableWithValue[] = []
Object.keys(predefinedVariables ?? {}).forEach((key) => {
const matchingVariable = typebot.variables.find(
(v) => v.name.toLowerCase() === key.toLowerCase()
)
if (!predefinedVariables || isNotDefined(matchingVariable)) return
2022-03-23 09:56:39 +01:00
const value = predefinedVariables[key]
if (!value) return
updateVariableValue(matchingVariable?.id, value)
prefilledVariables.push({ ...matchingVariable, value })
})
return prefilledVariables
}
2021-12-23 09:37:42 +01:00
useEffect(() => {
2022-02-10 10:25:38 +01:00
setCssVariablesValue(theme, frameDocument.body.style)
}, [theme, frameDocument])
2021-12-23 09:37:42 +01:00
const autoScrollToBottom = () => {
if (!scrollableContainer.current) return
setTimeout(() => {
scroll.scrollToBottom({
duration: 500,
container: scrollableContainer.current,
})
}, 1)
}
return (
<div
ref={scrollableContainer}
className="overflow-y-scroll w-full lg:w-3/4 min-h-full rounded lg:px-5 px-3 pt-10 relative scrollable-container typebot-chat-view"
>
2022-01-15 17:30:20 +01:00
{displayedBlocks.map((displayedBlock, idx) => (
<ChatBlock
2022-01-15 17:30:20 +01:00
key={displayedBlock.block.id + idx}
steps={displayedBlock.block.steps}
startStepIndex={displayedBlock.startStepIndex}
onScroll={autoScrollToBottom}
onBlockEnd={displayNextBlock}
/>
))}
{/* We use a block to simulate padding because it makes iOS scroll flicker */}
<div className="w-full h-32" ref={bottomAnchor} />
</div>
)
}