From 8c84d50159bdabc75a1199ffdf372b9586f67371 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Wed, 12 Oct 2022 23:24:11 +0200 Subject: [PATCH] feat: add progress indicator for uploading files --- frontend/src/components/navBar/NavBar.tsx | 10 ++--- frontend/src/components/upload/Dropzone.tsx | 19 ++++---- frontend/src/components/upload/FileList.tsx | 16 +++---- .../upload/UploadProgressIndicator.tsx | 20 +++++++++ frontend/src/pages/upload.tsx | 44 +++++++++++-------- frontend/src/services/share.service.ts | 14 +++++- frontend/src/types/File.type.ts | 3 +- 7 files changed, 77 insertions(+), 49 deletions(-) create mode 100644 frontend/src/components/upload/UploadProgressIndicator.tsx diff --git a/frontend/src/components/navBar/NavBar.tsx b/frontend/src/components/navBar/NavBar.tsx index 49be6ba..a732e95 100644 --- a/frontend/src/components/navBar/NavBar.tsx +++ b/frontend/src/components/navBar/NavBar.tsx @@ -152,14 +152,12 @@ const NavBar = () => { const { classes, cx } = useStyles(); const items = ( <> - {(user ? authenticatedLinks : unauthenticatedLinks).map((link) => { + {(user ? authenticatedLinks : unauthenticatedLinks).map((link, i) => { if (link.component) { return ( - <> - - {link.component} - - + + {link.component} + ); } return ( diff --git a/frontend/src/components/upload/Dropzone.tsx b/frontend/src/components/upload/Dropzone.tsx index e5a679c..4c55d3a 100644 --- a/frontend/src/components/upload/Dropzone.tsx +++ b/frontend/src/components/upload/Dropzone.tsx @@ -1,15 +1,9 @@ -import { - Button, - Center, - createStyles, - Group, - Text, - useMantineTheme, -} from "@mantine/core"; +import { Button, Center, createStyles, Group, Text } from "@mantine/core"; import { Dropzone as MantineDropzone } from "@mantine/dropzone"; import getConfig from "next/config"; import { Dispatch, ForwardedRef, SetStateAction, useRef } from "react"; import { CloudUpload, Upload } from "tabler-icons-react"; +import { FileUpload } from "../../types/File.type"; import { byteStringToHumanSizeString } from "../../utils/math/byteStringToHumanSizeString.util"; import toast from "../../utils/toast.util"; @@ -44,9 +38,8 @@ const Dropzone = ({ setFiles, }: { isUploading: boolean; - setFiles: Dispatch>; + setFiles: Dispatch>; }) => { - const theme = useMantineTheme(); const { classes } = useStyles(); const openRef = useRef<() => void>(); return ( @@ -62,7 +55,11 @@ const Dropzone = ({ if (files.length > 100) { toast.error("You can't upload more than 100 files per share."); } else { - setFiles(files); + const newFiles = files.map((file) => { + (file as FileUpload).uploadingProgress = 0; + return file as FileUpload; + }); + setFiles(newFiles); } }} className={classes.dropzone} diff --git a/frontend/src/components/upload/FileList.tsx b/frontend/src/components/upload/FileList.tsx index 51b3c62..c9a3260 100644 --- a/frontend/src/components/upload/FileList.tsx +++ b/frontend/src/components/upload/FileList.tsx @@ -1,8 +1,9 @@ -import { ActionIcon, Loader, Table } from "@mantine/core"; +import { ActionIcon, Table } from "@mantine/core"; import { Dispatch, SetStateAction } from "react"; -import { CircleCheck, Trash } from "tabler-icons-react"; +import { Trash } from "tabler-icons-react"; import { FileUpload } from "../../types/File.type"; import { byteStringToHumanSizeString } from "../../utils/math/byteStringToHumanSizeString.util"; +import UploadProgressIndicator from "./UploadProgressIndicator"; const FileList = ({ files, @@ -15,19 +16,12 @@ const FileList = ({ files.splice(index, 1); setFiles([...files]); }; - const rows = files.map((file, i) => ( {file.name} {byteStringToHumanSizeString(file.size.toString())} - {file.uploadingState ? ( - file.uploadingState != "finished" ? ( - - ) : ( - - ) - ) : ( + {file.uploadingProgress == 0 ? ( + ) : ( + )} diff --git a/frontend/src/components/upload/UploadProgressIndicator.tsx b/frontend/src/components/upload/UploadProgressIndicator.tsx new file mode 100644 index 0000000..95800bb --- /dev/null +++ b/frontend/src/components/upload/UploadProgressIndicator.tsx @@ -0,0 +1,20 @@ +import { RingProgress } from "@mantine/core"; +import { CircleCheck, CircleX } from "tabler-icons-react"; + +const UploadProgressIndicator = ({ progress }: { progress: number }) => { + if (progress > 0 && progress < 100) { + return ( + + ); + } else if (progress == 100) { + return ; + } else { + return ; + } +}; + +export default UploadProgressIndicator; diff --git a/frontend/src/pages/upload.tsx b/frontend/src/pages/upload.tsx index 2af653d..825f017 100644 --- a/frontend/src/pages/upload.tsx +++ b/frontend/src/pages/upload.tsx @@ -28,39 +28,47 @@ const Upload = () => { security: ShareSecurity ) => { setisUploading(true); - try { - files.forEach((file) => { - file.uploadingState = "inProgress"; - }); - setFiles([...files]); + setFiles((files) => + files.map((file) => { + file.uploadingProgress = 1; + return file; + }) + ); const share = await shareService.create(id, expiration, security); for (let i = 0; i < files.length; i++) { - await shareService.uploadFile(share.id, files[i]); + const progressCallBack = (bytesProgress: number) => { + setFiles((files) => { + return files.map((file, callbackIndex) => { + if (i == callbackIndex) { + file.uploadingProgress = Math.round( + (100 * bytesProgress) / files[i].size + ); + } + return file; + }); + }); + }; - files[i].uploadingState = "finished"; - setFiles([...files]); - if (!files.some((f) => f.uploadingState == "inProgress")) { + try { + await shareService.uploadFile(share.id, files[i], progressCallBack); + } catch { + files[i].uploadingProgress = -1; + } + + if (!files.some((f) => f.uploadingProgress != 100)) { await shareService.completeShare(share.id); setisUploading(false); - showCompletedUploadModal( - modals, - - share - ); + showCompletedUploadModal(modals, share); setFiles([]); } } } catch (e) { - files.forEach((file) => { - file.uploadingState = undefined; - }); if (axios.isAxiosError(e)) { toast.error(e.response?.data?.message ?? "An unkown error occured."); } else { toast.error("An unkown error occured."); } - setFiles([...files]); setisUploading(false); } }; diff --git a/frontend/src/services/share.service.ts b/frontend/src/services/share.service.ts index 0844677..a3648cc 100644 --- a/frontend/src/services/share.service.ts +++ b/frontend/src/services/share.service.ts @@ -68,10 +68,20 @@ const downloadFile = async (shareId: string, fileId: string) => { window.location.href = await getFileDownloadUrl(shareId, fileId); }; -const uploadFile = async (shareId: string, file: File) => { +const uploadFile = async ( + shareId: string, + file: File, + progressCallBack: (uploadingProgress: number) => void +) => { var formData = new FormData(); formData.append("file", file); - return (await api.post(`shares/${shareId}/files`, formData)).data; + + return ( + await api.post(`shares/${shareId}/files`, formData, { + onUploadProgress: (progressEvent) => + progressCallBack(progressEvent.loaded), + }) + ).data; }; export default { diff --git a/frontend/src/types/File.type.ts b/frontend/src/types/File.type.ts index 05e5779..dfb2390 100644 --- a/frontend/src/types/File.type.ts +++ b/frontend/src/types/File.type.ts @@ -1,2 +1 @@ -export type FileUpload = File & { uploadingState?: UploadState }; -export type UploadState = "finished" | "inProgress" | undefined; +export type FileUpload = File & { uploadingProgress: number };