import axios from 'axios'; import getFileUploadUrl from '@/api/server/files/getFileUploadUrl'; import tw from 'twin.macro'; import { Button } from '@/components/elements/button/index'; import React, { useEffect, useRef } from 'react'; import { ModalMask } from '@/components/elements/Modal'; import Fade from '@/components/elements/Fade'; import useEventListener from '@/plugins/useEventListener'; import { useFlashKey } from '@/plugins/useFlash'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; import { ServerContext } from '@/state/server'; import { WithClassname } from '@/components/types'; import Portal from '@/components/elements/Portal'; import { CloudUploadIcon } from '@heroicons/react/outline'; import { useSignal } from '@preact/signals-react'; function isFileOrDirectory(event: DragEvent): boolean { if (!event.dataTransfer?.types) { return false; } return event.dataTransfer.types.some((value) => value.toLowerCase() === 'files'); } export default ({ className }: WithClassname) => { const fileUploadInput = useRef(null); const visible = useSignal(false); const timeouts = useSignal([]); const { mutate } = useFileManagerSwr(); const { addError, clearAndAddHttpError } = useFlashKey('files'); const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid); const directory = ServerContext.useStoreState((state) => state.files.directory); const { clearFileUploads, removeFileUpload, pushFileUpload, setUploadProgress } = ServerContext.useStoreActions( (actions) => actions.files ); useEventListener( 'dragenter', (e) => { e.preventDefault(); e.stopPropagation(); if (isFileOrDirectory(e)) { visible.value = true; } }, { capture: true } ); useEventListener('dragexit', () => (visible.value = false), { capture: true }); useEventListener('keydown', () => (visible.value = false)); useEffect(() => { return () => timeouts.value.forEach(clearTimeout); }, []); const onUploadProgress = (data: ProgressEvent, name: string) => { setUploadProgress({ name, loaded: data.loaded }); if (data.loaded >= data.total) { timeouts.value.push(setTimeout(() => removeFileUpload(name), 500)); } }; const onFileSubmission = (files: FileList) => { clearAndAddHttpError(); const list = Array.from(files); if (list.some((file) => !file.size || (!file.type && file.size === 4096))) { return addError('Folder uploads are not supported at this time.', 'Error'); } const uploads = list.map((file) => { const controller = new AbortController(); pushFileUpload({ name: file.name, data: { abort: controller, loaded: 0, total: file.size } }); return () => getFileUploadUrl(uuid).then((url) => axios.post( url, { files: file }, { signal: controller.signal, headers: { 'Content-Type': 'multipart/form-data' }, params: { directory }, onUploadProgress: (data) => onUploadProgress(data, file.name), } ) ); }); Promise.all(uploads.map((fn) => fn())) .then(() => mutate()) .catch((error) => { clearFileUploads(); clearAndAddHttpError(error); }); }; return ( <> (visible.value = false)} onDragOver={(e) => e.preventDefault()} onDrop={(e) => { e.preventDefault(); e.stopPropagation(); visible.value = false; if (!e.dataTransfer?.files.length) return; onFileSubmission(e.dataTransfer.files); }} >

Drag and drop files to upload.

{ if (!e.currentTarget.files) return; onFileSubmission(e.currentTarget.files); if (fileUploadInput.current) { fileUploadInput.current.files = null; } }} multiple /> ); };