fileserver/frontend/src/components/DirViewer.svelte
Mutzi 21b4a8800e
All checks were successful
/ Build the server (push) Successful in 3m5s
Implemented #65
2023-10-23 17:05:31 +02:00

147 lines
6.5 KiB
Svelte

<script context="module" lang="ts">
import {writable} from 'svelte/store';
const show_preview = writable<boolean>(false);
</script>
<script lang="ts">
import {Checkbox, Dropdown, DropdownItem, Spinner, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, Tooltip} from 'flowbite-svelte';
import {Folder, FolderParent, DocumentBlank, CaretLeft} from '../icons';
import {filesize} from 'filesize';
import {api, changeStateFunction, download, StateE, token, rpc} from '../store';
import LinkButton from './LinkButton.svelte';
import DeleteModal from './DeleteModal.svelte';
export let node: api.Node;
let selected: number[] = [];
let nodes: api.Node[], dirs: api.Node[], files: api.Node[], previews: {[key: number]: string|null} = {};
let total_size: number;
$: { nodes = node.children!; selectNone(); }
$: dirs = nodes.filter(v => !v.file).sort((a, b) => a.name.localeCompare(b.name));
$: files = nodes.filter(v => v.file).sort((a, b) => a.name.localeCompare(b.name));
$: total_size = files.map(v => v.size).reduce<number>((a, b) => (a + b!), 0);
$: {
if ($show_preview) {
for (const file of files) {
if (!file.preview) continue;
getPreview(file.id);
}
}
}
async function getPreview(node: number) {
const resp = await rpc.FS_download_preview($token ?? '', node);
if (resp.o == null)
return;
previews[node] = 'data:image/png;base64,' + resp.o;
previews = previews;
}
let ctx_node: api.Node;
let ctx_hidden = true;
let ctx_x = 0, ctx_y = 0;
let ctx_style: string;
$: ctx_style = `top: ${ctx_y}px; left: ${ctx_x}px; position: fixed;`;
function onCtxMenu(node: api.Node, e: MouseEvent) {
e.preventDefault();
if (!ctx_hidden)
return ctx_hidden = true;
ctx_x = e.pageX;
ctx_y = e.pageY;
ctx_node = node;
ctx_hidden = false;
}
const selectAll = () => selected = nodes.map(v => v.id);
const selectFolders = () => selected = dirs.map(v => v.id);
const selectFiles = () => selected = files.map(v => v.id);
const selectNone = () => selected = [];
const downloadSelected = () => download($token ?? '', nodes.filter(v => selected.includes(v.id)));
const deleteSelected = () => del(selected);
const onCtxDownload = () => download($token ?? '', [ctx_node]);
let del: (nodes: number[]) => Promise<void>;
const onCtxDelete = () => del([ctx_node.id]);
const onShowPreview = (e: Event) => { show_preview.set((e.target as HTMLInputElement).checked); }
</script>
<svelte:body on:click={() => (ctx_hidden = true)} />
<DeleteModal bind:del={del} />
<Table hoverable>
<TableHead theadClass="text-xs">
<TableHeadCell class="p-2 pl-4 w-0 h-0">
<CaretLeft id="dropdown-button" />
</TableHeadCell>
<TableHeadCell class="p-2 w-0"><Checkbox checked={$show_preview} on:change={onShowPreview} /><Tooltip>Show image previews</Tooltip></TableHeadCell>
<TableHeadCell>Name</TableHeadCell>
<TableHeadCell>Size</TableHeadCell>
</TableHead>
<TableBody>
{#if node.parent !== null}
<TableBodyRow>
<TableBodyCell class="!p-4"></TableBodyCell>
<TableBodyCell class="px-2 w-0"><FolderParent /></TableBodyCell>
<TableBodyCell class="pl-0"><LinkButton on:click={changeStateFunction(StateE.VIEW, node.parent ?? 0)}>..</LinkButton></TableBodyCell>
<TableBodyCell></TableBodyCell>
</TableBodyRow>
{/if}
{#each dirs as node}
<TableBodyRow on:contextmenu={onCtxMenu.bind(null, node)}>
<TableBodyCell class="p-2 pl-4 w-0 h-0"><Checkbox bind:group={selected} value={node.id}/></TableBodyCell>
<TableBodyCell class="px-2 w-0"><Folder /></TableBodyCell>
<TableBodyCell class="pl-0"><LinkButton on:click={changeStateFunction(StateE.VIEW, node.id)}>{node.name}</LinkButton></TableBodyCell>
<TableBodyCell></TableBodyCell>
</TableBodyRow>
{/each}
{#each files as node}
<TableBodyRow on:contextmenu={onCtxMenu.bind(null, node)}>
<TableBodyCell class="p-2 pl-4 w-0 h-0"><Checkbox bind:group={selected} value={node.id}/></TableBodyCell>
<TableBodyCell class="px-2 min-w-0">
{#if $show_preview && node.preview}
{#if previews[node.id] !== undefined}
<img class="w-screen max-w-xs" alt="preview" src={previews[node.id]} />
{:else}
<Spinner size="4"/>
{/if}
{:else}
<DocumentBlank />
{/if}
</TableBodyCell>
<TableBodyCell class="pl-0"><LinkButton on:click={changeStateFunction(StateE.VIEW, node.id)}>{node.name}</LinkButton></TableBodyCell>
<TableBodyCell>{filesize(node.size ?? 0, {base: 2, standard: 'jedec'})}</TableBodyCell>
</TableBodyRow>
{/each}
</TableBody>
<tfoot class="text-gray-700 bg-gray-50">
<tr>
<td class="px-6 py-3" colspan="3">
{#if selected.length > 0}
<LinkButton on:click={downloadSelected}>Download</LinkButton>
<LinkButton color="red" on:click={deleteSelected}>Delete</LinkButton>
{/if}
</td>
<td class="px-6 py-3">{filesize(total_size, {base: 2, standard: 'jedec'})}</td>
</tr>
</tfoot>
</Table>
<Dropdown triggeredBy="#dropdown-button" trigger="hover" placement="left">
<DropdownItem on:click={selectAll}>Select all</DropdownItem>
<DropdownItem on:click={selectFolders}>Select folders</DropdownItem>
<DropdownItem on:click={selectFiles}>Select files</DropdownItem>
<DropdownItem on:click={selectNone}>Select none</DropdownItem>
</Dropdown>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div style={ctx_style} hidden={ctx_hidden} class="z-50 shadow-md rounded-lg border-gray-100 bg-white" on:contextmenu={() => (ctx_hidden = true)}>
<ul class="py-1">
<li><button class="font-medium py-2 px-4 text-sm hover:bg-gray-100 w-full text-left" on:click={onCtxDownload}>Download</button></li>
<li><button class="font-medium py-2 px-4 text-sm hover:bg-gray-100 text-red-400 w-full text-left" on:click={onCtxDelete}>Delete</button></li>
</ul>
</div>