Flask-Files/app/static/js/upload.js
2025-03-22 12:30:45 +01:00

470 lines
No EOL
18 KiB
JavaScript

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 = '<p class="empty-message">No files selected</p>';
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 = `
<div class="file-item-icon">
<i class="fas ${getFileIcon(file.name)}"></i>
</div>
<div class="file-item-details">
<p class="file-item-name" title="${displayName}">${displayName}</p>
<p class="file-item-size">${formatSize(file.size)}</p>
</div>
<div class="file-item-status">
<div class="status-indicator waiting" id="status-${i}"></div>
</div>
`;
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} <button class="close">&times;</button>`;
// 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);
});
}
}
});