mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-04 08:06:56 +00:00
파일 페이지에 다운로드 및 폴더로 이동 메뉴 추가
This commit is contained in:
@@ -40,6 +40,7 @@ export type DirectoryInfo =
|
|||||||
|
|
||||||
export interface FileInfo {
|
export interface FileInfo {
|
||||||
id: number;
|
id: number;
|
||||||
|
parentId: DirectoryId;
|
||||||
dataKey?: CryptoKey;
|
dataKey?: CryptoKey;
|
||||||
dataKeyVersion?: Date;
|
dataKeyVersion?: Date;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
@@ -199,6 +200,7 @@ const fetchFileInfoFromServer = async (
|
|||||||
|
|
||||||
info.set({
|
info.set({
|
||||||
id,
|
id,
|
||||||
|
parentId: metadata.parent,
|
||||||
dataKey,
|
dataKey,
|
||||||
dataKeyVersion: new Date(metadata.dekVersion),
|
dataKeyVersion: new Date(metadata.dekVersion),
|
||||||
contentType: metadata.contentType,
|
contentType: metadata.contentType,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { get, type Writable } from "svelte/store";
|
import { get, type Writable } from "svelte/store";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
|
import { page } from "$app/state";
|
||||||
import { FullscreenDiv } from "$lib/components/atoms";
|
import { FullscreenDiv } from "$lib/components/atoms";
|
||||||
import { Categories, IconEntryButton, TopBar } from "$lib/components/molecules";
|
import { Categories, IconEntryButton, TopBar } from "$lib/components/molecules";
|
||||||
import {
|
import {
|
||||||
@@ -21,7 +22,9 @@
|
|||||||
requestThumbnailUpload,
|
requestThumbnailUpload,
|
||||||
requestFileAdditionToCategory,
|
requestFileAdditionToCategory,
|
||||||
} from "./service";
|
} from "./service";
|
||||||
|
import TopBarMenu from "./TopBarMenu.svelte";
|
||||||
|
|
||||||
|
import IconMoreVert from "~icons/material-symbols/more-vert";
|
||||||
import IconCamera from "~icons/material-symbols/camera";
|
import IconCamera from "~icons/material-symbols/camera";
|
||||||
import IconClose from "~icons/material-symbols/close";
|
import IconClose from "~icons/material-symbols/close";
|
||||||
import IconAddCircle from "~icons/material-symbols/add-circle";
|
import IconAddCircle from "~icons/material-symbols/add-circle";
|
||||||
@@ -31,6 +34,7 @@
|
|||||||
let info: Writable<FileInfo | null> | undefined = $state();
|
let info: Writable<FileInfo | null> | undefined = $state();
|
||||||
let categories: Writable<CategoryInfo | null>[] = $state([]);
|
let categories: Writable<CategoryInfo | null>[] = $state([]);
|
||||||
|
|
||||||
|
let isMenuOpen = $state(false);
|
||||||
let isAddToCategoryBottomSheetOpen = $state(false);
|
let isAddToCategoryBottomSheetOpen = $state(false);
|
||||||
|
|
||||||
let downloadStatus = $derived(
|
let downloadStatus = $derived(
|
||||||
@@ -42,30 +46,26 @@
|
|||||||
|
|
||||||
let isDownloadRequested = $state(false);
|
let isDownloadRequested = $state(false);
|
||||||
let viewerType: "image" | "video" | undefined = $state();
|
let viewerType: "image" | "video" | undefined = $state();
|
||||||
|
let fileBlob: Blob | undefined = $state();
|
||||||
let fileBlobUrl: string | undefined = $state();
|
let fileBlobUrl: string | undefined = $state();
|
||||||
let heicBlob: Blob | undefined = $state();
|
|
||||||
let videoElement: HTMLVideoElement | undefined = $state();
|
let videoElement: HTMLVideoElement | undefined = $state();
|
||||||
|
|
||||||
const updateViewer = async (buffer: ArrayBuffer, contentType: string) => {
|
const updateViewer = async (buffer: ArrayBuffer, contentType: string) => {
|
||||||
const fileBlob = new Blob([buffer], { type: contentType });
|
fileBlob = new Blob([buffer], { type: contentType });
|
||||||
if (viewerType) {
|
|
||||||
fileBlobUrl = URL.createObjectURL(fileBlob);
|
fileBlobUrl = URL.createObjectURL(fileBlob);
|
||||||
heicBlob = contentType === "image/heic" ? fileBlob : undefined;
|
|
||||||
}
|
|
||||||
return fileBlob;
|
return fileBlob;
|
||||||
};
|
};
|
||||||
|
|
||||||
const convertHeicToJpeg = async () => {
|
const convertHeicToJpeg = async () => {
|
||||||
if (!heicBlob) return;
|
if (fileBlob?.type !== "image/heic") return;
|
||||||
|
|
||||||
URL.revokeObjectURL(fileBlobUrl!);
|
URL.revokeObjectURL(fileBlobUrl!);
|
||||||
fileBlobUrl = undefined;
|
fileBlobUrl = undefined;
|
||||||
|
|
||||||
const { default: heic2any } = await import("heic2any");
|
const { default: heic2any } = await import("heic2any");
|
||||||
fileBlobUrl = URL.createObjectURL(
|
fileBlobUrl = URL.createObjectURL(
|
||||||
(await heic2any({ blob: heicBlob, toType: "image/jpeg" })) as Blob,
|
(await heic2any({ blob: fileBlob, toType: "image/jpeg" })) as Blob,
|
||||||
);
|
);
|
||||||
heicBlob = undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateThumbnail = async (dataKey: CryptoKey, dataKeyVersion: Date) => {
|
const updateThumbnail = async (dataKey: CryptoKey, dataKeyVersion: Date) => {
|
||||||
@@ -133,7 +133,24 @@
|
|||||||
<title>파일</title>
|
<title>파일</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<TopBar title={$info?.name} />
|
<TopBar title={$info?.name}>
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
|
<div onclick={(e) => e.stopPropagation()}>
|
||||||
|
<button
|
||||||
|
onclick={() => (isMenuOpen = !isMenuOpen)}
|
||||||
|
class="w-[2.3rem] flex-shrink-0 rounded-full p-1 active:bg-black active:bg-opacity-[0.04]"
|
||||||
|
>
|
||||||
|
<IconMoreVert class="text-2xl" />
|
||||||
|
</button>
|
||||||
|
<TopBarMenu
|
||||||
|
bind:isOpen={isMenuOpen}
|
||||||
|
directoryId={page.url.searchParams.get("from") === "category" ? $info?.parentId : undefined}
|
||||||
|
{fileBlob}
|
||||||
|
filename={$info?.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TopBar>
|
||||||
<FullscreenDiv>
|
<FullscreenDiv>
|
||||||
<div class="space-y-4 pb-4">
|
<div class="space-y-4 pb-4">
|
||||||
<DownloadStatus status={downloadStatus} />
|
<DownloadStatus status={downloadStatus} />
|
||||||
|
|||||||
58
src/routes/(fullscreen)/file/[id]/TopBarMenu.svelte
Normal file
58
src/routes/(fullscreen)/file/[id]/TopBarMenu.svelte
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import FileSaver from "file-saver";
|
||||||
|
import type { Component } from "svelte";
|
||||||
|
import type { SvelteHTMLElements } from "svelte/elements";
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
|
import { goto } from "$app/navigation";
|
||||||
|
|
||||||
|
import IconFolderOpen from "~icons/material-symbols/folder-open";
|
||||||
|
import IconCloudDownload from "~icons/material-symbols/cloud-download";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
directoryId?: "root" | number;
|
||||||
|
fileBlob?: Blob;
|
||||||
|
filename?: string;
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { directoryId, fileBlob, filename, isOpen = $bindable() }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window onclick={() => (isOpen = false)} />
|
||||||
|
|
||||||
|
{#if isOpen && (directoryId || fileBlob)}
|
||||||
|
<div
|
||||||
|
class="absolute right-2 top-full z-20 space-y-1 rounded-lg bg-white px-1 py-2 shadow-2xl"
|
||||||
|
transition:fly={{ y: -8, duration: 200 }}
|
||||||
|
>
|
||||||
|
<p class="px-3 pt-2 text-sm font-semibold text-gray-600">더보기</p>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
{#snippet menuButton(
|
||||||
|
Icon: Component<SvelteHTMLElements["svg"]>,
|
||||||
|
text: string,
|
||||||
|
onclick: () => void,
|
||||||
|
)}
|
||||||
|
<button {onclick} class="rounded-xl active:bg-gray-100">
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-x-3 px-3 py-2 text-lg text-gray-700 transition active:scale-95"
|
||||||
|
>
|
||||||
|
<Icon />
|
||||||
|
<p class="font-medium">{text}</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#if directoryId}
|
||||||
|
{@render menuButton(IconFolderOpen, "폴더에서 보기", () =>
|
||||||
|
goto(directoryId === "root" ? "/directory" : `/directory/${directoryId}`),
|
||||||
|
)}
|
||||||
|
{/if}
|
||||||
|
{#if fileBlob}
|
||||||
|
{@render menuButton(IconCloudDownload, "다운로드", () => {
|
||||||
|
console.log(filename);
|
||||||
|
FileSaver.saveAs(fileBlob, filename);
|
||||||
|
})}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<Category
|
<Category
|
||||||
bind:isFileRecursive
|
bind:isFileRecursive
|
||||||
info={$info}
|
info={$info}
|
||||||
onFileClick={({ id }) => goto(`/file/${id}`)}
|
onFileClick={({ id }) => goto(`/file/${id}?from=category`)}
|
||||||
onFileRemoveClick={async ({ id }) => {
|
onFileRemoveClick={async ({ id }) => {
|
||||||
await requestFileRemovalFromCategory(id, data.id as number);
|
await requestFileRemovalFromCategory(id, data.id as number);
|
||||||
info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
|
info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
|
||||||
|
|||||||
Reference in New Issue
Block a user