카테고리에 파일을 추가할 수 있는 BottomSheet 구현 (WiP)

This commit is contained in:
static
2025-01-22 13:22:16 +09:00
parent dbe2262d07
commit a2402f37a0
13 changed files with 199 additions and 72 deletions

View File

@@ -28,7 +28,7 @@
<AdaptiveDiv>
<div
onclick={(e) => e.stopPropagation()}
class="flex max-h-[70vh] min-h-[30vh] rounded-t-2xl bg-white px-4"
class="flex max-h-[70vh] min-h-[30vh] overflow-y-auto rounded-t-2xl bg-white px-4"
transition:fly={{ y: 100, duration: 200 }}
>
{@render children?.()}

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import type { CategoryInfo } from "$lib/modules/filesystem";
import Category from "./Category.svelte";
import type { SelectedCategory } from "./service";
interface Props {
categories: Writable<CategoryInfo | null>[];
onCategoryClick: (category: SelectedCategory) => void;
}
let { categories, onCategoryClick }: Props = $props();
</script>
<div class="space-y-1">
{#each categories as category}
<Category info={category} onclick={onCategoryClick} />
{/each}
</div>

View File

@@ -1,14 +1,14 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import type { CategoryInfo } from "$lib/modules/filesystem";
import type { SelectedSubCategory } from "./service";
import type { SelectedCategory } from "./service";
import IconCategory from "~icons/material-symbols/category";
import IconMoreVert from "~icons/material-symbols/more-vert";
interface Props {
info: Writable<CategoryInfo | null>;
onclick: (selectedCategory: SelectedSubCategory) => void;
onclick: (category: SelectedCategory) => void;
}
let { info, onclick }: Props = $props();

View File

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

View File

@@ -0,0 +1,6 @@
export interface SelectedCategory {
id: number;
dataKey: CryptoKey;
dataKeyVersion: Date;
name: string;
}

View File

@@ -0,0 +1,57 @@
<script lang="ts">
import type { ClassValue } from "svelte/elements";
import type { Writable } from "svelte/store";
import { EntryButton } from "$lib/components/buttons";
import { getCategoryInfo, type CategoryInfo } from "$lib/modules/filesystem";
import Categories, { type SelectedCategory } from "$lib/molecules/Categories";
import { masterKeyStore } from "$lib/stores";
import IconAddCircle from "~icons/material-symbols/add-circle";
interface Props {
class?: ClassValue;
info: CategoryInfo;
onSubCategoryClick: (subCategory: SelectedCategory) => void;
onSubCategoryCreateClick: () => void;
subCategoryCreatePosition?: "top" | "bottom";
}
let {
info,
onSubCategoryClick,
onSubCategoryCreateClick,
subCategoryCreatePosition = "bottom",
...props
}: Props = $props();
let subCategories: Writable<CategoryInfo | null>[] = $state([]);
$effect(() => {
subCategories = info.subCategoryIds.map((id) =>
getCategoryInfo(id, $masterKeyStore?.get(1)?.key!),
);
// TODO: Sorting
});
</script>
<div class={["space-y-1", props.class]}>
{#snippet subCategoryCreate()}
<EntryButton onclick={onSubCategoryCreateClick}>
<div class="flex h-8 items-center gap-x-4">
<IconAddCircle class="text-lg text-gray-600" />
<p class="font-medium text-gray-700">카테고리 추가하기</p>
</div>
</EntryButton>
{/snippet}
{#if subCategoryCreatePosition === "top"}
{@render subCategoryCreate()}
{/if}
{#key info}
<Categories categories={subCategories} onCategoryClick={onSubCategoryClick} />
{/key}
{#if subCategoryCreatePosition === "bottom"}
{@render subCategoryCreate()}
{/if}
</div>

View File

@@ -1,23 +1,21 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import { EntryButton } from "$lib/components/buttons";
import {
getFileInfo,
getCategoryInfo,
type FileInfo,
type CategoryInfo,
} from "$lib/modules/filesystem";
import type { SelectedCategory } from "$lib/molecules/Categories";
import SubCategories from "$lib/molecules/SubCategories.svelte";
import { masterKeyStore } from "$lib/stores";
import File from "./File.svelte";
import SubCategory from "./SubCategory.svelte";
import type { SelectedSubCategory, SelectedFile } from "./service";
import IconAddCircle from "~icons/material-symbols/add-circle";
import type { SelectedFile } from "./service";
interface Props {
info: CategoryInfo;
onFileClick: (file: SelectedFile) => void;
onSubCategoryClick: (subCategory: SelectedSubCategory) => void;
onSubCategoryClick: (subCategory: SelectedCategory) => void;
onSubCategoryCreateClick: () => void;
}
@@ -41,19 +39,7 @@
{#if info.id !== "root"}
<p class="text-lg font-bold text-gray-800">하위 카테고리</p>
{/if}
<div class="space-y-1">
{#key info}
{#each subCategories as subCategory}
<SubCategory info={subCategory} onclick={onSubCategoryClick} />
{/each}
{/key}
<EntryButton onclick={onSubCategoryCreateClick}>
<div class="flex h-8 items-center gap-x-4">
<IconAddCircle class="text-lg text-gray-600" />
<p class="font-medium text-gray-700">카테고리 추가하기</p>
</div>
</EntryButton>
</div>
<SubCategories {info} {onSubCategoryClick} {onSubCategoryCreateClick} />
</div>
{#if info.id !== "root"}
<div class="space-y-4 bg-white p-4">

View File

@@ -1,10 +1,3 @@
export interface SelectedSubCategory {
id: number;
dataKey: CryptoKey;
dataKeyVersion: Date;
name: string;
}
export interface SelectedFile {
id: number;
dataKey: CryptoKey;

View File

@@ -5,13 +5,16 @@
import { TopBar } from "$lib/components";
import { getFileInfo, type FileInfo } from "$lib/modules/filesystem";
import { fileDownloadStatusStore, isFileDownloading, masterKeyStore } from "$lib/stores";
import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte";
import DownloadStatus from "./DownloadStatus.svelte";
import { requestFileDownload } from "./service";
import { requestFileDownload, requestFileAdditionToCategory } from "./service";
let { data } = $props();
let info: Writable<FileInfo | null> | undefined = $state();
let isAddToCategoryBottomSheetOpen = $state(true);
const downloadStatus = $derived(
$fileDownloadStatusStore.find((statusStore) => {
const { id, status } = get(statusStore);
@@ -44,6 +47,11 @@
return fileBlob;
};
const addToCategory = async (categoryId: number) => {
await requestFileAdditionToCategory(data.id, categoryId);
isAddToCategoryBottomSheetOpen = false;
};
$effect(() => {
info = getFileInfo(data.id, $masterKeyStore?.get(1)?.key!);
isDownloadRequested = false;
@@ -105,3 +113,8 @@
{/if}
</div>
</div>
<AddToCategoryBottomSheet
bind:isOpen={isAddToCategoryBottomSheetOpen}
onAddToCategoryClick={addToCategory}
/>

View File

@@ -0,0 +1,42 @@
<script lang="ts">
import { onMount } from "svelte";
import type { Writable } from "svelte/store";
import { BottomSheet } from "$lib/components";
import { Button } from "$lib/components/buttons";
import { BottomDiv } from "$lib/components/divs";
import { getCategoryInfo, type CategoryInfo } from "$lib/modules/filesystem";
import SubCategories from "$lib/molecules/SubCategories.svelte";
import { masterKeyStore } from "$lib/stores";
interface Props {
onAddToCategoryClick: (categoryId: number) => void;
isOpen: boolean;
}
let { onAddToCategoryClick, isOpen = $bindable() }: Props = $props();
let category: Writable<CategoryInfo | null> | undefined = $state();
onMount(() => {
category = getCategoryInfo("root", $masterKeyStore?.get(1)?.key!);
});
</script>
<BottomSheet bind:isOpen>
<div class="flex w-full flex-col justify-between">
{#if $category}
<SubCategories
class="h-fit py-4"
info={$category}
onSubCategoryClick={({ id }) =>
(category = getCategoryInfo(id, $masterKeyStore?.get(1)?.key!))}
subCategoryCreatePosition="top"
/>
{#if $category.id !== "root"}
<BottomDiv>
<Button onclick={() => onAddToCategoryClick($category.id)}> 카테고리에 추가하기</Button>
</BottomDiv>
{/if}
{/if}
</div>
</BottomSheet>

View File

@@ -1,4 +1,6 @@
import { callPostApi } from "$lib/hooks";
import { getFileCache, storeFileCache, downloadFile } from "$lib/modules/file";
import type { CategoryFileAddRequest } from "$lib/server/schemas";
export const requestFileDownload = async (
fileId: number,
@@ -12,3 +14,10 @@ export const requestFileDownload = async (
storeFileCache(fileId, fileBuffer); // Intended
return fileBuffer;
};
export const requestFileAdditionToCategory = async (fileId: number, categoryId: number) => {
const res = await callPostApi<CategoryFileAddRequest>(`/api/category/${categoryId}/file/add`, {
file: fileId,
});
return res.ok;
};