document.addEventListener('DOMContentLoaded', function () { // File Upload JavaScript const fileForm = document.getElementById('file-upload-form'); const folderForm = document.getElementById('folder-upload-form'); const fileInput = document.getElementById('file-input'); const folderInput = document.getElementById('folder-input'); const fileDropzone = document.getElementById('file-dropzone'); const folderDropzone = document.getElementById('folder-dropzone'); const fileList = document.getElementById('file-list'); const folderList = document.getElementById('folder-file-list'); // Progress elements const progressBar = document.getElementById('progress-bar'); const progressPercentage = document.getElementById('progress-percentage'); const folderProgressBar = document.getElementById('folder-progress-bar'); const folderProgressPercentage = document.getElementById('folder-progress-percentage'); const uploadSpeed = document.getElementById('upload-speed'); const folderUploadSpeed = document.getElementById('folder-upload-speed'); const uploadedSize = document.getElementById('uploaded-size'); const folderUploadedSize = document.getElementById('folder-uploaded-size'); const timeRemaining = document.getElementById('time-remaining'); const folderTimeRemaining = document.getElementById('folder-time-remaining'); // Variables for tracking upload progress let uploadStartTime = 0; let lastUploadedBytes = 0; let totalBytes = 0; let uploadedBytes = 0; let uploadIntervalId = null; // Initialize upload forms if (fileInput) { fileInput.addEventListener('change', function () { if (this.files.length > 0) { prepareAndUploadFiles(this.files, false); } }); } if (folderInput) { folderInput.addEventListener('change', function () { if (this.files.length > 0) { prepareAndUploadFiles(this.files, true); } }); } // Drag and drop setup if (fileDropzone) { ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(event => { fileDropzone.addEventListener(event, preventDefaults, false); }); ['dragenter', 'dragover'].forEach(event => { fileDropzone.addEventListener(event, function () { this.classList.add('highlight'); }, false); }); ['dragleave', 'drop'].forEach(event => { fileDropzone.addEventListener(event, function () { this.classList.remove('highlight'); }, false); }); fileDropzone.addEventListener('drop', function (e) { if (e.dataTransfer.files.length > 0) { prepareAndUploadFiles(e.dataTransfer.files, false); } }, false); } if (folderDropzone) { ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(event => { folderDropzone.addEventListener(event, preventDefaults, false); }); ['dragenter', 'dragover'].forEach(event => { folderDropzone.addEventListener(event, function () { this.classList.add('highlight'); }, false); }); ['dragleave', 'drop'].forEach(event => { folderDropzone.addEventListener(event, function () { this.classList.remove('highlight'); }, false); }); folderDropzone.addEventListener('drop', function (e) { // Check if items contains directories let hasFolder = false; if (e.dataTransfer.items) { for (let i = 0; i < e.dataTransfer.items.length; i++) { const item = e.dataTransfer.items[i].webkitGetAsEntry && e.dataTransfer.items[i].webkitGetAsEntry(); if (item && item.isDirectory) { hasFolder = true; break; } } } if (hasFolder) { showMessage('Folder detected, but browser API limitations prevent direct processing. Please use the Select Folder button.', 'info'); } else if (e.dataTransfer.files.length > 0) { showMessage('These appear to be files, not a folder. Using the Files tab instead.', 'info'); // Switch to files tab and upload there document.querySelector('[data-tab="file-tab"]').click(); setTimeout(() => { prepareAndUploadFiles(e.dataTransfer.files, false); }, 300); } }, false); } // Tab switching const tabBtns = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content'); tabBtns.forEach(btn => { btn.addEventListener('click', function () { const tabId = this.dataset.tab; // Remove active class from all tabs and contents tabBtns.forEach(b => b.classList.remove('active')); tabContents.forEach(c => c.classList.remove('active')); // Add active class to current tab and content this.classList.add('active'); document.getElementById(tabId).classList.add('active'); }); }); // Helper functions function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } function prepareAndUploadFiles(files, isFolder) { // Reset upload tracking uploadStartTime = Date.now(); lastUploadedBytes = 0; totalBytes = 0; uploadedBytes = 0; // Calculate total size for (let i = 0; i < files.length; i++) { totalBytes += files[i].size; } // Display files const targetList = isFolder ? folderList : fileList; displayFiles(files, targetList); // Start upload uploadFiles(files, isFolder); // Start progress tracking if (uploadIntervalId) { clearInterval(uploadIntervalId); } uploadIntervalId = setInterval(updateProgress, 1000); } function displayFiles(files, targetList) { targetList.innerHTML = ''; if (files.length === 0) { targetList.innerHTML = '

No files selected

'; return; } for (let i = 0; i < files.length; i++) { const file = files[i]; const item = document.createElement('div'); item.className = 'file-item'; item.id = `file-item-${i}`; // Get relative path for folder uploads let displayName = file.name; if (file.webkitRelativePath && file.webkitRelativePath !== '') { displayName = file.webkitRelativePath; } item.innerHTML = `

${displayName}

${formatSize(file.size)}

`; targetList.appendChild(item); } } function uploadFiles(files, isFolder) { const formData = new FormData(); const folderId = isFolder ? document.querySelector('#folder-upload-form input[name="folder_id"]').value : document.querySelector('#file-upload-form input[name="folder_id"]').value; formData.append('folder_id', folderId); formData.append('is_folder', isFolder ? '1' : '0'); // Add files to form data for (let i = 0; i < files.length; i++) { formData.append('files[]', files[i]); // If it's a folder upload, also include the path if (isFolder && files[i].webkitRelativePath) { formData.append('paths[]', files[i].webkitRelativePath); } else { formData.append('paths[]', ''); } // Update file status to uploading const statusIndicator = document.getElementById(`status-${i}`); if (statusIndicator) { statusIndicator.className = 'status-indicator uploading'; } } // Create and configure XHR request const xhr = new XMLHttpRequest(); xhr.open('POST', '/files/upload_xhr', true); // Set up progress event xhr.upload.onprogress = function (e) { if (e.lengthComputable) { uploadedBytes = e.loaded; const percent = Math.round((e.loaded / e.total) * 100); if (isFolder) { folderProgressBar.style.width = `${percent}%`; folderProgressPercentage.textContent = `${percent}%`; } else { progressBar.style.width = `${percent}%`; progressPercentage.textContent = `${percent}%`; } } }; // Set up completion and error handlers xhr.onload = function () { if (xhr.status === 200) { try { const response = JSON.parse(xhr.responseText); if (response.success) { showMessage(`Successfully uploaded ${response.successful} files.`, 'success'); // Update all file statuses to success for (let i = 0; i < files.length; i++) { const statusIndicator = document.getElementById(`status-${i}`); if (statusIndicator) { statusIndicator.className = 'status-indicator success'; } } // Mark specific failures if any if (response.errors && response.errors.length > 0) { for (let i = 0; i < response.errors.length; i++) { // Try to find the file by name const errorFileName = response.errors[i].split(':')[0]; for (let j = 0; j < files.length; j++) { if (files[j].name === errorFileName) { const statusIndicator = document.getElementById(`status-${j}`); if (statusIndicator) { statusIndicator.className = 'status-indicator error'; } break; } } } // Show error messages showMessage(`Failed to upload some files. See errors for details.`, 'warning'); response.errors.forEach(err => showMessage(err, 'error')); } } else { showMessage(response.error || 'Upload failed', 'error'); // Update all file statuses to error for (let i = 0; i < files.length; i++) { const statusIndicator = document.getElementById(`status-${i}`); if (statusIndicator) { statusIndicator.className = 'status-indicator error'; } } } } catch (e) { showMessage('Error parsing server response', 'error'); } } else { showMessage(`Upload failed with status ${xhr.status}`, 'error'); // Update all file statuses to error for (let i = 0; i < files.length; i++) { const statusIndicator = document.getElementById(`status-${i}`); if (statusIndicator) { statusIndicator.className = 'status-indicator error'; } } } // Stop progress updates if (uploadIntervalId) { clearInterval(uploadIntervalId); uploadIntervalId = null; } }; xhr.onerror = function () { showMessage('Network error during upload', 'error'); // Update all file statuses to error for (let i = 0; i < files.length; i++) { const statusIndicator = document.getElementById(`status-${i}`); if (statusIndicator) { statusIndicator.className = 'status-indicator error'; } } // Stop progress updates if (uploadIntervalId) { clearInterval(uploadIntervalId); uploadIntervalId = null; } }; // Send the request xhr.send(formData); } function updateProgress() { const currentTime = Date.now(); const elapsedSeconds = (currentTime - uploadStartTime) / 1000; // Calculate upload speed (bytes per second) const bytesPerSecond = elapsedSeconds > 0 ? uploadedBytes / elapsedSeconds : 0; // Calculate remaining time const remainingBytes = totalBytes - uploadedBytes; let remainingTime = 'calculating...'; if (bytesPerSecond > 0 && remainingBytes > 0) { const remainingSeconds = remainingBytes / bytesPerSecond; if (remainingSeconds < 60) { remainingTime = `${Math.round(remainingSeconds)} seconds`; } else if (remainingSeconds < 3600) { remainingTime = `${Math.round(remainingSeconds / 60)} minutes`; } else { remainingTime = `${Math.round(remainingSeconds / 3600)} hours`; } } // Update DOM elements const speed = formatSize(bytesPerSecond) + '/s'; const progress = `${formatSize(uploadedBytes)} / ${formatSize(totalBytes)}`; // Update regular upload view if (uploadSpeed) uploadSpeed.textContent = speed; if (uploadedSize) uploadedSize.textContent = progress; if (timeRemaining) timeRemaining.textContent = remainingTime; // Update folder upload view if (folderUploadSpeed) folderUploadSpeed.textContent = speed; if (folderUploadedSize) folderUploadedSize.textContent = progress; if (folderTimeRemaining) folderTimeRemaining.textContent = remainingTime; } // Helper Functions function formatSize(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]; } function getFileIcon(fileName) { if (!fileName) return 'fa-file'; const extension = fileName.split('.').pop().toLowerCase(); if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'].includes(extension)) { return 'fa-file-image'; } else if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'].includes(extension)) { return 'fa-file-video'; } else if (['mp3', 'wav', 'ogg', 'flac', 'm4a'].includes(extension)) { return 'fa-file-audio'; } else if (['doc', 'docx'].includes(extension)) { return 'fa-file-word'; } else if (['xls', 'xlsx'].includes(extension)) { return 'fa-file-excel'; } else if (['ppt', 'pptx'].includes(extension)) { return 'fa-file-powerpoint'; } else if (['pdf'].includes(extension)) { return 'fa-file-pdf'; } else if (['zip', 'rar', '7z', 'tar', 'gz'].includes(extension)) { return 'fa-file-archive'; } else if (['txt', 'rtf', 'md'].includes(extension)) { return 'fa-file-alt'; } else if (['html', 'css', 'js', 'php', 'py', 'java', 'c', 'cpp', 'h', 'json', 'xml'].includes(extension)) { return 'fa-file-code'; } return 'fa-file'; } function showMessage(message, type) { // Create alerts container if it doesn't exist let alertsContainer = document.querySelector('.alerts'); if (!alertsContainer) { alertsContainer = document.createElement('div'); alertsContainer.className = 'alerts'; document.body.appendChild(alertsContainer); } // Create alert const alert = document.createElement('div'); alert.className = `alert ${type}`; alert.innerHTML = `${message} `; // Add to container alertsContainer.appendChild(alert); // Set up close button const closeBtn = alert.querySelector('.close'); closeBtn.addEventListener('click', function () { alert.style.animation = 'alertOut 0.5s forwards'; setTimeout(() => alert.remove(), 500); }); // Auto close after 5 seconds setTimeout(function () { if (alert.parentNode) { alert.style.animation = 'alertOut 0.5s forwards'; setTimeout(() => alert.remove(), 500); } }, 5000); } function createAlertSection() { const alertSection = document.createElement('div'); alertSection.className = 'alerts'; document.body.appendChild(alertSection); return alertSection; } function setupAlertDismiss(alert) { const closeBtn = alert.querySelector('.close'); if (closeBtn) { closeBtn.addEventListener('click', function () { alert.style.animation = 'alertOut 0.5s forwards'; setTimeout(() => alert.remove(), 500); }); } } });