470 lines
No EOL
18 KiB
JavaScript
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">×</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);
|
|
});
|
|
}
|
|
}
|
|
});
|