import { Text } from "preact-i18n"; import { takeError } from "./util"; import classNames from "classnames"; import { AppContext } from "./RevoltClient"; import styles from './FileUploads.module.scss'; import Axios, { AxiosRequestConfig } from "axios"; import { useContext, useEffect, useState } from "preact/hooks"; import Preloader from "../../components/ui/Preloader"; import { determineFileSize } from "../../lib/fileSize"; import IconButton from '../../components/ui/IconButton'; import { Plus, X, XCircle } from "@styled-icons/boxicons-regular"; import { Pencil } from "@styled-icons/boxicons-solid"; import { useIntermediate } from "../intermediate/Intermediate"; type Props = { maxFileSize: number remove: () => Promise fileType: 'backgrounds' | 'icons' | 'avatars' | 'attachments' | 'banners' } & ( { behaviour: 'ask', onChange: (file: File) => void } | { behaviour: 'upload', onUpload: (id: string) => Promise } | { behaviour: 'multi', onChange: (files: File[]) => void, append?: (files: File[]) => void } ) & ( { style: 'icon' | 'banner', defaultPreview?: string, previewURL?: string, width?: number, height?: number } | { style: 'attachment', attached: boolean, uploading: boolean, cancel: () => void, size?: number } ) export async function uploadFile(autumnURL: string, tag: string, file: File, config?: AxiosRequestConfig) { const formData = new FormData(); formData.append("file", file); const res = await Axios.post(autumnURL + "/" + tag, formData, { headers: { "Content-Type": "multipart/form-data" }, ...config }); return res.data.id; } export function grabFiles(maxFileSize: number, cb: (files: File[]) => void, tooLarge: () => void, multiple?: boolean) { const input = document.createElement("input"); input.type = "file"; input.multiple = multiple ?? false; input.onchange = async (e) => { const files = (e.currentTarget as HTMLInputElement)?.files; if (!files) return; for (let file of files) { if (file.size > maxFileSize) { return tooLarge(); } } cb(Array.from(files)); }; input.click(); } export function FileUploader(props: Props) { const { fileType, maxFileSize, remove } = props; const { openScreen } = useIntermediate(); const client = useContext(AppContext); const [ uploading, setUploading ] = useState(false); function onClick() { if (uploading) return; grabFiles(maxFileSize, async files => { setUploading(true); try { if (props.behaviour === 'multi') { props.onChange(files); } else if (props.behaviour === 'ask') { props.onChange(files[0]); } else { await props.onUpload(await uploadFile(client.configuration!.features.autumn.url, fileType, files[0])); } } catch (err) { return openScreen({ id: "error", error: takeError(err) }); } finally { setUploading(false); } }, () => openScreen({ id: "error", error: "FileTooLarge" }), props.behaviour === 'multi'); } function removeOrUpload() { if (uploading) return; if (props.style === 'attachment') { if (props.attached) { props.remove(); } else { onClick(); } } else { if (props.previewURL) { props.remove(); } else { onClick(); } } } if (props.behaviour === 'multi' && props.append) { useEffect(() => { // File pasting. function paste(e: ClipboardEvent) { const items = e.clipboardData?.items; if (typeof items === "undefined") return; if (props.behaviour !== 'multi' || !props.append) return; let files = []; for (const item of items) { if (!item.type.startsWith("text/")) { const blob = item.getAsFile(); if (blob) { if (blob.size > props.maxFileSize) { openScreen({ id: 'error', error: 'FileTooLarge' }); } files.push(blob); } } } props.append(files); } // Let the browser know we can drop files. function dragover(e: DragEvent) { e.stopPropagation(); e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = "copy"; } // File dropping. function drop(e: DragEvent) { e.preventDefault(); if (props.behaviour !== 'multi' || !props.append) return; const dropped = e.dataTransfer?.files; if (dropped) { let files = []; for (const item of dropped) { if (item.size > props.maxFileSize) { openScreen({ id: 'error', error: 'FileTooLarge' }); } files.push(item); } props.append(files); } } document.addEventListener("paste", paste); document.addEventListener("dragover", dragover); document.addEventListener("drop", drop); return () => { document.removeEventListener("paste", paste); document.removeEventListener("dragover", dragover); document.removeEventListener("drop", drop); }; }, [ props.append ]); } if (props.style === 'icon' || props.style === 'banner') { const { style, previewURL, defaultPreview, width, height } = props; return (
{ uploading ?
:
}
{ uploading ? : props.previewURL ? : }
) } else if (props.style === 'attachment') { const { attached, uploading, cancel, size } = props; return ( { if (uploading) return cancel(); if (attached) return remove(); onClick(); }}> { uploading ? : attached ? : } ) } return null; }