2
0

♻️ Improve file upload management

Closes #138
This commit is contained in:
Baptiste Arnaud
2022-11-07 08:25:09 +01:00
parent 1f44e8f31f
commit d102fe118c
16 changed files with 110 additions and 52 deletions

View File

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

View File

@ -37,11 +37,14 @@ export const WorkspaceSettingsForm = ({ onClose }: { onClose: () => void }) => {
<FormControl>
<FormLabel>Icon</FormLabel>
<Flex>
<EditableEmojiOrImageIcon
icon={workspace?.icon}
onChangeIcon={handleChangeIcon}
boxSize="40px"
/>
{workspace && (
<EditableEmojiOrImageIcon
uploadFilePath={`workspaces/${workspace.id}/icon`}
icon={workspace.icon}
onChangeIcon={handleChangeIcon}
boxSize="40px"
/>
)}
</Flex>
</FormControl>
<FormControl>

View File

@ -16,12 +16,14 @@ import { CodeEditor } from 'components/shared/CodeEditor'
import { MoreInfoTooltip } from 'components/shared/MoreInfoTooltip'
type Props = {
typebotId: string
typebotName: string
metadata: Metadata
onMetadataChange: (metadata: Metadata) => void
}
export const MetadataForm = ({
typebotId,
typebotName,
metadata,
onMetadataChange,
@ -57,7 +59,8 @@ export const MetadataForm = ({
</PopoverTrigger>
<PopoverContent p="4">
<ImageUploadContent
url={metadata.favIconUrl ?? ''}
filePath={`typebots/${typebotId}/favIcon`}
defaultUrl={metadata.favIconUrl ?? ''}
onSubmit={handleFavIconSubmit}
isGiphyEnabled={false}
/>
@ -81,7 +84,8 @@ export const MetadataForm = ({
</PopoverTrigger>
<PopoverContent p="4">
<ImageUploadContent
url={metadata.imageUrl}
filePath={`typebots/${typebotId}/ogImage`}
defaultUrl={metadata.imageUrl}
onSubmit={handleImageSubmit}
isGiphyEnabled={false}
/>

View File

@ -90,6 +90,7 @@ export const SettingsSideMenu = () => {
<AccordionPanel pb={4} px="6">
{typebot && (
<MetadataForm
typebotId={typebot.id}
typebotName={typebot.name}
metadata={typebot.settings.metadata}
onMetadataChange={handleMetadataChange}

View File

@ -11,12 +11,14 @@ import { EmojiOrImageIcon } from './EmojiOrImageIcon'
import { ImageUploadContent } from './ImageUploadContent'
type Props = {
uploadFilePath: string
icon?: string | null
onChangeIcon: (icon: string) => void
boxSize?: string
}
export const EditableEmojiOrImageIcon = ({
uploadFilePath,
icon,
onChangeIcon,
boxSize,
@ -47,7 +49,8 @@ export const EditableEmojiOrImageIcon = ({
</Tooltip>
<PopoverContent p="2">
<ImageUploadContent
url={icon ?? ''}
filePath={uploadFilePath}
defaultUrl={icon ?? ''}
onSubmit={onChangeIcon}
isGiphyEnabled={false}
isEmojiEnabled={true}

View File

@ -55,7 +55,7 @@ export const BlockNode = ({
setFocusedGroupId,
previewingEdge,
} = useGraph()
const { updateBlock } = useTypebot()
const { typebot, updateBlock } = useTypebot()
const [isConnecting, setIsConnecting] = useState(false)
const [isPopoverOpened, setIsPopoverOpened] = useState(
openedBlockId === block.id
@ -227,8 +227,9 @@ export const BlockNode = ({
</SettingsModal>
</>
)}
{isMediaBubbleBlock(block) && (
{typebot && isMediaBubbleBlock(block) && (
<MediaBubblePopoverContent
typebotId={typebot.id}
block={block}
onContentChange={handleContentChange}
/>

View File

@ -16,6 +16,7 @@ import { EmbedUploadContent } from './EmbedUploadContent'
import { VideoUploadContent } from './VideoUploadContent'
type Props = {
typebotId: string
block: Exclude<BubbleBlock, TextBubbleBlock>
onContentChange: (content: BubbleBlockContent) => void
}
@ -39,14 +40,19 @@ export const MediaBubblePopoverContent = (props: Props) => {
)
}
export const MediaBubbleContent = ({ block, onContentChange }: Props) => {
export const MediaBubbleContent = ({
typebotId,
block,
onContentChange,
}: Props) => {
const handleImageUrlChange = (url: string) => onContentChange({ url })
switch (block.type) {
case BubbleBlockType.IMAGE: {
return (
<ImageUploadContent
url={block.content?.url}
filePath={`typebots/${typebotId}/blocks/${block.id}`}
defaultUrl={block.content?.url}
onSubmit={handleImageUrlChange}
/>
)

View File

@ -2,12 +2,13 @@ import { useState } from 'react'
import { Button, Flex, HStack, Stack } from '@chakra-ui/react'
import { UploadButton } from '../buttons/UploadButton'
import { GiphySearchForm } from './GiphySearchForm'
import { useTypebot } from 'contexts/TypebotContext'
import { Input } from '../Textbox/Input'
import { EmojiSearchableList } from './emoji/EmojiSearchableList'
type Props = {
url?: string
filePath: string
includeFileName?: boolean
defaultUrl?: string
isEmojiEnabled?: boolean
isGiphyEnabled?: boolean
onSubmit: (url: string) => void
@ -15,7 +16,9 @@ type Props = {
}
export const ImageUploadContent = ({
url,
filePath,
includeFileName,
defaultUrl,
onSubmit,
isEmojiEnabled = false,
isGiphyEnabled = true,
@ -67,25 +70,41 @@ export const ImageUploadContent = ({
)}
</HStack>
<BodyContent tab={currentTab} onSubmit={handleSubmit} url={url} />
<BodyContent
filePath={filePath}
includeFileName={includeFileName}
tab={currentTab}
onSubmit={handleSubmit}
defaultUrl={defaultUrl}
/>
</Stack>
)
}
const BodyContent = ({
includeFileName,
filePath,
tab,
url,
defaultUrl,
onSubmit,
}: {
includeFileName?: boolean
filePath: string
tab: 'upload' | 'link' | 'giphy' | 'emoji'
url?: string
defaultUrl?: string
onSubmit: (url: string) => void
}) => {
switch (tab) {
case 'upload':
return <UploadFileContent onNewUrl={onSubmit} />
return (
<UploadFileContent
filePath={filePath}
includeFileName={includeFileName}
onNewUrl={onSubmit}
/>
)
case 'link':
return <EmbedLinkContent initialUrl={url} onNewUrl={onSubmit} />
return <EmbedLinkContent defaultUrl={defaultUrl} onNewUrl={onSubmit} />
case 'giphy':
return <GiphyContent onNewUrl={onSubmit} />
case 'emoji':
@ -93,30 +112,34 @@ const BodyContent = ({
}
}
type ContentProps = { initialUrl?: string; onNewUrl: (url: string) => void }
type ContentProps = { onNewUrl: (url: string) => void }
const UploadFileContent = ({ onNewUrl }: ContentProps) => {
const { typebot } = useTypebot()
return (
<Flex justify="center" py="2">
<UploadButton
filePath={`public/typebots/${typebot?.id}`}
onFileUploaded={onNewUrl}
includeFileName
colorScheme="blue"
>
Choose an image
</UploadButton>
</Flex>
)
}
const UploadFileContent = ({
filePath,
includeFileName,
onNewUrl,
}: ContentProps & { filePath: string; includeFileName?: boolean }) => (
<Flex justify="center" py="2">
<UploadButton
filePath={filePath}
onFileUploaded={onNewUrl}
includeFileName={includeFileName}
colorScheme="blue"
>
Choose an image
</UploadButton>
</Flex>
)
const EmbedLinkContent = ({ initialUrl, onNewUrl }: ContentProps) => (
const EmbedLinkContent = ({
defaultUrl,
onNewUrl,
}: ContentProps & { defaultUrl?: string }) => (
<Stack py="2">
<Input
placeholder={'Paste the image link...'}
onChange={onNewUrl}
defaultValue={initialUrl ?? ''}
defaultValue={defaultUrl ?? ''}
/>
</Stack>
)

View File

@ -140,10 +140,13 @@ export const TypebotHeader = () => {
size="sm"
/>
<HStack spacing={1}>
<EditableEmojiOrImageIcon
icon={typebot?.icon}
onChangeIcon={handleChangeIcon}
/>
{typebot && (
<EditableEmojiOrImageIcon
uploadFilePath={`typebots/${typebot.id}/icon`}
icon={typebot?.icon}
onChangeIcon={handleChangeIcon}
/>
)}
{typebot?.name && (
<EditableTypebotName
name={typebot?.name}

View File

@ -25,7 +25,7 @@ export const UploadButton = ({
files: [
{
file: await compressFile(file),
path: filePath + (includeFileName ? `/${file.name}` : ''),
path: `public/${filePath}${includeFileName ? `/${file.name}` : ''}`,
},
],
})

View File

@ -17,6 +17,7 @@ import { ImageUploadContent } from 'components/shared/ImageUploadContent'
import { DefaultAvatar } from 'assets/DefaultAvatar'
type Props = {
uploadFilePath: string
title: string
avatarProps?: AvatarProps
isDefaultCheck?: boolean
@ -24,6 +25,7 @@ type Props = {
}
export const AvatarForm = ({
uploadFilePath,
title,
avatarProps,
isDefaultCheck = false,
@ -71,7 +73,8 @@ export const AvatarForm = ({
<Portal>
<PopoverContent p="4">
<ImageUploadContent
url={avatarProps?.url}
filePath={uploadFilePath}
defaultUrl={avatarProps?.url}
onSubmit={handleImageUrl}
/>
</PopoverContent>

View File

@ -8,11 +8,16 @@ import { HostBubbles } from './HostBubbles'
import { InputsTheme } from './InputsTheme'
type Props = {
typebotId: string
chatTheme: ChatTheme
onChatThemeChange: (chatTheme: ChatTheme) => void
}
export const ChatThemeSettings = ({ chatTheme, onChatThemeChange }: Props) => {
export const ChatThemeSettings = ({
typebotId,
chatTheme,
onChatThemeChange,
}: Props) => {
const handleHostBubblesChange = (hostBubbles: ContainerColors) =>
onChatThemeChange({ ...chatTheme, hostBubbles })
const handleGuestBubblesChange = (guestBubbles: ContainerColors) =>
@ -30,12 +35,14 @@ export const ChatThemeSettings = ({ chatTheme, onChatThemeChange }: Props) => {
return (
<Stack spacing={6}>
<AvatarForm
uploadFilePath={`typebots/${typebotId}/hostAvatar`}
title="Bot avatar"
avatarProps={chatTheme.hostAvatar}
isDefaultCheck
onAvatarChange={handleHostAvatarChange}
/>
<AvatarForm
uploadFilePath={`typebots/${typebotId}/guestAvatar`}
title="User avatar"
avatarProps={chatTheme.guestAvatar}
onAvatarChange={handleGuestAvatarChange}

View File

@ -72,6 +72,7 @@ export const ThemeSideMenu = () => {
<AccordionPanel pb={4}>
{typebot && (
<ChatThemeSettings
typebotId={typebot.id}
chatTheme={typebot.theme.chat}
onChatThemeChange={handleChatThemeChange}
/>

View File

@ -21,7 +21,9 @@ test('should display user info properly', async ({ page }) => {
await expect(page.locator('img >> nth=1')).toHaveAttribute(
'src',
new RegExp(
`${process.env.S3_ENDPOINT}/${process.env.S3_BUCKET}/public/users/${userId}/avatar`,
`${process.env.S3_ENDPOINT}${
process.env.S3_PORT ? `:${process.env.S3_PORT}` : ''
}/${process.env.S3_BUCKET}/public/users/${userId}/avatar`,
'gm'
)
)

View File

@ -32,10 +32,11 @@ test.describe.parallel('Image bubble block', () => {
)
await expect(page.locator('img')).toHaveAttribute(
'src',
new RegExp(
`${process.env.S3_ENDPOINT}/${process.env.S3_BUCKET}/public/typebots/${typebotId}/avatar.jpg`,
'gm'
)
`${process.env.S3_SSL === 'false' ? 'http://' : 'https://'}${
process.env.S3_ENDPOINT
}${process.env.S3_PORT ? `:${process.env.S3_PORT}` : ''}/${
process.env.S3_BUCKET
}/public/typebots/${typebotId}/blocks/block1`
)
})