feat(bubbles): ✨ Add video bubble
This commit is contained in:
@ -2,6 +2,7 @@ import { BubbleStep, BubbleStepType } from 'models'
|
||||
import React from 'react'
|
||||
import { ImageBubble } from './ImageBubble'
|
||||
import { TextBubble } from './TextBubble'
|
||||
import { VideoBubble } from './VideoBubble'
|
||||
|
||||
type Props = {
|
||||
step: BubbleStep
|
||||
@ -14,5 +15,7 @@ export const HostBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
return <TextBubble step={step} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleStepType.IMAGE:
|
||||
return <ImageBubble step={step} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleStepType.VIDEO:
|
||||
return <VideoBubble step={step} onTransitionEnd={onTransitionEnd} />
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,132 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useHostAvatars } from 'contexts/HostAvatarsContext'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import {
|
||||
Table,
|
||||
Variable,
|
||||
VideoBubbleContent,
|
||||
VideoBubbleContentType,
|
||||
VideoBubbleStep,
|
||||
} from 'models'
|
||||
import { TypingContent } from './TypingContent'
|
||||
import { parseVariables } from 'services/variable'
|
||||
|
||||
type Props = {
|
||||
step: VideoBubbleStep
|
||||
onTransitionEnd: () => void
|
||||
}
|
||||
|
||||
export const showAnimationDuration = 400
|
||||
|
||||
export const mediaLoadingFallbackTimeout = 5000
|
||||
|
||||
export const VideoBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
const { typebot } = useTypebot()
|
||||
const { updateLastAvatarOffset } = useHostAvatars()
|
||||
const messageContainer = useRef<HTMLDivElement | null>(null)
|
||||
const [isTyping, setIsTyping] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
showContentAfterMediaLoad()
|
||||
}, [])
|
||||
|
||||
const showContentAfterMediaLoad = () => {
|
||||
setTimeout(() => {
|
||||
setIsTyping(false)
|
||||
onTypingEnd()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
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>
|
||||
<VideoContent
|
||||
content={step.content}
|
||||
isTyping={isTyping}
|
||||
variables={typebot.variables}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const VideoContent = ({
|
||||
content,
|
||||
isTyping,
|
||||
variables,
|
||||
}: {
|
||||
content?: VideoBubbleContent
|
||||
isTyping: boolean
|
||||
variables: Table<Variable>
|
||||
}) => {
|
||||
const url = useMemo(
|
||||
() => parseVariables({ text: content?.url, variables: variables }),
|
||||
[variables]
|
||||
)
|
||||
if (!content?.type) return <></>
|
||||
switch (content.type) {
|
||||
case VideoBubbleContentType.URL:
|
||||
const isSafariBrowser = window.navigator.vendor.match(/apple/i)
|
||||
return (
|
||||
<video
|
||||
controls
|
||||
className={
|
||||
'p-4 focus:outline-none w-full z-10 content-opacity rounded-md ' +
|
||||
(isTyping ? 'opacity-0' : 'opacity-100')
|
||||
}
|
||||
style={{
|
||||
height: isTyping ? '2rem' : 'auto',
|
||||
maxHeight: isSafariBrowser ? '40vh' : '',
|
||||
}}
|
||||
autoPlay
|
||||
>
|
||||
<source src={url} type="video/mp4" />
|
||||
Sorry, your browser doesn't support embedded videos.
|
||||
</video>
|
||||
)
|
||||
case VideoBubbleContentType.VIMEO:
|
||||
case VideoBubbleContentType.YOUTUBE: {
|
||||
const baseUrl =
|
||||
content.type === VideoBubbleContentType.VIMEO
|
||||
? 'https://player.vimeo.com/video'
|
||||
: 'https://www.youtube.com/embed'
|
||||
return (
|
||||
<iframe
|
||||
src={`${baseUrl}/${content.id}`}
|
||||
className={
|
||||
'w-full p-4 content-opacity z-10 rounded-md ' +
|
||||
(isTyping ? 'opacity-0' : 'opacity-100')
|
||||
}
|
||||
height={isTyping ? '2rem' : '200px'}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,18 @@
|
||||
import { StepBase } from '.'
|
||||
|
||||
export type BubbleStep = TextBubbleStep | ImageBubbleStep
|
||||
export type BubbleStep = TextBubbleStep | ImageBubbleStep | VideoBubbleStep
|
||||
|
||||
export enum BubbleStepType {
|
||||
TEXT = 'text',
|
||||
IMAGE = 'image',
|
||||
VIDEO = 'video',
|
||||
}
|
||||
|
||||
export type BubbleStepContent =
|
||||
| TextBubbleContent
|
||||
| ImageBubbleContent
|
||||
| VideoBubbleContent
|
||||
|
||||
export type TextBubbleStep = StepBase & {
|
||||
type: BubbleStepType.TEXT
|
||||
content: TextBubbleContent
|
||||
@ -17,6 +23,11 @@ export type ImageBubbleStep = StepBase & {
|
||||
content?: ImageBubbleContent
|
||||
}
|
||||
|
||||
export type VideoBubbleStep = StepBase & {
|
||||
type: BubbleStepType.VIDEO
|
||||
content?: VideoBubbleContent
|
||||
}
|
||||
|
||||
export type TextBubbleContent = {
|
||||
html: string
|
||||
richText: unknown[]
|
||||
@ -26,3 +37,15 @@ export type TextBubbleContent = {
|
||||
export type ImageBubbleContent = {
|
||||
url?: string
|
||||
}
|
||||
|
||||
export enum VideoBubbleContentType {
|
||||
URL = 'url',
|
||||
YOUTUBE = 'youtube',
|
||||
VIMEO = 'vimeo',
|
||||
}
|
||||
|
||||
export type VideoBubbleContent = {
|
||||
type?: VideoBubbleContentType
|
||||
url?: string
|
||||
id?: string
|
||||
}
|
||||
|
Reference in New Issue
Block a user