feat(bubbles): ✨ Add image bubble
This commit is contained in:
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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} />
|
||||
}
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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()
|
Reference in New Issue
Block a user