Merge pull request #13 from kmc7468/dev

v0.5.1
This commit is contained in:
static
2025-07-12 19:56:12 +09:00
committed by GitHub
7 changed files with 121 additions and 38 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "arkvault",
"private": true,
"version": "0.5.0",
"version": "0.5.1",
"type": "module",
"scripts": {
"dev": "vite dev",

View File

@@ -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<CategoryInfo, "id">;
};
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);
};

View File

@@ -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<DirectoryId, Writable<DirectoryInfo | null>>();
@@ -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;
});
};

View File

@@ -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);

View File

@@ -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 @@
<TextInput bind:value={oldPassword} placeholder="기존 비밀번호" type="password" />
<TextInput bind:value={newPassword} placeholder="새 비밀번호" type="password" />
<TextInput bind:value={confirmPassword} placeholder="새 비밀번호 확인" type="password" />
</TitledDiv>
<BottomDiv>
<Button onclick={changePassword} class="w-full">비밀번호 바꾸기</Button>

View File

@@ -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}
<img src={fileBlobUrl} alt={$info.name} />
<img src={fileBlobUrl} alt={$info.name} onerror={convertHeicToJpeg} />
{:else}
{@render viewerLoading("이미지를 불러오고 있어요.")}
{/if}

View File

@@ -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<CategoryInfo | null> | 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 });
}
});
</script>
@@ -41,7 +54,7 @@
<TopBar title={$info?.name} />
{/if}
<div class="min-h-full bg-gray-100 pb-[5.5em]">
{#if $info}
{#if $info && isFileRecursive !== undefined}
<Category
bind:isFileRecursive
info={$info}