104 lines
4.5 KiB
Svelte
104 lines
4.5 KiB
Svelte
<script lang="ts">
|
|
import { Button } from '$lib/components/ui/button';
|
|
import FileIcon from '@lucide/svelte/icons/file';
|
|
import FolderIcon from '@lucide/svelte/icons/folder';
|
|
import FolderOpenIcon from '@lucide/svelte/icons/folder-open';
|
|
import TrashIcon from '@lucide/svelte/icons/trash';
|
|
import DownloadIcon from '@lucide/svelte/icons/download';
|
|
|
|
export let tree: any;
|
|
export let parentPath = '';
|
|
export let expanded: Set<string>;
|
|
export let onToggle: (path: string) => void;
|
|
export let onDelete: (key: string, isFolder: boolean) => void;
|
|
export let onDownload: (key: string) => void = () => {};
|
|
export let isRoot: boolean = false;
|
|
|
|
function handleToggle(path: string) {
|
|
onToggle(path);
|
|
}
|
|
function handleDelete(key: string, isFolder: boolean) {
|
|
onDelete(key, isFolder);
|
|
}
|
|
function handleDownload(key: string) {
|
|
onDownload(key);
|
|
}
|
|
function handleFolderClick(event: Event, path: string) {
|
|
event.stopPropagation();
|
|
handleToggle(path);
|
|
}
|
|
function handleFolderDelete(event: Event, path: string) {
|
|
event.stopPropagation();
|
|
handleDelete(path, true);
|
|
}
|
|
function formatSize(size: number) {
|
|
if (!size && size !== 0) return '';
|
|
if (size < 1024) return `${size} B`;
|
|
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
|
|
if (size < 1024 * 1024 * 1024) return `${(size / 1024 / 1024).toFixed(1)} MB`;
|
|
return `${(size / 1024 / 1024 / 1024).toFixed(1)} GB`;
|
|
}
|
|
function formatDate(date: string | Date) {
|
|
if (!date) return '';
|
|
const d = typeof date === 'string' ? new Date(date) : date;
|
|
return d.toLocaleString();
|
|
}
|
|
</script>
|
|
|
|
<ul class="ml-2">
|
|
{#if isRoot}
|
|
<li class="flex items-center gap-2 py-1 pl-2 text-xs font-semibold text-muted-foreground select-none border-b border-border mb-1">
|
|
<span class="flex-1 min-w-0">Name</span>
|
|
<span class="w-24 text-right">Size</span>
|
|
<span class="w-40 text-right">Last Uploaded</span>
|
|
<span class="w-16"></span>
|
|
</li>
|
|
{/if}
|
|
{#each Object.entries(tree) as [name, node] (parentPath + '/' + name)}
|
|
{#if (node as any).isFile}
|
|
<li class="flex items-center gap-2 py-1 pl-2 hover:bg-accent rounded group text-sm">
|
|
<span class="flex-1 min-w-0 flex items-center gap-1">
|
|
<FileIcon class="w-4 h-4 text-muted-foreground shrink-0" />
|
|
<span class="truncate" title={name || ''}>{name || ''}</span>
|
|
</span>
|
|
<span class="w-24 text-xs text-muted-foreground text-right ml-2 whitespace-nowrap">{formatSize((node as any).size)}</span>
|
|
<span class="w-40 text-xs text-muted-foreground text-right ml-2 whitespace-nowrap">{formatDate((node as any).lastModified)}</span>
|
|
<span class="w-16 flex items-center justify-end gap-1">
|
|
<button class="p-1 hover:text-primary" title="Download" on:click={() => handleDownload((node as any).key)}><DownloadIcon class="w-4 h-4" /></button>
|
|
<button class="p-1 hover:text-destructive" title="Delete" on:click={() => handleDelete((node as any).key, false)}><TrashIcon class="w-4 h-4" /></button>
|
|
</span>
|
|
</li>
|
|
{:else}
|
|
<li>
|
|
<div class="flex items-center gap-1 px-1 py-1 hover:bg-accent rounded cursor-pointer text-sm" >
|
|
<span class="flex-1 min-w-0 flex items-center gap-1" on:click={(e) => handleFolderClick(e, parentPath ? `${parentPath}/${name}` : name)}>
|
|
<span class="flex-shrink-0 w-5 h-5 flex items-center justify-center">
|
|
{#if expanded.has(parentPath ? `${parentPath}/${name}` : name)}
|
|
<FolderOpenIcon class="w-4 h-4 text-muted-foreground" />
|
|
{:else}
|
|
<FolderIcon class="w-4 h-4 text-muted-foreground" />
|
|
{/if}
|
|
</span>
|
|
<span class="font-semibold truncate" title={name || ''}>{name || ''}</span>
|
|
</span>
|
|
<span class="w-24"></span>
|
|
<span class="w-40"></span>
|
|
<span class="w-16 flex items-center justify-end">
|
|
<button class="p-1 hover:text-destructive" title="Delete folder" on:click={(e) => handleFolderDelete(e, (parentPath ? `${parentPath}/${name}` : name))}><TrashIcon class="w-4 h-4" /></button>
|
|
</span>
|
|
</div>
|
|
{#if expanded.has(parentPath ? `${parentPath}/${name}` : name)}
|
|
<svelte:self
|
|
tree={(node as any).children}
|
|
parentPath={parentPath ? `${parentPath}/${name}` : name}
|
|
{expanded}
|
|
{onToggle}
|
|
{onDelete}
|
|
{onDownload}
|
|
isRoot={false}
|
|
/>
|
|
{/if}
|
|
</li>
|
|
{/if}
|
|
{/each}
|
|
</ul> |