diff --git a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte index 5d4fb81..9878e26 100644 --- a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte +++ b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte @@ -10,18 +10,45 @@ name: string; subtext?: string; textClass?: ClassValue; + thumbnail?: ArrayBuffer; type: "directory" | "file"; } - let { class: className, name, subtext, textClass: textClassName, type }: Props = $props(); + let { + class: className, + name, + subtext, + textClass: textClassName, + thumbnail, + type, + }: Props = $props(); + + let thumbnailUrl: string | undefined = $state(); + + $effect(() => { + thumbnailUrl = thumbnail && URL.createObjectURL(new Blob([thumbnail])); + return () => thumbnailUrl && URL.revokeObjectURL(thumbnailUrl); + }); +{#snippet iconSnippet()} +
+ {#if thumbnailUrl} + {name} + {:else if type === "directory"} + + {:else} + + {/if} +
+{/snippet} + {#snippet subtextSnippet()} {subtext} {/snippet} ; + icon: Component | Snippet; iconClass?: ClassValue; subtext?: Snippet; textClass?: ClassValue; diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte index fd59d03..22870e6 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte @@ -4,6 +4,7 @@ import { DirectoryEntryLabel } from "$lib/components/molecules"; import type { FileInfo } from "$lib/modules/filesystem"; import { formatDateTime } from "$lib/modules/util"; + import { getFileThumbnail } from "./service"; import type { SelectedEntry } from "../service.svelte"; import IconMoreVert from "~icons/material-symbols/more-vert"; @@ -16,6 +17,8 @@ let { info, onclick, onOpenMenuClick }: Props = $props(); + let thumbnail: ArrayBuffer | undefined = $state(); + const openFile = () => { const { id, dataKey, dataKeyVersion, name } = $info!; if (!dataKey || !dataKeyVersion) return; // TODO: Error handling @@ -29,6 +32,21 @@ onOpenMenuClick({ type: "file", id, dataKey, dataKeyVersion, name }); }; + + $effect(() => { + if ($info?.dataKey) { + getFileThumbnail($info.id, $info.dataKey) + .then((thumbnailData) => { + thumbnail = thumbnailData ?? undefined; + }) + .catch(() => { + // TODO: Error handling + thumbnail = undefined; + }); + } else { + thumbnail = undefined; + } + }); {#if $info} @@ -40,6 +58,7 @@ > diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts new file mode 100644 index 0000000..a14a866 --- /dev/null +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts @@ -0,0 +1,17 @@ +import { callGetApi } from "$lib/hooks"; +import { decryptData } from "$lib/modules/crypto"; +import type { FileThumbnailInfoResponse } from "$lib/server/schemas"; + +export const getFileThumbnail = async (fileId: number, dataKey: CryptoKey) => { + let res = await callGetApi(`/api/file/${fileId}/thumbnail`); + if (!res.ok) return null; + + const { contentIv: thumbnailEncryptedIv }: FileThumbnailInfoResponse = await res.json(); + + res = await callGetApi(`/api/file/${fileId}/thumbnail/download`); + if (!res.ok) return null; + + const thumbnailEncrypted = await res.arrayBuffer(); + const thumbnail = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); + return thumbnail; +};