Overhaul trash page
This commit is contained in:
@@ -1,28 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { formatDataVolume, formatDate } from "util/Formatting";
|
import { formatDataVolume, formatDate } from "util/Formatting";
|
||||||
import { fs_check_response, fs_encode_path, fs_get_trash, fs_node_icon, fs_path_url, type TrashEntry } from "lib/FilesystemAPI.svelte";
|
import { fs_check_response, fs_encode_path, fs_get_trash, fs_node_icon, fs_path_url, type TrashEntry } from "lib/FilesystemAPI.svelte";
|
||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { loading_finish, loading_run, loading_start, loading_store } from "lib/Loading";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import { breadcrumbs_store } from "wrap/BreadcrumbStore";
|
||||||
|
import { FSNavigator } from "filesystem/FSNavigator";
|
||||||
|
|
||||||
let { root_path = "/me" }: { root_path?: string } = $props();
|
let {
|
||||||
|
nav,
|
||||||
|
root_path = "/me",
|
||||||
|
}: {
|
||||||
|
nav: FSNavigator
|
||||||
|
root_path?: string
|
||||||
|
} = $props();
|
||||||
|
|
||||||
let entries: TrashEntry[] = $state([]);
|
let entries: TrashEntry[] = $state([]);
|
||||||
let error: string = $state("");
|
let error: string = $state("");
|
||||||
let loaded = $state(false);
|
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
loaded = false;
|
loading_run(async () => {
|
||||||
entries = [];
|
|
||||||
try {
|
try {
|
||||||
loading_start();
|
|
||||||
entries = await fs_get_trash(root_path)
|
entries = await fs_get_trash(root_path)
|
||||||
error = "";
|
error = "";
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
error = err?.message ?? JSON.stringify(err);
|
error = err?.message ?? JSON.stringify(err);
|
||||||
} finally {
|
|
||||||
loaded = true;
|
|
||||||
loading_finish();
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const delete_entry = async (entry: TrashEntry) => {
|
const delete_entry = async (entry: TrashEntry) => {
|
||||||
@@ -76,30 +79,46 @@ const empty_trash = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(load);
|
onMount(() => {
|
||||||
|
breadcrumbs_store.set(breadcrumbs)
|
||||||
|
|
||||||
|
load()
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="trash_container">
|
|
||||||
<div class="header">
|
{#snippet breadcrumbs()}
|
||||||
<h2 class="title"><i class="icon">delete</i> Trash</h2>
|
<i class="icon">delete</i>
|
||||||
<div class="actions">
|
<div>
|
||||||
<button onclick={load}>
|
Trash bin
|
||||||
<i class="icon">refresh</i>
|
{#if $loading_store === 0 && entries.length === 0}
|
||||||
<span>Refresh</span>
|
(empty)
|
||||||
</button>
|
{:else if entries.length !== 0}
|
||||||
{#if entries.length > 0}
|
({formatDataVolume(entries.reduce(
|
||||||
<button class="danger" onclick={empty_trash}>
|
(acc, entry) => acc + entry.node.file_size, 0
|
||||||
<i class="icon">delete_forever</i>
|
), 3)})
|
||||||
<span>Empty trash</span>
|
|
||||||
</button>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="separator"></div>
|
||||||
|
<button onclick={() => window.history.back()} title="Back">
|
||||||
|
<i class="icon">arrow_back</i>
|
||||||
|
</button>
|
||||||
|
<button onclick={() => nav.navigate_up()} disabled={$nav.path.length <= 1} title="Up">
|
||||||
|
<i class="icon">north</i>
|
||||||
|
</button>
|
||||||
|
<button onclick={() => nav.reload()} title="Refresh directory listing">
|
||||||
|
<i class="icon">refresh</i>
|
||||||
|
</button>
|
||||||
|
{#if entries.length !== 0}
|
||||||
|
<button class="button button_red" onclick={empty_trash}>
|
||||||
|
<i class="icon">delete_forever</i>
|
||||||
|
<span>Delete all</span>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
{#if error}
|
{#if error}
|
||||||
<p class="error">{error}</p>
|
<p class="error">{error}</p>
|
||||||
{:else if loaded && entries.length === 0}
|
|
||||||
<p class="empty">The trash is empty.</p>
|
|
||||||
{:else if entries.length > 0}
|
{:else if entries.length > 0}
|
||||||
<table class="trash_table">
|
<table class="trash_table">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -107,7 +126,7 @@ onMount(load);
|
|||||||
<td></td>
|
<td></td>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td class="hide_small">Original location</td>
|
<td class="hide_small">Original location</td>
|
||||||
<td class="hide_small">Deleted</td>
|
<td class="hide_small">Deleted at</td>
|
||||||
<td class="hide_small">Size</td>
|
<td class="hide_small">Size</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -127,23 +146,23 @@ onMount(load);
|
|||||||
{entry.node.name}
|
{entry.node.name}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="hide_small original_path">
|
<td class="hide_small">
|
||||||
{entry.original_path || "—"}
|
{entry.original_path ? entry.original_path.slice(0, -entry.node.name.length) : "—"}
|
||||||
</td>
|
</td>
|
||||||
<td class="hide_small date_cell">
|
<td class="hide_small">
|
||||||
{entry.deletion_date ? formatDate(entry.deletion_date) : "—"}
|
{entry.deletion_date ? formatDate(entry.deletion_date) : "—"}
|
||||||
</td>
|
</td>
|
||||||
<td class="hide_small size_cell">
|
<td class="hide_small">
|
||||||
{#if entry.node.type === "file"}
|
{#if entry.node.type === "file"}
|
||||||
{formatDataVolume(entry.node.file_size, 3)}
|
{formatDataVolume(entry.node.file_size, 3)}
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td class="button_cell">
|
<td>
|
||||||
<div class="row_actions">
|
<div class="row_actions">
|
||||||
<button title="Restore to original location" onclick={() => restore_entry(entry)}>
|
<button title="Restore to original location" onclick={() => restore_entry(entry)}>
|
||||||
<i class="icon">restore_from_trash</i>
|
<i class="icon">restore_from_trash</i>
|
||||||
</button>
|
</button>
|
||||||
<button class="danger" title="Delete permanently" onclick={() => delete_entry(entry)}>
|
<button class="button button_red" title="Delete permanently" onclick={() => delete_entry(entry)}>
|
||||||
<i class="icon">delete_forever</i>
|
<i class="icon">delete_forever</i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,45 +172,19 @@ onMount(load);
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.trash_container {
|
.separator {
|
||||||
max-width: 1200px;
|
flex: 1 1 auto;
|
||||||
margin: auto;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.3em;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.2em;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5em;
|
|
||||||
}
|
}
|
||||||
.error {
|
.error {
|
||||||
color: var(--danger_color, #c0392b);
|
color: var(--danger_color, #c0392b);
|
||||||
}
|
}
|
||||||
.empty {
|
|
||||||
color: var(--body_text_color);
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
.trash_table {
|
.trash_table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
background: var(--body_background);
|
background: var(--body_background);
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
.trash_table td {
|
.trash_table td {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -205,7 +198,6 @@ onMount(load);
|
|||||||
}
|
}
|
||||||
.icon_cell {
|
.icon_cell {
|
||||||
width: 2.5em;
|
width: 2.5em;
|
||||||
padding: 0.3em 0.3em 0.3em 0.5em;
|
|
||||||
}
|
}
|
||||||
.node_icon {
|
.node_icon {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -214,7 +206,6 @@ onMount(load);
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
.name_cell {
|
.name_cell {
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
.entry_link {
|
.entry_link {
|
||||||
@@ -224,34 +215,10 @@ onMount(load);
|
|||||||
.entry_link:hover {
|
.entry_link:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
.original_path {
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
color: var(--body_text_color);
|
|
||||||
opacity: 0.7;
|
|
||||||
font-size: 0.85em;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
.date_cell {
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 0.85em;
|
|
||||||
}
|
|
||||||
.size_cell {
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 0.85em;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.button_cell {
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
}
|
|
||||||
.row_actions {
|
.row_actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
.danger {
|
|
||||||
color: var(--danger_color, #c0392b);
|
|
||||||
}
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.hide_small {
|
.hide_small {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export const seek = (delta: number) => {
|
|||||||
<Spinner/>
|
<Spinner/>
|
||||||
</div>
|
</div>
|
||||||
{:else if viewer_type === "dir" && $nav.base.name === ".Trash"}
|
{:else if viewer_type === "dir" && $nav.base.name === ".Trash"}
|
||||||
<TrashBin root_path={$nav.path[$nav.path.length - 2].path}/>
|
<TrashBin nav={nav} root_path={$nav.path[$nav.path.length - 2].path}/>
|
||||||
{:else if viewer_type === "dir"}
|
{:else if viewer_type === "dir"}
|
||||||
<FileManager nav={nav} upload_widget={upload_widget} edit_window={edit_window} search_bar={search_bar}>
|
<FileManager nav={nav} upload_widget={upload_widget} edit_window={edit_window} search_bar={search_bar}>
|
||||||
<CustomBanner path={$nav.path}/>
|
<CustomBanner path={$nav.path}/>
|
||||||
|
|||||||
Reference in New Issue
Block a user