2
0

feat(bubbles): Add image bubble

This commit is contained in:
Baptiste Arnaud
2022-01-20 16:14:47 +01:00
parent c43fd1d386
commit 2d178978ef
33 changed files with 848 additions and 142 deletions

View File

@ -4,14 +4,14 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group'
import { ChatStep } from './ChatStep'
import { AvatarSideContainer } from './AvatarSideContainer'
import { HostAvatarsContext } from '../../contexts/HostAvatarsContext'
import { Edge, Step, Target } from 'models'
import { Step } from 'models'
import { useTypebot } from '../../contexts/TypebotContext'
import {
isBubbleStep,
isChoiceInput,
isInputStep,
isIntegrationStep,
isLogicStep,
isTextBubbleStep,
} from 'utils'
import { executeLogic } from 'services/logic'
import { getSingleChoiceTargetId } from 'services/inputs'
@ -104,7 +104,7 @@ export const ChatBlock = ({
<div className="flex flex-col w-full">
<TransitionGroup>
{displayedSteps
.filter((step) => isInputStep(step) || isTextBubbleStep(step))
.filter((step) => isInputStep(step) || isBubbleStep(step))
.map((step) => (
<CSSTransition
key={step.id}

View File

@ -3,11 +3,11 @@ import { useAnswers } from '../../../contexts/AnswersContext'
import { useHostAvatars } from '../../../contexts/HostAvatarsContext'
import { InputStep, InputStepType, Step } from 'models'
import { GuestBubble } from './bubbles/GuestBubble'
import { HostMessageBubble } from './bubbles/HostMessageBubble'
import { TextForm } from './inputs/TextForm'
import { isInputStep, isTextBubbleStep } from 'utils'
import { isBubbleStep, isInputStep } from 'utils'
import { DateForm } from './inputs/DateForm'
import { ChoiceForm } from './inputs/ChoiceForm'
import { HostBubble } from './bubbles/HostBubble'
export const ChatStep = ({
step,
@ -23,8 +23,8 @@ export const ChatStep = ({
onTransitionEnd(content)
}
if (isTextBubbleStep(step))
return <HostMessageBubble step={step} onTransitionEnd={onTransitionEnd} />
if (isBubbleStep(step))
return <HostBubble step={step} onTransitionEnd={onTransitionEnd} />
if (isInputStep(step))
return <InputChatStep step={step} onSubmit={handleInputSubmit} />
return <span>No step</span>

View File

@ -0,0 +1,18 @@
import { BubbleStep, BubbleStepType } from 'models'
import React from 'react'
import { ImageBubble } from './ImageBubble'
import { TextBubble } from './TextBubble'
type Props = {
step: BubbleStep
onTransitionEnd: () => void
}
export const HostBubble = ({ step, onTransitionEnd }: Props) => {
switch (step.type) {
case BubbleStepType.TEXT:
return <TextBubble step={step} onTransitionEnd={onTransitionEnd} />
case BubbleStepType.IMAGE:
return <ImageBubble step={step} onTransitionEnd={onTransitionEnd} />
}
}

View File

@ -0,0 +1,91 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { useHostAvatars } from 'contexts/HostAvatarsContext'
import { useTypebot } from 'contexts/TypebotContext'
import { ImageBubbleStep } from 'models'
import { TypingContent } from './TypingContent'
import { parseVariables } from 'services/variable'
type Props = {
step: ImageBubbleStep
onTransitionEnd: () => void
}
export const showAnimationDuration = 400
export const mediaLoadingFallbackTimeout = 5000
export const ImageBubble = ({ step, onTransitionEnd }: Props) => {
const { typebot } = useTypebot()
const { updateLastAvatarOffset } = useHostAvatars()
const messageContainer = useRef<HTMLDivElement | null>(null)
const image = useRef<HTMLImageElement | null>(null)
const [isTyping, setIsTyping] = useState(true)
const url = useMemo(
() =>
parseVariables({ text: step.content?.url, variables: typebot.variables }),
[typebot.variables]
)
useEffect(() => {
showContentAfterMediaLoad()
}, [])
const showContentAfterMediaLoad = () => {
if (!image.current) return
const timeout = setTimeout(() => {
setIsTyping(false)
onTypingEnd()
}, mediaLoadingFallbackTimeout)
image.current.onload = () => {
clearTimeout(timeout)
setIsTyping(false)
onTypingEnd()
}
}
const onTypingEnd = () => {
setIsTyping(false)
setTimeout(() => {
sendAvatarOffset()
onTransitionEnd()
}, showAnimationDuration)
}
const sendAvatarOffset = () => {
if (!messageContainer.current) return
const containerDimensions = messageContainer.current.getBoundingClientRect()
updateLastAvatarOffset(containerDimensions.top + containerDimensions.height)
}
return (
<div className="flex flex-col" ref={messageContainer}>
<div className="flex mb-2 w-full lg:w-11/12 items-center">
<div className={'flex relative z-10 items-start typebot-host-bubble'}>
<div
className="flex items-center absolute px-4 py-2 rounded-lg bubble-typing z-10 "
style={{
width: isTyping ? '4rem' : '100%',
height: isTyping ? '2rem' : '100%',
}}
>
{isTyping ? <TypingContent /> : <></>}
</div>
<img
ref={image}
src={url}
className={
'p-4 content-opacity z-10 w-auto ' +
(isTyping ? 'opacity-0' : 'opacity-100')
}
style={{
maxHeight: '32rem',
height: isTyping ? '2rem' : 'auto',
maxWidth: '100%',
}}
/>
</div>
</div>
</div>
)
}

View File

@ -1,24 +1,19 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { useHostAvatars } from 'contexts/HostAvatarsContext'
import { useTypebot } from 'contexts/TypebotContext'
import { BubbleStepType, TextStep } from 'models'
import { BubbleStepType, TextBubbleStep } from 'models'
import { computeTypingTimeout } from 'services/chat'
import { TypingContent } from './TypingContent'
import { parseVariables } from 'services/variable'
type HostMessageBubbleProps = {
step: TextStep
type Props = {
step: TextBubbleStep
onTransitionEnd: () => void
}
export const showAnimationDuration = 400
export const mediaLoadingFallbackTimeout = 5000
export const HostMessageBubble = ({
step,
onTransitionEnd,
}: HostMessageBubbleProps) => {
export const TextBubble = ({ step, onTransitionEnd }: Props) => {
const { typebot } = useTypebot()
const { typingEmulation } = typebot.settings
const { updateLastAvatarOffset } = useHostAvatars()