@ -1,3 +1,4 @@
|
||||
import { AudioBubble } from '@/features/blocks/bubbles/audio'
|
||||
import { EmbedBubble } from '@/features/blocks/bubbles/embed'
|
||||
import { ImageBubble } from '@/features/blocks/bubbles/image'
|
||||
import { TextBubble } from '@/features/blocks/bubbles/textBubble'
|
||||
@ -20,5 +21,12 @@ export const HostBubble = ({ block, onTransitionEnd }: Props) => {
|
||||
return <VideoBubble block={block} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleBlockType.EMBED:
|
||||
return <EmbedBubble block={block} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleBlockType.AUDIO:
|
||||
return (
|
||||
<AudioBubble
|
||||
url={block.content.url}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTypebot } from '@/providers/TypebotProvider'
|
||||
import { AudioBubbleContent } from 'models'
|
||||
import { TypingBubble } from '@/components/TypingBubble'
|
||||
import { parseVariables } from '@/features/variables'
|
||||
|
||||
type Props = {
|
||||
url: AudioBubbleContent['url']
|
||||
onTransitionEnd: () => void
|
||||
}
|
||||
|
||||
const showAnimationDuration = 400
|
||||
const typingDuration = 500
|
||||
|
||||
export const AudioBubble = ({ url, onTransitionEnd }: Props) => {
|
||||
const { typebot, isLoading } = useTypebot()
|
||||
const audio = useRef<HTMLAudioElement | null>(null)
|
||||
const [isTyping, setIsTyping] = useState(true)
|
||||
|
||||
const parsedUrl = useMemo(
|
||||
() => parseVariables(typebot.variables)(url),
|
||||
[url, typebot.variables]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTyping || isLoading) return
|
||||
|
||||
const typingTimeout = setTimeout(() => {
|
||||
setIsTyping(false)
|
||||
setTimeout(() => {
|
||||
onTransitionEnd()
|
||||
}, showAnimationDuration)
|
||||
}, typingDuration)
|
||||
|
||||
return () => {
|
||||
clearTimeout(typingTimeout)
|
||||
}
|
||||
}, [isLoading, isTyping, onTransitionEnd])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<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 ? <TypingBubble /> : null}
|
||||
</div>
|
||||
<audio
|
||||
ref={audio}
|
||||
src={parsedUrl}
|
||||
className={
|
||||
'z-10 content-opacity m-2 ' +
|
||||
(isTyping ? 'opacity-0' : 'opacity-100')
|
||||
}
|
||||
style={{ height: isTyping ? '2rem' : 'revert' }}
|
||||
autoPlay
|
||||
controls
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './AudioBubble'
|
@ -0,0 +1 @@
|
||||
export * from './components'
|
18
packages/models/src/features/blocks/bubbles/audio.ts
Normal file
18
packages/models/src/features/blocks/bubbles/audio.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { z } from 'zod'
|
||||
import { blockBaseSchema, BubbleBlockType } from '../shared'
|
||||
|
||||
export const audioBubbleContentSchema = z.object({
|
||||
url: z.string().optional(),
|
||||
})
|
||||
|
||||
export const audioBubbleBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([BubbleBlockType.AUDIO]),
|
||||
content: audioBubbleContentSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export const defaultAudioBubbleContent = {}
|
||||
|
||||
export type AudioBubbleBlock = z.infer<typeof audioBubbleBlockSchema>
|
||||
export type AudioBubbleContent = z.infer<typeof audioBubbleContentSchema>
|
@ -1,4 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
import { audioBubbleBlockSchema, audioBubbleContentSchema } from './audio'
|
||||
import { embedBubbleContentSchema, embedBubbleBlockSchema } from './embed'
|
||||
import { imageBubbleContentSchema, imageBubbleBlockSchema } from './image'
|
||||
import { textBubbleContentSchema, textBubbleBlockSchema } from './text'
|
||||
@ -8,11 +9,13 @@ export const bubbleBlockContentSchema = textBubbleContentSchema
|
||||
.or(imageBubbleContentSchema)
|
||||
.or(videoBubbleContentSchema)
|
||||
.or(embedBubbleContentSchema)
|
||||
.or(audioBubbleContentSchema)
|
||||
|
||||
export const bubbleBlockSchema = textBubbleBlockSchema
|
||||
.or(imageBubbleBlockSchema)
|
||||
.or(videoBubbleBlockSchema)
|
||||
.or(embedBubbleBlockSchema)
|
||||
.or(audioBubbleBlockSchema)
|
||||
|
||||
export type BubbleBlock = z.infer<typeof bubbleBlockSchema>
|
||||
export type BubbleBlockContent = z.infer<typeof bubbleBlockContentSchema>
|
||||
|
@ -3,3 +3,4 @@ export * from './text'
|
||||
export * from './image'
|
||||
export * from './video'
|
||||
export * from './embed'
|
||||
export * from './audio'
|
||||
|
@ -28,6 +28,7 @@ export enum BubbleBlockType {
|
||||
IMAGE = 'image',
|
||||
VIDEO = 'video',
|
||||
EMBED = 'embed',
|
||||
AUDIO = 'audio',
|
||||
}
|
||||
|
||||
export enum InputBlockType {
|
||||
|
Reference in New Issue
Block a user