diff --git a/web/src/lib/components/shared-components/drag-and-drop-upload-overlay.svelte b/web/src/lib/components/shared-components/drag-and-drop-upload-overlay.svelte index 466b3d083e..935c63500d 100644 --- a/web/src/lib/components/shared-components/drag-and-drop-upload-overlay.svelte +++ b/web/src/lib/components/shared-components/drag-and-drop-upload-overlay.svelte @@ -26,12 +26,67 @@ const onDrop = async (e: DragEvent) => { dragStartTarget = null; - await handleFiles(e.dataTransfer?.files); + await handleDataTransfer(e.dataTransfer); }; - const onPaste = ({ clipboardData }: ClipboardEvent) => handleFiles(clipboardData?.files); + const onPaste = ({ clipboardData }: ClipboardEvent) => handleDataTransfer(clipboardData); - const handleFiles = async (files?: FileList) => { + const handleDataTransfer = async (dataTransfer?: DataTransfer | null) => { + if (!dataTransfer) { + return; + } + + if (!browserSupportsDirectoryUpload()) { + return handleFiles(dataTransfer.files); + } + + const transferEntries = Array.from(dataTransfer.items) + .map((i: DataTransferItem) => i.webkitGetAsEntry()) + .filter((i) => i !== null); + const files = await getAllFilesFromTransferEntries(transferEntries); + return handleFiles(files); + }; + + const browserSupportsDirectoryUpload = () => typeof DataTransferItem.prototype.webkitGetAsEntry === 'function'; + + const getAllFilesFromTransferEntries = async (transferEntries: FileSystemEntry[]): Promise => { + const allFiles: File[] = []; + let entriesToCheckForSubDirectories = [...transferEntries]; + while (entriesToCheckForSubDirectories.length > 0) { + const currentEntry = entriesToCheckForSubDirectories.pop(); + + if (isFileSystemDirectoryEntry(currentEntry)) { + entriesToCheckForSubDirectories = entriesToCheckForSubDirectories.concat( + await getContentsFromFileSystemDirectoryEntry(currentEntry), + ); + } else if (isFileSystemFileEntry(currentEntry)) { + allFiles.push(await getFileFromFileSystemEntry(currentEntry)); + } + } + + return allFiles; + }; + + const isFileSystemDirectoryEntry = (entry?: FileSystemEntry): entry is FileSystemDirectoryEntry => + !!entry && entry.isDirectory; + const isFileSystemFileEntry = (entry?: FileSystemEntry): entry is FileSystemFileEntry => !!entry && entry.isFile; + + const getFileFromFileSystemEntry = async (fileSystemFileEntry: FileSystemFileEntry): Promise => { + return new Promise((resolve, reject) => { + fileSystemFileEntry.file(resolve, reject); + }); + }; + + const getContentsFromFileSystemDirectoryEntry = async ( + fileSystemDirectoryEntry: FileSystemDirectoryEntry, + ): Promise => { + return new Promise((resolve, reject) => { + const reader = fileSystemDirectoryEntry.createReader(); + reader.readEntries(resolve, reject); + }); + }; + + const handleFiles = async (files?: FileList | File[]) => { if (!files) { return; }