diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index 4a6b8a6..2518c7f 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -174,6 +174,10 @@ export const uploadFile = async ( value.status = "canceled"; return value; }); + fileUploadStatusStore.update((value) => { + value = value.filter((v) => v !== status); + return value; + }); return false; } diff --git a/src/lib/modules/util.ts b/src/lib/modules/util.ts index 31e971b..67e1b3b 100644 --- a/src/lib/modules/util.ts +++ b/src/lib/modules/util.ts @@ -20,3 +20,10 @@ export const formatFileSize = (size: number) => { if (size < 1024 * 1024 * 1024) return `${(size / 1024 / 1024).toFixed(1)} MiB`; return `${(size / 1024 / 1024 / 1024).toFixed(1)} GiB`; }; + +export const formatNetworkSpeed = (speed: number) => { + if (speed < 1000) return `${speed} bps`; + if (speed < 1000 * 1000) return `${(speed / 1000).toFixed(1)} kbps`; + if (speed < 1000 * 1000 * 1000) return `${(speed / 1000 / 1000).toFixed(1)} Mbps`; + return `${(speed / 1000 / 1000 / 1000).toFixed(1)} Gbps`; +}; diff --git a/src/lib/stores/file.ts b/src/lib/stores/file.ts index 4dd2df4..61db95d 100644 --- a/src/lib/stores/file.ts +++ b/src/lib/stores/file.ts @@ -1,4 +1,5 @@ import { writable, type Writable } from "svelte/store"; + export interface FileUploadStatus { name: string; parentId: "root" | number; @@ -34,3 +35,15 @@ export interface FileDownloadStatus { export const fileUploadStatusStore = writable[]>([]); export const fileDownloadStatusStore = writable[]>([]); + +export const isFileUploading = ( + status: FileUploadStatus["status"], +): status is "encryption-pending" | "encrypting" | "upload-pending" | "uploading" => { + return ["encryption-pending", "encrypting", "upload-pending", "uploading"].includes(status); +}; + +export const isFileDownloading = ( + status: FileDownloadStatus["status"], +): status is "download-pending" | "downloading" | "decryption-pending" | "decrypting" => { + return ["download-pending", "downloading", "decryption-pending", "decrypting"].includes(status); +}; diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 8d8e1eb..15c4571 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -87,7 +87,7 @@
- +
{#snippet viewerLoading(message: string)}
diff --git a/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte b/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte index f1b4d89..48a0459 100644 --- a/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte +++ b/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte @@ -1,31 +1,32 @@ -{#if $info && $info.status !== "decrypted" && $info.status !== "canceled" && $info.status !== "error"} +{#if $status && $status.status !== "decrypted" && $status.status !== "canceled" && $status.status !== "error"}

- {#if $info.status === "download-pending"} + {#if $status.status === "download-pending"} 다운로드를 기다리는 중 - {:else if $info.status === "downloading"} + {:else if $status.status === "downloading"} 다운로드하는 중 - {:else if $info.status === "decryption-pending"} + {:else if $status.status === "decryption-pending"} 복호화를 기다리는 중 - {:else if $info.status === "decrypting"} + {:else if $status.status === "decrypting"} 복호화하는 중 {/if}

- {#if $info.status === "downloading"} - 전송됨 {formatDownloadProgress($info.progress)} · {formatDownloadRate($info.rate)} + {#if $status.status === "downloading"} + 전송됨 + {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed($status.rate ?? 0)} {/if}

diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index 32ee666..fcc5ce7 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -1,5 +1,4 @@ import { getFileCache, storeFileCache, downloadFile } from "$lib/modules/file"; -import { formatFileSize } from "$lib/modules/util"; export const requestFileDownload = async ( fileId: number, @@ -13,11 +12,3 @@ export const requestFileDownload = async ( storeFileCache(fileId, fileBuffer); // Intended return fileBuffer; }; - -export const formatDownloadProgress = (progress?: number) => { - return `${Math.floor((progress ?? 0) * 100)}%`; -}; - -export const formatDownloadRate = (rate?: number) => { - return `${formatFileSize((rate ?? 0) / 8)}/s`; -}; diff --git a/src/routes/(fullscreen)/file/downloads/+page.svelte b/src/routes/(fullscreen)/file/downloads/+page.svelte new file mode 100644 index 0000000..a29b147 --- /dev/null +++ b/src/routes/(fullscreen)/file/downloads/+page.svelte @@ -0,0 +1,29 @@ + + + + 진행 중인 다운로드 + + +
+ +
+ {#each downloadingFiles as status} + + {/each} +
+
diff --git a/src/routes/(fullscreen)/file/downloads/File.svelte b/src/routes/(fullscreen)/file/downloads/File.svelte new file mode 100644 index 0000000..670324c --- /dev/null +++ b/src/routes/(fullscreen)/file/downloads/File.svelte @@ -0,0 +1,66 @@ + + +{#if $fileInfo} +
+
+ {#if $status.status === "download-pending"} + + {:else if $status.status === "downloading"} + + {:else if $status.status === "decryption-pending"} + + {:else if $status.status === "decrypting"} + + {:else if $status.status === "decrypted"} + + {:else if $status.status === "error"} + + {/if} +
+
+

+ {$fileInfo.name} +

+

+ {#if $status.status === "download-pending"} + 다운로드를 기다리는 중 + {:else if $status.status === "downloading"} + 전송됨 + {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed($status.rate ?? 0)} + {:else if $status.status === "decryption-pending"} + 복호화를 기다리는 중 + {:else if $status.status === "decrypting"} + 복호화하는 중 + {:else if $status.status === "decrypted"} + 다운로드 완료 + {:else if $status.status === "error"} + 다운로드 실패 + {/if} +

+
+
+{/if} diff --git a/src/routes/(fullscreen)/file/uploads/+page.svelte b/src/routes/(fullscreen)/file/uploads/+page.svelte new file mode 100644 index 0000000..9f59e3f --- /dev/null +++ b/src/routes/(fullscreen)/file/uploads/+page.svelte @@ -0,0 +1,29 @@ + + + + 진행 중인 업로드 + + +
+ +
+ {#each uploadingFiles as status} + + {/each} +
+
diff --git a/src/routes/(fullscreen)/file/uploads/File.svelte b/src/routes/(fullscreen)/file/uploads/File.svelte new file mode 100644 index 0000000..e0d235d --- /dev/null +++ b/src/routes/(fullscreen)/file/uploads/File.svelte @@ -0,0 +1,57 @@ + + +
+
+ {#if $status.status === "encryption-pending"} + + {:else if $status.status === "encrypting"} + + {:else if $status.status === "upload-pending"} + + {:else if $status.status === "uploading"} + + {:else if $status.status === "uploaded"} + + {:else if $status.status === "error"} + + {/if} +
+
+

+ {$status.name} +

+

+ {#if $status.status === "encryption-pending"} + 준비 중 + {:else if $status.status === "encrypting"} + 암호화하는 중 + {:else if $status.status === "upload-pending"} + 업로드를 기다리는 중 + {:else if $status.status === "uploading"} + 전송됨 + {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed($status.rate ?? 0)} + {:else if $status.status === "uploaded"} + 업로드 완료 + {:else if $status.status === "error"} + 업로드 실패 + {/if} +

+
+
diff --git a/src/routes/(fullscreen)/setting/cache/+page.svelte b/src/routes/(fullscreen)/settings/cache/+page.svelte similarity index 94% rename from src/routes/(fullscreen)/setting/cache/+page.svelte rename to src/routes/(fullscreen)/settings/cache/+page.svelte index f7581ea..9f61afd 100644 --- a/src/routes/(fullscreen)/setting/cache/+page.svelte +++ b/src/routes/(fullscreen)/settings/cache/+page.svelte @@ -5,9 +5,10 @@ import type { FileCacheIndex } from "$lib/indexedDB"; import { getFileCacheIndex } from "$lib/modules/file"; import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; + import { formatFileSize } from "$lib/modules/util"; import { masterKeyStore } from "$lib/stores"; import File from "./File.svelte"; - import { formatFileSize, deleteFileCache as doDeleteFileCache } from "./service"; + import { deleteFileCache as doDeleteFileCache } from "./service"; interface FileCache { index: FileCacheIndex; diff --git a/src/routes/(fullscreen)/setting/cache/File.svelte b/src/routes/(fullscreen)/settings/cache/File.svelte similarity index 95% rename from src/routes/(fullscreen)/setting/cache/File.svelte rename to src/routes/(fullscreen)/settings/cache/File.svelte index 2ee02c7..9e9a88a 100644 --- a/src/routes/(fullscreen)/setting/cache/File.svelte +++ b/src/routes/(fullscreen)/settings/cache/File.svelte @@ -2,7 +2,7 @@ import type { Writable } from "svelte/store"; import type { FileCacheIndex } from "$lib/indexedDB"; import type { FileInfo } from "$lib/modules/filesystem"; - import { formatDate, formatFileSize } from "./service"; + import { formatDate, formatFileSize } from "$lib/modules/util"; import IconDraft from "~icons/material-symbols/draft"; import IconScanDelete from "~icons/material-symbols/scan-delete"; diff --git a/src/routes/(fullscreen)/setting/cache/service.ts b/src/routes/(fullscreen)/settings/cache/service.ts similarity index 72% rename from src/routes/(fullscreen)/setting/cache/service.ts rename to src/routes/(fullscreen)/settings/cache/service.ts index a8fc1c6..35b0251 100644 --- a/src/routes/(fullscreen)/setting/cache/service.ts +++ b/src/routes/(fullscreen)/settings/cache/service.ts @@ -1,7 +1,5 @@ import { deleteFileCache as doDeleteFileCache } from "$lib/modules/file"; -export { formatDate, formatFileSize } from "$lib/modules/util"; - export const deleteFileCache = async (fileId: number) => { await doDeleteFileCache(fileId); }; diff --git a/src/routes/(main)/directory/[[id]]/+page.svelte b/src/routes/(main)/directory/[[id]]/+page.svelte index 9c496cb..f8bd0c9 100644 --- a/src/routes/(main)/directory/[[id]]/+page.svelte +++ b/src/routes/(main)/directory/[[id]]/+page.svelte @@ -11,8 +11,10 @@ import DeleteDirectoryEntryModal from "./DeleteDirectoryEntryModal.svelte"; import DirectoryEntries from "./DirectoryEntries"; import DirectoryEntryMenuBottomSheet from "./DirectoryEntryMenuBottomSheet.svelte"; + import DownloadStatusCard from "./DownloadStatusCard.svelte"; import DuplicateFileModal from "./DuplicateFileModal.svelte"; import RenameDirectoryEntryModal from "./RenameDirectoryEntryModal.svelte"; + import UploadStatusCard from "./UploadStatusCard.svelte"; import { requestHmacSecretDownload, requestDirectoryCreation, @@ -99,6 +101,10 @@ {#if $info} {@const topMargin = data.id === "root" ? "mt-4" : ""}
+
+ goto("/file/uploads")} /> + goto("/file/downloads")} /> +
{#key $info} import type { Writable } from "svelte/store"; import type { FileInfo } from "$lib/modules/filesystem"; - import { formatDateTime } from "./service"; + import { formatDateTime } from "$lib/modules/util"; import type { SelectedDirectoryEntry } from "../service"; import IconDraft from "~icons/material-symbols/draft"; diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte index e50d4bf..bc410e4 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte @@ -1,7 +1,7 @@ + +{#if downloadingFiles.length > 0} + +{/if} diff --git a/src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte b/src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte new file mode 100644 index 0000000..885673d --- /dev/null +++ b/src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte @@ -0,0 +1,39 @@ + + +{#if uploadingFiles.length > 0} + +{/if} diff --git a/src/routes/(main)/menu/+page.svelte b/src/routes/(main)/menu/+page.svelte index 80187d4..5ca21b5 100644 --- a/src/routes/(main)/menu/+page.svelte +++ b/src/routes/(main)/menu/+page.svelte @@ -27,7 +27,7 @@

설정

goto("/setting/cache")} + onclick={() => goto("/settings/cache")} icon={IconStorage} iconColor="text-green-500" > diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 3d058ec..e4bca97 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -2,22 +2,22 @@ import { onMount } from "svelte"; import { get } from "svelte/store"; import { goto as svelteGoto } from "$app/navigation"; - import { fileUploadStatusStore, clientKeyStore, masterKeyStore } from "$lib/stores"; + import { + fileUploadStatusStore, + fileDownloadStatusStore, + isFileUploading, + isFileDownloading, + clientKeyStore, + masterKeyStore, + } from "$lib/stores"; import "../app.css"; let { children } = $props(); - const checkFileUploadStatus = (e: BeforeUnloadEvent) => { + const protectFileUploadAndDownload = (e: BeforeUnloadEvent) => { if ( - $fileUploadStatusStore.some((statusStore) => { - const status = get(statusStore); - return ( - status.status === "encryption-pending" || - status.status === "encrypting" || - status.status === "upload-pending" || - status.status === "uploading" - ); - }) + $fileUploadStatusStore.some((status) => isFileUploading(get(status).status)) || + $fileDownloadStatusStore.some((status) => isFileDownloading(get(status).status)) ) { e.preventDefault(); } @@ -41,6 +41,6 @@ }); - + {@render children()}