mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-12 21:08:46 +00:00
썸네일 표시 구현
This commit is contained in:
@@ -10,18 +10,45 @@
|
|||||||
name: string;
|
name: string;
|
||||||
subtext?: string;
|
subtext?: string;
|
||||||
textClass?: ClassValue;
|
textClass?: ClassValue;
|
||||||
|
thumbnail?: ArrayBuffer;
|
||||||
type: "directory" | "file";
|
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);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#snippet iconSnippet()}
|
||||||
|
<div class="flex h-10 w-10 items-center justify-center overflow-y-hidden text-xl">
|
||||||
|
{#if thumbnailUrl}
|
||||||
|
<img src={thumbnailUrl} alt={name} loading="lazy" />
|
||||||
|
{:else if type === "directory"}
|
||||||
|
<IconFolder />
|
||||||
|
{:else}
|
||||||
|
<IconDraft />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
{#snippet subtextSnippet()}
|
{#snippet subtextSnippet()}
|
||||||
{subtext}
|
{subtext}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<IconLabel
|
<IconLabel
|
||||||
icon={type === "directory" ? IconFolder : IconDraft}
|
icon={iconSnippet}
|
||||||
iconClass={type === "file" ? "text-blue-400" : undefined}
|
iconClass={type === "file" ? "text-blue-400" : undefined}
|
||||||
subtext={subtext ? subtextSnippet : undefined}
|
subtext={subtext ? subtextSnippet : undefined}
|
||||||
class={className}
|
class={className}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
class?: ClassValue;
|
class?: ClassValue;
|
||||||
icon: Component<SvelteHTMLElements["svg"]>;
|
icon: Component<SvelteHTMLElements["svg"]> | Snippet;
|
||||||
iconClass?: ClassValue;
|
iconClass?: ClassValue;
|
||||||
subtext?: Snippet;
|
subtext?: Snippet;
|
||||||
textClass?: ClassValue;
|
textClass?: ClassValue;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
||||||
import type { FileInfo } from "$lib/modules/filesystem";
|
import type { FileInfo } from "$lib/modules/filesystem";
|
||||||
import { formatDateTime } from "$lib/modules/util";
|
import { formatDateTime } from "$lib/modules/util";
|
||||||
|
import { getFileThumbnail } from "./service";
|
||||||
import type { SelectedEntry } from "../service.svelte";
|
import type { SelectedEntry } from "../service.svelte";
|
||||||
|
|
||||||
import IconMoreVert from "~icons/material-symbols/more-vert";
|
import IconMoreVert from "~icons/material-symbols/more-vert";
|
||||||
@@ -16,6 +17,8 @@
|
|||||||
|
|
||||||
let { info, onclick, onOpenMenuClick }: Props = $props();
|
let { info, onclick, onOpenMenuClick }: Props = $props();
|
||||||
|
|
||||||
|
let thumbnail: ArrayBuffer | undefined = $state();
|
||||||
|
|
||||||
const openFile = () => {
|
const openFile = () => {
|
||||||
const { id, dataKey, dataKeyVersion, name } = $info!;
|
const { id, dataKey, dataKeyVersion, name } = $info!;
|
||||||
if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
|
if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
|
||||||
@@ -29,6 +32,21 @@
|
|||||||
|
|
||||||
onOpenMenuClick({ type: "file", id, dataKey, dataKeyVersion, name });
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $info}
|
{#if $info}
|
||||||
@@ -40,6 +58,7 @@
|
|||||||
>
|
>
|
||||||
<DirectoryEntryLabel
|
<DirectoryEntryLabel
|
||||||
type="file"
|
type="file"
|
||||||
|
{thumbnail}
|
||||||
name={$info.name}
|
name={$info.name}
|
||||||
subtext={formatDateTime($info.createdAt ?? $info.lastModifiedAt)}
|
subtext={formatDateTime($info.createdAt ?? $info.lastModifiedAt)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user