From ff6ea3a0b90df576303294e10cbe7bad50701173 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 17 Jan 2026 20:11:01 +0900 Subject: [PATCH] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EC=97=90=EC=84=9C=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=EC=84=A4=EC=A0=95=EC=9D=B4=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0=EC=97=90=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=EB=90=9C=20=EA=B2=BD=EC=9A=B0=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=EB=A5=BC=20=EC=95=84=EC=9D=B4=EC=BD=98?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=91=9C=EC=8B=9C=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../atoms/buttons/FileThumbnailButton.svelte | 12 +++++++++- .../labels/DirectoryEntryLabel.svelte | 13 ++++++++++- src/lib/indexedDB/filesystem.ts | 2 ++ src/lib/modules/filesystem/category.ts | 2 ++ src/lib/modules/filesystem/file.ts | 3 +++ src/lib/server/db/file.ts | 1 + .../(fullscreen)/file/[id]/+page.svelte | 12 ++++++++++ .../(fullscreen)/file/[id]/TopBarMenu.svelte | 23 +++++++++++++++++-- src/routes/(fullscreen)/file/[id]/service.ts | 13 +++++++++++ .../(fullscreen)/search/Directory.svelte | 2 +- src/routes/(fullscreen)/search/File.svelte | 1 + src/routes/(fullscreen)/search/service.ts | 2 ++ src/routes/(main)/category/[[id]]/File.svelte | 7 +++++- .../[[id]]/DirectoryEntries/File.svelte | 1 + .../DirectoryEntries/SubDirectory.svelte | 2 +- src/trpc/routers/category.ts | 1 + src/trpc/routers/file.ts | 2 ++ src/trpc/routers/search.ts | 2 ++ 18 files changed, 94 insertions(+), 7 deletions(-) diff --git a/src/lib/components/atoms/buttons/FileThumbnailButton.svelte b/src/lib/components/atoms/buttons/FileThumbnailButton.svelte index 6c5632c..e95d07c 100644 --- a/src/lib/components/atoms/buttons/FileThumbnailButton.svelte +++ b/src/lib/components/atoms/buttons/FileThumbnailButton.svelte @@ -2,6 +2,8 @@ import { getFileThumbnail } from "$lib/modules/file"; import type { SummarizedFileInfo } from "$lib/modules/filesystem"; + import IconFavorite from "~icons/material-symbols/favorite"; + interface Props { info: SummarizedFileInfo; onclick?: (file: SummarizedFileInfo) => void; @@ -14,11 +16,19 @@ diff --git a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte index e85665d..001631b 100644 --- a/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte +++ b/src/lib/components/molecules/labels/DirectoryEntryLabel.svelte @@ -5,9 +5,11 @@ import IconFolder from "~icons/material-symbols/folder"; import IconDriveFolderUpload from "~icons/material-symbols/drive-folder-upload"; import IconDraft from "~icons/material-symbols/draft"; + import IconFavorite from "~icons/material-symbols/favorite"; interface Props { class?: ClassValue; + isFavorite?: boolean; name: string; subtext?: string; textClass?: ClassValue; @@ -17,6 +19,7 @@ let { class: className, + isFavorite = false, name, subtext, textClass: textClassName, @@ -26,7 +29,7 @@ {#snippet iconSnippet()} -
+
{#if thumbnail} {name} {:else if type === "directory"} @@ -36,6 +39,14 @@ {:else} {/if} + {#if isFavorite} +
+ +
+ {/if}
{/snippet} diff --git a/src/lib/indexedDB/filesystem.ts b/src/lib/indexedDB/filesystem.ts index 87c0d70..d5c3e4d 100644 --- a/src/lib/indexedDB/filesystem.ts +++ b/src/lib/indexedDB/filesystem.ts @@ -4,6 +4,7 @@ interface DirectoryInfo { id: number; parentId: DirectoryId; name: string; + isFavorite?: boolean; } interface FileInfo { @@ -14,6 +15,7 @@ interface FileInfo { createdAt?: Date; lastModifiedAt: Date; categoryIds?: number[]; + isFavorite?: boolean; } interface CategoryInfo { diff --git a/src/lib/modules/filesystem/category.ts b/src/lib/modules/filesystem/category.ts index 94740c6..db0ec45 100644 --- a/src/lib/modules/filesystem/category.ts +++ b/src/lib/modules/filesystem/category.ts @@ -22,6 +22,7 @@ const cache = new FilesystemCache({ name: fileInfo.name, createdAt: fileInfo.createdAt, lastModifiedAt: fileInfo.lastModifiedAt, + isFavorite: fileInfo.isFavorite, isRecursive: file.isRecursive, } : undefined; @@ -66,6 +67,7 @@ const cache = new FilesystemCache({ parentId: file.parent, contentType: file.contentType, isRecursive: file.isRecursive, + isFavorite: file.isFavorite, ...(await decryptFileMetadata(file, masterKey)), })), ), diff --git a/src/lib/modules/filesystem/file.ts b/src/lib/modules/filesystem/file.ts index 5ef0fae..2517c02 100644 --- a/src/lib/modules/filesystem/file.ts +++ b/src/lib/modules/filesystem/file.ts @@ -27,6 +27,7 @@ const cache = new FilesystemCache({ name: file.name, createdAt: file.createdAt, lastModifiedAt: file.lastModifiedAt, + isFavorite: file.isFavorite, categories: categories?.filter((category) => !!category) ?? [], }; } @@ -55,6 +56,7 @@ const cache = new FilesystemCache({ name: metadata.name, createdAt: metadata.createdAt, lastModifiedAt: metadata.lastModifiedAt, + isFavorite: file.isFavorite, categories, }); } catch (e) { @@ -121,6 +123,7 @@ const cache = new FilesystemCache({ parentId: metadataRaw.parent, contentType: metadataRaw.contentType, categories, + isFavorite: metadataRaw.isFavorite, ...metadata, }; }), diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index d70abf8..b6967be 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -641,6 +641,7 @@ export const searchFiles = async ( encName: file.encrypted_name, encCreatedAt: file.encrypted_created_at, encLastModifiedAt: file.encrypted_last_modified_at, + isFavorite: file.is_favorite, })); }; diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 9ae8825..5922410 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -18,6 +18,7 @@ requestThumbnailUpload, requestFileAdditionToCategory, requestVideoStream, + requestFavoriteToggle, } from "./service"; import TopBarMenu from "./TopBarMenu.svelte"; @@ -75,6 +76,15 @@ void getFileInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME }; + const toggleFavorite = async () => { + if (!info?.exists) return; + const isFavorite = !!info.isFavorite; + const success = await requestFavoriteToggle(data.id, isFavorite); + if (success) { + info.isFavorite = !isFavorite; + } + }; + $effect(() => { HybridPromise.resolve(getFileInfo(data.id, $masterKeyStore?.get(1)?.key!)).then((result) => { if (data.id === result.id) { @@ -158,6 +168,8 @@ {fileBlob} downloadUrl={videoStreamUrl} filename={info?.name} + isFavorite={info?.isFavorite} + onToggleFavorite={toggleFavorite} />
diff --git a/src/routes/(fullscreen)/file/[id]/TopBarMenu.svelte b/src/routes/(fullscreen)/file/[id]/TopBarMenu.svelte index d713e8c..923cff9 100644 --- a/src/routes/(fullscreen)/file/[id]/TopBarMenu.svelte +++ b/src/routes/(fullscreen)/file/[id]/TopBarMenu.svelte @@ -5,6 +5,8 @@ import { fly } from "svelte/transition"; import { goto } from "$app/navigation"; + import IconFavorite from "~icons/material-symbols/favorite"; + import IconFavoriteOutline from "~icons/material-symbols/favorite-outline"; import IconFolderOpen from "~icons/material-symbols/folder-open"; import IconCloudDownload from "~icons/material-symbols/cloud-download"; @@ -13,10 +15,20 @@ downloadUrl?: string; fileBlob?: Blob; filename?: string; + isFavorite?: boolean; isOpen: boolean; + onToggleFavorite?: () => void; } - let { directoryId, downloadUrl, fileBlob, filename, isOpen = $bindable() }: Props = $props(); + let { + directoryId, + downloadUrl, + fileBlob, + filename, + isFavorite, + isOpen = $bindable(), + onToggleFavorite, + }: Props = $props(); const handleDownload = () => { if (fileBlob && filename) { @@ -34,7 +46,7 @@ {#if isOpen && (directoryId || downloadUrl || fileBlob)}

더보기

@@ -54,6 +66,13 @@ {/snippet} + {#if typeof isFavorite === "boolean"} + {@render menuButton( + isFavorite ? IconFavorite : IconFavoriteOutline, + isFavorite ? "즐겨찾기 해제" : "즐겨찾기", + onToggleFavorite ?? (() => {}), + )} + {/if} {#if directoryId} {@render menuButton(IconFolderOpen, "폴더에서 보기", () => goto( diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index 598418b..09b7d28 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -48,3 +48,16 @@ export const requestFileAdditionToCategory = async (fileId: number, categoryId: return false; } }; + +export const requestFavoriteToggle = async (fileId: number, isFavorite: boolean) => { + try { + if (isFavorite) { + await trpc().favorites.removeFile.mutate({ id: fileId }); + } else { + await trpc().favorites.addFile.mutate({ id: fileId }); + } + return true; + } catch { + return false; + } +}; diff --git a/src/routes/(fullscreen)/search/Directory.svelte b/src/routes/(fullscreen)/search/Directory.svelte index 56448a6..700bd54 100644 --- a/src/routes/(fullscreen)/search/Directory.svelte +++ b/src/routes/(fullscreen)/search/Directory.svelte @@ -12,5 +12,5 @@ - + diff --git a/src/routes/(fullscreen)/search/File.svelte b/src/routes/(fullscreen)/search/File.svelte index 3db8915..4e5c631 100644 --- a/src/routes/(fullscreen)/search/File.svelte +++ b/src/routes/(fullscreen)/search/File.svelte @@ -21,5 +21,6 @@ thumbnail={$thumbnail} name={info.name} subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)} + isFavorite={info.isFavorite} /> diff --git a/src/routes/(fullscreen)/search/service.ts b/src/routes/(fullscreen)/search/service.ts index d12ea1a..3f250dc 100644 --- a/src/routes/(fullscreen)/search/service.ts +++ b/src/routes/(fullscreen)/search/service.ts @@ -46,6 +46,7 @@ export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey) exists: true, parentId: directory.parent, ...metadata, + isFavorite: !!directory.isFavorite, }; }, }), @@ -65,6 +66,7 @@ export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey) exists: true, parentId: file.parent, contentType: file.contentType, + isFavorite: !!file.isFavorite, ...metadata, }; }, diff --git a/src/routes/(main)/category/[[id]]/File.svelte b/src/routes/(main)/category/[[id]]/File.svelte index 06ed8b3..90fb659 100644 --- a/src/routes/(main)/category/[[id]]/File.svelte +++ b/src/routes/(main)/category/[[id]]/File.svelte @@ -24,5 +24,10 @@ actionButtonIcon={onRemoveClick && IconClose} onActionButtonClick={() => onRemoveClick?.(info)} > - + diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte index a6c30c4..d45beb7 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte @@ -40,5 +40,6 @@ thumbnail={$thumbnail} name={info.name} subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)} + isFavorite={info.isFavorite} /> diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/SubDirectory.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/SubDirectory.svelte index 3f86025..33f4a47 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/SubDirectory.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/SubDirectory.svelte @@ -31,5 +31,5 @@ actionButtonIcon={IconMoreVert} onActionButtonClick={() => action(onOpenMenuClick)} > - + diff --git a/src/trpc/routers/category.ts b/src/trpc/routers/category.ts index 34887f7..1d4fcee 100644 --- a/src/trpc/routers/category.ts +++ b/src/trpc/routers/category.ts @@ -57,6 +57,7 @@ const categoryRouter = router({ createdAtIv: file.encCreatedAt?.iv, lastModifiedAt: file.encLastModifiedAt.ciphertext, lastModifiedAtIv: file.encLastModifiedAt.iv, + isFavorite: file.isFavorite, isRecursive: file.isRecursive, })), }; diff --git a/src/trpc/routers/file.ts b/src/trpc/routers/file.ts index ce6e5a6..ad06cdc 100644 --- a/src/trpc/routers/file.ts +++ b/src/trpc/routers/file.ts @@ -31,6 +31,7 @@ const fileRouter = router({ createdAtIv: file.encCreatedAt?.iv, lastModifiedAt: file.encLastModifiedAt.ciphertext, lastModifiedAtIv: file.encLastModifiedAt.iv, + isFavorite: file.isFavorite, categories: categories.map((category) => ({ id: category.id, parent: category.parentId, @@ -65,6 +66,7 @@ const fileRouter = router({ createdAtIv: file.encCreatedAt?.iv, lastModifiedAt: file.encLastModifiedAt.ciphertext, lastModifiedAtIv: file.encLastModifiedAt.iv, + isFavorite: file.isFavorite, categories: file.categories.map((category) => ({ id: category.id, parent: category.parentId, diff --git a/src/trpc/routers/search.ts b/src/trpc/routers/search.ts index 5aa2820..34e814d 100644 --- a/src/trpc/routers/search.ts +++ b/src/trpc/routers/search.ts @@ -32,6 +32,7 @@ const searchRouter = router({ dekVersion: directory.dekVersion, name: directory.encName.ciphertext, nameIv: directory.encName.iv, + isFavorite: directory.isFavorite, })), files: files.map((file) => ({ id: file.id, @@ -46,6 +47,7 @@ const searchRouter = router({ createdAtIv: file.encCreatedAt?.iv, lastModifiedAt: file.encLastModifiedAt.ciphertext, lastModifiedAtIv: file.encLastModifiedAt.iv, + isFavorite: file.isFavorite, })), }; }),