From 387934b19e9c59a3c89b3fb862ee90b10c6422b6 Mon Sep 17 00:00:00 2001 From: b3ni15 Date: Thu, 16 Oct 2025 09:24:22 +0200 Subject: [PATCH] feat: Enhance file upload progress tracking with detailed metrics --- src/app/page.tsx | 91 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 5 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index fbbb0cc..bf96422 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect, useRef } from 'react'; const CHUNK_SIZE = 8 * 1024 * 1024; // 8 MB const PARALLEL_UPLOADS = 4; @@ -16,11 +16,79 @@ function bufferToBase64(buffer: ArrayBuffer) { return window.btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); } +// Helper to format seconds into MM:SS +function formatTime(seconds: number) { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.floor(seconds % 60); + return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; +} + +// Helper to format bytes into a readable string +function formatBytes(bytes: number, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +} + export default function Home() { const [selectedFile, setSelectedFile] = useState(null); const [uploadState, setUploadState] = useState<'idle' | 'processing' | 'uploading' | 'complete'>('idle'); const [progress, setProgress] = useState(0); const [downloadUrl, setDownloadUrl] = useState(''); + + // New state for detailed progress + const [uploadedSize, setUploadedSize] = useState(0); + const [uploadSpeed, setUploadSpeed] = useState(0); + const [elapsedTime, setElapsedTime] = useState(0); + const [etr, setEtr] = useState(0); + + const timerRef = useRef(null); + const lastUploadedSizeRef = useRef(0); + + useEffect(() => { + if (uploadState === 'uploading') { + const startTime = Date.now(); + lastUploadedSizeRef.current = 0; + + timerRef.current = setInterval(() => { + // elapsed time is based on a fixed start time + setElapsedTime((Date.now() - startTime) / 1000); + + // Speed calculation + const currentUploadedSize = uploadedSizeRef.current; // Get latest size from ref + const speed = currentUploadedSize - lastUploadedSizeRef.current; + setUploadSpeed(speed > 0 ? speed : 0); + + // ETR calculation + if (speed > 0 && selectedFile) { + const remainingSize = selectedFile.size - currentUploadedSize; + setEtr(remainingSize / speed); + } + + lastUploadedSizeRef.current = currentUploadedSize; + }, 1000); // Update every second + + } else if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + setUploadSpeed(0); + } + + return () => { + if (timerRef.current) { + clearInterval(timerRef.current); + } + }; + }, [uploadState, selectedFile]); + + // Ref to hold the latest uploaded size for the timer to access + const uploadedSizeRef = useRef(0); + useEffect(() => { + uploadedSizeRef.current = uploadedSize; + }, [uploadedSize]); const handleFileChange = (event: React.ChangeEvent) => { const files = event.target.files; @@ -52,6 +120,9 @@ export default function Home() { setUploadState('processing'); setProgress(0); setDownloadUrl(''); + setUploadedSize(0); + setElapsedTime(0); + setEtr(0); // 1. Generate encryption key const fileKey = await window.crypto.subtle.generateKey( @@ -96,6 +167,8 @@ export default function Home() { formData.append('encrypted_chunk', chunk.data); await fetch('/api/upload-part', { method: 'POST', body: formData }); uploadedCount++; + const currentSize = uploadedCount * CHUNK_SIZE > file.size ? file.size : uploadedCount * CHUNK_SIZE; + setUploadedSize(currentSize); setProgress(Math.round((uploadedCount / numParts) * 100)); }; @@ -150,12 +223,20 @@ export default function Home() { )} {(uploadState === 'processing' || uploadState === 'uploading') && ( -
-

{selectedFile?.name}

-
+
+

{selectedFile?.name}

+
-

{uploadState === 'processing' ? 'Processing file...' : `Uploading... ${progress}%`}

+
+ {formatBytes(uploadedSize)} / {selectedFile ? formatBytes(selectedFile.size) : '0 Bytes'} + {progress}% +
+
+ Speed: {formatBytes(uploadSpeed)}/s + Elapsed: {formatTime(elapsedTime)} + ETR: {etr > 0 && etr < Infinity ? formatTime(etr) : '--:--'} +
)}