mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-14 22:08:45 +00:00
파일을 업로드하는 중에, 해당되는 디렉터리에 업로드 Progress를 표시하도록 구현
This commit is contained in:
@@ -1,11 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
import { get, type Writable } from "svelte/store";
|
||||||
import { getDirectoryInfo, getFileInfo } from "$lib/modules/file";
|
import { getDirectoryInfo, getFileInfo } from "$lib/modules/file";
|
||||||
import { masterKeyStore, type DirectoryInfo, type FileInfo } from "$lib/stores";
|
import {
|
||||||
|
fileUploadStatusStore,
|
||||||
|
masterKeyStore,
|
||||||
|
type DirectoryInfo,
|
||||||
|
type FileInfo,
|
||||||
|
type FileUploadStatus,
|
||||||
|
} from "$lib/stores";
|
||||||
import File from "./File.svelte";
|
import File from "./File.svelte";
|
||||||
import SubDirectory from "./SubDirectory.svelte";
|
import SubDirectory from "./SubDirectory.svelte";
|
||||||
import { SortBy, sortEntries } from "./service";
|
import { SortBy, sortEntries } from "./service";
|
||||||
|
import UploadingFile from "./UploadingFile.svelte";
|
||||||
import type { SelectedDirectoryEntry } from "../service";
|
import type { SelectedDirectoryEntry } from "../service";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -17,37 +24,102 @@
|
|||||||
|
|
||||||
let { info, onEntryClick, onEntryMenuClick, sortBy = SortBy.NAME_ASC }: Props = $props();
|
let { info, onEntryClick, onEntryMenuClick, sortBy = SortBy.NAME_ASC }: Props = $props();
|
||||||
|
|
||||||
let subDirectoryInfos: Writable<DirectoryInfo | null>[] = $state([]);
|
interface DirectoryEntry {
|
||||||
let fileInfos: Writable<FileInfo | null>[] = $state([]);
|
name?: string;
|
||||||
|
info: Writable<DirectoryInfo | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileEntry =
|
||||||
|
| {
|
||||||
|
type: "file";
|
||||||
|
name?: string;
|
||||||
|
info: Writable<FileInfo | null>;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "uploading-file";
|
||||||
|
name: string;
|
||||||
|
info: Writable<FileUploadStatus>;
|
||||||
|
};
|
||||||
|
|
||||||
|
let subDirectories: DirectoryEntry[] = $state([]);
|
||||||
|
let files: FileEntry[] = $state([]);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// TODO: Fix duplicated requests
|
// TODO: Fix duplicated requests
|
||||||
|
|
||||||
subDirectoryInfos = info.subDirectoryIds.map((id) =>
|
subDirectories = info.subDirectoryIds.map((id) => {
|
||||||
getDirectoryInfo(id, $masterKeyStore?.get(1)?.key!),
|
const info = getDirectoryInfo(id, $masterKeyStore?.get(1)?.key!);
|
||||||
);
|
return { name: get(info)?.name, info };
|
||||||
fileInfos = info.fileIds.map((id) => getFileInfo(id, $masterKeyStore?.get(1)?.key!));
|
});
|
||||||
|
files = info.fileIds
|
||||||
|
.map((id): FileEntry => {
|
||||||
|
const info = getFileInfo(id, $masterKeyStore?.get(1)?.key!);
|
||||||
|
return {
|
||||||
|
type: "file",
|
||||||
|
name: get(info)?.name,
|
||||||
|
info,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.concat(
|
||||||
|
$fileUploadStatusStore
|
||||||
|
.filter((statusStore) => {
|
||||||
|
const status = get(statusStore);
|
||||||
|
return (
|
||||||
|
status.parentId === info.id &&
|
||||||
|
status.status !== "uploaded" &&
|
||||||
|
status.status !== "canceled" &&
|
||||||
|
status.status !== "error"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map(
|
||||||
|
(status): FileEntry => ({
|
||||||
|
type: "uploading-file",
|
||||||
|
name: get(status).name,
|
||||||
|
info: status,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const sort = () => {
|
const sort = () => {
|
||||||
sortEntries(subDirectoryInfos, sortBy);
|
sortEntries(subDirectories, sortBy);
|
||||||
sortEntries(fileInfos, sortBy);
|
sortEntries(files, sortBy);
|
||||||
};
|
};
|
||||||
|
sort();
|
||||||
|
|
||||||
return untrack(() => {
|
return untrack(() => {
|
||||||
const unsubscribes = subDirectoryInfos
|
const unsubscribes = subDirectories
|
||||||
.map((subDirectoryInfo) => subDirectoryInfo.subscribe(sort))
|
.map((subDirectory) =>
|
||||||
.concat(fileInfos.map((fileInfo) => fileInfo.subscribe(sort)));
|
subDirectory.info.subscribe((value) => {
|
||||||
|
if (subDirectory.name === value?.name) return;
|
||||||
|
subDirectory.name = value?.name;
|
||||||
|
sort();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.concat(
|
||||||
|
files.map((file) =>
|
||||||
|
file.info.subscribe((value) => {
|
||||||
|
if (file.name === value?.name) return;
|
||||||
|
file.name = value?.name;
|
||||||
|
sort();
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
return () => unsubscribes.forEach((unsubscribe) => unsubscribe());
|
return () => unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if info.subDirectoryIds.length + info.fileIds.length > 0}
|
{#if subDirectories.length + files.length > 0}
|
||||||
<div class="pb-[4.5rem]">
|
<div class="pb-[4.5rem]">
|
||||||
{#each subDirectoryInfos as subDirectory}
|
{#each subDirectories as { info }}
|
||||||
<SubDirectory info={subDirectory} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
<SubDirectory {info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
||||||
{/each}
|
{/each}
|
||||||
{#each fileInfos as file}
|
{#each files as file}
|
||||||
<File info={file} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
{#if file.type === "file"}
|
||||||
|
<File info={file.info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
||||||
|
{:else}
|
||||||
|
<UploadingFile info={file.info} />
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Writable } from "svelte/store";
|
||||||
|
import type { FileUploadStatus } from "$lib/stores";
|
||||||
|
import { formatUploadProgress, formatUploadRate } from "./service";
|
||||||
|
|
||||||
|
import IconDraft from "~icons/material-symbols/draft";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
info: Writable<FileUploadStatus>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { info }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $info.status !== "uploaded" && $info.status !== "canceled" && $info.status !== "error"}
|
||||||
|
<div class="flex h-14 items-center gap-x-4 p-2">
|
||||||
|
<div class="flex-shrink-0 text-lg">
|
||||||
|
<IconDraft class="text-gray-600" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-grow flex-col overflow-hidden">
|
||||||
|
<p title={$info.name} class="truncate font-medium text-gray-800">
|
||||||
|
{$info.name}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-800">
|
||||||
|
{#if $info.status === "encryption-pending"}
|
||||||
|
준비 중
|
||||||
|
{:else if $info.status === "encrypting"}
|
||||||
|
암호화하는 중
|
||||||
|
{:else if $info.status === "upload-pending"}
|
||||||
|
업로드를 기다리는 중
|
||||||
|
{:else if $info.status === "uploading"}
|
||||||
|
전송됨 {formatUploadProgress($info.progress)} · {formatUploadRate($info.rate)}
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { get, type Writable } from "svelte/store";
|
import { formatFileSize } from "$lib/modules/util";
|
||||||
import type { DirectoryInfo, FileInfo } from "$lib/stores";
|
|
||||||
|
|
||||||
export { formatDateTime } from "$lib/modules/util";
|
export { formatDateTime } from "$lib/modules/util";
|
||||||
|
|
||||||
@@ -8,17 +7,19 @@ export enum SortBy {
|
|||||||
NAME_DESC,
|
NAME_DESC,
|
||||||
}
|
}
|
||||||
|
|
||||||
type SortFunc = (a: DirectoryInfo | FileInfo | null, b: DirectoryInfo | FileInfo | null) => number;
|
type SortFunc = (a?: string, b?: string) => number;
|
||||||
|
|
||||||
const sortByNameAsc: SortFunc = (a, b) => {
|
const sortByNameAsc: SortFunc = (a, b) => {
|
||||||
if (a && b) return a.name!.localeCompare(b.name!);
|
if (a && b) return a.localeCompare(b);
|
||||||
|
if (a) return -1;
|
||||||
|
if (b) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortByNameDesc: SortFunc = (a, b) => -sortByNameAsc(a, b);
|
const sortByNameDesc: SortFunc = (a, b) => -sortByNameAsc(a, b);
|
||||||
|
|
||||||
export const sortEntries = <T extends DirectoryInfo | FileInfo>(
|
export const sortEntries = <T extends { name?: string }>(
|
||||||
entries: Writable<T | null>[],
|
entries: T[],
|
||||||
sortBy: SortBy = SortBy.NAME_ASC,
|
sortBy: SortBy = SortBy.NAME_ASC,
|
||||||
) => {
|
) => {
|
||||||
let sortFunc: SortFunc;
|
let sortFunc: SortFunc;
|
||||||
@@ -28,5 +29,13 @@ export const sortEntries = <T extends DirectoryInfo | FileInfo>(
|
|||||||
sortFunc = sortByNameDesc;
|
sortFunc = sortByNameDesc;
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.sort((a, b) => sortFunc(get(a), get(b)));
|
entries.sort((a, b) => sortFunc(a.name, b.name));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatUploadProgress = (progress?: number) => {
|
||||||
|
return `${Math.floor((progress ?? 0) * 100)}%`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatUploadRate = (rate?: number) => {
|
||||||
|
return `${formatFileSize((rate ?? 0) / 8)}/s`;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user