mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-15 06:18:48 +00:00
파일/폴더 삭제 및 이름 변경 레이아웃 구현
This commit is contained in:
@@ -5,21 +5,24 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
|
onclose?: () => void;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { children, isOpen = $bindable() }: Props = $props();
|
let { children, onclose, isOpen = $bindable() }: Props = $props();
|
||||||
|
|
||||||
|
const closeBottomSheet = $derived(
|
||||||
|
onclose ||
|
||||||
|
(() => {
|
||||||
|
isOpen = false;
|
||||||
|
}),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div onclick={closeBottomSheet} class="fixed inset-0 z-10 flex items-end justify-center">
|
||||||
onclick={() => {
|
|
||||||
isOpen = false;
|
|
||||||
}}
|
|
||||||
class="fixed inset-0 z-10 flex items-end justify-center"
|
|
||||||
>
|
|
||||||
<div class="absolute inset-0 bg-black bg-opacity-50" transition:fade={{ duration: 100 }}></div>
|
<div class="absolute inset-0 bg-black bg-opacity-50" transition:fade={{ duration: 100 }}></div>
|
||||||
<div class="z-20 w-full">
|
<div class="z-20 w-full">
|
||||||
<AdaptiveDiv>
|
<AdaptiveDiv>
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
|
<script module lang="ts">
|
||||||
|
export interface SelectedDiretoryEntry {
|
||||||
|
type: "directory" | "file";
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
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 { clientKeyStore, masterKeyStore } from "$lib/stores";
|
import { clientKeyStore, masterKeyStore } 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 DirectoryEntry from "./DirectoryEntry.svelte";
|
import DirectoryEntry from "./DirectoryEntry.svelte";
|
||||||
|
import DirectoryEntryMenuBottomSheet from "./DirectoryEntryMenuBottomSheet.svelte";
|
||||||
|
import RenameDirectoryEntryModal from "./RenameDirectoryEntryModal.svelte";
|
||||||
import {
|
import {
|
||||||
decryptDirectroyMetadata,
|
decryptDirectroyMetadata,
|
||||||
decryptFileMetadata,
|
decryptFileMetadata,
|
||||||
@@ -17,10 +29,15 @@
|
|||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
let fileInput: HTMLInputElement | undefined = $state();
|
let fileInput: HTMLInputElement | undefined = $state();
|
||||||
|
let selectedEntry: SelectedDiretoryEntry | undefined = $state();
|
||||||
|
|
||||||
let isCreateBottomSheetOpen = $state(false);
|
let isCreateBottomSheetOpen = $state(false);
|
||||||
let isCreateDirectoryModalOpen = $state(false);
|
let isCreateDirectoryModalOpen = $state(false);
|
||||||
|
|
||||||
|
let isDirectoryEntryMenuBottomSheetOpen = $state(false);
|
||||||
|
let isRenameDirectoryEntryModalOpen = $state(false);
|
||||||
|
let isDeleteDirectoryEntryModalOpen = $state(false);
|
||||||
|
|
||||||
// TODO: FIX ME
|
// TODO: FIX ME
|
||||||
const metadata = $derived.by(() => {
|
const metadata = $derived.by(() => {
|
||||||
const { metadata } = data;
|
const { metadata } = data;
|
||||||
@@ -103,14 +120,30 @@
|
|||||||
{#if subDirectories}
|
{#if subDirectories}
|
||||||
{#await subDirectories then subDirectories}
|
{#await subDirectories then subDirectories}
|
||||||
{#each subDirectories as { id, name }}
|
{#each subDirectories as { id, name }}
|
||||||
<DirectoryEntry {id} {name} type="directory" />
|
<DirectoryEntry
|
||||||
|
{name}
|
||||||
|
onclick={() => goto(`/directory/${id}`)}
|
||||||
|
onOpenMenuClick={() => {
|
||||||
|
selectedEntry = { type: "directory", id, name };
|
||||||
|
isDirectoryEntryMenuBottomSheetOpen = true;
|
||||||
|
}}
|
||||||
|
type="directory"
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
{#if files}
|
{#if files}
|
||||||
{#await files then files}
|
{#await files then files}
|
||||||
{#each files as { id, name }}
|
{#each files as { id, name }}
|
||||||
<DirectoryEntry {id} {name} type="file" />
|
<DirectoryEntry
|
||||||
|
{name}
|
||||||
|
onclick={() => goto(`/file/${id}`)}
|
||||||
|
onOpenMenuClick={() => {
|
||||||
|
selectedEntry = { type: "file", id, name };
|
||||||
|
isDirectoryEntryMenuBottomSheetOpen = true;
|
||||||
|
}}
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -126,13 +159,28 @@
|
|||||||
|
|
||||||
<CreateBottomSheet
|
<CreateBottomSheet
|
||||||
bind:isOpen={isCreateBottomSheetOpen}
|
bind:isOpen={isCreateBottomSheetOpen}
|
||||||
onDirectoryCreate={() => {
|
onDirectoryCreateClick={() => {
|
||||||
isCreateBottomSheetOpen = false;
|
isCreateBottomSheetOpen = false;
|
||||||
isCreateDirectoryModalOpen = true;
|
isCreateDirectoryModalOpen = true;
|
||||||
}}
|
}}
|
||||||
onFileUpload={() => {
|
onFileUploadClick={() => {
|
||||||
isCreateBottomSheetOpen = false;
|
isCreateBottomSheetOpen = false;
|
||||||
fileInput?.click();
|
fileInput?.click();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<CreateDirectoryModal bind:isOpen={isCreateDirectoryModalOpen} onCreateClick={createDirectory} />
|
<CreateDirectoryModal bind:isOpen={isCreateDirectoryModalOpen} onCreateClick={createDirectory} />
|
||||||
|
|
||||||
|
<DirectoryEntryMenuBottomSheet
|
||||||
|
bind:isOpen={isDirectoryEntryMenuBottomSheetOpen}
|
||||||
|
bind:selectedEntry
|
||||||
|
onRenameClick={() => {
|
||||||
|
isDirectoryEntryMenuBottomSheetOpen = false;
|
||||||
|
isRenameDirectoryEntryModalOpen = true;
|
||||||
|
}}
|
||||||
|
onDeleteClick={() => {
|
||||||
|
isDirectoryEntryMenuBottomSheetOpen = false;
|
||||||
|
isDeleteDirectoryEntryModalOpen = true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<RenameDirectoryEntryModal bind:isOpen={isRenameDirectoryEntryModalOpen} bind:selectedEntry />
|
||||||
|
<DeleteDirectoryEntryModal bind:isOpen={isDeleteDirectoryEntryModalOpen} bind:selectedEntry />
|
||||||
|
|||||||
@@ -6,23 +6,23 @@
|
|||||||
import IconUploadFile from "~icons/material-symbols/upload-file";
|
import IconUploadFile from "~icons/material-symbols/upload-file";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onDirectoryCreate: () => void;
|
onDirectoryCreateClick: () => void;
|
||||||
onFileUpload: () => void;
|
onFileUploadClick: () => void;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { onDirectoryCreate, onFileUpload, isOpen = $bindable() }: Props = $props();
|
let { onDirectoryCreateClick, onFileUploadClick, isOpen = $bindable() }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<BottomSheet bind:isOpen>
|
<BottomSheet bind:isOpen>
|
||||||
<div class="flex w-full flex-col py-4">
|
<div class="flex w-full flex-col py-4">
|
||||||
<EntryButton onclick={onDirectoryCreate}>
|
<EntryButton onclick={onDirectoryCreateClick}>
|
||||||
<div class="flex h-12 items-center justify-center gap-x-4">
|
<div class="flex h-12 items-center justify-center gap-x-4">
|
||||||
<IconCreateNewFolder class="text-2xl text-yellow-500" />
|
<IconCreateNewFolder class="text-2xl text-yellow-500" />
|
||||||
<p class="font-medium">폴더 만들기</p>
|
<p class="font-medium">폴더 만들기</p>
|
||||||
</div>
|
</div>
|
||||||
</EntryButton>
|
</EntryButton>
|
||||||
<EntryButton onclick={onFileUpload}>
|
<EntryButton onclick={onFileUploadClick}>
|
||||||
<div class="flex h-12 items-center justify-center gap-x-4">
|
<div class="flex h-12 items-center justify-center gap-x-4">
|
||||||
<IconUploadFile class="text-2xl text-blue-400" />
|
<IconUploadFile class="text-2xl text-blue-400" />
|
||||||
<p class="font-medium">파일 업로드</p>
|
<p class="font-medium">파일 업로드</p>
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Modal } from "$lib/components";
|
||||||
|
import { Button } from "$lib/components/buttons";
|
||||||
|
import type { SelectedDiretoryEntry } from "./+page.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isOpen: boolean;
|
||||||
|
selectedEntry: SelectedDiretoryEntry | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { isOpen = $bindable(), selectedEntry = $bindable() }: Props = $props();
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
isOpen = false;
|
||||||
|
selectedEntry = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteEntry = () => {
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal bind:isOpen onclose={closeModal}>
|
||||||
|
{#if selectedEntry}
|
||||||
|
{@const { type, name } = selectedEntry}
|
||||||
|
{@const nameShort = name.length > 20 ? `${name.slice(0, 20)}...` : name}
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<p class="break-keep text-xl font-bold">
|
||||||
|
{#if type === "directory"}
|
||||||
|
'{nameShort}' 폴더를 삭제할까요?
|
||||||
|
{:else}
|
||||||
|
'{nameShort}' 파일을 삭제할까요?
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
<p class="break-keep">
|
||||||
|
{#if type === "directory"}
|
||||||
|
삭제한 폴더는 복구할 수 없어요. <br />
|
||||||
|
폴더 안의 모든 파일과 폴더도 함께 삭제돼요.
|
||||||
|
{:else}
|
||||||
|
삭제한 파일은 복구할 수 없어요.
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button
|
||||||
|
color="gray"
|
||||||
|
onclick={() => {
|
||||||
|
isOpen = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
아니요
|
||||||
|
</Button>
|
||||||
|
<Button onclick={deleteEntry}>삭제할게요</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Modal>
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
|
||||||
|
|
||||||
import IconFolder from "~icons/material-symbols/folder";
|
import IconFolder from "~icons/material-symbols/folder";
|
||||||
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 {
|
||||||
id: number;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
onclick: () => void;
|
||||||
|
onOpenMenuClick: () => void;
|
||||||
type: "directory" | "file";
|
type: "directory" | "file";
|
||||||
}
|
}
|
||||||
|
|
||||||
let { id, name, type }: Props = $props();
|
let { name, onclick, onOpenMenuClick, type }: Props = $props();
|
||||||
|
|
||||||
const open = () => {
|
const openMenu = (e: Event) => {
|
||||||
|
e.stopPropagation();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
goto(`/${type}/${id}`);
|
onOpenMenuClick();
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- 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={open} class="h-12 w-full rounded-xl">
|
<div id="button" onclick={() => setTimeout(onclick, 100)} 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">
|
||||||
{#if type === "directory"}
|
{#if type === "directory"}
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
id="open-menu"
|
id="open-menu"
|
||||||
onclick={(e) => e.stopPropagation()}
|
onclick={openMenu}
|
||||||
class="flex-shrink-0 rounded-full p-1 active:bg-gray-100"
|
class="flex-shrink-0 rounded-full p-1 active:bg-gray-100"
|
||||||
>
|
>
|
||||||
<IconMoreVert class="text-lg transition active:scale-95" />
|
<IconMoreVert class="text-lg transition active:scale-95" />
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { BottomSheet } from "$lib/components";
|
||||||
|
import { EntryButton } from "$lib/components/buttons";
|
||||||
|
import type { SelectedDiretoryEntry } from "./+page.svelte";
|
||||||
|
|
||||||
|
import IconFolder from "~icons/material-symbols/folder";
|
||||||
|
import IconDraft from "~icons/material-symbols/draft";
|
||||||
|
import IconEdit from "~icons/material-symbols/edit";
|
||||||
|
import IconDelete from "~icons/material-symbols/delete";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onRenameClick: () => void;
|
||||||
|
onDeleteClick: () => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
selectedEntry: SelectedDiretoryEntry | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
onRenameClick,
|
||||||
|
onDeleteClick,
|
||||||
|
isOpen = $bindable(),
|
||||||
|
selectedEntry = $bindable(),
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
const closeBottomSheet = () => {
|
||||||
|
isOpen = false;
|
||||||
|
selectedEntry = undefined;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<BottomSheet bind:isOpen onclose={closeBottomSheet}>
|
||||||
|
<div class="flex w-full flex-col py-4">
|
||||||
|
{#if selectedEntry}
|
||||||
|
{@const { type, name } = selectedEntry}
|
||||||
|
<div class="flex h-12 items-center gap-x-4 p-2">
|
||||||
|
<div class="flex-shrink-0 text-lg">
|
||||||
|
{#if type === "directory"}
|
||||||
|
<IconFolder />
|
||||||
|
{:else if type === "file"}
|
||||||
|
<IconDraft class="text-blue-400" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<p title={name} class="flex-grow truncate font-semibold">
|
||||||
|
{name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="my-2 h-px w-full bg-gray-200"></div>
|
||||||
|
{/if}
|
||||||
|
<EntryButton onclick={onRenameClick}>
|
||||||
|
<div class="flex h-8 items-center justify-center gap-x-4">
|
||||||
|
<IconEdit class="text-lg" />
|
||||||
|
<p class="font-medium">이름 바꾸기</p>
|
||||||
|
</div>
|
||||||
|
</EntryButton>
|
||||||
|
<EntryButton onclick={onDeleteClick}>
|
||||||
|
<div class="flex h-8 items-center justify-center gap-x-4 text-red-500">
|
||||||
|
<IconDelete class="text-lg" />
|
||||||
|
<p class="font-medium">삭제하기</p>
|
||||||
|
</div>
|
||||||
|
</EntryButton>
|
||||||
|
</div>
|
||||||
|
</BottomSheet>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Modal } from "$lib/components";
|
||||||
|
import { Button } from "$lib/components/buttons";
|
||||||
|
import { TextInput } from "$lib/components/inputs";
|
||||||
|
import type { SelectedDiretoryEntry } from "./+page.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isOpen: boolean;
|
||||||
|
selectedEntry: SelectedDiretoryEntry | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { isOpen = $bindable(), selectedEntry = $bindable() }: Props = $props();
|
||||||
|
|
||||||
|
let name = $state("");
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
name = "";
|
||||||
|
isOpen = false;
|
||||||
|
selectedEntry = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renameEntry = () => {
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (selectedEntry) {
|
||||||
|
name = selectedEntry.name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal bind:isOpen onclose={closeModal}>
|
||||||
|
<div class="flex flex-col px-1">
|
||||||
|
<p class="text-xl font-bold">이름 바꾸기</p>
|
||||||
|
<div class="my-4 flex w-full">
|
||||||
|
<TextInput bind:value={name} placeholder="이름" />
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 flex gap-2">
|
||||||
|
<Button color="gray" onclick={closeModal}>닫기</Button>
|
||||||
|
<Button onclick={renameEntry}>바꾸기</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
Reference in New Issue
Block a user