정렬 관련 코드를 derived를 사용하도록 재작성

This commit is contained in:
static
2025-07-19 10:06:15 +09:00
parent 7f4ebd8601
commit 82c270ae99
12 changed files with 194 additions and 221 deletions

View File

@@ -1,10 +1,9 @@
<script lang="ts">
import { untrack } from "svelte";
import { get, type Writable } from "svelte/store";
import { derived } from "svelte/store";
import { CheckBox } from "$lib/components/atoms";
import { SubCategories, type SelectedCategory } from "$lib/components/molecules";
import type { CategoryInfo } from "$lib/modules/filesystem";
import { getFileInfo, type FileInfoStore } from "$lib/modules/filesystem2";
import { getFileInfo } from "$lib/modules/filesystem2";
import { SortBy, sortEntries } from "$lib/modules/util";
import { masterKeyStore } from "$lib/stores";
import File from "./File.svelte";
@@ -34,37 +33,35 @@
isFileRecursive = $bindable(),
}: Props = $props();
let files: { name?: string; info: FileInfoStore; isRecursive: boolean }[] = $state([]);
$effect(() => {
files =
info.files
?.filter(({ isRecursive }) => isFileRecursive || !isRecursive)
.map(({ id, isRecursive }) => {
const info = getFileInfo(id, $masterKeyStore?.get(1)?.key!);
return {
name: get(info).data?.name,
info,
isRecursive,
};
}) ?? [];
const sort = () => {
sortEntries(files, sortBy);
};
return untrack(() => {
sort();
const unsubscribes = files.map((file) =>
file.info.subscribe((value) => {
if (file.name === value.data?.name) return;
file.name = value.data?.name;
sort();
}),
);
return () => unsubscribes.forEach((unsubscribe) => unsubscribe());
});
});
let fileInfos = $derived(
info.files
?.filter(({ isRecursive }) => isFileRecursive || !isRecursive)
.map(({ id, isRecursive }) => ({
info: getFileInfo(id, $masterKeyStore?.get(1)?.key!),
isRecursive,
})) ?? [],
);
let files = $derived(
derived(
fileInfos.map(({ info }) => info),
(infos) => {
const files = infos
.map(($info, i) => {
if ($info.status === "success") {
return {
name: $info.data.name,
isRecursive: fileInfos[i]!.isRecursive,
info: $info.data,
};
}
return undefined;
})
.filter((info) => info !== undefined);
sortEntries(files, sortBy);
return files;
},
),
);
</script>
<div class="space-y-4">
@@ -90,7 +87,7 @@
</div>
<div class="space-y-1">
{#key info}
{#each files as { info, isRecursive }}
{#each $files as { info, isRecursive }}
<File
{info}
onclick={onFileClick}

View File

@@ -1,14 +1,13 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import { ActionEntryButton } from "$lib/components/atoms";
import { DirectoryEntryLabel } from "$lib/components/molecules";
import type { FileInfo, FileInfoStore } from "$lib/modules/filesystem2";
import type { FileInfo } from "$lib/modules/filesystem2";
import { requestFileThumbnailDownload, type SelectedFile } from "./service";
import IconClose from "~icons/material-symbols/close";
interface Props {
info: FileInfoStore;
info: FileInfo;
onclick: (selectedFile: SelectedFile) => void;
onRemoveClick?: (selectedFile: SelectedFile) => void;
}
@@ -18,22 +17,22 @@
let thumbnail: string | undefined = $state();
const openFile = () => {
const { id, dataKey, dataKeyVersion, name } = $info.data as FileInfo;
const { id, dataKey, dataKeyVersion, name } = info;
if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
onclick({ id, dataKey, dataKeyVersion, name });
};
const removeFile = () => {
const { id, dataKey, dataKeyVersion, name } = $info.data as FileInfo;
const { id, dataKey, dataKeyVersion, name } = info;
if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
onRemoveClick!({ id, dataKey, dataKeyVersion, name });
};
$effect(() => {
if ($info.data?.dataKey) {
requestFileThumbnailDownload($info.data.id, $info.data.dataKey)
if (info.dataKey) {
requestFileThumbnailDownload(info.id, info.dataKey)
.then((thumbnailUrl) => {
thumbnail = thumbnailUrl ?? undefined;
})
@@ -47,13 +46,11 @@
});
</script>
{#if $info.status === "success"}
<ActionEntryButton
class="h-12"
onclick={openFile}
actionButtonIcon={onRemoveClick && IconClose}
onActionButtonClick={removeFile}
>
<DirectoryEntryLabel type="file" {thumbnail} name={$info.data.name} />
</ActionEntryButton>
{/if}
<ActionEntryButton
class="h-12"
onclick={openFile}
actionButtonIcon={onRemoveClick && IconClose}
onActionButtonClick={removeFile}
>
<DirectoryEntryLabel type="file" {thumbnail} name={info.name} />
</ActionEntryButton>

View File

@@ -1,11 +1,24 @@
export const callGetApi = async (input: RequestInfo, fetchInternal = fetch) => {
return await fetchInternal(input);
interface FetchOptions {
fetch?: typeof fetch;
signal?: AbortSignal;
}
export const callGetApi = async (
input: RequestInfo,
{ fetch = globalThis.fetch, signal }: FetchOptions = {},
) => {
return await fetch(input, { method: "GET", signal });
};
export const callPostApi = async <T>(input: RequestInfo, payload?: T, fetchInternal = fetch) => {
return await fetchInternal(input, {
export const callPostApi = async <T>(
input: RequestInfo,
payload?: T,
{ fetch = globalThis.fetch, signal }: FetchOptions = {},
) => {
return await fetch(input, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: payload ? JSON.stringify(payload) : undefined,
signal,
});
};

View File

@@ -1,5 +1,4 @@
import { useQueryClient, createQuery, createMutation } from "@tanstack/svelte-query";
import { browser } from "$app/environment";
import { callGetApi, callPostApi } from "$lib/hooks";
import {
getDirectoryInfos as getDirectoryInfosFromIndexedDB,
@@ -44,17 +43,11 @@ export type DirectoryInfo =
subDirectoryIds: number[];
fileIds: number[];
};
export type SubDirectoryInfo = DirectoryInfo & { id: number };
const initializedIds = new Set<DirectoryId>();
let temporaryIdCounter = -1;
const getInitialDirectoryInfo = async (id: DirectoryId) => {
if (!browser || initializedIds.has(id)) {
return undefined;
} else {
initializedIds.add(id);
}
const [directory, subDirectories, files] = await Promise.all([
id !== "root" ? getDirectoryInfoFromIndexedDB(id) : undefined,
getDirectoryInfosFromIndexedDB(id),
@@ -72,16 +65,18 @@ const getInitialDirectoryInfo = async (id: DirectoryId) => {
};
export const getDirectoryInfo = (id: DirectoryId, masterKey: CryptoKey) => {
const queryClient = useQueryClient();
getInitialDirectoryInfo(id).then((info) => {
if (info && !queryClient.getQueryData(["directory", id])) {
queryClient.setQueryData<DirectoryInfo>(["directory", id], info);
}
}); // Intended
return createQuery<DirectoryInfo>({
queryKey: ["directory", id],
queryFn: async () => {
const res = await callGetApi(`/api/directory/${id}`); // TODO: 404
queryFn: async ({ client, signal }) => {
if (!client.getQueryData(["directory", id])) {
const initialInfo = await getInitialDirectoryInfo(id);
if (initialInfo) {
setTimeout(() => client.invalidateQueries({ queryKey: ["directory", id] }), 0);
return initialInfo;
}
}
const res = await callGetApi(`/api/directory/${id}`, { signal }); // TODO: 404
const {
metadata,
subDirectories: subDirectoryIds,
@@ -202,7 +197,7 @@ export const useDirectoryRename = () => {
onMutate: async ({ id, newName }) => {
await queryClient.cancelQueries({ queryKey: ["directory", id] });
const prevInfo = queryClient.getQueryData<DirectoryInfo & { id: number }>(["directory", id]);
const prevInfo = queryClient.getQueryData<SubDirectoryInfo>(["directory", id]);
if (prevInfo) {
queryClient.setQueryData<DirectoryInfo>(["directory", id], {
...prevInfo,
@@ -214,7 +209,7 @@ export const useDirectoryRename = () => {
},
onError: (_error, { id }, context) => {
if (context?.oldName) {
queryClient.setQueryData<DirectoryInfo & { id: number }>(["directory", id], (prevInfo) => {
queryClient.setQueryData<SubDirectoryInfo>(["directory", id], (prevInfo) => {
if (!prevInfo) return undefined;
return { ...prevInfo, name: context.oldName! };
});

View File

@@ -1,5 +1,4 @@
import { useQueryClient, createQuery, createMutation } from "@tanstack/svelte-query";
import { browser } from "$app/environment";
import { callGetApi, callPostApi } from "$lib/hooks";
import {
getFileInfo as getFileInfoFromIndexedDB,
@@ -26,31 +25,23 @@ export interface FileInfo {
categoryIds: number[];
}
const initializedFileIds = new Set<number>();
const getInitialFileInfo = async (id: number) => {
if (!browser || initializedFileIds.has(id)) {
return undefined;
}
initializedFileIds.add(id);
return await getFileInfoFromIndexedDB(id);
};
const decryptDate = async (ciphertext: string, iv: string, dataKey: CryptoKey) => {
return new Date(parseInt(await decryptString(ciphertext, iv, dataKey), 10));
};
export const getFileInfo = (id: number, masterKey: CryptoKey) => {
const queryClient = useQueryClient();
getInitialFileInfo(id).then((info) => {
if (info && !queryClient.getQueryData(["file", id])) {
queryClient.setQueryData<FileInfo>(["file", id], info);
}
}); // Intended
return createQuery<FileInfo>({
queryKey: ["file", id],
queryFn: async () => {
const res = await callGetApi(`/api/file/${id}`); // TODO: 404
queryFn: async ({ client, signal }) => {
if (!client.getQueryData(["file", id])) {
const initialInfo = await getFileInfoFromIndexedDB(id);
if (initialInfo) {
setTimeout(() => client.invalidateQueries({ queryKey: ["file", id] }), 0);
return initialInfo;
}
}
const res = await callGetApi(`/api/file/${id}`, { signal }); // TODO: 404
const metadata: FileInfoResponse = await res.json();
const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);