-
-
diff --git a/svelte/src/filesystem/viewers/FilePreview.svelte b/svelte/src/filesystem/viewers/FilePreview.svelte
index 846849c..40c847d 100644
--- a/svelte/src/filesystem/viewers/FilePreview.svelte
+++ b/svelte/src/filesystem/viewers/FilePreview.svelte
@@ -14,18 +14,15 @@ import Torrent from "./Torrent.svelte";
import Zip from "./Zip.svelte";
import CustomBanner from "./CustomBanner.svelte";
import type { FSNavigator } from "filesystem/FSNavigator";
-import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
import EditWindow from "filesystem/edit_window/EditWindow.svelte";
import DetailsWindow from "filesystem/DetailsWindow.svelte";
let {
nav,
- upload_widget,
edit_window,
details_window,
}: {
nav: FSNavigator
- upload_widget: FsUploadWidget
edit_window: EditWindow
details_window: DetailsWindow
} = $props();
@@ -89,7 +86,7 @@ export const seek = (delta: number) => {
{:else if viewer_type === "dir" && $nav.base.name === ".Trash"}
{:else if viewer_type === "dir"}
-
+
{:else if viewer_type === "audio"}
diff --git a/svelte/src/home_page/HomePage.svelte b/svelte/src/home_page/HomePage.svelte
index 7616542..57d239c 100644
--- a/svelte/src/home_page/HomePage.svelte
+++ b/svelte/src/home_page/HomePage.svelte
@@ -1,12 +1,9 @@
diff --git a/svelte/src/lib/DropTarget.ts b/svelte/src/lib/DropTarget.ts
index 535dced..fd31d5c 100644
--- a/svelte/src/lib/DropTarget.ts
+++ b/svelte/src/lib/DropTarget.ts
@@ -1,7 +1,11 @@
+import type { Upload } from "filesystem/upload_widget/FSUploadWidget.svelte"
+
export const drop_target = (
node: HTMLElement,
args: {
- upload: (files: File[]) => void,
+ // Called as soon as the files are dropped, the promise resolves when
+ // all the dropped directories have been read
+ upload: (files: Promise) => void,
shadow: string | undefined,
},
) => {
@@ -39,7 +43,7 @@ export const drop_target = (
const dragleave = (e: DragEvent) => {
node.style.boxShadow = ""
}
- const drop = async (e: DragEvent) => {
+ const drop = (e: DragEvent) => {
node.style.boxShadow = ""
if (can_upload(e)) {
@@ -49,39 +53,59 @@ export const drop_target = (
return
}
- // if directory support is available
- if (e.dataTransfer && e.dataTransfer.items && e.dataTransfer.items.length > 0) {
- for (let i = 0; i < e.dataTransfer.items.length; i++) {
- const entry: FileSystemEntry | null = e.dataTransfer.items[i].webkitGetAsEntry();
- if (entry !== null) {
- read_dir_recursive(entry);
- continue
- }
- const file: File | null = e.dataTransfer.items[i].getAsFile();
- if (file !== null) {
- args.upload([file]);
- continue
- }
+ // The items are only valid until this function returns, so every entry
+ // has to be taken out of them before the first await
+ const files: Upload[] = []
+ const reads: Promise[] = []
+ for (let i = 0; i < e.dataTransfer.items.length; i++) {
+ const entry = e.dataTransfer.items[i].webkitGetAsEntry()
+ if (entry !== null) {
+ reads.push(read_entry(entry, files))
+ continue
}
- } else if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
- const files: File[] = []
- for (let i = 0; i < e.dataTransfer.files.length; i++) {
- files.push(e.dataTransfer.files[i])
+
+ // Files which are not on the disk have no entry, like an image
+ // which is dragged out of another web page
+ const file = e.dataTransfer.items[i].getAsFile()
+ if (file !== null) {
+ files.push({ file, path: file.name })
}
- args.upload(files)
}
+ args.upload(Promise.all(reads).then(() => files))
}
- const read_dir_recursive = (item: FileSystemEntry) => {
- if (item.isDirectory) {
- (item as FileSystemDirectoryEntry).createReader().readEntries(entries => {
- entries.forEach(entry => {
- read_dir_recursive(entry);
- });
- });
- } else {
- (item as FileSystemFileEntry).file(file => {
- args.upload([file])
- });
+
+ // Adds a file, or all the files in a directory, to the list. The path comes
+ // from the entry. Firefox also puts it in the file's webkitRelativePath,
+ // but Chromium leaves that empty for dropped files
+ const read_entry = async (entry: FileSystemEntry, files: Upload[]) => {
+ if (entry.isFile) {
+ files.push({
+ path: entry.fullPath.slice(1),
+ file: await new Promise((resolve, reject) =>
+ (entry as FileSystemFileEntry).file(resolve, reject)
+ ),
+ })
+ return
+ }
+
+ // A reader returns the directory in parts, Chromium gives 100 entries
+ // at a time. An empty part marks the end
+ const reader = (entry as FileSystemDirectoryEntry).createReader()
+ while (true) {
+ const entries = await new Promise(
+ (resolve, reject) => reader.readEntries(resolve, reject)
+ )
+ if (entries.length === 0) {
+ return
+ }
+
+ // One at a time on purpose. Chromium asks another process for
+ // each file, and handles all the answers which have arrived in
+ // one go. Asking for a whole tree at once is a third faster, but
+ // freezes the page for seconds: 7 for 100k files
+ for (const e of entries) {
+ await read_entry(e, files)
+ }
}
}
diff --git a/svelte/src/wrap/App.svelte b/svelte/src/wrap/App.svelte
index 815a650..6000c36 100644
--- a/svelte/src/wrap/App.svelte
+++ b/svelte/src/wrap/App.svelte
@@ -17,6 +17,7 @@ import { breadcrumbs_store, toolbar_store } from "./HeaderStore";
import ProgressBar from "util/ProgressBar.svelte";
import NovaLogo, { update_favicon } from "util/NovaLogo.svelte";
import SearchBar from "filesystem/SearchBar.svelte";
+import FSUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
// The menu swipe will be detected if it was less than this much pixels from the
// screen edge
@@ -320,6 +321,7 @@ const set_offset = (off: number) => {
+