mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-04 16:16:55 +00:00
기존에 제작된 모달들을 ActionModal 컴포넌트 기반으로 재구성
This commit is contained in:
@@ -2,31 +2,32 @@
|
||||
import type { Writable } from "svelte/store";
|
||||
import { goto } from "$app/navigation";
|
||||
import { TopBar } from "$lib/components";
|
||||
import { CategoryCreateModal, RenameModal } from "$lib/components/organisms";
|
||||
import { CategoryCreateModal } from "$lib/components/organisms";
|
||||
import { getCategoryInfo, type CategoryInfo } from "$lib/modules/filesystem";
|
||||
import type { SelectedCategory } from "$lib/molecules/Categories";
|
||||
import Category from "$lib/organisms/Category";
|
||||
import { masterKeyStore } from "$lib/stores";
|
||||
import CategoryDeleteModal from "./CategoryDeleteModal.svelte";
|
||||
import CategoryMenuBottomSheet from "./CategoryMenuBottomSheet.svelte";
|
||||
import DeleteCategoryModal from "./DeleteCategoryModal.svelte";
|
||||
import CategoryRenameModal from "./CategoryRenameModal.svelte";
|
||||
import {
|
||||
createContext,
|
||||
requestCategoryCreation,
|
||||
requestFileRemovalFromCategory,
|
||||
requestCategoryRename,
|
||||
requestCategoryDeletion,
|
||||
} from "./service";
|
||||
} from "./service.svelte";
|
||||
|
||||
let { data } = $props();
|
||||
let context = createContext();
|
||||
|
||||
let info: Writable<CategoryInfo | null> | undefined = $state();
|
||||
let selectedSubCategory: SelectedCategory | undefined = $state();
|
||||
|
||||
let isFileRecursive = $state(false);
|
||||
|
||||
let isCategoryCreateModalOpen = $state(false);
|
||||
let isSubCategoryMenuBottomSheetOpen = $state(false);
|
||||
let isCategoryMenuBottomSheetOpen = $state(false);
|
||||
let isCategoryRenameModalOpen = $state(false);
|
||||
let isDeleteCategoryModalOpen = $state(false);
|
||||
let isCategoryDeleteModalOpen = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!);
|
||||
@@ -54,8 +55,8 @@
|
||||
onSubCategoryClick={({ id }) => goto(`/category/${id}`)}
|
||||
onSubCategoryCreateClick={() => (isCategoryCreateModalOpen = true)}
|
||||
onSubCategoryMenuClick={(subCategory) => {
|
||||
selectedSubCategory = subCategory;
|
||||
isSubCategoryMenuBottomSheetOpen = true;
|
||||
context.selectedCategory = subCategory;
|
||||
isCategoryMenuBottomSheetOpen = true;
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
@@ -64,7 +65,7 @@
|
||||
|
||||
<CategoryCreateModal
|
||||
bind:isOpen={isCategoryCreateModalOpen}
|
||||
oncreate={async (name: string) => {
|
||||
onCreateClick={async (name: string) => {
|
||||
if (await requestCategoryCreation(name, data.id, $masterKeyStore?.get(1)!)) {
|
||||
info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
|
||||
return true;
|
||||
@@ -74,35 +75,30 @@
|
||||
/>
|
||||
|
||||
<CategoryMenuBottomSheet
|
||||
bind:isOpen={isSubCategoryMenuBottomSheetOpen}
|
||||
bind:selectedCategory={selectedSubCategory}
|
||||
bind:isOpen={isCategoryMenuBottomSheetOpen}
|
||||
onRenameClick={() => {
|
||||
isSubCategoryMenuBottomSheetOpen = false;
|
||||
isCategoryMenuBottomSheetOpen = false;
|
||||
isCategoryRenameModalOpen = true;
|
||||
}}
|
||||
onDeleteClick={() => {
|
||||
isSubCategoryMenuBottomSheetOpen = false;
|
||||
isDeleteCategoryModalOpen = true;
|
||||
isCategoryMenuBottomSheetOpen = false;
|
||||
isCategoryDeleteModalOpen = true;
|
||||
}}
|
||||
/>
|
||||
<RenameModal
|
||||
<CategoryRenameModal
|
||||
bind:isOpen={isCategoryRenameModalOpen}
|
||||
onbeforeclose={() => (selectedSubCategory = undefined)}
|
||||
originalName={selectedSubCategory?.name}
|
||||
onRenameClick={async (newName: string) => {
|
||||
if (await requestCategoryRename(selectedSubCategory!, newName)) {
|
||||
if (await requestCategoryRename(context.selectedCategory!, newName)) {
|
||||
info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
/>
|
||||
<DeleteCategoryModal
|
||||
bind:isOpen={isDeleteCategoryModalOpen}
|
||||
bind:selectedCategory={selectedSubCategory}
|
||||
<CategoryDeleteModal
|
||||
bind:isOpen={isCategoryDeleteModalOpen}
|
||||
onDeleteClick={async () => {
|
||||
if (selectedSubCategory) {
|
||||
await requestCategoryDeletion(selectedSubCategory);
|
||||
if (await requestCategoryDeletion(context.selectedCategory!)) {
|
||||
info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
|
||||
return true;
|
||||
}
|
||||
|
||||
29
src/routes/(main)/category/[[id]]/CategoryDeleteModal.svelte
Normal file
29
src/routes/(main)/category/[[id]]/CategoryDeleteModal.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { ActionModal } from "$lib/components/molecules";
|
||||
import { truncateString } from "$lib/modules/util";
|
||||
import { useContext } from "./service.svelte";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onDeleteClick: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
let { isOpen = $bindable(), onDeleteClick }: Props = $props();
|
||||
let context = useContext();
|
||||
</script>
|
||||
|
||||
{#if context.selectedCategory}
|
||||
{@const { name } = context.selectedCategory}
|
||||
<ActionModal
|
||||
bind:isOpen
|
||||
title="'{truncateString(name)}' 카테고리를 삭제할까요?"
|
||||
cancelText="아니요"
|
||||
confirmText="삭제할게요"
|
||||
onConfirmClick={onDeleteClick}
|
||||
>
|
||||
<p>
|
||||
모든 하위 카테고리도 함께 삭제돼요. <br />
|
||||
하지만 카테고리에 추가된 파일들은 삭제되지 않아요.
|
||||
</p>
|
||||
</ActionModal>
|
||||
{/if}
|
||||
@@ -1,36 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { BottomSheet } from "$lib/components";
|
||||
import { EntryButton } from "$lib/components/atoms";
|
||||
import type { SelectedCategory } from "$lib/molecules/Categories";
|
||||
import { useContext } from "./service.svelte";
|
||||
|
||||
import IconCategory from "~icons/material-symbols/category";
|
||||
import IconEdit from "~icons/material-symbols/edit";
|
||||
import IconDelete from "~icons/material-symbols/delete";
|
||||
|
||||
interface Props {
|
||||
onRenameClick: () => void;
|
||||
onDeleteClick: () => void;
|
||||
isOpen: boolean;
|
||||
selectedCategory: SelectedCategory | undefined;
|
||||
onDeleteClick: () => void;
|
||||
onRenameClick: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
onRenameClick,
|
||||
onDeleteClick,
|
||||
isOpen = $bindable(),
|
||||
selectedCategory = $bindable(),
|
||||
}: Props = $props();
|
||||
|
||||
const closeBottomSheet = () => {
|
||||
isOpen = false;
|
||||
selectedCategory = undefined;
|
||||
};
|
||||
let { isOpen = $bindable(), onDeleteClick, onRenameClick }: Props = $props();
|
||||
let context = useContext();
|
||||
</script>
|
||||
|
||||
<BottomSheet bind:isOpen onclose={closeBottomSheet}>
|
||||
<div class="w-full py-4">
|
||||
{#if selectedCategory}
|
||||
{@const { name } = selectedCategory}
|
||||
{#if context.selectedCategory}
|
||||
{@const { name } = context.selectedCategory}
|
||||
<BottomSheet bind:isOpen>
|
||||
<div class="w-full py-4">
|
||||
<div class="flex h-12 items-center gap-x-4 p-2">
|
||||
<div class="flex-shrink-0 text-lg">
|
||||
<IconCategory />
|
||||
@@ -40,18 +30,18 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="my-2 h-px w-full bg-gray-200"></div>
|
||||
{/if}
|
||||
<EntryButton onclick={onRenameClick} class="w-full">
|
||||
<div class="flex h-8 items-center gap-x-4">
|
||||
<IconEdit class="text-lg" />
|
||||
<p class="font-medium">이름 바꾸기</p>
|
||||
</div>
|
||||
</EntryButton>
|
||||
<EntryButton onclick={onDeleteClick} class="w-full">
|
||||
<div class="flex h-8 items-center gap-x-4 text-red-500">
|
||||
<IconDelete class="text-lg" />
|
||||
<p class="font-medium">삭제하기</p>
|
||||
</div>
|
||||
</EntryButton>
|
||||
</div>
|
||||
</BottomSheet>
|
||||
<EntryButton onclick={onRenameClick} class="w-full">
|
||||
<div class="flex h-8 items-center gap-x-4">
|
||||
<IconEdit class="text-lg" />
|
||||
<p class="font-medium">이름 바꾸기</p>
|
||||
</div>
|
||||
</EntryButton>
|
||||
<EntryButton onclick={onDeleteClick} class="w-full">
|
||||
<div class="flex h-8 items-center gap-x-4 text-red-500">
|
||||
<IconDelete class="text-lg" />
|
||||
<p class="font-medium">삭제하기</p>
|
||||
</div>
|
||||
</EntryButton>
|
||||
</div>
|
||||
</BottomSheet>
|
||||
{/if}
|
||||
|
||||
17
src/routes/(main)/category/[[id]]/CategoryRenameModal.svelte
Normal file
17
src/routes/(main)/category/[[id]]/CategoryRenameModal.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { RenameModal } from "$lib/components/organisms";
|
||||
import { useContext } from "./service.svelte";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onRenameClick: (newName: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
let { isOpen = $bindable(), onRenameClick }: Props = $props();
|
||||
let context = useContext();
|
||||
</script>
|
||||
|
||||
{#if context.selectedCategory}
|
||||
{@const { name } = context.selectedCategory}
|
||||
<RenameModal bind:isOpen originalName={name} {onRenameClick} />
|
||||
{/if}
|
||||
@@ -1,47 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button, Modal } from "$lib/components/atoms";
|
||||
import type { SelectedCategory } from "$lib/molecules/Categories";
|
||||
|
||||
interface Props {
|
||||
onDeleteClick: () => Promise<boolean>;
|
||||
isOpen: boolean;
|
||||
selectedCategory: SelectedCategory | undefined;
|
||||
}
|
||||
|
||||
let { onDeleteClick, isOpen = $bindable(), selectedCategory = $bindable() }: Props = $props();
|
||||
|
||||
const closeModal = () => {
|
||||
isOpen = false;
|
||||
selectedCategory = undefined;
|
||||
};
|
||||
|
||||
const deleteEntry = async () => {
|
||||
// TODO: Validation
|
||||
|
||||
if (await onDeleteClick()) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal bind:isOpen onclose={closeModal}>
|
||||
{#if selectedCategory}
|
||||
{@const { name } = selectedCategory}
|
||||
{@const nameShort = name.length > 20 ? `${name.slice(0, 20)}...` : name}
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2 break-keep">
|
||||
<p class="text-xl font-bold">
|
||||
'{nameShort}' 카테고리를 삭제할까요?
|
||||
</p>
|
||||
<p>
|
||||
모든 하위 카테고리도 함께 삭제돼요. <br />
|
||||
하지만 카테고리에 추가된 파일들은 삭제되지 않아요.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-x-2">
|
||||
<Button color="gray" onclick={closeModal} class="flex-1">아니요</Button>
|
||||
<Button onclick={deleteEntry} class="flex-1">삭제할게요</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</Modal>
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getContext, setContext } from "svelte";
|
||||
import { callPostApi } from "$lib/hooks";
|
||||
import { encryptString } from "$lib/modules/crypto";
|
||||
import type { SelectedCategory } from "$lib/molecules/Categories";
|
||||
@@ -5,6 +6,17 @@ import type { CategoryRenameRequest } from "$lib/server/schemas";
|
||||
|
||||
export { requestCategoryCreation, requestFileRemovalFromCategory } from "$lib/services/category";
|
||||
|
||||
export const createContext = () => {
|
||||
const context = $state({
|
||||
selectedCategory: undefined as SelectedCategory | undefined,
|
||||
});
|
||||
return setContext("context", context);
|
||||
};
|
||||
|
||||
export const useContext = () => {
|
||||
return getContext<ReturnType<typeof createContext>>("context");
|
||||
};
|
||||
|
||||
export const requestCategoryRename = async (category: SelectedCategory, newName: string) => {
|
||||
const newNameEncrypted = await encryptString(newName, category.dataKey);
|
||||
|
||||
Reference in New Issue
Block a user