정렬 관련 코드를 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

@@ -109,7 +109,7 @@
<UploadStatusCard onclick={() => goto("/file/uploads")} />
<DownloadStatusCard onclick={() => goto("/file/downloads")} />
</div>
{#key $info}
{#key $info.data.id}
<DirectoryEntries
info={$info.data}
onEntryClick={({ type, id }) => goto(`/${type}/${id}`)}

View File

@@ -1,12 +1,11 @@
<script lang="ts">
import { untrack } from "svelte";
import { get, type Writable } from "svelte/store";
import { derived } from "svelte/store";
import {
getDirectoryInfo,
getFileInfo,
type DirectoryInfo,
type DirectoryInfoStore,
type FileInfoStore,
type SubDirectoryInfo,
type FileInfo,
} from "$lib/modules/filesystem2";
import { SortBy, sortEntries } from "$lib/modules/util";
import {
@@ -31,96 +30,84 @@
interface DirectoryEntry {
name?: string;
info: DirectoryInfoStore;
info: SubDirectoryInfo;
}
type FileEntry =
| {
type: "file";
name?: string;
info: FileInfoStore;
info: FileInfo;
}
| {
type: "uploading-file";
name: string;
info: Writable<FileUploadStatus>;
info: FileUploadStatus;
};
let subDirectories: DirectoryEntry[] = $state([]);
let files: FileEntry[] = $state([]);
$effect(() => {
// TODO: Fix duplicated requests
subDirectories = info.subDirectoryIds.map((id) => {
const info = getDirectoryInfo(id, $masterKeyStore?.get(1)?.key!);
return { name: get(info).data?.name, info };
});
files = info.fileIds
.map((id): FileEntry => {
const info = getFileInfo(id, $masterKeyStore?.get(1)?.key!);
return {
type: "file",
name: get(info).data?.name,
info,
};
})
.concat(
$fileUploadStatusStore
.filter((statusStore) => {
const { parentId, status } = get(statusStore);
return parentId === info.id && isFileUploading(status);
})
.map((status) => ({
type: "uploading-file",
name: get(status).name,
info: status,
})),
);
const sort = () => {
sortEntries(subDirectories, sortBy);
sortEntries(files, sortBy);
};
return untrack(() => {
sort();
const unsubscribes = subDirectories
.map((subDirectory) =>
subDirectory.info.subscribe((value) => {
if (subDirectory.name === value.data?.name) return;
subDirectory.name = value.data?.name;
sort();
}),
)
.concat(
files.map((file) => {
if (file.type === "file") {
return file.info.subscribe((value) => {
if (file.name === value.data?.name) return;
file.name = value.data?.name;
sort();
});
} else {
return file.info.subscribe((value) => {
if (file.name === value.name) return;
file.name = value.name;
sort();
});
}
}),
);
return () => unsubscribes.forEach((unsubscribe) => unsubscribe());
});
});
let subDirectories = $derived(
derived(
info.subDirectoryIds.map((id) => getDirectoryInfo(id, $masterKeyStore?.get(1)?.key!)),
(infos) => {
const subDirectories = infos
.filter(($info) => $info.status === "success")
.map(
($info) =>
({
name: $info.data.name,
info: $info.data as SubDirectoryInfo,
}) satisfies DirectoryEntry,
);
sortEntries(subDirectories, sortBy);
return subDirectories;
},
),
);
let files = $derived(
derived(
info.fileIds.map((id) => getFileInfo(id, $masterKeyStore?.get(1)?.key!)),
(infos) =>
infos
.filter(($info) => $info.status === "success")
.map(
($info) =>
({
type: "file",
name: $info.data.name,
info: $info.data,
}) satisfies FileEntry,
),
),
);
let uploadingFiles = $derived(
derived($fileUploadStatusStore, (statuses) =>
statuses
.filter(({ parentId, status }) => parentId === info.id && isFileUploading(status))
.map(
($status) =>
({
type: "uploading-file",
name: $status.name,
info: $status,
}) satisfies FileEntry,
),
),
);
let everyFiles = $derived(
derived([files, uploadingFiles], ([$files, $uploadingFiles]) => {
const allFiles = [...$files, ...$uploadingFiles];
sortEntries(allFiles, sortBy);
return allFiles;
}),
);
</script>
{#if subDirectories.length + files.length > 0}
{#if $subDirectories.length + $everyFiles.length > 0}
<div class="space-y-1 pb-[4.5rem]">
{#each subDirectories as { info }}
{#each $subDirectories as { info }}
<SubDirectory {info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
{/each}
{#each files as file}
{#each $everyFiles as file}
{#if file.type === "file"}
<File info={file.info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
{:else}

View File

@@ -1,7 +1,7 @@
<script lang="ts">
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 { formatDateTime } from "$lib/modules/util";
import { requestFileThumbnailDownload } from "./service";
import type { SelectedEntry } from "../service.svelte";
@@ -9,7 +9,7 @@
import IconMoreVert from "~icons/material-symbols/more-vert";
interface Props {
info: FileInfoStore;
info: FileInfo;
onclick: (selectedEntry: SelectedEntry) => void;
onOpenMenuClick: (selectedEntry: SelectedEntry) => void;
}
@@ -19,22 +19,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({ type: "file", id, dataKey, dataKeyVersion, name });
};
const openMenu = () => {
const { id, dataKey, dataKeyVersion, name } = $info.data as FileInfo;
const { id, dataKey, dataKeyVersion, name } = info;
if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
onOpenMenuClick({ type: "file", 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;
})
@@ -48,18 +48,16 @@
});
</script>
{#if $info.status === "success"}
<ActionEntryButton
class="h-14"
onclick={openFile}
actionButtonIcon={IconMoreVert}
onActionButtonClick={openMenu}
>
<DirectoryEntryLabel
type="file"
{thumbnail}
name={$info.data.name}
subtext={formatDateTime($info.data.createdAt ?? $info.data.lastModifiedAt)}
/>
</ActionEntryButton>
{/if}
<ActionEntryButton
class="h-14"
onclick={openFile}
actionButtonIcon={IconMoreVert}
onActionButtonClick={openMenu}
>
<DirectoryEntryLabel
type="file"
{thumbnail}
name={info.name}
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
/>
</ActionEntryButton>

View File

@@ -1,15 +1,13 @@
<script lang="ts">
import { ActionEntryButton } from "$lib/components/atoms";
import { DirectoryEntryLabel } from "$lib/components/molecules";
import type { DirectoryInfo, DirectoryInfoStore } from "$lib/modules/filesystem2";
import type { SubDirectoryInfo } from "$lib/modules/filesystem2";
import type { SelectedEntry } from "../service.svelte";
import IconMoreVert from "~icons/material-symbols/more-vert";
type SubDirectoryInfo = DirectoryInfo & { id: number };
interface Props {
info: DirectoryInfoStore;
info: SubDirectoryInfo;
onclick: (selectedEntry: SelectedEntry) => void;
onOpenMenuClick: (selectedEntry: SelectedEntry) => void;
}
@@ -17,27 +15,25 @@
let { info, onclick, onOpenMenuClick }: Props = $props();
const openDirectory = () => {
const { id, dataKey, dataKeyVersion, name } = $info.data as SubDirectoryInfo;
const { id, dataKey, dataKeyVersion, name } = info;
if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
onclick({ type: "directory", id, dataKey, dataKeyVersion, name });
};
const openMenu = () => {
const { id, dataKey, dataKeyVersion, name } = $info.data as SubDirectoryInfo;
const { id, dataKey, dataKeyVersion, name } = info;
if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
onOpenMenuClick({ type: "directory", id, dataKey, dataKeyVersion, name });
};
</script>
{#if $info}
<ActionEntryButton
class="h-14"
onclick={openDirectory}
actionButtonIcon={IconMoreVert}
onActionButtonClick={openMenu}
>
<DirectoryEntryLabel type="directory" name={$info.data?.name!} />
</ActionEntryButton>
{/if}
<ActionEntryButton
class="h-14"
onclick={openDirectory}
actionButtonIcon={IconMoreVert}
onActionButtonClick={openMenu}
>
<DirectoryEntryLabel type="directory" name={info.name} />
</ActionEntryButton>

View File

@@ -1,36 +1,35 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import { formatNetworkSpeed } from "$lib/modules/util";
import { isFileUploading, type FileUploadStatus } from "$lib/stores";
import IconDraft from "~icons/material-symbols/draft";
interface Props {
status: Writable<FileUploadStatus>;
status: FileUploadStatus;
}
let { status }: Props = $props();
</script>
{#if isFileUploading($status.status)}
{#if isFileUploading(status.status)}
<div class="flex h-14 gap-x-4 p-2">
<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center text-xl">
<IconDraft class="text-gray-600" />
</div>
<div class="flex flex-grow flex-col overflow-hidden text-gray-800">
<p title={$status.name} class="truncate font-medium">
{$status.name}
<p title={status.name} class="truncate font-medium">
{status.name}
</p>
<p class="text-xs">
{#if $status.status === "encryption-pending"}
{#if status.status === "encryption-pending"}
준비 중
{:else if $status.status === "encrypting"}
{:else if status.status === "encrypting"}
암호화하는 중
{:else if $status.status === "upload-pending"}
{:else if status.status === "upload-pending"}
업로드를 기다리는 중
{:else if $status.status === "uploading"}
전송됨 {Math.floor(($status.progress ?? 0) * 100)}% ·
{formatNetworkSpeed(($status.rate ?? 0) * 8)}
{:else if status.status === "uploading"}
전송됨 {Math.floor((status.progress ?? 0) * 100)}% ·
{formatNetworkSpeed((status.rate ?? 0) * 8)}
{/if}
</p>
</div>