mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-12 21:08:46 +00:00
파일/폴더 삭제 및 이름 변경 구현 완료
This commit is contained in:
@@ -20,7 +20,7 @@ export const callGetApi = async (input: RequestInfo, fetchInternal?: typeof fetc
|
||||
|
||||
export const callPostApi = async <T>(
|
||||
input: RequestInfo,
|
||||
payload: T,
|
||||
payload?: T,
|
||||
fetchInternal?: typeof fetch,
|
||||
) => {
|
||||
return await callApi(
|
||||
@@ -28,7 +28,7 @@ export const callPostApi = async <T>(
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
body: payload ? JSON.stringify(payload) : undefined,
|
||||
},
|
||||
fetchInternal,
|
||||
);
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
<script module lang="ts">
|
||||
export interface SelectedDirectoryEntry {
|
||||
type: "directory" | "file";
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { TopBar } from "$lib/components";
|
||||
@@ -22,6 +14,9 @@
|
||||
decryptFileMetadata,
|
||||
requestDirectoryCreation,
|
||||
requestFileUpload,
|
||||
requestDirectoryEntryRename,
|
||||
requestDirectoryEntryDeletion,
|
||||
type SelectedDirectoryEntry,
|
||||
} from "./service";
|
||||
|
||||
import IconAdd from "~icons/material-symbols/add";
|
||||
@@ -114,12 +109,12 @@
|
||||
<div class="my-4 pb-[4.5rem]">
|
||||
{#if subDirectories}
|
||||
{#await subDirectories then subDirectories}
|
||||
{#each subDirectories as { id, name }}
|
||||
{#each subDirectories as { id, dataKey, name }}
|
||||
<DirectoryEntry
|
||||
{name}
|
||||
onclick={() => goto(`/directory/${id}`)}
|
||||
onOpenMenuClick={() => {
|
||||
selectedEntry = { type: "directory", id, name };
|
||||
selectedEntry = { type: "directory", id, dataKey, name };
|
||||
isDirectoryEntryMenuBottomSheetOpen = true;
|
||||
}}
|
||||
type="directory"
|
||||
@@ -129,12 +124,12 @@
|
||||
{/if}
|
||||
{#if files}
|
||||
{#await files then files}
|
||||
{#each files as { id, name }}
|
||||
{#each files as { id, dataKey, name }}
|
||||
<DirectoryEntry
|
||||
{name}
|
||||
onclick={() => goto(`/file/${id}`)}
|
||||
onOpenMenuClick={() => {
|
||||
selectedEntry = { type: "file", id, name };
|
||||
selectedEntry = { type: "file", id, dataKey, name };
|
||||
isDirectoryEntryMenuBottomSheetOpen = true;
|
||||
}}
|
||||
type="file"
|
||||
@@ -177,5 +172,19 @@
|
||||
isDeleteDirectoryEntryModalOpen = true;
|
||||
}}
|
||||
/>
|
||||
<RenameDirectoryEntryModal bind:isOpen={isRenameDirectoryEntryModalOpen} bind:selectedEntry />
|
||||
<DeleteDirectoryEntryModal bind:isOpen={isDeleteDirectoryEntryModalOpen} bind:selectedEntry />
|
||||
<RenameDirectoryEntryModal
|
||||
bind:isOpen={isRenameDirectoryEntryModalOpen}
|
||||
bind:selectedEntry
|
||||
onRenameClick={async (newName) => {
|
||||
await requestDirectoryEntryRename(selectedEntry!, newName);
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
<DeleteDirectoryEntryModal
|
||||
bind:isOpen={isDeleteDirectoryEntryModalOpen}
|
||||
bind:selectedEntry
|
||||
onDeleteClick={async () => {
|
||||
await requestDirectoryEntryDeletion(selectedEntry!);
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from "$lib/components";
|
||||
import { Button } from "$lib/components/buttons";
|
||||
import type { SelectedDirectoryEntry } from "./+page.svelte";
|
||||
import type { SelectedDirectoryEntry } from "./service";
|
||||
|
||||
interface Props {
|
||||
onDeleteClick: () => Promise<boolean>;
|
||||
isOpen: boolean;
|
||||
selectedEntry: SelectedDirectoryEntry | undefined;
|
||||
}
|
||||
|
||||
let { isOpen = $bindable(), selectedEntry = $bindable() }: Props = $props();
|
||||
let { onDeleteClick, isOpen = $bindable(), selectedEntry = $bindable() }: Props = $props();
|
||||
|
||||
const closeModal = () => {
|
||||
isOpen = false;
|
||||
selectedEntry = undefined;
|
||||
};
|
||||
|
||||
const deleteEntry = () => {
|
||||
// TODO
|
||||
const deleteEntry = async () => {
|
||||
// TODO: Validation
|
||||
|
||||
closeModal();
|
||||
if (await onDeleteClick()) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { BottomSheet } from "$lib/components";
|
||||
import { EntryButton } from "$lib/components/buttons";
|
||||
import type { SelectedDirectoryEntry } from "./+page.svelte";
|
||||
import type { SelectedDirectoryEntry } from "./service";
|
||||
|
||||
import IconFolder from "~icons/material-symbols/folder";
|
||||
import IconDraft from "~icons/material-symbols/draft";
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
import { Modal } from "$lib/components";
|
||||
import { Button } from "$lib/components/buttons";
|
||||
import { TextInput } from "$lib/components/inputs";
|
||||
import type { SelectedDirectoryEntry } from "./+page.svelte";
|
||||
import type { SelectedDirectoryEntry } from "./service";
|
||||
|
||||
interface Props {
|
||||
onRenameClick: (newName: string) => Promise<boolean>;
|
||||
isOpen: boolean;
|
||||
selectedEntry: SelectedDirectoryEntry | undefined;
|
||||
}
|
||||
|
||||
let { isOpen = $bindable(), selectedEntry = $bindable() }: Props = $props();
|
||||
let { onRenameClick, isOpen = $bindable(), selectedEntry = $bindable() }: Props = $props();
|
||||
|
||||
let name = $state("");
|
||||
|
||||
@@ -19,10 +20,12 @@
|
||||
selectedEntry = undefined;
|
||||
};
|
||||
|
||||
const renameEntry = () => {
|
||||
// TODO
|
||||
const renameEntry = async () => {
|
||||
// TODO: Validation
|
||||
|
||||
closeModal();
|
||||
if (await onRenameClick(name)) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
$effect(() => {
|
||||
|
||||
@@ -9,20 +9,30 @@ import {
|
||||
decryptString,
|
||||
} from "$lib/modules/crypto";
|
||||
import type {
|
||||
DirectoryRenameRequest,
|
||||
DirectoryInfoResponse,
|
||||
DirectoryCreateRequest,
|
||||
FileRenameRequest,
|
||||
FileUploadRequest,
|
||||
} from "$lib/server/schemas";
|
||||
import type { MasterKey } from "$lib/stores";
|
||||
|
||||
export { decryptFileMetadata } from "$lib/services/file";
|
||||
|
||||
export interface SelectedDirectoryEntry {
|
||||
type: "directory" | "file";
|
||||
id: number;
|
||||
dataKey: CryptoKey;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const decryptDirectoryMetadata = async (
|
||||
metadata: NonNullable<DirectoryInfoResponse["metadata"]>,
|
||||
masterKey: CryptoKey,
|
||||
) => {
|
||||
const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
|
||||
return {
|
||||
dataKey,
|
||||
name: await decryptString(metadata.name, metadata.nameIv, dataKey),
|
||||
};
|
||||
};
|
||||
@@ -71,3 +81,26 @@ export const requestFileUpload = async (
|
||||
xhr.open("POST", "/api/file/upload");
|
||||
xhr.send(form);
|
||||
};
|
||||
|
||||
export const requestDirectoryEntryRename = async (
|
||||
entry: SelectedDirectoryEntry,
|
||||
newName: string,
|
||||
) => {
|
||||
const newNameEncrypted = await encryptString(newName, entry.dataKey);
|
||||
|
||||
if (entry.type === "directory") {
|
||||
await callPostApi<DirectoryRenameRequest>(`/api/directory/${entry.id}/rename`, {
|
||||
name: newNameEncrypted.ciphertext,
|
||||
nameIv: newNameEncrypted.iv,
|
||||
});
|
||||
} else {
|
||||
await callPostApi<FileRenameRequest>(`/api/file/${entry.id}/rename`, {
|
||||
name: newNameEncrypted.ciphertext,
|
||||
nameIv: newNameEncrypted.iv,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const requestDirectoryEntryDeletion = async (entry: SelectedDirectoryEntry) => {
|
||||
await callPostApi(`/api/${entry.type}/${entry.id}/delete`);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user