feat: overhaul upload frontend

This commit is contained in:
diced 2022-11-28 19:58:21 -08:00
parent 577195b578
commit 2bec45411f
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
10 changed files with 433 additions and 325 deletions

View file

@ -0,0 +1,5 @@
import { Key } from 'react-feather';
export default function KeyIcon({ ...props }) {
return <Key size={15} {...props} />;
}

View file

@ -32,6 +32,7 @@ import DiscordIcon from './DiscordIcon';
import GoogleIcon from './GoogleIcon'; import GoogleIcon from './GoogleIcon';
import EyeIcon from './EyeIcon'; import EyeIcon from './EyeIcon';
import RefreshIcon from './RefreshIcon'; import RefreshIcon from './RefreshIcon';
import KeyIcon from './KeyIcon';
export { export {
ActivityIcon, ActivityIcon,
@ -68,4 +69,5 @@ export {
GoogleIcon, GoogleIcon,
EyeIcon, EyeIcon,
RefreshIcon, RefreshIcon,
KeyIcon,
}; };

View file

@ -10,27 +10,29 @@ import {
Tooltip, Tooltip,
} from '@mantine/core'; } from '@mantine/core';
import { randomId, useClipboard } from '@mantine/hooks'; import { randomId, useClipboard } from '@mantine/hooks';
import { useModals } from '@mantine/modals';
import { showNotification, updateNotification } from '@mantine/notifications'; import { showNotification, updateNotification } from '@mantine/notifications';
import Dropzone from 'components/dropzone/Dropzone'; import Dropzone from 'components/dropzone/Dropzone';
import FileDropzone from 'components/dropzone/DropzoneFile'; import FileDropzone from 'components/dropzone/DropzoneFile';
import { ClockIcon, CrossIcon, UploadIcon } from 'components/icons'; import { ClockIcon, CrossIcon, UploadIcon } from 'components/icons';
import Link from 'components/Link';
import { invalidateFiles } from 'lib/queries/files'; import { invalidateFiles } from 'lib/queries/files';
import { userSelector } from 'lib/recoil/user'; import { userSelector } from 'lib/recoil/user';
import { randomChars } from 'lib/utils/client'; import { expireReadToDate, randomChars } from 'lib/utils/client';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import showFilesModal from './showFilesModal';
import useUploadOptions from './useUploadOptions';
export default function Upload({ chunks: chunks_config }) { export default function File({ chunks: chunks_config }) {
const clipboard = useClipboard(); const clipboard = useClipboard();
const modals = useModals();
const user = useRecoilValue(userSelector); const user = useRecoilValue(userSelector);
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
const [progress, setProgress] = useState(0); const [progress, setProgress] = useState(0);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [expires, setExpires] = useState('never');
const [password, setPassword] = useState(''); const [options, setOpened, OptionsModal] = useUploadOptions();
const [maxViews, setMaxViews] = useState<number>(undefined);
useEffect(() => { useEffect(() => {
window.addEventListener('paste', (e: ClipboardEvent) => { window.addEventListener('paste', (e: ClipboardEvent) => {
@ -110,21 +112,11 @@ export default function Upload({ chunks: chunks_config }) {
updateNotification({ updateNotification({
id: 'upload-chunked', id: 'upload-chunked',
title: 'Upload Successful', title: 'Upload Successful',
message: ( message: '',
<>
Copied first file to clipboard! <br />
{json.files.map((x) => (
<Link key={x} href={x}>
{x}
<br />
</Link>
))}
</>
),
color: 'green', color: 'green',
icon: <UploadIcon />, icon: <UploadIcon />,
}); });
showFilesModal(clipboard, modals, json.files);
invalidateFiles(); invalidateFiles();
setFiles([]); setFiles([]);
setProgress(100); setProgress(100);
@ -157,9 +149,16 @@ export default function Upload({ chunks: chunks_config }) {
req.setRequestHeader('X-Zipline-Partial-MimeType', file.type); req.setRequestHeader('X-Zipline-Partial-MimeType', file.type);
req.setRequestHeader('X-Zipline-Partial-Identifier', identifier); req.setRequestHeader('X-Zipline-Partial-Identifier', identifier);
req.setRequestHeader('X-Zipline-Partial-LastChunk', j === chunks.length - 1 ? 'true' : 'false'); req.setRequestHeader('X-Zipline-Partial-LastChunk', j === chunks.length - 1 ? 'true' : 'false');
expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString()); options.expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString());
password !== '' && req.setRequestHeader('Password', password); options.password.trim() !== '' && req.setRequestHeader('Password', options.password);
maxViews && maxViews !== 0 && req.setRequestHeader('Max-Views', String(maxViews)); options.maxViews &&
options.maxViews !== 0 &&
req.setRequestHeader('Max-Views', String(options.maxViews));
options.compression !== 'none' &&
req.setRequestHeader('Image-Compression-Percent', options.compression);
options.embedded && req.setRequestHeader('Embed', 'true');
options.zeroWidth && req.setRequestHeader('Zws', 'true');
options.format !== 'default' && req.setRequestHeader('Format', options.format);
req.send(body); req.send(body);
@ -169,40 +168,7 @@ export default function Upload({ chunks: chunks_config }) {
}; };
const handleUpload = async () => { const handleUpload = async () => {
const expires_at = const expires_at = options.expires === 'never' ? null : expireReadToDate(options.expires);
expires === 'never'
? null
: new Date(
{
'5min': Date.now() + 5 * 60 * 1000,
'10min': Date.now() + 10 * 60 * 1000,
'15min': Date.now() + 15 * 60 * 1000,
'30min': Date.now() + 30 * 60 * 1000,
'1h': Date.now() + 60 * 60 * 1000,
'2h': Date.now() + 2 * 60 * 60 * 1000,
'3h': Date.now() + 3 * 60 * 60 * 1000,
'4h': Date.now() + 4 * 60 * 60 * 1000,
'5h': Date.now() + 5 * 60 * 60 * 1000,
'6h': Date.now() + 6 * 60 * 60 * 1000,
'8h': Date.now() + 8 * 60 * 60 * 1000,
'12h': Date.now() + 12 * 60 * 60 * 1000,
'1d': Date.now() + 24 * 60 * 60 * 1000,
'3d': Date.now() + 3 * 24 * 60 * 60 * 1000,
'5d': Date.now() + 5 * 24 * 60 * 60 * 1000,
'7d': Date.now() + 7 * 24 * 60 * 60 * 1000,
'1w': Date.now() + 7 * 24 * 60 * 60 * 1000,
'1.5w': Date.now() + 1.5 * 7 * 24 * 60 * 60 * 1000,
'2w': Date.now() + 2 * 7 * 24 * 60 * 60 * 1000,
'3w': Date.now() + 3 * 7 * 24 * 60 * 60 * 1000,
'1m': Date.now() + 30 * 24 * 60 * 60 * 1000,
'1.5m': Date.now() + 1.5 * 30 * 24 * 60 * 60 * 1000,
'2m': Date.now() + 2 * 30 * 24 * 60 * 60 * 1000,
'3m': Date.now() + 3 * 30 * 24 * 60 * 60 * 1000,
'6m': Date.now() + 6 * 30 * 24 * 60 * 60 * 1000,
'8m': Date.now() + 8 * 30 * 24 * 60 * 60 * 1000,
'1y': Date.now() + 365 * 24 * 60 * 60 * 1000,
}[expires]
);
setProgress(0); setProgress(0);
setLoading(true); setLoading(true);
@ -254,25 +220,15 @@ export default function Upload({ chunks: chunks_config }) {
const json = JSON.parse(e.target.response); const json = JSON.parse(e.target.response);
setLoading(false); setLoading(false);
if (json.error === undefined) { if (!json.error) {
updateNotification({ updateNotification({
id: 'upload', id: 'upload',
title: 'Upload Successful', title: 'Upload Successful',
message: ( message: '',
<>
Copied first file to clipboard! <br />
{json.files.map((x) => (
<Link key={x} href={x}>
{x}
<br />
</Link>
))}
</>
),
color: 'green', color: 'green',
icon: <UploadIcon />, icon: <UploadIcon />,
}); });
clipboard.copy(json.files[0]); showFilesModal(clipboard, modals, json.files);
setFiles([]); setFiles([]);
invalidateFiles(); invalidateFiles();
@ -304,9 +260,16 @@ export default function Upload({ chunks: chunks_config }) {
if (bodyLength !== 0) { if (bodyLength !== 0) {
req.open('POST', '/api/upload'); req.open('POST', '/api/upload');
req.setRequestHeader('Authorization', user.token); req.setRequestHeader('Authorization', user.token);
expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString()); options.expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString());
password !== '' && req.setRequestHeader('Password', password); options.password.trim() !== '' && req.setRequestHeader('Password', options.password);
maxViews && maxViews !== 0 && req.setRequestHeader('Max-Views', String(maxViews)); options.maxViews &&
options.maxViews !== 0 &&
req.setRequestHeader('Max-Views', String(options.maxViews));
options.compression !== 'none' &&
req.setRequestHeader('Image-Compression-Percent', options.compression);
options.embedded && req.setRequestHeader('Embed', 'true');
options.zeroWidth && req.setRequestHeader('Zws', 'true');
options.format !== 'default' && req.setRequestHeader('Format', options.format);
req.send(body); req.send(body);
} }
@ -314,6 +277,7 @@ export default function Upload({ chunks: chunks_config }) {
return ( return (
<> <>
<OptionsModal />
<Title mb='md'>Upload Files</Title> <Title mb='md'>Upload Files</Title>
<Dropzone loading={loading} onDrop={(f) => setFiles([...files, ...f])}> <Dropzone loading={loading} onDrop={(f) => setFiles([...files, ...f])}>
@ -329,54 +293,12 @@ export default function Upload({ chunks: chunks_config }) {
</Collapse> </Collapse>
<Group position='right' mt='md'> <Group position='right' mt='md'>
<Tooltip label='After the file reaches this amount of views, it will be deleted automatically. Leave blank for no limit.'> <Button onClick={() => setOpened(true)} variant='outline'>
<NumberInput placeholder='Max Views' min={0} value={maxViews} onChange={(x) => setMaxViews(x)} /> Options
</Tooltip> </Button>
<Tooltip label='Add a password to your files (optional, leave blank for none)'> <Button onClick={() => setFiles([])} color='red' variant='outline'>
<PasswordInput Clear Files
style={{ width: '252px' }} </Button>
placeholder='Password'
value={password}
onChange={(e) => setPassword(e.currentTarget.value)}
/>
</Tooltip>
<Tooltip label='Set an expiration date for your files (optional, defaults to never)'>
<Select
value={expires}
onChange={(e) => setExpires(e)}
icon={<ClockIcon size={14} />}
data={[
{ value: 'never', label: 'Never' },
{ value: '5min', label: '5 minutes' },
{ value: '10min', label: '10 minutes' },
{ value: '15min', label: '15 minutes' },
{ value: '30min', label: '30 minutes' },
{ value: '1h', label: '1 hour' },
{ value: '2h', label: '2 hours' },
{ value: '3h', label: '3 hours' },
{ value: '4h', label: '4 hours' },
{ value: '5h', label: '5 hours' },
{ value: '6h', label: '6 hours' },
{ value: '8h', label: '8 hours' },
{ value: '12h', label: '12 hours' },
{ value: '1d', label: '1 day' },
{ value: '3d', label: '3 days' },
{ value: '5d', label: '5 days' },
{ value: '7d', label: '7 days' },
{ value: '1w', label: '1 week' },
{ value: '1.5w', label: '1.5 weeks' },
{ value: '2w', label: '2 weeks' },
{ value: '3w', label: '3 weeks' },
{ value: '1m', label: '1 month' },
{ value: '1.5m', label: '1.5 months' },
{ value: '2m', label: '2 months' },
{ value: '3m', label: '3 months' },
{ value: '6m', label: '6 months' },
{ value: '8m', label: '8 months' },
{ value: '1y', label: '1 year' },
]}
/>
</Tooltip>
<Button leftIcon={<UploadIcon />} onClick={handleUpload} disabled={files.length === 0 ? true : false}> <Button leftIcon={<UploadIcon />} onClick={handleUpload} disabled={files.length === 0 ? true : false}>
Upload Upload
</Button> </Button>

View file

@ -0,0 +1,127 @@
import { Button, Group, NumberInput, PasswordInput, Select, Tabs, Title, Tooltip } from '@mantine/core';
import { useClipboard } from '@mantine/hooks';
import { useModals } from '@mantine/modals';
import { showNotification, updateNotification } from '@mantine/notifications';
import { Prism } from '@mantine/prism';
import CodeInput from 'components/CodeInput';
import { ClockIcon, ImageIcon, TypeIcon, UploadIcon } from 'components/icons';
import exts from 'lib/exts';
import { userSelector } from 'lib/recoil/user';
import { expireReadToDate } from 'lib/utils/client';
import { Language } from 'prism-react-renderer';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import showFilesModal from './showFilesModal';
import useUploadOptions from './useUploadOptions';
export default function Text() {
const clipboard = useClipboard();
const modals = useModals();
const user = useRecoilValue(userSelector);
const [value, setValue] = useState('');
const [lang, setLang] = useState('txt');
const [options, setOpened, OptionsModal] = useUploadOptions();
const handleUpload = async () => {
const file = new File([value], 'text.' + lang);
const expires_at = options.expires === 'never' ? null : expireReadToDate(options.expires);
showNotification({
id: 'upload-text',
title: 'Uploading...',
message: '',
loading: true,
autoClose: false,
});
const req = new XMLHttpRequest();
req.addEventListener('load', (e) => {
// @ts-ignore not sure why it thinks response doesnt exist, but it does.
const json = JSON.parse(e.target.response);
if (!json.error) {
updateNotification({
id: 'upload-text',
title: 'Upload Successful',
message: '',
});
showFilesModal(clipboard, modals, json.files);
}
});
const body = new FormData();
body.append('file', file);
req.open('POST', '/api/upload');
req.setRequestHeader('Authorization', user.token);
req.setRequestHeader('UploadText', 'true');
options.expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString());
options.password.trim() !== '' && req.setRequestHeader('Password', options.password);
options.maxViews && options.maxViews !== 0 && req.setRequestHeader('Max-Views', String(options.maxViews));
options.compression !== 'none' && req.setRequestHeader('Image-Compression-Percent', options.compression);
options.embedded && req.setRequestHeader('Embed', 'true');
options.zeroWidth && req.setRequestHeader('Zws', 'true');
options.format !== 'default' && req.setRequestHeader('Format', options.format);
req.send(body);
};
return (
<>
<OptionsModal />
<Title mb='md'>Upload Text</Title>
<Tabs defaultValue='text' variant='pills'>
<Tabs.List>
<Tabs.Tab value='text' icon={<TypeIcon />}>
Text
</Tabs.Tab>
<Tabs.Tab value='preview' icon={<ImageIcon />}>
Preview
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel mt='sm' value='text'>
<CodeInput value={value} onChange={(e) => setValue(e.target.value)} />
</Tabs.Panel>
<Tabs.Panel mt='sm' value='preview'>
<Prism
sx={(t) => ({ height: '80vh', backgroundColor: t.colors.dark[8] })}
withLineNumbers
language={lang as Language}
>
{value}
</Prism>
</Tabs.Panel>
</Tabs>
<Group position='right' mt='md'>
<Select
value={lang}
onChange={setLang}
dropdownPosition='top'
data={Object.keys(exts).map((x) => ({ value: x, label: exts[x] }))}
icon={<TypeIcon />}
searchable
/>
<Button onClick={() => setOpened(true)} variant='outline'>
Options
</Button>
<Button
leftIcon={<UploadIcon />}
onClick={handleUpload}
disabled={value.trim().length === 0 ? true : false}
>
Upload
</Button>
</Group>
</>
);
}

View file

@ -0,0 +1,44 @@
import { Button, Table, Title } from '@mantine/core';
import { showNotification } from '@mantine/notifications';
import { CopyIcon } from 'components/icons';
import Link from 'components/Link';
export default function showFilesModal(clipboard, modals, files: string[]) {
const open = (idx: number) => window.open(files[idx], '_blank');
const copy = (idx: number) => {
clipboard.copy(files[idx]);
showNotification({
title: 'Copied to clipboard',
message: <Link href={files[idx]}>{files[idx]}</Link>,
icon: <CopyIcon />,
});
};
modals.openModal({
title: <Title>Uploaded Files</Title>,
size: 'auto',
children: (
<Table withBorder={false} withColumnBorders={false} highlightOnHover horizontalSpacing={'sm'}>
<tbody>
{files.map((file, idx) => (
<tr key={file}>
<td>
<Link href={file}>{file}</Link>
</td>
<td>
<Button.Group>
<Button variant='outline' onClick={() => copy(idx)}>
Copy
</Button>
<Button variant='outline' onClick={() => open(idx)}>
Open
</Button>
</Button.Group>
</td>
</tr>
))}
</tbody>
</Table>
),
});
}

View file

@ -0,0 +1,174 @@
import {
Button,
Group,
Modal,
NumberInput,
PasswordInput,
Select,
Stack,
Switch,
Title,
} from '@mantine/core';
import { ClockIcon, ImageIcon, KeyIcon, TypeIcon, UserIcon } from 'components/icons';
import React, { Dispatch, SetStateAction, useState } from 'react';
export default function useUploadOptions(): [
{
expires: string;
password: string;
maxViews: number;
compression: string;
zeroWidth: boolean;
embedded: boolean;
format: string;
},
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 [opened, setOpened] = useState(false);
const reset = () => {
setExpires('never');
setPassword('');
setMaxViews(0);
setCompression('none');
setZeroWidth(false);
setEmbedded(false);
setFormat('default');
};
const OptionsModal: React.FC = () => (
<Modal title={<Title>Upload Options</Title>} size='auto' opened={opened} onClose={() => setOpened(false)}>
<Stack>
<NumberInput
label='Max Views'
description='The maximum number of times this file can be viewed. Leave blank for unlimited views.'
value={maxViews}
onChange={setMaxViews}
min={0}
icon={<UserIcon />}
/>
<Select
label='Expires'
description='The date and time this file will expire. Leave blank for never.'
value={expires}
onChange={(e) => setExpires(e)}
icon={<ClockIcon size={14} />}
data={[
{ value: 'never', label: 'Never' },
{ value: '5min', label: '5 minutes' },
{ value: '10min', label: '10 minutes' },
{ value: '15min', label: '15 minutes' },
{ value: '30min', label: '30 minutes' },
{ value: '1h', label: '1 hour' },
{ value: '2h', label: '2 hours' },
{ value: '3h', label: '3 hours' },
{ value: '4h', label: '4 hours' },
{ value: '5h', label: '5 hours' },
{ value: '6h', label: '6 hours' },
{ value: '8h', label: '8 hours' },
{ value: '12h', label: '12 hours' },
{ value: '1d', label: '1 day' },
{ value: '3d', label: '3 days' },
{ value: '5d', label: '5 days' },
{ value: '7d', label: '7 days' },
{ value: '1w', label: '1 week' },
{ value: '1.5w', label: '1.5 weeks' },
{ value: '2w', label: '2 weeks' },
{ value: '3w', label: '3 weeks' },
{ value: '1m', label: '1 month' },
{ value: '1.5m', label: '1.5 months' },
{ value: '2m', label: '2 months' },
{ value: '3m', label: '3 months' },
{ value: '6m', label: '6 months' },
{ value: '8m', label: '8 months' },
{ value: '1y', label: '1 year' },
]}
/>
<Select
label='Compression'
description='The compression level to use when uploading this file. Leave blank for default.'
value={compression}
onChange={(e) => setCompression(e)}
icon={<ImageIcon />}
data={[
{ value: 'none', label: 'None' },
{ value: '25', label: 'Low (25%)' },
{ value: '50', label: 'Medium (50%)' },
{ value: '75', label: 'High (75%)' },
]}
/>
<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)}
icon={<TypeIcon />}
data={[
{ value: 'default', label: 'Default' },
{ value: 'RANDOM', label: 'Random' },
{ value: 'NAME', label: 'Original Name' },
{ value: 'DATE', label: 'Date (format configured by server)' },
{ value: 'UUID', label: 'UUID' },
]}
/>
<PasswordInput
label='Password'
description='The password required to view this file. Leave blank for no password.'
value={password}
onChange={(e) => setPassword(e.currentTarget.value)}
icon={<KeyIcon />}
/>
<Group>
<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)}
/>
<Switch
label='Embedded'
description='Whether or not to embed with OG tags for this file.'
checked={embedded}
onChange={(e) => setEmbedded(e.currentTarget.checked)}
/>
</Group>
<Group grow>
<Button onClick={() => reset()} color='red'>
Reset Options
</Button>
<Button onClick={() => setOpened(false)}>Close</Button>
</Group>
</Stack>
</Modal>
);
return [
{
expires,
password,
maxViews,
compression,
zeroWidth,
embedded,
format,
},
setOpened,
OptionsModal,
];
}

View file

@ -1,202 +0,0 @@
import { Button, Group, NumberInput, PasswordInput, Select, Tabs, Title, Tooltip } from '@mantine/core';
import { showNotification, updateNotification } from '@mantine/notifications';
import { Prism } from '@mantine/prism';
import CodeInput from 'components/CodeInput';
import { ClockIcon, ImageIcon, TypeIcon, UploadIcon } from 'components/icons';
import Link from 'components/Link';
import exts from 'lib/exts';
import { userSelector } from 'lib/recoil/user';
import { Language } from 'prism-react-renderer';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
export default function Upload() {
const user = useRecoilValue(userSelector);
const [value, setValue] = useState('');
const [lang, setLang] = useState('txt');
const [password, setPassword] = useState('');
const [expires, setExpires] = useState('never');
const [maxViews, setMaxViews] = useState<number>(undefined);
const handleUpload = async () => {
const file = new File([value], 'text.' + lang);
const expires_at =
expires === 'never'
? null
: new Date(
{
'5min': Date.now() + 5 * 60 * 1000,
'10min': Date.now() + 10 * 60 * 1000,
'15min': Date.now() + 15 * 60 * 1000,
'30min': Date.now() + 30 * 60 * 1000,
'1h': Date.now() + 60 * 60 * 1000,
'2h': Date.now() + 2 * 60 * 60 * 1000,
'3h': Date.now() + 3 * 60 * 60 * 1000,
'4h': Date.now() + 4 * 60 * 60 * 1000,
'5h': Date.now() + 5 * 60 * 60 * 1000,
'6h': Date.now() + 6 * 60 * 60 * 1000,
'8h': Date.now() + 8 * 60 * 60 * 1000,
'12h': Date.now() + 12 * 60 * 60 * 1000,
'1d': Date.now() + 24 * 60 * 60 * 1000,
'3d': Date.now() + 3 * 24 * 60 * 60 * 1000,
'5d': Date.now() + 5 * 24 * 60 * 60 * 1000,
'7d': Date.now() + 7 * 24 * 60 * 60 * 1000,
'1w': Date.now() + 7 * 24 * 60 * 60 * 1000,
'1.5w': Date.now() + 1.5 * 7 * 24 * 60 * 60 * 1000,
'2w': Date.now() + 2 * 7 * 24 * 60 * 60 * 1000,
'3w': Date.now() + 3 * 7 * 24 * 60 * 60 * 1000,
'1m': Date.now() + 30 * 24 * 60 * 60 * 1000,
'1.5m': Date.now() + 1.5 * 30 * 24 * 60 * 60 * 1000,
'2m': Date.now() + 2 * 30 * 24 * 60 * 60 * 1000,
'3m': Date.now() + 3 * 30 * 24 * 60 * 60 * 1000,
'6m': Date.now() + 6 * 30 * 24 * 60 * 60 * 1000,
'8m': Date.now() + 8 * 30 * 24 * 60 * 60 * 1000,
'1y': Date.now() + 365 * 24 * 60 * 60 * 1000,
}[expires]
);
showNotification({
id: 'upload-text',
title: 'Uploading...',
message: '',
loading: true,
autoClose: false,
});
const req = new XMLHttpRequest();
req.addEventListener('load', (e) => {
// @ts-ignore not sure why it thinks response doesnt exist, but it does.
const json = JSON.parse(e.target.response);
if (!json.error) {
updateNotification({
id: 'upload-text',
title: 'Upload Successful',
message: (
<>
Copied first file to clipboard! <br />
{json.files.map((x) => (
<Link key={x} href={x}>
{x}
<br />
</Link>
))}
</>
),
});
}
});
const body = new FormData();
body.append('file', file);
req.open('POST', '/api/upload');
req.setRequestHeader('Authorization', user.token);
req.setRequestHeader('UploadText', 'true');
expires !== 'never' && req.setRequestHeader('Expires-At', 'date=' + expires_at.toISOString());
password !== '' && req.setRequestHeader('Password', password);
maxViews && maxViews !== 0 && req.setRequestHeader('Max-Views', String(maxViews));
req.send(body);
};
return (
<>
<Title mb='md'>Upload Text</Title>
<Tabs defaultValue='text' variant='pills'>
<Tabs.List>
<Tabs.Tab value='text' icon={<TypeIcon />}>
Text
</Tabs.Tab>
<Tabs.Tab value='preview' icon={<ImageIcon />}>
Preview
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel mt='sm' value='text'>
<CodeInput value={value} onChange={(e) => setValue(e.target.value)} />
</Tabs.Panel>
<Tabs.Panel mt='sm' value='preview'>
<Prism
sx={(t) => ({ height: '80vh', backgroundColor: t.colors.dark[8] })}
withLineNumbers
language={lang as Language}
>
{value}
</Prism>
</Tabs.Panel>
</Tabs>
<Group position='right' mt='md'>
<Select
value={lang}
onChange={setLang}
dropdownPosition='top'
data={Object.keys(exts).map((x) => ({ value: x, label: exts[x] }))}
icon={<TypeIcon />}
searchable
/>
<Tooltip label='After the file reaches this amount of views, it will be deleted automatically. Leave blank for no limit.'>
<NumberInput placeholder='Max Views' min={0} value={maxViews} onChange={(x) => setMaxViews(x)} />
</Tooltip>
<Tooltip label='Add a password to your files (optional, leave blank for none)'>
<PasswordInput
style={{ width: '252px' }}
placeholder='Password'
value={password}
onChange={(e) => setPassword(e.currentTarget.value)}
/>
</Tooltip>
<Tooltip label='Set an expiration date for your files (optional, defaults to never)'>
<Select
value={expires}
onChange={(e) => setExpires(e)}
icon={<ClockIcon size={14} />}
data={[
{ value: 'never', label: 'Never' },
{ value: '5min', label: '5 minutes' },
{ value: '10min', label: '10 minutes' },
{ value: '15min', label: '15 minutes' },
{ value: '30min', label: '30 minutes' },
{ value: '1h', label: '1 hour' },
{ value: '2h', label: '2 hours' },
{ value: '3h', label: '3 hours' },
{ value: '4h', label: '4 hours' },
{ value: '5h', label: '5 hours' },
{ value: '6h', label: '6 hours' },
{ value: '8h', label: '8 hours' },
{ value: '12h', label: '12 hours' },
{ value: '1d', label: '1 day' },
{ value: '3d', label: '3 days' },
{ value: '5d', label: '5 days' },
{ value: '7d', label: '7 days' },
{ value: '1w', label: '1 week' },
{ value: '1.5w', label: '1.5 weeks' },
{ value: '2w', label: '2 weeks' },
{ value: '3w', label: '3 weeks' },
{ value: '1m', label: '1 month' },
{ value: '1.5m', label: '1.5 months' },
{ value: '2m', label: '2 months' },
{ value: '3m', label: '3 months' },
{ value: '6m', label: '6 months' },
{ value: '8m', label: '8 months' },
{ value: '1y', label: '1 year' },
]}
/>
</Tooltip>
<Button
leftIcon={<UploadIcon />}
onClick={handleUpload}
disabled={value.trim().length === 0 ? true : false}
>
Upload
</Button>
</Group>
</>
);
}

View file

@ -102,3 +102,39 @@ export function expireText(to_: string, from_: string = new Date().toLocaleStrin
return `Expired ${dayjs(from).to(to)}`; return `Expired ${dayjs(from).to(to)}`;
} }
} }
export function expireReadToDate(expires: string): Date {
if (expires === 'never') return null;
return new Date(
{
'5min': Date.now() + 5 * 60 * 1000,
'10min': Date.now() + 10 * 60 * 1000,
'15min': Date.now() + 15 * 60 * 1000,
'30min': Date.now() + 30 * 60 * 1000,
'1h': Date.now() + 60 * 60 * 1000,
'2h': Date.now() + 2 * 60 * 60 * 1000,
'3h': Date.now() + 3 * 60 * 60 * 1000,
'4h': Date.now() + 4 * 60 * 60 * 1000,
'5h': Date.now() + 5 * 60 * 60 * 1000,
'6h': Date.now() + 6 * 60 * 60 * 1000,
'8h': Date.now() + 8 * 60 * 60 * 1000,
'12h': Date.now() + 12 * 60 * 60 * 1000,
'1d': Date.now() + 24 * 60 * 60 * 1000,
'3d': Date.now() + 3 * 24 * 60 * 60 * 1000,
'5d': Date.now() + 5 * 24 * 60 * 60 * 1000,
'7d': Date.now() + 7 * 24 * 60 * 60 * 1000,
'1w': Date.now() + 7 * 24 * 60 * 60 * 1000,
'1.5w': Date.now() + 1.5 * 7 * 24 * 60 * 60 * 1000,
'2w': Date.now() + 2 * 7 * 24 * 60 * 60 * 1000,
'3w': Date.now() + 3 * 7 * 24 * 60 * 60 * 1000,
'1m': Date.now() + 30 * 24 * 60 * 60 * 1000,
'1.5m': Date.now() + 1.5 * 30 * 24 * 60 * 60 * 1000,
'2m': Date.now() + 2 * 30 * 24 * 60 * 60 * 1000,
'3m': Date.now() + 3 * 30 * 24 * 60 * 60 * 1000,
'6m': Date.now() + 6 * 30 * 24 * 60 * 60 * 1000,
'8m': Date.now() + 8 * 30 * 24 * 60 * 60 * 1000,
'1y': Date.now() + 365 * 24 * 60 * 60 * 1000,
}[expires]
);
}

View file

@ -1,6 +1,6 @@
import { LoadingOverlay } from '@mantine/core'; import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout'; import Layout from 'components/Layout';
import Upload from 'components/pages/Upload'; import File from 'components/pages/Upload/File';
import useLogin from 'hooks/useLogin'; import useLogin from 'hooks/useLogin';
import Head from 'next/head'; import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps'; export { getServerSideProps } from 'middleware/getServerSideProps';
@ -17,7 +17,7 @@ export default function UploadPage(props) {
<title>{title}</title> <title>{title}</title>
</Head> </Head>
<Layout props={props}> <Layout props={props}>
<Upload chunks={{ chunks_size: props.chunks_size, max_size: props.max_size }} /> <File chunks={{ chunks_size: props.chunks_size, max_size: props.max_size }} />
</Layout> </Layout>
</> </>
); );

View file

@ -1,6 +1,6 @@
import { LoadingOverlay } from '@mantine/core'; import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout'; import Layout from 'components/Layout';
import UploadText from 'components/pages/UploadText'; import Text from 'components/pages/Upload/Text';
import useLogin from 'hooks/useLogin'; import useLogin from 'hooks/useLogin';
import Head from 'next/head'; import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps'; export { getServerSideProps } from 'middleware/getServerSideProps';
@ -17,7 +17,7 @@ export default function UploadTextPage(props) {
<title>{title}</title> <title>{title}</title>
</Head> </Head>
<Layout props={props}> <Layout props={props}>
<UploadText /> <Text />
</Layout> </Layout>
</> </>
); );