파일/디렉터리 목록 정렬 재구현

This commit is contained in:
static
2025-01-06 21:15:23 +09:00
parent 10b7472ee9
commit 183a3590a9
6 changed files with 128 additions and 40 deletions

View File

@@ -0,0 +1,57 @@
<script lang="ts">
import { untrack } from "svelte";
import type { Writable } from "svelte/store";
import { getDirectoryInfo, getFileInfo } from "$lib/modules/file";
import { masterKeyStore, type DirectoryInfo, type FileInfo } from "$lib/stores";
import File from "./File.svelte";
import SubDirectory from "./SubDirectory.svelte";
import { SortBy, sortEntries } from "./service";
import type { SelectedDirectoryEntry } from "../service";
interface Props {
info: DirectoryInfo;
onEntryClick: (entry: SelectedDirectoryEntry) => void;
onEntryMenuClick: (entry: SelectedDirectoryEntry) => void;
sortBy?: SortBy;
}
let { info, onEntryClick, onEntryMenuClick, sortBy = SortBy.NAME_ASC }: Props = $props();
let subDirectoryInfos: Writable<DirectoryInfo | null>[] = $state([]);
let fileInfos: Writable<FileInfo | null>[] = $state([]);
$effect(() => {
// TODO: Fix duplicated requests
subDirectoryInfos = info.subDirectoryIds.map((id) =>
getDirectoryInfo(id, $masterKeyStore?.get(1)?.key!),
);
fileInfos = info.fileIds.map((id) => getFileInfo(id, $masterKeyStore?.get(1)?.key!));
const sort = () => {
sortEntries(subDirectoryInfos, sortBy);
sortEntries(fileInfos, sortBy);
};
return untrack(() => {
const unsubscribes = subDirectoryInfos
.map((subDirectoryInfo) => subDirectoryInfo.subscribe(sort))
.concat(fileInfos.map((fileInfo) => fileInfo.subscribe(sort)));
return () => unsubscribes.forEach((unsubscribe) => unsubscribe());
});
});
</script>
{#if info.subDirectoryIds.length + info.fileIds.length > 0}
<div class="my-4 pb-[4.5rem]">
{#each subDirectoryInfos as subDirectory}
<SubDirectory info={subDirectory} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
{/each}
{#each fileInfos as file}
<File info={file} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
{/each}
</div>
{:else}
<div class="my-4 flex flex-grow items-center justify-center">
<p class="text-gray-500">폴더가 비어있어요.</p>
</div>
{/if}

View File

@@ -0,0 +1,63 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import type { FileInfo } from "$lib/stores";
import type { SelectedDirectoryEntry } from "../service";
import IconDraft from "~icons/material-symbols/draft";
import IconMoreVert from "~icons/material-symbols/more-vert";
interface Props {
info: Writable<FileInfo | null>;
onclick: (selectedEntry: SelectedDirectoryEntry) => void;
onOpenMenuClick: (selectedEntry: SelectedDirectoryEntry) => void;
}
let { info, onclick, onOpenMenuClick }: Props = $props();
const openFile = () => {
const { id, dataKey, dataKeyVersion, name } = $info!;
setTimeout(() => {
onclick({ type: "file", id, dataKey, dataKeyVersion, name });
}, 100);
};
const openMenu = (e: Event) => {
e.stopPropagation();
const { id, dataKey, dataKeyVersion, name } = $info!;
setTimeout(() => {
onOpenMenuClick({ type: "file", id, dataKey, dataKeyVersion, name });
}, 100);
};
</script>
{#if $info}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div id="button" onclick={openFile} class="h-12 w-full rounded-xl">
<div id="button-content" class="flex h-full items-center gap-x-4 p-2 transition">
<div class="flex-shrink-0 text-lg">
<IconDraft class="text-blue-400" />
</div>
<p title={$info.name} class="flex-grow truncate font-medium">
{$info.name}
</p>
<button
id="open-menu"
onclick={openMenu}
class="flex-shrink-0 rounded-full p-1 active:bg-gray-100"
>
<IconMoreVert class="text-lg transition active:scale-95" />
</button>
</div>
</div>
{/if}
<style>
#button:active:not(:has(#open-menu:active)) {
@apply bg-gray-100;
}
#button-content:active:not(:has(#open-menu:active)) {
@apply scale-95;
}
</style>

View File

@@ -0,0 +1,65 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import type { DirectoryInfo } from "$lib/stores";
import type { SelectedDirectoryEntry } from "../service";
import IconFolder from "~icons/material-symbols/folder";
import IconMoreVert from "~icons/material-symbols/more-vert";
type SubDirectoryInfo = DirectoryInfo & { id: number };
interface Props {
info: Writable<DirectoryInfo | null>;
onclick: (selectedEntry: SelectedDirectoryEntry) => void;
onOpenMenuClick: (selectedEntry: SelectedDirectoryEntry) => void;
}
let { info, onclick, onOpenMenuClick }: Props = $props();
const openDirectory = () => {
const { id, dataKey, dataKeyVersion, name } = $info as SubDirectoryInfo;
setTimeout(() => {
onclick({ type: "directory", id, dataKey, dataKeyVersion, name });
}, 100);
};
const openMenu = (e: Event) => {
e.stopPropagation();
const { id, dataKey, dataKeyVersion, name } = $info as SubDirectoryInfo;
setTimeout(() => {
onOpenMenuClick({ type: "directory", id, dataKey, dataKeyVersion, name });
}, 100);
};
</script>
{#if $info}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div id="button" onclick={openDirectory} class="h-12 w-full rounded-xl">
<div id="button-content" class="flex h-full items-center gap-x-4 p-2 transition">
<div class="flex-shrink-0 text-lg">
<IconFolder />
</div>
<p title={$info.name} class="flex-grow truncate font-medium">
{$info.name}
</p>
<button
id="open-menu"
onclick={openMenu}
class="flex-shrink-0 rounded-full p-1 active:bg-gray-100"
>
<IconMoreVert class="text-lg transition active:scale-95" />
</button>
</div>
</div>
{/if}
<style>
#button:active:not(:has(#open-menu:active)) {
@apply bg-gray-100;
}
#button-content:active:not(:has(#open-menu:active)) {
@apply scale-95;
}
</style>

View File

@@ -0,0 +1,2 @@
export { default } from "./DirectoryEntries.svelte";
export * from "./service";

View File

@@ -0,0 +1,30 @@
import { get, type Writable } from "svelte/store";
import type { DirectoryInfo, FileInfo } from "$lib/stores";
export enum SortBy {
NAME_ASC,
NAME_DESC,
}
type SortFunc = (a: DirectoryInfo | FileInfo | null, b: DirectoryInfo | FileInfo | null) => number;
const sortByNameAsc: SortFunc = (a, b) => {
if (a && b) return a.name!.localeCompare(b.name!);
return 0;
};
const sortByNameDesc: SortFunc = (a, b) => -sortByNameAsc(a, b);
export const sortEntries = <T extends DirectoryInfo | FileInfo>(
entries: Writable<T | null>[],
sortBy: SortBy = SortBy.NAME_ASC,
) => {
let sortFunc: SortFunc;
if (sortBy === SortBy.NAME_ASC) {
sortFunc = sortByNameAsc;
} else {
sortFunc = sortByNameDesc;
}
entries.sort((a, b) => sortFunc(get(a), get(b)));
};