From 3ebfcdaa7d2c582e105e799266abd552e0df1fa2 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 18:14:33 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=ED=95=98=EC=9C=84=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=EC=9D=98=20=ED=8C=8C=EC=9D=BC=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=20=EC=97=AC=EB=B6=80=EB=A5=BC=20=EA=B8=B0=EC=96=B5?= =?UTF-8?q?=ED=95=98=EB=8F=84=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 --- src/lib/indexedDB/filesystem.ts | 25 ++++++++-- src/lib/modules/filesystem.ts | 47 +++++++++++++++---- .../(main)/category/[[id]]/+page.svelte | 19 ++++++-- 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/src/lib/indexedDB/filesystem.ts b/src/lib/indexedDB/filesystem.ts index 1c2c060..cf60b93 100644 --- a/src/lib/indexedDB/filesystem.ts +++ b/src/lib/indexedDB/filesystem.ts @@ -25,6 +25,7 @@ interface CategoryInfo { parentId: CategoryId; name: string; files: { id: number; isRecursive: boolean }[]; + isFileRecursive: boolean; } const filesystem = new Dexie("filesystem") as Dexie & { @@ -33,11 +34,21 @@ const filesystem = new Dexie("filesystem") as Dexie & { category: EntityTable; }; -filesystem.version(2).stores({ - directory: "id, parentId", - file: "id, parentId", - category: "id, parentId", -}); +filesystem + .version(3) + .stores({ + directory: "id, parentId", + file: "id, parentId", + category: "id, parentId", + }) + .upgrade(async (trx) => { + await trx + .table("category") + .toCollection() + .modify((category) => { + category.isFileRecursive = false; + }); + }); export const getDirectoryInfos = async (parentId: DirectoryId) => { return await filesystem.directory.where({ parentId }).toArray(); @@ -87,6 +98,10 @@ export const storeCategoryInfo = async (categoryInfo: CategoryInfo) => { await filesystem.category.put(categoryInfo); }; +export const updateCategoryInfo = async (id: number, changes: { isFileRecursive?: boolean }) => { + await filesystem.category.update(id, changes); +}; + export const deleteCategoryInfo = async (id: number) => { await filesystem.category.delete(id); }; diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts index eaf1d1a..c160534 100644 --- a/src/lib/modules/filesystem.ts +++ b/src/lib/modules/filesystem.ts @@ -12,6 +12,7 @@ import { getCategoryInfos as getCategoryInfosFromIndexedDB, getCategoryInfo as getCategoryInfoFromIndexedDB, storeCategoryInfo, + updateCategoryInfo as updateCategoryInfoInIndexedDB, deleteCategoryInfo, type DirectoryId, type CategoryId, @@ -62,6 +63,7 @@ export type CategoryInfo = name?: undefined; subCategoryIds: number[]; files?: undefined; + isFileRecursive?: undefined; } | { id: number; @@ -70,6 +72,7 @@ export type CategoryInfo = name: string; subCategoryIds: number[]; files: { id: number; isRecursive: boolean }[]; + isFileRecursive: boolean; }; const directoryInfoStore = new Map>(); @@ -255,7 +258,13 @@ const fetchCategoryInfoFromIndexedDB = async ( info.set({ id, subCategoryIds }); } else { if (!category) return; - info.set({ id, name: category.name, subCategoryIds, files: category.files }); + info.set({ + id, + name: category.name, + subCategoryIds, + files: category.files, + isFileRecursive: category.isFileRecursive, + }); } }; @@ -288,20 +297,28 @@ const fetchCategoryInfoFromServer = async ( const { files }: CategoryFileListResponse = await res.json(); const filesMapped = files.map(({ file, isRecursive }) => ({ id: file, isRecursive })); + let isFileRecursive: boolean | undefined = undefined; - info.set({ - id, - dataKey, - dataKeyVersion: new Date(metadata!.dekVersion), - name, - subCategoryIds: subCategories, - files: filesMapped, + info.update((value) => { + const newValue = { + isFileRecursive: false, + ...value, + id, + dataKey, + dataKeyVersion: new Date(metadata!.dekVersion), + name, + subCategoryIds: subCategories, + files: filesMapped, + }; + isFileRecursive = newValue.isFileRecursive; + return newValue; }); await storeCategoryInfo({ id, parentId: metadata!.parent, name, files: filesMapped, + isFileRecursive: isFileRecursive!, }); } }; @@ -327,3 +344,17 @@ export const getCategoryInfo = (categoryId: CategoryId, masterKey: CryptoKey) => fetchCategoryInfo(categoryId, info, masterKey); // Intended return info; }; + +export const updateCategoryInfo = async ( + categoryId: number, + changes: { isFileRecursive?: boolean }, +) => { + await updateCategoryInfoInIndexedDB(categoryId, changes); + categoryInfoStore.get(categoryId)?.update((value) => { + if (!value) return value; + if (changes.isFileRecursive !== undefined) { + value.isFileRecursive = changes.isFileRecursive; + } + return value; + }); +}; diff --git a/src/routes/(main)/category/[[id]]/+page.svelte b/src/routes/(main)/category/[[id]]/+page.svelte index 9bfa2a4..9cbc41c 100644 --- a/src/routes/(main)/category/[[id]]/+page.svelte +++ b/src/routes/(main)/category/[[id]]/+page.svelte @@ -3,7 +3,7 @@ import { goto } from "$app/navigation"; import { TopBar } from "$lib/components/molecules"; import { Category, CategoryCreateModal } from "$lib/components/organisms"; - import { getCategoryInfo, type CategoryInfo } from "$lib/modules/filesystem"; + import { getCategoryInfo, updateCategoryInfo, type CategoryInfo } from "$lib/modules/filesystem"; import { masterKeyStore } from "$lib/stores"; import CategoryDeleteModal from "./CategoryDeleteModal.svelte"; import CategoryMenuBottomSheet from "./CategoryMenuBottomSheet.svelte"; @@ -21,7 +21,7 @@ let info: Writable | undefined = $state(); - let isFileRecursive = $state(false); + let isFileRecursive: boolean | undefined = $state(); let isCategoryCreateModalOpen = $state(false); let isCategoryMenuBottomSheetOpen = $state(false); @@ -30,6 +30,19 @@ $effect(() => { info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); + isFileRecursive = undefined; + }); + + $effect(() => { + if ($info && isFileRecursive === undefined) { + isFileRecursive = $info.isFileRecursive ?? false; + } + }); + + $effect(() => { + if (data.id !== "root" && $info?.isFileRecursive !== isFileRecursive) { + updateCategoryInfo(data.id as number, { isFileRecursive }); + } }); @@ -41,7 +54,7 @@ {/if}
- {#if $info} + {#if $info && isFileRecursive !== undefined} Date: Sat, 12 Jul 2025 18:24:43 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=8B=9C=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=ED=99=95=EC=9D=B8=20=ED=95=84=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/(fullscreen)/auth/changePassword/+page.svelte | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/routes/(fullscreen)/auth/changePassword/+page.svelte b/src/routes/(fullscreen)/auth/changePassword/+page.svelte index 25ded91..b8cabff 100644 --- a/src/routes/(fullscreen)/auth/changePassword/+page.svelte +++ b/src/routes/(fullscreen)/auth/changePassword/+page.svelte @@ -6,8 +6,14 @@ let oldPassword = $state(""); let newPassword = $state(""); + let confirmPassword = $state(""); const changePassword = async () => { + if (newPassword !== confirmPassword) { + // TODO: Alert + return; + } + if (await requestPasswordChange(oldPassword, newPassword)) { await goto("/menu"); } @@ -30,6 +36,7 @@ + From 301216915e5ace0822b313eea343a92b0a3dc136 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 19:44:16 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=EB=B8=8C=EB=9D=BC=EC=9A=B0=EC=A0=80?= =?UTF-8?q?=EA=B0=80=20heic=20=EB=94=94=EC=BD=94=EB=94=A9=EC=9D=84=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=ED=95=98=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20hei?= =?UTF-8?q?c2any=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B8=8C=EB=9D=BC=EC=9A=B0=EC=A0=80=EA=B0=80=20webp=20?= =?UTF-8?q?=EC=9D=B8=EC=BD=94=EB=94=A9=EC=9D=84=20=EC=A7=80=EC=9B=90?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EC=8D=B8=EB=84=A4=EC=9D=BC=EC=9D=84=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/thumbnail.ts | 34 ++++++++++++------- .../(fullscreen)/file/[id]/+page.svelte | 25 +++++++++----- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/lib/modules/thumbnail.ts b/src/lib/modules/thumbnail.ts index 9c009bd..2d5cd55 100644 --- a/src/lib/modules/thumbnail.ts +++ b/src/lib/modules/thumbnail.ts @@ -32,7 +32,7 @@ const capture = ( drawer(context, scaledWidth, scaledHeight); canvas.toBlob((blob) => { - if (blob) { + if (blob && blob.type === "image/webp") { resolve(blob); } else { reject(new Error("Failed to generate thumbnail")); @@ -83,18 +83,26 @@ const generateVideoThumbnail = (videoUrl: string, time = 0) => { export const generateThumbnail = async (fileBuffer: ArrayBuffer, fileType: string) => { let url; try { - if (fileType === "image/heic") { - const { default: heic2any } = await import("heic2any"); - url = URL.createObjectURL( - (await heic2any({ - blob: new Blob([fileBuffer], { type: fileType }), - toType: "image/png", - })) as Blob, - ); - return await generateImageThumbnail(url); - } else if (fileType.startsWith("image/")) { - url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); - return await generateImageThumbnail(url); + if (fileType.startsWith("image/")) { + const fileBlob = new Blob([fileBuffer], { type: fileType }); + url = URL.createObjectURL(fileBlob); + + try { + return await generateImageThumbnail(url); + } catch { + URL.revokeObjectURL(url); + url = undefined; + + if (fileType === "image/heic") { + const { default: heic2any } = await import("heic2any"); + url = URL.createObjectURL( + (await heic2any({ blob: fileBlob, toType: "image/png" })) as Blob, + ); + return await generateImageThumbnail(url); + } else { + return null; + } + } } else if (fileType.startsWith("video/")) { url = URL.createObjectURL(new Blob([fileBuffer], { type: fileType })); return await generateVideoThumbnail(url); diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 61465bd..4047e7c 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -43,22 +43,31 @@ let isDownloadRequested = $state(false); let viewerType: "image" | "video" | undefined = $state(); let fileBlobUrl: string | undefined = $state(); + let heicBlob: Blob | undefined = $state(); let videoElement: HTMLVideoElement | undefined = $state(); const updateViewer = async (buffer: ArrayBuffer, contentType: string) => { const fileBlob = new Blob([buffer], { type: contentType }); - if (contentType === "image/heic") { - const { default: heic2any } = await import("heic2any"); - fileBlobUrl = URL.createObjectURL( - (await heic2any({ blob: fileBlob, toType: "image/jpeg" })) as Blob, - ); - } else if (viewerType) { + if (viewerType) { fileBlobUrl = URL.createObjectURL(fileBlob); + heicBlob = contentType === "image/heic" ? fileBlob : undefined; } - return fileBlob; }; + const convertHeicToJpeg = async () => { + if (!heicBlob) return; + + URL.revokeObjectURL(fileBlobUrl!); + fileBlobUrl = undefined; + + const { default: heic2any } = await import("heic2any"); + fileBlobUrl = URL.createObjectURL( + (await heic2any({ blob: heicBlob, toType: "image/jpeg" })) as Blob, + ); + heicBlob = undefined; + }; + const updateThumbnail = async (dataKey: CryptoKey, dataKeyVersion: Date) => { const thumbnail = await captureVideoThumbnail(videoElement!); await requestThumbnailUpload(data.id, thumbnail, dataKey, dataKeyVersion); @@ -136,7 +145,7 @@ {#if viewerType === "image"} {#if fileBlobUrl} - {$info.name} + {$info.name} {:else} {@render viewerLoading("이미지를 불러오고 있어요.")} {/if} From af20f6ec4e32b29eb58ca4e8d7030ea95f1bd69c Mon Sep 17 00:00:00 2001 From: static Date: Sat, 12 Jul 2025 19:47:59 +0900 Subject: [PATCH 4/4] =?UTF-8?q?package.json=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 67eb017..e91eafa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "arkvault", "private": true, - "version": "0.5.0", + "version": "0.5.1", "type": "module", "scripts": { "dev": "vite dev",