♻️ Re-organize workspace folders

This commit is contained in:
Baptiste Arnaud
2023-03-15 08:35:16 +01:00
parent 25c367901f
commit cbc8194f19
987 changed files with 2716 additions and 2770 deletions

View File

@@ -0,0 +1,64 @@
import { useEffect, useRef, useState } from 'react'
import { useTypebot } from '@/providers/TypebotProvider'
import { AudioBubbleContent } from '@typebot.io/schemas'
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] = useState(parseVariables(typebot.variables)(url))
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>
)
}

View File

@@ -0,0 +1 @@
export * from './AudioBubble'

View File

@@ -0,0 +1 @@
export * from './components'

View File

@@ -0,0 +1,78 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { EmbedBubbleBlock } from '@typebot.io/schemas'
import { TypingBubble } from '../../../../../components/TypingBubble'
import { parseVariables } from '@/features/variables'
import { useTypebot } from '@/providers/TypebotProvider'
type Props = {
block: EmbedBubbleBlock
onTransitionEnd: () => void
}
export const showAnimationDuration = 400
export const EmbedBubble = ({ block, onTransitionEnd }: Props) => {
const { typebot, isLoading } = useTypebot()
const messageContainer = useRef<HTMLDivElement | null>(null)
const [isTyping, setIsTyping] = useState(true)
const [url] = useState(parseVariables(typebot.variables)(block.content?.url))
const onTypingEnd = useCallback(() => {
setIsTyping(false)
setTimeout(() => {
onTransitionEnd()
}, showAnimationDuration)
}, [onTransitionEnd])
useEffect(() => {
if (!isTyping || isLoading) return
const timeout = setTimeout(() => {
setIsTyping(false)
onTypingEnd()
}, 1000)
return () => {
clearTimeout(timeout)
}
}, [isLoading, isTyping, onTypingEnd])
const height = block.content.height
? typeof block.content.height === 'string'
? parseVariables(typebot.variables)(block.content.height) + 'px'
: block.content.height
: '2rem'
return (
<div className="flex flex-col w-full" 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 w-full'
}
>
<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 /> : <></>}
</div>
<iframe
id="embed-bubble-content"
src={url}
className={
'w-full z-20 p-4 content-opacity ' +
(isTyping ? 'opacity-0' : 'opacity-100')
}
style={{
height: isTyping ? '2rem' : height,
borderRadius: '15px',
}}
/>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1 @@
export { EmbedBubble } from './components/EmbedBubble'

View File

@@ -0,0 +1,86 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { useTypebot } from '@/providers/TypebotProvider'
import { ImageBubbleBlock } from '@typebot.io/schemas'
import { TypingBubble } from '@/components/TypingBubble'
import { parseVariables } from '@/features/variables'
type Props = {
block: ImageBubbleBlock
onTransitionEnd: () => void
}
export const showAnimationDuration = 400
export const mediaLoadingFallbackTimeout = 5000
export const ImageBubble = ({ block, onTransitionEnd }: Props) => {
const { typebot, isLoading } = useTypebot()
const messageContainer = useRef<HTMLDivElement | null>(null)
const image = useRef<HTMLImageElement | null>(null)
const [isTyping, setIsTyping] = useState(true)
const [url] = useState(parseVariables(typebot.variables)(block.content?.url))
const onTypingEnd = useCallback(() => {
setIsTyping(false)
setTimeout(() => {
onTransitionEnd()
}, showAnimationDuration)
}, [onTransitionEnd])
useEffect(() => {
if (!isTyping || isLoading) return
const timeout = setTimeout(() => {
setIsTyping(false)
onTypingEnd()
}, mediaLoadingFallbackTimeout)
return () => {
clearTimeout(timeout)
}
}, [isLoading, isTyping, onTypingEnd])
useEffect(() => {
const currentImage = image.current
if (!currentImage || isLoading || !isTyping) return
currentImage.onload = () => {
setIsTyping(false)
onTypingEnd()
}
return () => {
currentImage.onload = null
}
}, [isLoading, isTyping, onTypingEnd])
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 ? <TypingBubble /> : null}
</div>
<img
ref={image}
src={url}
className={
'p-4 content-opacity z-10 w-auto rounded-lg ' +
(isTyping ? 'opacity-0' : 'opacity-100')
}
style={{
maxHeight: '32rem',
height: isTyping ? '2rem' : 'auto',
maxWidth: '100%',
}}
alt="Bubble image"
/>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1 @@
export { ImageBubble } from './components/ImageBubble'

View File

@@ -0,0 +1,91 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { useTypebot } from '@/providers/TypebotProvider'
import { BubbleBlockType, TextBubbleBlock } from '@typebot.io/schemas'
import { computeTypingDuration } from '../utils/computeTypingDuration'
import { parseVariables } from '@/features/variables'
import { TypingBubble } from '@/components/TypingBubble'
type Props = {
block: TextBubbleBlock
onTransitionEnd: () => void
}
export const showAnimationDuration = 400
const defaultTypingEmulation = {
enabled: true,
speed: 300,
maxDelay: 1.5,
}
export const TextBubble = ({ block, onTransitionEnd }: Props) => {
const { typebot, isLoading } = useTypebot()
const messageContainer = useRef<HTMLDivElement | null>(null)
const [isTyping, setIsTyping] = useState(true)
const [content] = useState(
parseVariables(typebot.variables)(block.content.html)
)
const onTypingEnd = useCallback(() => {
setIsTyping(false)
setTimeout(() => {
onTransitionEnd()
}, showAnimationDuration)
}, [onTransitionEnd])
useEffect(() => {
if (!isTyping || isLoading) return
const typingTimeout = computeTypingDuration(
block.content.plainText,
typebot.settings?.typingEmulation ?? defaultTypingEmulation
)
const timeout = setTimeout(() => {
onTypingEnd()
}, typingTimeout)
return () => {
clearTimeout(timeout)
}
}, [
block.content.plainText,
isLoading,
isTyping,
onTypingEnd,
typebot.settings?.typingEmulation,
])
return (
<div className="flex flex-col" ref={messageContainer}>
<div className="flex mb-2 w-full items-center">
<div className={'flex relative items-start typebot-host-bubble'}>
<div
className="flex items-center absolute px-4 py-2 rounded-lg bubble-typing "
style={{
width: isTyping ? '4rem' : '100%',
height: isTyping ? '2rem' : '100%',
}}
data-testid="host-bubble"
>
{isTyping ? <TypingBubble /> : null}
</div>
{block.type === BubbleBlockType.TEXT && (
<p
style={{
textOverflow: 'ellipsis',
}}
className={
'overflow-hidden content-opacity mx-4 my-2 whitespace-pre-wrap slate-html-container relative ' +
(isTyping ? 'opacity-0 h-6' : 'opacity-100 h-full')
}
dangerouslySetInnerHTML={{
__html: content,
}}
/>
)}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1 @@
export { TextBubble } from './components/TextBubble'

View File

@@ -0,0 +1,16 @@
import { TypingEmulation } from '@typebot.io/schemas'
export const computeTypingDuration = (
bubbleContent: string,
typingSettings: TypingEmulation
) => {
let wordCount = bubbleContent.match(/(\w+)/g)?.length ?? 0
if (wordCount === 0) wordCount = bubbleContent.length
const typedWordsPerMinute = typingSettings.speed
let typingTimeout = typingSettings.enabled
? (wordCount / typedWordsPerMinute) * 60000
: 0
if (typingTimeout > typingSettings.maxDelay * 1000)
typingTimeout = typingSettings.maxDelay * 1000
return typingTimeout
}

View File

@@ -0,0 +1,123 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { useTypebot } from '@/providers/TypebotProvider'
import {
Variable,
VideoBubbleContent,
VideoBubbleContentType,
VideoBubbleBlock,
} from '@typebot.io/schemas'
import { TypingBubble } from '@/components/TypingBubble'
import { parseVariables } from '@/features/variables'
type Props = {
block: VideoBubbleBlock
onTransitionEnd: () => void
}
export const showAnimationDuration = 400
export const mediaLoadingFallbackTimeout = 5000
export const VideoBubble = ({ block, onTransitionEnd }: Props) => {
const { typebot, isLoading } = useTypebot()
const messageContainer = useRef<HTMLDivElement | null>(null)
const [isTyping, setIsTyping] = useState(true)
const onTypingEnd = useCallback(() => {
setIsTyping(false)
setTimeout(() => {
onTransitionEnd()
}, showAnimationDuration)
}, [onTransitionEnd])
useEffect(() => {
if (!isTyping || isLoading) return
const timeout = setTimeout(() => {
setIsTyping(false)
onTypingEnd()
}, 1000)
return () => {
clearTimeout(timeout)
}
}, [isLoading, isTyping, onTypingEnd])
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 ? <TypingBubble /> : <></>}
</div>
<VideoContent
content={block.content}
isTyping={isTyping}
variables={typebot.variables}
/>
</div>
</div>
</div>
)
}
const VideoContent = ({
content,
isTyping,
variables,
}: {
content?: VideoBubbleContent
isTyping: boolean
variables: Variable[]
}) => {
const [url] = useState(parseVariables(variables)(content?.url))
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&apos;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
/>
)
}
}
}

View File

@@ -0,0 +1 @@
export { VideoBubble } from './components/VideoBubble'