파일을 업로드하는 중에, 해당되는 디렉터리에 업로드 Progress를 표시하도록 구현

This commit is contained in:
static
2025-01-16 04:23:31 +09:00
parent 937c4e2453
commit cc9d355ac1
3 changed files with 143 additions and 25 deletions

View File

@@ -1,11 +1,18 @@
<script lang="ts">
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 { 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 SubDirectory from "./SubDirectory.svelte";
import { SortBy, sortEntries } from "./service";
import UploadingFile from "./UploadingFile.svelte";
import type { SelectedDirectoryEntry } from "../service";
interface Props {
@@ -17,37 +24,102 @@
let { info, onEntryClick, onEntryMenuClick, sortBy = SortBy.NAME_ASC }: Props = $props();
let subDirectoryInfos: Writable<DirectoryInfo | null>[] = $state([]);
let fileInfos: Writable<FileInfo | null>[] = $state([]);
interface DirectoryEntry {
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(() => {
// TODO: Fix duplicated requests
subDirectoryInfos = info.subDirectoryIds.map((id) =>
getDirectoryInfo(id, $masterKeyStore?.get(1)?.key!),
subDirectories = info.subDirectoryIds.map((id) => {
const info = getDirectoryInfo(id, $masterKeyStore?.get(1)?.key!);
return { name: get(info)?.name, info };
});
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,
}),
),
);
fileInfos = info.fileIds.map((id) => getFileInfo(id, $masterKeyStore?.get(1)?.key!));
const sort = () => {
sortEntries(subDirectoryInfos, sortBy);
sortEntries(fileInfos, sortBy);
sortEntries(subDirectories, sortBy);
sortEntries(files, sortBy);
};
sort();
return untrack(() => {
const unsubscribes = subDirectoryInfos
.map((subDirectoryInfo) => subDirectoryInfo.subscribe(sort))
.concat(fileInfos.map((fileInfo) => fileInfo.subscribe(sort)));
const unsubscribes = subDirectories
.map((subDirectory) =>
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());
});
});
</script>
{#if info.subDirectoryIds.length + info.fileIds.length > 0}
{#if subDirectories.length + files.length > 0}
<div class="pb-[4.5rem]">
{#each subDirectoryInfos as subDirectory}
<SubDirectory info={subDirectory} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
{#each subDirectories as { info }}
<SubDirectory {info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
{/each}
{#each fileInfos as file}
<File info={file} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
{#each files as file}
{#if file.type === "file"}
<File info={file.info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
{:else}
<UploadingFile info={file.info} />
{/if}
{/each}
</div>
{:else}

View File

@@ -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}

View File

@@ -1,5 +1,4 @@
import { get, type Writable } from "svelte/store";
import type { DirectoryInfo, FileInfo } from "$lib/stores";
import { formatFileSize } from "$lib/modules/util";
export { formatDateTime } from "$lib/modules/util";
@@ -8,17 +7,19 @@ export enum SortBy {
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) => {
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;
};
const sortByNameDesc: SortFunc = (a, b) => -sortByNameAsc(a, b);
export const sortEntries = <T extends DirectoryInfo | FileInfo>(
entries: Writable<T | null>[],
export const sortEntries = <T extends { name?: string }>(
entries: T[],
sortBy: SortBy = SortBy.NAME_ASC,
) => {
let sortFunc: SortFunc;
@@ -28,5 +29,13 @@ export const sortEntries = <T extends DirectoryInfo | FileInfo>(
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`;
};