mirror of https://github.com/sylv/micro.git
177 lines
5.3 KiB
TypeScript
177 lines
5.3 KiB
TypeScript
import { Button, Card, Container, Spinner, useToasts } from '@ryanke/pandora';
|
|
import { useRouter } from 'next/router';
|
|
import type { ChangeEventHandler, DragEventHandler } from 'react';
|
|
import { useRef, useState } from 'react';
|
|
import { Upload as UploadIcon } from 'react-feather';
|
|
import { Select } from '../components/input/select';
|
|
import { PageLoader } from '../components/page-loader';
|
|
import { Title } from '../components/title';
|
|
import { getErrorMessage } from '../helpers/get-error-message.helper';
|
|
import { http } from '../helpers/http.helper';
|
|
import { replaceUsername } from '../helpers/replace-username.helper';
|
|
import { useConfig } from '../hooks/useConfig';
|
|
import { useUser } from '../hooks/useUser';
|
|
|
|
interface CreateFileResponse {
|
|
id: string;
|
|
hostname?: string;
|
|
urls: {
|
|
view: string;
|
|
};
|
|
}
|
|
|
|
export default function Upload() {
|
|
const user = useUser(true);
|
|
const router = useRouter();
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const [uploading, setUploading] = useState(false);
|
|
const [file, setFile] = useState<File | null>(null);
|
|
const [hover, setHover] = useState(false);
|
|
const createToast = useToasts();
|
|
const [selectedHost, setSelectedHost] = useState<string | undefined>();
|
|
const config = useConfig();
|
|
|
|
const onDragEvent =
|
|
(entering?: boolean): DragEventHandler =>
|
|
(event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
if (entering === true) setHover(true);
|
|
else if (entering === false) setHover(false);
|
|
};
|
|
|
|
const onDrop: DragEventHandler = (event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
setHover(false);
|
|
const transfer = event.dataTransfer;
|
|
const file = transfer.files.item(0);
|
|
if (file) {
|
|
setFile(file);
|
|
}
|
|
};
|
|
|
|
const onFileChange: ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
const file = event.target.files?.[0];
|
|
if (file) {
|
|
setFile(file);
|
|
}
|
|
};
|
|
|
|
const handleUpload = async () => {
|
|
try {
|
|
if (!file || !user.data || !config.data) return;
|
|
setUploading(true);
|
|
const form = new FormData();
|
|
form.append(file.name, file);
|
|
const headers: HeadersInit = {};
|
|
if (selectedHost) headers['X-Micro-Host'] = selectedHost;
|
|
const response = await http(`file`, {
|
|
method: 'POST',
|
|
body: form,
|
|
headers: headers,
|
|
});
|
|
|
|
const body: CreateFileResponse = await response.json();
|
|
const route = `/file/${body.id}`;
|
|
const isSameHost = body.hostname === config.data.currentHost.normalised;
|
|
if (isSameHost) {
|
|
router.push(route);
|
|
}
|
|
|
|
location.href = body.urls.view;
|
|
} catch (error: unknown) {
|
|
const message = getErrorMessage(error) ?? 'An unknown error occured.';
|
|
createToast({ error: true, text: message });
|
|
} finally {
|
|
setFile(null);
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
const openFileBrowser = () => {
|
|
if (file) return;
|
|
inputRef.current?.click();
|
|
};
|
|
|
|
if (!user.data || !config.data) {
|
|
return <PageLoader title="Upload" />;
|
|
}
|
|
|
|
if (uploading) {
|
|
return (
|
|
<Container center>
|
|
<Title>Uploading</Title>
|
|
<Card className="flex flex-col items-center justify-center w-full h-2/4">
|
|
<Spinner />
|
|
<p className="text-gray-400 select-none">Uploading</p>
|
|
</Card>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
if (file) {
|
|
return (
|
|
<Container center>
|
|
<Title>Upload {file.name}</Title>
|
|
<Card className="flex flex-col items-center justify-center w-full h-2/4">
|
|
<h1 className="mb-4 text-2xl">{file.name}</h1>
|
|
<div className="flex items-center justify-center">
|
|
<Select
|
|
prefix="Host"
|
|
className="shrink-0 w-40 mr-2"
|
|
value={selectedHost}
|
|
onChange={(event) => setSelectedHost(event.target.value)}
|
|
>
|
|
{config.data.hosts.map((host) => (
|
|
<option key={host.normalised} value={host.normalised} selected={host.normalised === selectedHost}>
|
|
{replaceUsername(host.normalised, user.data!.username)}
|
|
</option>
|
|
))}
|
|
</Select>
|
|
<Button onClick={handleUpload}>Upload</Button>
|
|
</div>
|
|
<span
|
|
className="mt-4 cursor-pointer text-primary"
|
|
onClick={() => {
|
|
setFile(null);
|
|
}}
|
|
>
|
|
Cancel
|
|
</span>
|
|
</Card>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Container center>
|
|
<Title>Upload</Title>
|
|
<Card
|
|
className="flex flex-col items-center justify-center w-full h-2/4"
|
|
onDrop={onDrop}
|
|
onDragOver={onDragEvent()}
|
|
onDragEnter={onDragEvent(true)}
|
|
onDragLeave={onDragEvent(false)}
|
|
onClick={openFileBrowser}
|
|
>
|
|
<input type="file" id="file" className="hidden" ref={inputRef} onChange={onFileChange} />
|
|
<h1 className="mb-2 text-2xl">
|
|
{hover ? (
|
|
<span className="flex items-center">
|
|
Release to upload <UploadIcon className="ml-2" />
|
|
</span>
|
|
) : (
|
|
<span>Drag and drop a file to upload</span>
|
|
)}
|
|
</h1>
|
|
<p className="text-gray-400 select-none">
|
|
Or <span className="text-primary">click here</span> to select a file.
|
|
</p>
|
|
</Card>
|
|
</Container>
|
|
);
|
|
}
|