mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-14 22:08:45 +00:00
파일/디렉터리 목록 정렬 재구현
This commit is contained in:
@@ -3,15 +3,14 @@
|
|||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { TopBar } from "$lib/components";
|
import { TopBar } from "$lib/components";
|
||||||
import { FloatingButton } from "$lib/components/buttons";
|
import { FloatingButton } from "$lib/components/buttons";
|
||||||
import { getDirectoryInfo, getFileInfo } from "$lib/modules/file";
|
import { getDirectoryInfo } from "$lib/modules/file";
|
||||||
import { masterKeyStore, type DirectoryInfo } from "$lib/stores";
|
import { masterKeyStore, type DirectoryInfo } from "$lib/stores";
|
||||||
import CreateBottomSheet from "./CreateBottomSheet.svelte";
|
import CreateBottomSheet from "./CreateBottomSheet.svelte";
|
||||||
import CreateDirectoryModal from "./CreateDirectoryModal.svelte";
|
import CreateDirectoryModal from "./CreateDirectoryModal.svelte";
|
||||||
import DeleteDirectoryEntryModal from "./DeleteDirectoryEntryModal.svelte";
|
import DeleteDirectoryEntryModal from "./DeleteDirectoryEntryModal.svelte";
|
||||||
|
import DirectoryEntries from "./DirectoryEntries";
|
||||||
import DirectoryEntryMenuBottomSheet from "./DirectoryEntryMenuBottomSheet.svelte";
|
import DirectoryEntryMenuBottomSheet from "./DirectoryEntryMenuBottomSheet.svelte";
|
||||||
import File from "./File.svelte";
|
|
||||||
import RenameDirectoryEntryModal from "./RenameDirectoryEntryModal.svelte";
|
import RenameDirectoryEntryModal from "./RenameDirectoryEntryModal.svelte";
|
||||||
import SubDirectory from "./SubDirectory.svelte";
|
|
||||||
import {
|
import {
|
||||||
requestDirectoryCreation,
|
requestDirectoryCreation,
|
||||||
requestFileUpload,
|
requestFileUpload,
|
||||||
@@ -64,35 +63,15 @@
|
|||||||
<TopBar title={$info?.name} />
|
<TopBar title={$info?.name} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if $info && $info.subDirectoryIds.length + $info.fileIds.length > 0}
|
{#if $info}
|
||||||
<div class="my-4 pb-[4.5rem]">
|
<DirectoryEntries
|
||||||
{#each $info.subDirectoryIds as subDirectoryId}
|
info={$info}
|
||||||
{@const subDirectoryInfo = getDirectoryInfo(subDirectoryId, $masterKeyStore?.get(1)?.key!)}
|
onEntryClick={({ type, id }) => goto(`/${type}/${id}`)}
|
||||||
<SubDirectory
|
onEntryMenuClick={(entry) => {
|
||||||
info={subDirectoryInfo}
|
selectedEntry = entry;
|
||||||
onclick={() => goto(`/directory/${subDirectoryId}`)}
|
isDirectoryEntryMenuBottomSheetOpen = true;
|
||||||
onOpenMenuClick={({ id, dataKey, dataKeyVersion, name }) => {
|
}}
|
||||||
selectedEntry = { type: "directory", id, dataKey, dataKeyVersion, name };
|
/>
|
||||||
isDirectoryEntryMenuBottomSheetOpen = true;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
{#each $info.fileIds as fileId}
|
|
||||||
{@const fileInfo = getFileInfo(fileId, $masterKeyStore?.get(1)?.key!)}
|
|
||||||
<File
|
|
||||||
info={fileInfo}
|
|
||||||
onclick={() => goto(`/file/${fileId}`)}
|
|
||||||
onOpenMenuClick={({ dataKey, id, dataKeyVersion, name }) => {
|
|
||||||
selectedEntry = { type: "file", id, dataKey, dataKeyVersion, name };
|
|
||||||
isDirectoryEntryMenuBottomSheetOpen = true;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="my-4 flex flex-grow items-center justify-center">
|
|
||||||
<p class="text-gray-500">폴더가 비어있어요.</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
@@ -1,22 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { FileInfo } from "$lib/stores";
|
import type { FileInfo } from "$lib/stores";
|
||||||
|
import type { SelectedDirectoryEntry } from "../service";
|
||||||
|
|
||||||
import IconDraft from "~icons/material-symbols/draft";
|
import IconDraft from "~icons/material-symbols/draft";
|
||||||
import IconMoreVert from "~icons/material-symbols/more-vert";
|
import IconMoreVert from "~icons/material-symbols/more-vert";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
info: Writable<FileInfo | null>;
|
info: Writable<FileInfo | null>;
|
||||||
onclick: () => void;
|
onclick: (selectedEntry: SelectedDirectoryEntry) => void;
|
||||||
onOpenMenuClick: (metadata: FileInfo) => void;
|
onOpenMenuClick: (selectedEntry: SelectedDirectoryEntry) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { info, onclick, onOpenMenuClick }: Props = $props();
|
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) => {
|
const openMenu = (e: Event) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const { id, dataKey, dataKeyVersion, name } = $info!;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onOpenMenuClick($info!);
|
onOpenMenuClick({ type: "file", id, dataKey, dataKeyVersion, name });
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -24,7 +34,7 @@
|
|||||||
{#if $info}
|
{#if $info}
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<div id="button" onclick={() => setTimeout(onclick, 100)} class="h-12 w-full rounded-xl">
|
<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 id="button-content" class="flex h-full items-center gap-x-4 p-2 transition">
|
||||||
<div class="flex-shrink-0 text-lg">
|
<div class="flex-shrink-0 text-lg">
|
||||||
<IconDraft class="text-blue-400" />
|
<IconDraft class="text-blue-400" />
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { DirectoryInfo } from "$lib/stores";
|
import type { DirectoryInfo } from "$lib/stores";
|
||||||
|
import type { SelectedDirectoryEntry } from "../service";
|
||||||
|
|
||||||
import IconFolder from "~icons/material-symbols/folder";
|
import IconFolder from "~icons/material-symbols/folder";
|
||||||
import IconMoreVert from "~icons/material-symbols/more-vert";
|
import IconMoreVert from "~icons/material-symbols/more-vert";
|
||||||
@@ -9,16 +10,25 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
info: Writable<DirectoryInfo | null>;
|
info: Writable<DirectoryInfo | null>;
|
||||||
onclick: () => void;
|
onclick: (selectedEntry: SelectedDirectoryEntry) => void;
|
||||||
onOpenMenuClick: (metadata: SubDirectoryInfo) => void;
|
onOpenMenuClick: (selectedEntry: SelectedDirectoryEntry) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { info, onclick, onOpenMenuClick }: Props = $props();
|
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) => {
|
const openMenu = (e: Event) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const { id, dataKey, dataKeyVersion, name } = $info as SubDirectoryInfo;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onOpenMenuClick($info as SubDirectoryInfo);
|
onOpenMenuClick({ type: "directory", id, dataKey, dataKeyVersion, name });
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -26,7 +36,7 @@
|
|||||||
{#if $info}
|
{#if $info}
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<div id="button" onclick={() => setTimeout(onclick, 100)} class="h-12 w-full rounded-xl">
|
<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 id="button-content" class="flex h-full items-center gap-x-4 p-2 transition">
|
||||||
<div class="flex-shrink-0 text-lg">
|
<div class="flex-shrink-0 text-lg">
|
||||||
<IconFolder />
|
<IconFolder />
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { default } from "./DirectoryEntries.svelte";
|
||||||
|
export * from "./service";
|
||||||
@@ -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)));
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user