2
0

Add audio bubble block

Closes #167
This commit is contained in:
Baptiste Arnaud
2022-11-17 10:33:17 +01:00
parent 473d315e0f
commit 7db0e01aca
29 changed files with 306 additions and 26 deletions

View File

@ -121,6 +121,7 @@ const UploadFileContent = ({
}: ContentProps & { filePath: string; includeFileName?: boolean }) => (
<Flex justify="center" py="2">
<UploadButton
fileType="image"
filePath={filePath}
onFileUploaded={onNewUrl}
includeFileName={includeFileName}

View File

@ -4,12 +4,14 @@ import { ChangeEvent, useState } from 'react'
import { uploadFiles } from 'utils'
type UploadButtonProps = {
fileType: 'image' | 'audio'
filePath: string
includeFileName?: boolean
onFileUploaded: (url: string) => void
} & ButtonProps
export const UploadButton = ({
fileType,
filePath,
includeFileName,
onFileUploaded,
@ -41,7 +43,11 @@ export const UploadButton = ({
id="file-input"
display="none"
onChange={handleInputChange}
accept=".jpg, .jpeg, .png, .svg, .gif"
accept={
fileType === 'image'
? '.jpg, .jpeg, .png, .svg, .gif'
: '.mp3, .wav, .ogg'
}
/>
<Button
as="label"

View File

@ -1,6 +1,6 @@
import { IconProps, Icon } from '@chakra-ui/react'
const featherIconsBaseProps: IconProps = {
export const featherIconsBaseProps: IconProps = {
fill: 'none',
stroke: 'currentColor',
strokeWidth: '2px',

View File

@ -52,6 +52,7 @@ export const MyAccountForm = () => {
<Stack>
<UploadButton
size="sm"
fileType="image"
filePath={`users/${user?.id}/avatar`}
leftIcon={<UploadIcon />}
onFileUploaded={handleFileUploaded}

View File

@ -0,0 +1,41 @@
import test, { expect } from '@playwright/test'
import { createTypebots } from 'utils/playwright/databaseActions'
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
import { BubbleBlockType, defaultAudioBubbleContent } from 'models'
import cuid from 'cuid'
import { getTestAsset } from '@/test/utils/playwright'
import { typebotViewer } from 'utils/playwright/testHelpers'
const audioSampleUrl =
'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'
test('should work as expected', async ({ page }) => {
const typebotId = cuid()
await createTypebots([
{
id: typebotId,
...parseDefaultGroupWithBlock({
type: BubbleBlockType.AUDIO,
content: defaultAudioBubbleContent,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.getByText('Click to edit...').click()
await page
.getByPlaceholder('Paste the audio file link...')
.fill(audioSampleUrl)
await expect(page.locator('audio')).toHaveAttribute('src', audioSampleUrl)
await page.getByRole('button', { name: 'Upload' }).click()
await page.setInputFiles('input[type="file"]', getTestAsset('sample.mp3'))
await expect(page.locator('audio')).toHaveAttribute(
'src',
RegExp(`/public/typebots/${typebotId}/blocks`, 'gm')
)
await page.getByRole('button', { name: 'Preview' }).click()
await expect(typebotViewer(page).locator('audio')).toHaveAttribute(
'src',
RegExp(`/public/typebots/${typebotId}/blocks`, 'gm')
)
})

View File

@ -0,0 +1,68 @@
import { Button, Flex, HStack, Stack, Text } from '@chakra-ui/react'
import { AudioBubbleContent } from 'models'
import { Input } from '@/components/inputs'
import { useState } from 'react'
import { UploadButton } from '@/components/ImageUploadContent/UploadButton'
type Props = {
fileUploadPath: string
content: AudioBubbleContent
onSubmit: (content: AudioBubbleContent) => void
}
export const AudioBubbleForm = ({
fileUploadPath,
content,
onSubmit,
}: Props) => {
const [currentTab, setCurrentTab] = useState<'link' | 'upload'>('link')
const submit = (url: string) => onSubmit({ url })
return (
<Stack>
<HStack>
<Button
variant={currentTab === 'upload' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('upload')}
size="sm"
>
Upload
</Button>
<Button
variant={currentTab === 'link' ? 'solid' : 'ghost'}
onClick={() => setCurrentTab('link')}
size="sm"
>
Embed link
</Button>
</HStack>
<Stack p="2">
{currentTab === 'upload' && (
<Flex justify="center" py="2">
<UploadButton
fileType="audio"
filePath={fileUploadPath}
onFileUploaded={submit}
colorScheme="blue"
>
Choose a file
</UploadButton>
</Flex>
)}
{currentTab === 'link' && (
<>
<Input
placeholder="Paste the audio file link..."
defaultValue={content.url ?? ''}
onChange={submit}
/>
<Text fontSize="sm" color="gray.400" textAlign="center">
Works with .MP3s, .WAVs and .OGGs
</Text>
</>
)}
</Stack>
</Stack>
)
}

View File

@ -0,0 +1,10 @@
import { featherIconsBaseProps } from '@/components/icons'
import { Icon, IconProps } from '@chakra-ui/react'
import React from 'react'
export const AudioBubbleIcon = (props: IconProps) => (
<Icon color="blue.500" {...featherIconsBaseProps} {...props}>
<path d="M3 18v-6a9 9 0 0 1 18 0v6"></path>
<path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path>
</Icon>
)

View File

@ -0,0 +1,14 @@
import { Text } from '@chakra-ui/react'
import { AudioBubbleContent } from 'models'
import { isDefined } from 'utils'
type Props = {
url: AudioBubbleContent['url']
}
export const AudioBubbleNode = ({ url }: Props) =>
isDefined(url) ? (
<audio src={url} controls />
) : (
<Text color={'gray.500'}>Click to edit...</Text>
)

View File

@ -0,0 +1,2 @@
export * from './AudioBubbleNode'
export * from './AudioBubbleIcon'

View File

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

View File

@ -35,6 +35,7 @@ import { NumberInputIcon } from '@/features/blocks/inputs/number'
import { TextInputIcon } from '@/features/blocks/inputs/textInput'
import { EmbedBubbleIcon } from '@/features/blocks/bubbles/embed'
import { GoogleAnalyticsLogo } from '@/features/blocks/integrations/googleAnalytics'
import { AudioBubbleIcon } from '@/features/blocks/bubbles/audio'
type BlockIconProps = { type: BlockType } & IconProps
@ -47,37 +48,39 @@ export const BlockIcon = ({ type, ...props }: BlockIconProps) => {
case BubbleBlockType.VIDEO:
return <VideoBubbleIcon {...props} />
case BubbleBlockType.EMBED:
return <EmbedBubbleIcon color="blue.500" {...props} />
return <EmbedBubbleIcon {...props} />
case BubbleBlockType.AUDIO:
return <AudioBubbleIcon {...props} />
case InputBlockType.TEXT:
return <TextInputIcon color="orange.500" {...props} />
return <TextInputIcon {...props} />
case InputBlockType.NUMBER:
return <NumberInputIcon color="orange.500" {...props} />
return <NumberInputIcon {...props} />
case InputBlockType.EMAIL:
return <EmailInputIcon color="orange.500" {...props} />
return <EmailInputIcon {...props} />
case InputBlockType.URL:
return <UrlInputIcon color="orange.500" {...props} />
return <UrlInputIcon {...props} />
case InputBlockType.DATE:
return <DateInputIcon color="orange.500" {...props} />
return <DateInputIcon {...props} />
case InputBlockType.PHONE:
return <PhoneInputIcon color="orange.500" {...props} />
return <PhoneInputIcon {...props} />
case InputBlockType.CHOICE:
return <ButtonsInputIcon color="orange.500" {...props} />
return <ButtonsInputIcon {...props} />
case InputBlockType.PAYMENT:
return <PaymentInputIcon color="orange.500" {...props} />
return <PaymentInputIcon {...props} />
case InputBlockType.RATING:
return <RatingInputIcon color="orange.500" {...props} />
return <RatingInputIcon {...props} />
case InputBlockType.FILE:
return <FileInputIcon color="orange.500" {...props} />
return <FileInputIcon {...props} />
case LogicBlockType.SET_VARIABLE:
return <SetVariableIcon color="purple.500" {...props} />
return <SetVariableIcon {...props} />
case LogicBlockType.CONDITION:
return <ConditionIcon color="purple.500" {...props} />
return <ConditionIcon {...props} />
case LogicBlockType.REDIRECT:
return <RedirectIcon color="purple.500" {...props} />
return <RedirectIcon {...props} />
case LogicBlockType.CODE:
return <CodeIcon color="purple.500" {...props} />
return <CodeIcon {...props} />
case LogicBlockType.TYPEBOT_LINK:
return <TypebotLinkIcon color="purple.500" {...props} />
return <TypebotLinkIcon {...props} />
case IntegrationBlockType.GOOGLE_SHEETS:
return <GoogleSheetsLogo {...props} />
case IntegrationBlockType.GOOGLE_ANALYTICS:

View File

@ -32,6 +32,8 @@ export const BlockTypeLabel = ({ type }: Props): JSX.Element => {
<Text>Embed</Text>
</Tooltip>
)
case BubbleBlockType.AUDIO:
return <Text>Audio</Text>
case InputBlockType.NUMBER:
return <Text>Number</Text>
case InputBlockType.EMAIL:

View File

@ -36,6 +36,7 @@ import { ZapierContent } from '@/features/blocks/integrations/zapier'
import { SendEmailContent } from '@/features/blocks/integrations/sendEmail'
import { isInputBlock, isChoiceInput, blockHasItems } from 'utils'
import { MakeComNodeContent } from '@/features/blocks/integrations/makeCom'
import { AudioBubbleNode } from '@/features/blocks/bubbles/audio'
type Props = {
block: Block | StartBlock
@ -66,6 +67,9 @@ export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
case BubbleBlockType.EMBED: {
return <EmbedBubbleContent block={block} />
}
case BubbleBlockType.AUDIO: {
return <AudioBubbleNode url={block.content.url} />
}
case InputBlockType.TEXT: {
return (
<TextInputNodeContent

View File

@ -1,4 +1,5 @@
import { ImageUploadContent } from '@/components/ImageUploadContent'
import { AudioBubbleForm } from '@/features/blocks/bubbles/audio/components/AudioBubbleForm'
import { EmbedUploadContent } from '@/features/blocks/bubbles/embed'
import { VideoUploadContent } from '@/features/blocks/bubbles/video'
import {
@ -73,5 +74,14 @@ export const MediaBubbleContent = ({
/>
)
}
case BubbleBlockType.AUDIO: {
return (
<AudioBubbleForm
content={block.content}
fileUploadPath={`typebots/${typebotId}/blocks/${block.id}`}
onSubmit={onContentChange}
/>
)
}
}
}

View File

@ -15,6 +15,7 @@ import {
defaultGoogleAnalyticsOptions,
defaultGoogleSheetsOptions,
defaultImageBubbleContent,
defaultAudioBubbleContent,
defaultNumberInputOptions,
defaultPaymentInputOptions,
defaultPhoneInputOptions,
@ -400,6 +401,8 @@ const parseDefaultContent = (type: BubbleBlockType): BubbleBlockContent => {
return defaultVideoBubbleContent
case BubbleBlockType.EMBED:
return defaultEmbedBubbleContent
case BubbleBlockType.AUDIO:
return defaultAudioBubbleContent
}
}

Binary file not shown.