mirror of
				https://github.com/DumbWareio/DumbDrop.git
				synced 2025-11-03 21:43:26 +00:00 
			
		
		
		
	feat: enhance file upload progress tracking and user experience
- Add detailed upload progress tracking with speed and time remaining - Implement dynamic waiting messages during upload initialization - Create utility functions for file size and speed formatting - Improve progress bar UI with more informative status details - Add interval-based speed and progress updates for smoother UI
This commit is contained in:
		@@ -48,15 +48,7 @@
 | 
				
			|||||||
        <button id="uploadButton" class="upload-button" style="display: none;">Upload Files</button>
 | 
					        <button id="uploadButton" class="upload-button" style="display: none;">Upload Files</button>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <style>
 | 
					    <script defer>
 | 
				
			||||||
        .button-group {
 | 
					 | 
				
			||||||
            display: flex;
 | 
					 | 
				
			||||||
            gap: 10px;
 | 
					 | 
				
			||||||
            justify-content: center;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <script>
 | 
					 | 
				
			||||||
        const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
 | 
					        const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
 | 
				
			||||||
        const MAX_RETRIES = 3;
 | 
					        const MAX_RETRIES = 3;
 | 
				
			||||||
        const RETRY_DELAY = 1000;
 | 
					        const RETRY_DELAY = 1000;
 | 
				
			||||||
@@ -66,23 +58,178 @@
 | 
				
			|||||||
            return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
 | 
					            return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Utility function to format file sizes
 | 
				
			||||||
 | 
					        function formatFileSize(bytes) {
 | 
				
			||||||
 | 
					            if (bytes === 0) return '0 Bytes';
 | 
				
			||||||
 | 
					            const k = 1024;
 | 
				
			||||||
 | 
					            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
 | 
				
			||||||
 | 
					            const i = Math.floor(Math.log(bytes) / Math.log(k));
 | 
				
			||||||
 | 
					            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class FileUploader {
 | 
					        class FileUploader {
 | 
				
			||||||
            constructor(file, batchId) {
 | 
					            constructor(file, batchId) {
 | 
				
			||||||
                this.file = file;
 | 
					                this.file = file;
 | 
				
			||||||
                this.batchId = batchId;
 | 
					                this.batchId = batchId;
 | 
				
			||||||
                this.uploadId = null;
 | 
					                this.uploadId = null;
 | 
				
			||||||
                this.position = 0;
 | 
					                this.position = 0;
 | 
				
			||||||
 | 
					                this.bytesReceived = 0;
 | 
				
			||||||
                this.progressElement = null;
 | 
					                this.progressElement = null;
 | 
				
			||||||
                this.retries = 0;
 | 
					                this.retries = 0;
 | 
				
			||||||
 | 
					                this.startTime = null;
 | 
				
			||||||
 | 
					                this.lastUpdate = null;
 | 
				
			||||||
 | 
					                this.lastBytes = 0;
 | 
				
			||||||
 | 
					                this.speedSamples = [];
 | 
				
			||||||
 | 
					                this.currentSpeed = 0;
 | 
				
			||||||
 | 
					                this.speedUpdateTimer = null;
 | 
				
			||||||
 | 
					                this.progressUpdateTimer = null;
 | 
				
			||||||
 | 
					                this.waitingMessages = [
 | 
				
			||||||
 | 
					                    "Preparing upload...",
 | 
				
			||||||
 | 
					                    "Establishing connection...",
 | 
				
			||||||
 | 
					                    "Starting transfer...",
 | 
				
			||||||
 | 
					                    "Waiting for first chunk..."
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					                this.waitingMessageIndex = 0;
 | 
				
			||||||
 | 
					                this.waitingMessageInterval = null;
 | 
				
			||||||
 | 
					                this.dotsCount = 1;
 | 
				
			||||||
 | 
					                this.dotsIncreasing = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cycleWaitingMessage() {
 | 
				
			||||||
 | 
					                if (!this.progressElement || !this.progressElement.infoSpan) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (this.waitingMessageIndex < this.waitingMessages.length) {
 | 
				
			||||||
 | 
					                    this.progressElement.infoSpan.textContent = this.waitingMessages[this.waitingMessageIndex];
 | 
				
			||||||
 | 
					                    this.waitingMessageIndex++;
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // When we finish the initial messages, add a delay before switching to dot animation
 | 
				
			||||||
 | 
					                    if (this.waitingMessageIndex >= this.waitingMessages.length) {
 | 
				
			||||||
 | 
					                        clearInterval(this.waitingMessageInterval);
 | 
				
			||||||
 | 
					                        // Keep the last message visible for 2 seconds before starting dot animation
 | 
				
			||||||
 | 
					                        setTimeout(() => {
 | 
				
			||||||
 | 
					                            this.waitingMessageInterval = setInterval(() => this.cycleWaitingMessage(), 500);
 | 
				
			||||||
 | 
					                        }, 2000);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // Create the dots string
 | 
				
			||||||
 | 
					                    const dots = '.'.repeat(this.dotsCount);
 | 
				
			||||||
 | 
					                    this.progressElement.infoSpan.textContent = `Still working${dots}`;
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Update dots count
 | 
				
			||||||
 | 
					                    if (this.dotsIncreasing) {
 | 
				
			||||||
 | 
					                        this.dotsCount++;
 | 
				
			||||||
 | 
					                        if (this.dotsCount > 6) {
 | 
				
			||||||
 | 
					                            this.dotsCount = 1;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            startWaitingMessages() {
 | 
				
			||||||
 | 
					                if (this.waitingMessageInterval) {
 | 
				
			||||||
 | 
					                    clearInterval(this.waitingMessageInterval);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                // Start with 2 second interval for main messages
 | 
				
			||||||
 | 
					                this.waitingMessageInterval = setInterval(() => this.cycleWaitingMessage(), 2000);
 | 
				
			||||||
 | 
					                this.cycleWaitingMessage(); // Show first message immediately
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            stopWaitingMessages() {
 | 
				
			||||||
 | 
					                if (this.waitingMessageInterval) {
 | 
				
			||||||
 | 
					                    clearInterval(this.waitingMessageInterval);
 | 
				
			||||||
 | 
					                    this.waitingMessageInterval = null;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            calculateSpeed(bytesReceived) {
 | 
				
			||||||
 | 
					                const now = Date.now();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Only calculate speed if at least 1 second has passed since last update
 | 
				
			||||||
 | 
					                if (!this.lastUpdate || (now - this.lastUpdate) >= 1000) {
 | 
				
			||||||
 | 
					                    if (this.lastUpdate) {
 | 
				
			||||||
 | 
					                        const timeDiff = (now - this.lastUpdate) / 1000; // Convert to seconds
 | 
				
			||||||
 | 
					                        const bytesDiff = bytesReceived - this.lastBytes;
 | 
				
			||||||
 | 
					                        const instantSpeed = bytesDiff / timeDiff; // Bytes per second
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Keep last 3 samples for smoother average
 | 
				
			||||||
 | 
					                        this.speedSamples.push(instantSpeed);
 | 
				
			||||||
 | 
					                        if (this.speedSamples.length > 3) {
 | 
				
			||||||
 | 
					                            this.speedSamples.shift();
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Calculate weighted moving average with more weight on recent samples
 | 
				
			||||||
 | 
					                        const weights = [0.5, 0.3, 0.2];
 | 
				
			||||||
 | 
					                        const samples = this.speedSamples.slice().reverse(); // Most recent first
 | 
				
			||||||
 | 
					                        this.currentSpeed = samples.reduce((acc, speed, i) => {
 | 
				
			||||||
 | 
					                            return acc + (speed * (weights[i] || 0));
 | 
				
			||||||
 | 
					                        }, 0);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    this.lastUpdate = now;
 | 
				
			||||||
 | 
					                    this.lastBytes = bytesReceived;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return this.currentSpeed;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            formatSpeed(bytesPerSecond) {
 | 
				
			||||||
 | 
					                const units = ['B/s', 'KB/s', 'MB/s', 'GB/s'];
 | 
				
			||||||
 | 
					                let value = bytesPerSecond;
 | 
				
			||||||
 | 
					                let unitIndex = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                while (value >= 1024 && unitIndex < units.length - 1) {
 | 
				
			||||||
 | 
					                    value /= 1024;
 | 
				
			||||||
 | 
					                    unitIndex++;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return `${value.toFixed(1)} ${units[unitIndex]}`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            calculateTimeRemaining(bytesReceived, speed) {
 | 
				
			||||||
 | 
					                if (speed === 0) return 'calculating...';
 | 
				
			||||||
 | 
					                const bytesRemaining = this.file.size - bytesReceived;
 | 
				
			||||||
 | 
					                const secondsRemaining = bytesRemaining / speed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (secondsRemaining < 60) {
 | 
				
			||||||
 | 
					                    return `${Math.ceil(secondsRemaining)}s`;
 | 
				
			||||||
 | 
					                } else if (secondsRemaining < 3600) {
 | 
				
			||||||
 | 
					                    return `${Math.ceil(secondsRemaining / 60)}m`;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    return `${Math.ceil(secondsRemaining / 3600)}h`;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            async start() {
 | 
					            async start() {
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
 | 
					                    this.startTime = Date.now();
 | 
				
			||||||
 | 
					                    // Initialize speed update timer
 | 
				
			||||||
 | 
					                    this.speedUpdateTimer = setInterval(() => {
 | 
				
			||||||
 | 
					                        if (this.bytesReceived > 0) {
 | 
				
			||||||
 | 
					                            this.calculateSpeed(this.bytesReceived);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }, 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Add progress update timer for more frequent UI updates
 | 
				
			||||||
 | 
					                    this.progressUpdateTimer = setInterval(() => {
 | 
				
			||||||
 | 
					                        if (this.bytesReceived > 0) {
 | 
				
			||||||
 | 
					                            const progress = (this.bytesReceived / this.file.size) * 100;
 | 
				
			||||||
 | 
					                            this.updateProgress(progress);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }, 200); // Update every 200ms
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
                    await this.initUpload();
 | 
					                    await this.initUpload();
 | 
				
			||||||
                    await this.uploadChunks();
 | 
					                    await this.uploadChunks();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Clear the timers when upload is complete
 | 
				
			||||||
 | 
					                    this.clearTimers();
 | 
				
			||||||
                    return true;
 | 
					                    return true;
 | 
				
			||||||
                } catch (error) {
 | 
					                } catch (error) {
 | 
				
			||||||
 | 
					                    this.clearTimers();
 | 
				
			||||||
                    console.error('Upload failed:', error);
 | 
					                    console.error('Upload failed:', error);
 | 
				
			||||||
 | 
					                    if (this.progressElement) {
 | 
				
			||||||
 | 
					                        this.progressElement.infoSpan.textContent = `Error: ${error.message}`;
 | 
				
			||||||
 | 
					                        this.progressElement.infoSpan.style.color = 'var(--danger-color)';
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    this.stopWaitingMessages();
 | 
				
			||||||
                    return false;
 | 
					                    return false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -141,16 +288,19 @@
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            async uploadChunk(chunk) {
 | 
					            async uploadChunk(chunk) {
 | 
				
			||||||
 | 
					                const controller = new AbortController();
 | 
				
			||||||
                const response = await fetch(`/upload/chunk/${this.uploadId}`, {
 | 
					                const response = await fetch(`/upload/chunk/${this.uploadId}`, {
 | 
				
			||||||
                    method: 'POST',
 | 
					                    method: 'POST',
 | 
				
			||||||
                    headers: {
 | 
					                    headers: {
 | 
				
			||||||
                        'Content-Type': 'application/octet-stream'
 | 
					                        'Content-Type': 'application/octet-stream'
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    body: chunk
 | 
					                    body: chunk,
 | 
				
			||||||
 | 
					                    signal: controller.signal
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (!response.ok) throw new Error('Chunk upload failed');
 | 
					                if (!response.ok) throw new Error('Chunk upload failed');
 | 
				
			||||||
                const data = await response.json();
 | 
					                const data = await response.json();
 | 
				
			||||||
 | 
					                this.bytesReceived = Math.floor((data.progress / 100) * this.file.size);
 | 
				
			||||||
                this.updateProgress(data.progress);
 | 
					                this.updateProgress(data.progress);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -160,10 +310,15 @@
 | 
				
			|||||||
                
 | 
					                
 | 
				
			||||||
                const label = document.createElement('div');
 | 
					                const label = document.createElement('div');
 | 
				
			||||||
                label.className = 'progress-label';
 | 
					                label.className = 'progress-label';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const pathSpan = document.createElement('span');
 | 
				
			||||||
 | 
					                pathSpan.className = 'progress-path';
 | 
				
			||||||
                const displayPath = this.file.relativePath ? 
 | 
					                const displayPath = this.file.relativePath ? 
 | 
				
			||||||
                    `📁 ${this.file.relativePath.split('/')[0]}/` : 
 | 
					                    `📁 ${this.file.relativePath}${this.file.name}` : 
 | 
				
			||||||
                    `📄 ${this.file.name}`;
 | 
					                    `📄 ${this.file.name}`;
 | 
				
			||||||
                label.textContent = displayPath;
 | 
					                pathSpan.textContent = displayPath;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                label.appendChild(pathSpan);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const progress = document.createElement('div');
 | 
					                const progress = document.createElement('div');
 | 
				
			||||||
                progress.className = 'progress';
 | 
					                progress.className = 'progress';
 | 
				
			||||||
@@ -172,24 +327,73 @@
 | 
				
			|||||||
                bar.className = 'progress-bar';
 | 
					                bar.className = 'progress-bar';
 | 
				
			||||||
                bar.style.width = '0%';
 | 
					                bar.style.width = '0%';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const statusDiv = document.createElement('div');
 | 
				
			||||||
 | 
					                statusDiv.className = 'progress-status';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const infoSpan = document.createElement('div');
 | 
				
			||||||
 | 
					                infoSpan.className = 'progress-info';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const details = document.createElement('div');
 | 
				
			||||||
 | 
					                details.className = 'progress-details';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                statusDiv.appendChild(infoSpan);
 | 
				
			||||||
 | 
					                statusDiv.appendChild(details);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                progress.appendChild(bar);
 | 
					                progress.appendChild(bar);
 | 
				
			||||||
                container.appendChild(label);
 | 
					                container.appendChild(label);
 | 
				
			||||||
                container.appendChild(progress);
 | 
					                container.appendChild(progress);
 | 
				
			||||||
 | 
					                container.appendChild(statusDiv);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                document.getElementById('uploadProgress').appendChild(container);
 | 
					                document.getElementById('uploadProgress').appendChild(container);
 | 
				
			||||||
                this.progressElement = { container, bar };
 | 
					                this.progressElement = { container, bar, infoSpan, details };
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Start cycling waiting messages
 | 
				
			||||||
 | 
					                this.startWaitingMessages();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            updateProgress(percent) {
 | 
					            updateProgress(percent) {
 | 
				
			||||||
                if (this.progressElement) {
 | 
					                if (this.progressElement) {
 | 
				
			||||||
                    this.progressElement.bar.style.width = `${percent}%`;
 | 
					                    // Use the current speed value instead of calculating it every time
 | 
				
			||||||
 | 
					                    const speed = this.currentSpeed;
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Stop waiting messages once we start receiving data
 | 
				
			||||||
 | 
					                    if (this.bytesReceived > 0) {
 | 
				
			||||||
 | 
					                        this.stopWaitingMessages();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    const timeRemaining = this.calculateTimeRemaining(this.bytesReceived, speed);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    this.progressElement.bar.style.width = `${percent.toFixed(1)}%`;
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Only show speed and time if we've received data
 | 
				
			||||||
 | 
					                    if (this.bytesReceived > 0) {
 | 
				
			||||||
 | 
					                        this.progressElement.infoSpan.textContent = `${this.formatSpeed(speed)} · ${timeRemaining}`;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Update details with progress and file size
 | 
				
			||||||
 | 
					                    this.progressElement.details.textContent = 
 | 
				
			||||||
 | 
					                        `${formatFileSize(this.bytesReceived)} of ${formatFileSize(this.file.size)} (${percent.toFixed(1)}%)`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (percent === 100) {
 | 
					                    if (percent === 100) {
 | 
				
			||||||
 | 
					                        this.stopWaitingMessages();
 | 
				
			||||||
 | 
					                        this.clearTimers();
 | 
				
			||||||
                        setTimeout(() => {
 | 
					                        setTimeout(() => {
 | 
				
			||||||
                            this.progressElement.container.remove();
 | 
					                            this.progressElement.container.remove();
 | 
				
			||||||
                        }, 1000);
 | 
					                        }, 1000);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            clearTimers() {
 | 
				
			||||||
 | 
					                if (this.speedUpdateTimer) {
 | 
				
			||||||
 | 
					                    clearInterval(this.speedUpdateTimer);
 | 
				
			||||||
 | 
					                    this.speedUpdateTimer = null;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (this.progressUpdateTimer) {
 | 
				
			||||||
 | 
					                    clearInterval(this.progressUpdateTimer);
 | 
				
			||||||
 | 
					                    this.progressUpdateTimer = null;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // UI Event Handlers
 | 
					        // UI Event Handlers
 | 
				
			||||||
@@ -370,14 +574,6 @@
 | 
				
			|||||||
            uploadButton.style.display = files.length > 0 ? 'block' : 'none';
 | 
					            uploadButton.style.display = files.length > 0 ? 'block' : 'none';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        function formatFileSize(bytes) {
 | 
					 | 
				
			||||||
            if (bytes === 0) return '0 Bytes';
 | 
					 | 
				
			||||||
            const k = 1024;
 | 
					 | 
				
			||||||
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
 | 
					 | 
				
			||||||
            const i = Math.floor(Math.log(bytes) / Math.log(k));
 | 
					 | 
				
			||||||
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        async function startUploads() {
 | 
					        async function startUploads() {
 | 
				
			||||||
            uploadButton.disabled = true;
 | 
					            uploadButton.disabled = true;
 | 
				
			||||||
            document.getElementById('uploadProgress').innerHTML = '';
 | 
					            document.getElementById('uploadProgress').innerHTML = '';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -159,6 +159,12 @@ button:disabled {
 | 
				
			|||||||
    margin-right: auto;
 | 
					    margin-right: auto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.button-group {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    gap: 10px;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Progress Bar Styles */
 | 
					/* Progress Bar Styles */
 | 
				
			||||||
#uploadProgress {
 | 
					#uploadProgress {
 | 
				
			||||||
    margin: 20px 0;
 | 
					    margin: 20px 0;
 | 
				
			||||||
@@ -181,11 +187,26 @@ button:disabled {
 | 
				
			|||||||
    font-size: 0.9rem;
 | 
					    font-size: 0.9rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.progress-info {
 | 
				
			||||||
 | 
					    font-size: 0.8rem;
 | 
				
			||||||
 | 
					    color: var(--text-color);
 | 
				
			||||||
 | 
					    opacity: 0.8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.progress-path {
 | 
				
			||||||
 | 
					    color: var(--text-color);
 | 
				
			||||||
 | 
					    opacity: 0.9;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    word-break: break-all;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.progress {
 | 
					.progress {
 | 
				
			||||||
    background: var(--progress-bg);
 | 
					    background: var(--progress-bg);
 | 
				
			||||||
    border-radius: 10px;
 | 
					    border-radius: 10px;
 | 
				
			||||||
    height: 8px;
 | 
					    height: 8px;
 | 
				
			||||||
    overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    margin-top: 8px;
 | 
				
			||||||
 | 
					    margin-bottom: 8px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.progress-bar {
 | 
					.progress-bar {
 | 
				
			||||||
@@ -194,6 +215,19 @@ button:disabled {
 | 
				
			|||||||
    transition: width 0.3s ease;
 | 
					    transition: width 0.3s ease;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.progress-status {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    font-size: 0.8rem;
 | 
				
			||||||
 | 
					    color: var(--text-color);
 | 
				
			||||||
 | 
					    opacity: 0.8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.progress-details {
 | 
				
			||||||
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Modal Styles */
 | 
					/* Modal Styles */
 | 
				
			||||||
.modal {
 | 
					.modal {
 | 
				
			||||||
    position: fixed;
 | 
					    position: fixed;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user