카테고리 목록 이름 기반 정렬 구현

This commit is contained in:
static
2025-01-23 00:28:30 +09:00
parent b48b9719ca
commit 606609d468
6 changed files with 100 additions and 46 deletions

View File

@@ -27,3 +27,32 @@ export const formatNetworkSpeed = (speed: number) => {
if (speed < 1000 * 1000 * 1000) return `${(speed / 1000 / 1000).toFixed(1)} Mbps`; if (speed < 1000 * 1000 * 1000) return `${(speed / 1000 / 1000).toFixed(1)} Mbps`;
return `${(speed / 1000 / 1000 / 1000).toFixed(1)} Gbps`; return `${(speed / 1000 / 1000 / 1000).toFixed(1)} Gbps`;
}; };
export enum SortBy {
NAME_ASC,
NAME_DESC,
}
type SortFunc = (a?: string, b?: string) => number;
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
const sortByNameAsc: SortFunc = (a, b) => {
if (a && b) return collator.compare(a, b);
if (a) return -1;
if (b) return 1;
return 0;
};
const sortByNameDesc: SortFunc = (a, b) => -sortByNameAsc(a, b);
export const sortEntries = <T extends { name?: string }>(entries: T[], sortBy: SortBy) => {
let sortFunc: SortFunc;
if (sortBy === SortBy.NAME_ASC) {
sortFunc = sortByNameAsc;
} else {
sortFunc = sortByNameDesc;
}
entries.sort((a, b) => sortFunc(a.name, b.name));
};

View File

@@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts">
import type { Component } from "svelte"; import { untrack, type Component } from "svelte";
import type { SvelteHTMLElements } from "svelte/elements"; import type { SvelteHTMLElements } from "svelte/elements";
import type { Writable } from "svelte/store"; import { get, type Writable } from "svelte/store";
import type { CategoryInfo } from "$lib/modules/filesystem"; import type { CategoryInfo } from "$lib/modules/filesystem";
import { SortBy, sortEntries } from "$lib/modules/util";
import Category from "./Category.svelte"; import Category from "./Category.svelte";
import type { SelectedCategory } from "./service"; import type { SelectedCategory } from "./service";
@@ -11,16 +12,48 @@
categoryMenuIcon?: Component<SvelteHTMLElements["svg"]>; categoryMenuIcon?: Component<SvelteHTMLElements["svg"]>;
onCategoryClick: (category: SelectedCategory) => void; onCategoryClick: (category: SelectedCategory) => void;
onCategoryMenuClick?: (category: SelectedCategory) => void; onCategoryMenuClick?: (category: SelectedCategory) => void;
sortBy?: SortBy;
} }
let { categories, categoryMenuIcon, onCategoryClick, onCategoryMenuClick }: Props = $props(); let {
categories,
categoryMenuIcon,
onCategoryClick,
onCategoryMenuClick,
sortBy = SortBy.NAME_ASC,
}: Props = $props();
let categoriesWithName: { name?: string; info: Writable<CategoryInfo | null> }[] = $state([]);
$effect(() => {
categoriesWithName = categories.map((category) => ({
name: get(category)?.name,
info: category,
}));
const sort = () => {
sortEntries(categoriesWithName, sortBy);
};
return untrack(() => {
sort();
const unsubscribes = categoriesWithName.map((category) =>
category.info.subscribe((value) => {
if (category.name === value?.name) return;
category.name = value?.name;
sort();
}),
);
return () => unsubscribes.forEach((unsubscribe) => unsubscribe());
});
});
</script> </script>
{#if categories.length > 0} {#if categoriesWithName.length > 0}
<div class="space-y-1"> <div class="space-y-1">
{#each categories as category} {#each categoriesWithName as { info }}
<Category <Category
info={category} {info}
menuIcon={categoryMenuIcon} menuIcon={categoryMenuIcon}
onclick={onCategoryClick} onclick={onCategoryClick}
onMenuClick={onCategoryMenuClick} onMenuClick={onCategoryMenuClick}

View File

@@ -1,7 +1,9 @@
<script lang="ts"> <script lang="ts">
import type { Writable } from "svelte/store"; import { untrack } from "svelte";
import { get, type Writable } from "svelte/store";
import { CheckBox } from "$lib/components/inputs"; import { CheckBox } from "$lib/components/inputs";
import { getFileInfo, type FileInfo, type CategoryInfo } from "$lib/modules/filesystem"; import { getFileInfo, type FileInfo, type CategoryInfo } from "$lib/modules/filesystem";
import { SortBy, sortEntries } from "$lib/modules/util";
import type { SelectedCategory } from "$lib/molecules/Categories"; import type { SelectedCategory } from "$lib/molecules/Categories";
import SubCategories from "$lib/molecules/SubCategories.svelte"; import SubCategories from "$lib/molecules/SubCategories.svelte";
import { masterKeyStore } from "$lib/stores"; import { masterKeyStore } from "$lib/stores";
@@ -17,6 +19,7 @@
onSubCategoryClick: (subCategory: SelectedCategory) => void; onSubCategoryClick: (subCategory: SelectedCategory) => void;
onSubCategoryCreateClick: () => void; onSubCategoryCreateClick: () => void;
onSubCategoryMenuClick: (subCategory: SelectedCategory) => void; onSubCategoryMenuClick: (subCategory: SelectedCategory) => void;
sortBy?: SortBy;
isFileRecursive: boolean; isFileRecursive: boolean;
} }
@@ -27,21 +30,42 @@
onSubCategoryClick, onSubCategoryClick,
onSubCategoryCreateClick, onSubCategoryCreateClick,
onSubCategoryMenuClick, onSubCategoryMenuClick,
sortBy = SortBy.NAME_ASC,
isFileRecursive = $bindable(), isFileRecursive = $bindable(),
}: Props = $props(); }: Props = $props();
let files: { info: Writable<FileInfo | null>; isRecursive: boolean }[] = $state([]); let files: { name?: string; info: Writable<FileInfo | null>; isRecursive: boolean }[] = $state(
[],
);
$effect(() => { $effect(() => {
files = files =
info.files info.files
?.filter(({ isRecursive }) => isFileRecursive || !isRecursive) ?.filter(({ isRecursive }) => isFileRecursive || !isRecursive)
.map(({ id, isRecursive }) => ({ .map(({ id, isRecursive }) => {
info: getFileInfo(id, $masterKeyStore?.get(1)?.key!), const info = getFileInfo(id, $masterKeyStore?.get(1)?.key!);
isRecursive, return {
})) ?? []; name: get(info)?.name,
info,
isRecursive,
};
}) ?? [];
// TODO: Sorting const sort = () => {
sortEntries(files, sortBy);
};
return untrack(() => {
sort();
const unsubscribes = files.map((file) =>
file.info.subscribe((value) => {
if (file.name === value?.name) return;
file.name = value?.name;
sort();
}),
);
return () => unsubscribes.forEach((unsubscribe) => unsubscribe());
});
}); });
</script> </script>

View File

@@ -7,6 +7,7 @@
type DirectoryInfo, type DirectoryInfo,
type FileInfo, type FileInfo,
} from "$lib/modules/filesystem"; } from "$lib/modules/filesystem";
import { SortBy, sortEntries } from "$lib/modules/util";
import { import {
fileUploadStatusStore, fileUploadStatusStore,
isFileUploading, isFileUploading,
@@ -15,7 +16,6 @@
} from "$lib/stores"; } from "$lib/stores";
import File from "./File.svelte"; import File from "./File.svelte";
import SubDirectory from "./SubDirectory.svelte"; import SubDirectory from "./SubDirectory.svelte";
import { SortBy, sortEntries } from "./service";
import UploadingFile from "./UploadingFile.svelte"; import UploadingFile from "./UploadingFile.svelte";
import type { SelectedDirectoryEntry } from "../service"; import type { SelectedDirectoryEntry } from "../service";

View File

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

View File

@@ -1,31 +0,0 @@
export enum SortBy {
NAME_ASC,
NAME_DESC,
}
type SortFunc = (a?: string, b?: string) => number;
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
const sortByNameAsc: SortFunc = (a, b) => {
if (a && b) return collator.compare(a, b);
if (a) return -1;
if (b) return 1;
return 0;
};
const sortByNameDesc: SortFunc = (a, b) => -sortByNameAsc(a, b);
export const sortEntries = <T extends { name?: string }>(
entries: T[],
sortBy: SortBy = SortBy.NAME_ASC,
) => {
let sortFunc: SortFunc;
if (sortBy === SortBy.NAME_ASC) {
sortFunc = sortByNameAsc;
} else {
sortFunc = sortByNameDesc;
}
entries.sort((a, b) => sortFunc(a.name, b.name));
};