feat: overhaul image upload

This commit is contained in:
diced 2023-01-14 12:04:30 -08:00
parent 516e93cee2
commit 894b5c5c6c
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
6 changed files with 135 additions and 81 deletions

View file

@ -37,8 +37,11 @@ function Placeholder({ text, Icon, ...props }) {
}
export default function Type({ file, popup = false, disableMediaPreview, ...props }) {
const type = (file.type || file.mimetype).split('/')[0];
const name = file.name || file.file;
const type =
(file.type ?? file.mimetype) === ''
? file.name.split('.').pop()
: (file.type ?? file.mimetype).split('/')[0];
const name = file.name ?? file.file;
const media = /^(video|audio|image|text)/.test(type);

View file

@ -1,4 +1,4 @@
import { Group, Text, useMantineTheme } from '@mantine/core';
import { Box, Group, SimpleGrid, Text, useMantineTheme } from '@mantine/core';
import { Dropzone as MantineDropzone } from '@mantine/dropzone';
import { ImageIcon } from 'components/icons';
@ -6,16 +6,23 @@ export default function Dropzone({ loading, onDrop, children }) {
const theme = useMantineTheme();
return (
<MantineDropzone onDrop={onDrop}>
<Group position='center' spacing='xl' style={{ minHeight: 440 }}>
<ImageIcon size={80} />
<SimpleGrid
cols={2}
breakpoints={[
{ maxWidth: 'md', cols: 1 },
{ maxWidth: 'xs', cols: 1 },
]}
>
<MantineDropzone onDrop={onDrop} styles={{ inner: { pointerEvents: 'none' } }}>
<Group position='center' spacing='xl' style={{ minHeight: 440 }}>
<ImageIcon size={80} />
<Text size='xl' inline>
Drag files here or click to select files
</Text>
</Group>
<div style={{ pointerEvents: 'all' }}>{children}</div>
</MantineDropzone>
<Text size='xl' inline>
Drag files here or click to select files
</Text>
</Group>
</MantineDropzone>
<Box>{children}</Box>
</SimpleGrid>
);
}

View file

@ -1,5 +1,6 @@
import { Badge, Group, HoverCard, Table, useMantineTheme } from '@mantine/core';
import { ActionIcon, Badge, Box, Card, Group, HoverCard, Table, useMantineTheme } from '@mantine/core';
import Type from 'components/Type';
import { X } from 'react-feather';
export function FilePreview({ file }: { file: File }) {
return (
@ -16,15 +17,36 @@ export function FilePreview({ file }: { file: File }) {
);
}
export default function FileDropzone({ file }: { file: File }) {
export default function FileDropzone({ file, onRemove }: { file: File; onRemove: () => void }) {
const theme = useMantineTheme();
return (
<HoverCard shadow='md'>
<HoverCard.Target>
<Badge size='lg'>{file.name}</Badge>
{/* <Badge size='lg'>{file.name}</Badge> */}
<Card shadow='sm' radius='sm' p='sm'>
<Group position='center' spacing='xl'>
{file.name}
</Group>
</Card>
</HoverCard.Target>
<HoverCard.Dropdown>
{/* x button that will remove file */}
<Box
sx={{
position: 'absolute',
top: 0,
right: 0,
zIndex: 1,
color: theme.colorScheme === 'dark' ? 'white' : 'white',
}}
m='xs'
>
<ActionIcon onClick={onRemove} size='sm' color='red' variant='filled'>
<X />
</ActionIcon>
</Box>
<Group grow>
<FilePreview file={file} />

View file

@ -60,8 +60,6 @@ export default function Flameshot({ user, open, setOpen }) {
curl.push(`"${key}: ${value}"`);
}
console.log(curl);
let shell;
if (values.type === 'upload-file') {
shell = `#!/bin/bash${values.wlCompositorNotSupported ? '\nexport XDG_CURRENT_DESKTOP=sway\n' : ''}

View file

@ -1,4 +1,5 @@
import {
Box,
Button,
Collapse,
Group,
@ -6,6 +7,8 @@ import {
PasswordInput,
Progress,
Select,
Stack,
Text,
Title,
Tooltip,
} from '@mantine/core';
@ -15,6 +18,7 @@ import { showNotification, updateNotification } from '@mantine/notifications';
import Dropzone from 'components/dropzone/Dropzone';
import FileDropzone from 'components/dropzone/DropzoneFile';
import { ClockIcon, CrossIcon, UploadIcon } from 'components/icons';
import MutedText from 'components/MutedText';
import { invalidateFiles } from 'lib/queries/files';
import { userSelector } from 'lib/recoil/user';
import { expireReadToDate, randomChars } from 'lib/utils/client';
@ -281,28 +285,46 @@ export default function File({ chunks: chunks_config }) {
<Title mb='md'>Upload Files</Title>
<Dropzone loading={loading} onDrop={(f) => setFiles([...files, ...f])}>
<Group position='center' spacing='md'>
{files.map((file) => (
<FileDropzone key={randomId()} file={file} />
))}
</Group>
<Stack justify='space-between' h='100%'>
{files.length ? (
<Group spacing='md'>
{files.map((file) => (
<FileDropzone
key={randomId()}
file={file}
onRemove={() => setFiles(files.filter((f) => f !== file))}
/>
))}
</Group>
) : (
<Group position='center'>
<MutedText>Files will appear here once you drop/select them</MutedText>
</Group>
)}
<Stack>
<Group position='right' mt='md'>
<Button onClick={() => setOpened(true)} variant='outline'>
Options
</Button>
<Button onClick={() => setFiles([])} color='red' variant='outline'>
Clear Files
</Button>
<Button
leftIcon={<UploadIcon />}
onClick={handleUpload}
disabled={files.length === 0 ? true : false}
>
Upload
</Button>
</Group>
<Collapse in={progress !== 0}>
{progress !== 0 && <Progress mt='md' value={progress} animate />}
</Collapse>
</Stack>
</Stack>
</Dropzone>
<Collapse in={progress !== 0}>
{progress !== 0 && <Progress mt='md' value={progress} animate />}
</Collapse>
<Group position='right' mt='md'>
<Button onClick={() => setOpened(true)} variant='outline'>
Options
</Button>
<Button onClick={() => setFiles([])} color='red' variant='outline'>
Clear Files
</Button>
<Button leftIcon={<UploadIcon />} onClick={handleUpload} disabled={files.length === 0 ? true : false}>
Upload
</Button>
</Group>
</>
);
}

View file

@ -10,7 +10,7 @@ import {
Title,
} from '@mantine/core';
import { ClockIcon, ImageIcon, KeyIcon, TypeIcon, UserIcon } from 'components/icons';
import React, { Dispatch, SetStateAction, useState } from 'react';
import React, { Dispatch, SetStateAction, useReducer, useState } from 'react';
export default function useUploadOptions(): [
{
@ -25,24 +25,38 @@ export default function useUploadOptions(): [
Dispatch<SetStateAction<boolean>>,
React.FC
] {
const [expires, setExpires] = useState('never');
const [password, setPassword] = useState('');
const [maxViews, setMaxViews] = useState(0);
const [compression, setCompression] = useState<string>('none');
const [zeroWidth, setZeroWidth] = useState(false);
const [embedded, setEmbedded] = useState(false);
const [format, setFormat] = useState('default');
// const [expires, setExpires] = useState('never');
// const [password, setPassword] = useState('');
// const [maxViews, setMaxViews] = useState(0);
// const [compression, setCompression] = useState<string>('none');
// const [zeroWidth, setZeroWidth] = useState(false);
// const [embedded, setEmbedded] = useState(false);
// const [format, setFormat] = useState('default');
// migrate this to useReducer
const [state, setState] = useReducer((state, newState) => ({ ...state, ...newState }), {
expires: 'never',
password: '',
maxViews: 0,
compression: 'none',
zeroWidth: false,
embedded: false,
format: 'default',
});
const [opened, setOpened] = useState(false);
const reset = () => {
setExpires('never');
setPassword('');
setMaxViews(0);
setCompression('none');
setZeroWidth(false);
setEmbedded(false);
setFormat('default');
setState({
expires: 'never',
password: '',
maxViews: 0,
compression: 'none',
zeroWidth: false,
embedded: false,
format: 'default',
});
};
const OptionsModal: React.FC = () => (
@ -51,8 +65,8 @@ export default function useUploadOptions(): [
<NumberInput
label='Max Views'
description='The maximum number of times this file can be viewed. Leave blank for unlimited views.'
value={maxViews}
onChange={setMaxViews}
value={state.maxViews}
onChange={(e) => setState({ maxViews: e })}
min={0}
icon={<UserIcon />}
/>
@ -60,8 +74,8 @@ export default function useUploadOptions(): [
<Select
label='Expires'
description='The date and time this file will expire. Leave blank for never.'
value={expires}
onChange={(e) => setExpires(e)}
value={state.expires}
onChange={(e) => setState({ expires: e })}
icon={<ClockIcon size={14} />}
data={[
{ value: 'never', label: 'Never' },
@ -98,8 +112,8 @@ export default function useUploadOptions(): [
<Select
label='Compression'
description='The compression level to use when uploading this file. Leave blank for default.'
value={compression}
onChange={(e) => setCompression(e)}
value={state.compression}
onChange={(e) => setState({ compression: e })}
icon={<ImageIcon />}
data={[
{ value: 'none', label: 'None' },
@ -112,8 +126,8 @@ export default function useUploadOptions(): [
<Select
label='Format'
description="The file name format to use when uploading this file. Leave blank for the server's default."
value={format}
onChange={(e) => setFormat(e)}
value={state.format}
onChange={(e) => setState({ format: e })}
icon={<TypeIcon />}
data={[
{ value: 'default', label: 'Default' },
@ -127,8 +141,8 @@ export default function useUploadOptions(): [
<PasswordInput
label='Password'
description='The password required to view this file. Leave blank for no password.'
value={password}
onChange={(e) => setPassword(e.currentTarget.value)}
value={state.password}
onChange={(e) => setState({ password: e })}
icon={<KeyIcon />}
/>
@ -136,15 +150,15 @@ export default function useUploadOptions(): [
<Switch
label='Zero Width'
description='Whether or not to use zero width characters for the file name.'
checked={zeroWidth}
onChange={(e) => setZeroWidth(e.currentTarget.checked)}
checked={state.zeroWidth}
onChange={(e) => setState({ zeroWidth: e.currentTarget.checked })}
/>
<Switch
label='Embedded'
description='Whether or not to embed with OG tags for this file.'
checked={embedded}
onChange={(e) => setEmbedded(e.currentTarget.checked)}
checked={state.embedded}
onChange={(e) => setState({ embedded: e.currentTarget.checked })}
/>
</Group>
@ -158,17 +172,5 @@ export default function useUploadOptions(): [
</Modal>
);
return [
{
expires,
password,
maxViews,
compression,
zeroWidth,
embedded,
format,
},
setOpened,
OptionsModal,
];
return [state, setOpened, OptionsModal];
}