diff --git a/svelte/src/filesystem/Breadcrumbs.svelte b/svelte/src/filesystem/Breadcrumbs.svelte index c79a233..8b29b78 100644 --- a/svelte/src/filesystem/Breadcrumbs.svelte +++ b/svelte/src/filesystem/Breadcrumbs.svelte @@ -1,6 +1,6 @@ scroll_positions.set(nav.base.path, window.scrollY)} /> +{#snippet nav_buttons()} + + arrow_back + + nav.navigate_up()} use:drag_click disabled={$nav.path.length <= 1} title="Up"> + north + + nav.reload()} use:drag_click title="Refresh directory listing"> + refresh + +{/snippet} + {#snippet toolbar()} - {#if mode === "viewing"} + {#if !$nav.selecting} {search_bar.open()}} title="Press / to search"> search - - arrow_back - - nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up"> - north - - nav.reload()} title="Refresh directory listing"> - refresh - + {@render nav_buttons()} {#if $nav.children.some(c => c.name === ".Trash" && c.type === "dir")} nav.navigate($nav.children.find(c => c.name === ".Trash").path, true)} @@ -352,32 +252,40 @@ onMount(() => { {creating_dir = !creating_dir}} highlight={creating_dir} icon="create_new_folder" title="Make folder"/> - + nav.set_selecting(true)} title="Select and delete files"> select_all {/if} - {:else if mode === "selecting"} + {:else} - - Selecting files - - - delete - Delete - - - {:else if mode === "moving"} - - - nav.navigate_up()} disabled={$nav.path.length <= 1} icon="north"/> - - Moving {moving_files} files and {moving_directories} directories + + nav.set_selecting(false)} icon="close"/> + {@render nav_buttons()} + + + {moving ? "Moving" : "Selected"} {$nav.selection.size} items + + + {#if moving} + {creating_dir = !creating_dir}} highlight={creating_dir} icon="create_new_folder" title="Make folder"/> + nav.move_selection(nav.base.path)} + disabled={!$nav.can_move($nav.base.path)} + highlight + icon="done" + label="Move here" + /> + {:else} + {moving = true}} icon="drive_file_move" label="Move"/> + + delete + Delete + + {/if} - {creating_dir = !creating_dir}} highlight={creating_dir} icon="create_new_folder" title="Make folder"/> - {/if} @@ -388,6 +296,7 @@ onMount(() => { nav.base}} use:drop_target={{ upload: (files) => upload_widget.upload_files(files), shadow: "var(--accent_color) 0 0 10px 2px inset", diff --git a/svelte/src/filesystem/filemanager/FileManagerLib.ts b/svelte/src/filesystem/filemanager/FileManagerLib.ts index 98bb840..94c0a2a 100644 --- a/svelte/src/filesystem/filemanager/FileManagerLib.ts +++ b/svelte/src/filesystem/filemanager/FileManagerLib.ts @@ -1,17 +1,10 @@ -export type FileEvent = { - index: number, - action: FileAction, - original: MouseEvent, -} export enum FileAction { Click, Context, - Edit, Share, Branding, - Select, - Download, Menu, + Drag, } export type FileActionHandler = ( action: FileAction, diff --git a/svelte/src/filesystem/filemanager/FileMenu.svelte b/svelte/src/filesystem/filemanager/FileMenu.svelte index b3e03f5..293889e 100644 --- a/svelte/src/filesystem/filemanager/FileMenu.svelte +++ b/svelte/src/filesystem/filemanager/FileMenu.svelte @@ -63,6 +63,9 @@ const delete_node = async () => { {dialog.close(); bookmark_add(node)}} icon="bookmark_add" label="Add bookmark"/> {/if} {#if $nav.permissions.write} + {#if $nav.children.includes(node)} + {dialog.close(); nav.select(node)}} icon="select_all" label="Select"/> + {/if} {dialog.close(); delete_node()}} icon="delete" label="Delete"/> {#if edit_window !== null} {dialog.close(); edit_window.edit(node, false, "file")}} icon="edit" label="Edit"/> diff --git a/svelte/src/filesystem/filemanager/FilePicker.svelte b/svelte/src/filesystem/filemanager/FilePicker.svelte index bb4500b..6e44e5a 100644 --- a/svelte/src/filesystem/filemanager/FilePicker.svelte +++ b/svelte/src/filesystem/filemanager/FilePicker.svelte @@ -24,16 +24,10 @@ let { export const open = (path: string) => { modal.show() + nav.set_selecting(false) nav.navigate(path, false) } -let selected_files = $derived($nav.children.reduce((acc, file) => { - if (file.fm_selected) { - acc++ - } - return acc -}, 0)) - // Navigation functions const file_event: FileActionHandler = (action: FileAction, index: number, orig: Event) => { @@ -43,20 +37,16 @@ const file_event: FileActionHandler = (action: FileAction, index: number, orig: if (nav.children[index].type === "dir") { nav.navigate(nav.children[index].path, true) } else { - select_node(index) + select_node(index, orig) } break case FileAction.Context: // If this is a touch event we will select the item if (navigator.maxTouchPoints && navigator.maxTouchPoints > 0) { orig.preventDefault() - select_node(index) + select_node(index, orig) } break - case FileAction.Select: - orig.preventDefault() - nav.children[index].fm_selected = !nav.children[index].fm_selected - break } } @@ -72,50 +62,16 @@ const toggle_view = () => { localStorage.setItem("directory_view", directory_view) } -// We need to detect if shift is pressed so we can select multiple items -let shift_pressed = false -let last_selected_node = -1 -const detect_shift = (e: KeyboardEvent) => { - if (e.key === "Shift") { - shift_pressed = e.type === "keydown" +const select_node = (index: number, e: Event) => { + if (!select_multiple) { + nav.selection.clear() } -} - -const select_node = (index: number) => { - if (select_multiple && shift_pressed) { - // If shift is pressed we do a range select. We select all files between - // the last selected file and the file that is being selected now - let id_low = Math.min(last_selected_node, index) - let id_high = Math.max(last_selected_node, index) - - for (let i = id_low; i <= id_high; i++) { - if (i != last_selected_node) { - nav.children[i].fm_selected = !nav.children[i].fm_selected - } - } - } else { - // Deselect all other entries first - if (!select_multiple) { - for (let i = 0; i < nav.children.length; i++) { - nav.children[i].fm_selected = false - } - } - nav.children[index].fm_selected = !nav.children[index].fm_selected - } - - last_selected_node = index + nav.select(nav.children[index], select_multiple && (e as MouseEvent).shiftKey, show_hidden) } let done = () => { - let selected_files: FSNode[] = [] - for (let i = 0; i < nav.children.length; i++) { - if (nav.children[i].fm_selected) { - selected_files.push(nav.children[i]) - } - } - - if (selected_files.length > 0) { - callback(selected_files) + if (nav.selection.size > 0) { + callback([...nav.selection.values()]) } modal.hide() } @@ -131,8 +87,6 @@ onMount(() => { }) - - {#snippet header()} @@ -147,7 +101,7 @@ onMount(() => { - Selected {selected_files} files + Selected {$nav.selection.size} files {show_hidden = !show_hidden}} title="Toggle hidden files"> diff --git a/svelte/src/filesystem/filemanager/GalleryView.svelte b/svelte/src/filesystem/filemanager/GalleryView.svelte index 24836ae..5616a46 100644 --- a/svelte/src/filesystem/filemanager/GalleryView.svelte +++ b/svelte/src/filesystem/filemanager/GalleryView.svelte @@ -1,6 +1,6 @@ - -{#snippet breadcrumbs()} - delete - - Trash bin - {#if $loading_store === 0 && entries.length === 0} - (empty) - {:else if entries.length !== 0} - ({formatDataVolume(entries.reduce( - (acc, entry) => acc + entry.node.file_size, 0 - ), 3)}) - {/if} - - - window.history.back()} title="Back"> - arrow_back - - nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up"> - north - - nav.reload()} title="Refresh directory listing"> - refresh - - {#if entries.length !== 0} - +{#snippet toolbar()} + {#if $trash.selecting} + + trash.set_selecting(false)}/> + Selecting files + + delete_forever - Delete all + Delete - {/if} + + {:else} + + + history.back()} title="Back"> + arrow_back + + nav.navigate_up()} title="Up"> + north + + + refresh + + + Trash bin ({formatDataVolume(size, 3)}) + + {#if $trash.children.length !== 0} + trash.set_selecting(true)} title="Select files to restore or delete"> + select_all + + + delete_forever + Delete all + + {/if} + + + {/if} {/snippet} -{#if error} - {error} -{:else if entries.length > 0} - - - - - Name - Original location - Deleted at - Size - - - - - {#each entries as entry (entry.node.id)} - - - - - - - {entry.node.name} - - - - {entry.original_path ? entry.original_path.slice(0, -entry.node.name.length) : "—"} - - - {entry.deletion_date ? formatDate(entry.deletion_date) : "—"} - - - {#if entry.node.type === "file"} - {formatDataVolume(entry.node.file_size, 3)} - {/if} - - - - restore_entry(entry)}> - restore_from_trash - - delete_entry(entry)}> - delete_forever - - - - - {/each} - - -{/if} + diff --git a/svelte/src/lib/Bookmarks.ts b/svelte/src/lib/Bookmarks.ts index 1057c02..83d4de9 100644 --- a/svelte/src/lib/Bookmarks.ts +++ b/svelte/src/lib/Bookmarks.ts @@ -12,13 +12,16 @@ export type Bookmark = { label: string, } +export let bookmarks_failed = writable(false) + export let bookmarks_store = writable( [], (set: (value: Bookmark[]) => void) => { bookmarks_get().then((bm: Bookmark[]) => { set(bm) }).catch((err: any) => { - alert("Could not fetch bookmarks:\n" + JSON.stringify(err)) + console.error("Could not fetch bookmarks", err) + bookmarks_failed.set(true) }) }, ) diff --git a/svelte/src/lib/FilesystemAPI.svelte.ts b/svelte/src/lib/FilesystemAPI.svelte.ts index 44849f1..4c07e21 100644 --- a/svelte/src/lib/FilesystemAPI.svelte.ts +++ b/svelte/src/lib/FilesystemAPI.svelte.ts @@ -52,10 +52,9 @@ export class FSNode { user_permissions?: { [index: string]: FSPermissions } password_permissions?: { [index: string]: FSPermissions } - // Added by us - - // Indicates whether the file is selected in the file manager - fm_selected?: boolean = $state(false) + // Only set on the nodes returned by fs_get_trash + original_path?: string + deletion_date?: string is_shared = (): boolean => { return (this.link_permissions !== undefined && this.link_permissions.read === true) || @@ -251,22 +250,15 @@ export const fs_trash = async (path: string) => { ) as GenericResponse } -export type TrashEntry = { - node: FSNode; - original_path: string; - deletion_date: string; -}; - export const fs_get_trash = async (path: string) => { - let resp = await fs_check_response( + const resp = await fs_check_response( await fetch(fs_path_url(path) + "?trash") - ) as TrashEntry[] - resp.forEach((entry, index) => { - resp[index].node = Object.assign(new FSNode(), entry.node) - }) + ) as { node: FSNode, original_path: string, deletion_date: string }[] - return resp -}; + // The trash metadata is moved into the node, so the trash can be listed + // like the children of a directory + return resp.map(({ node, ...info }) => Object.assign(new FSNode(), node, info)) +} export const fs_search = async (path: string, term: string, limit = 10) => { return await fs_check_response( diff --git a/svelte/src/wrap/Bookmarks.svelte b/svelte/src/wrap/Bookmarks.svelte index 8189c49..47b8184 100644 --- a/svelte/src/wrap/Bookmarks.svelte +++ b/svelte/src/wrap/Bookmarks.svelte @@ -1,6 +1,7 @@
{error}