export class UploadManager { constructor(roomManager) { this.roomManager = roomManager; this.pendingUploads = []; this.pendingUploadIdx = 0; this.overwriteAll = false; this.skipAll = false; // Initialize upload-related elements this.uploadBtn = document.getElementById('uploadBtn'); this.fileInput = document.getElementById('fileInput'); this.uploadForm = document.getElementById('uploadForm'); this.fileGrid = document.getElementById('fileGrid'); this.dropZoneOverlay = document.getElementById('dropZoneOverlay'); this.uploadProgressContainer = document.getElementById('uploadProgressContainer'); this.uploadProgressBar = document.getElementById('uploadProgressBar'); this.uploadProgressText = document.getElementById('uploadProgressText'); this.initializeUploadHandlers(); } initializeUploadHandlers() { if (!this.roomManager.canUpload) return; // Initialize drag and drop ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { this.fileGrid.addEventListener(eventName, this.preventDefaults.bind(this), false); document.body.addEventListener(eventName, this.preventDefaults.bind(this), false); }); ['dragenter', 'dragover'].forEach(eventName => { this.fileGrid.addEventListener(eventName, this.highlight.bind(this), false); }); ['dragleave', 'drop'].forEach(eventName => { this.fileGrid.addEventListener(eventName, this.unhighlight.bind(this), false); }); // Handle dropped files this.fileGrid.addEventListener('drop', this.handleDrop.bind(this), false); // Handle file input change this.uploadBtn.addEventListener('click', () => this.fileInput.click()); this.fileInput.addEventListener('change', this.handleFileSelect.bind(this)); } preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } highlight() { this.dropZoneOverlay.style.display = 'block'; } unhighlight() { this.dropZoneOverlay.style.display = 'none'; } async handleDrop(e) { const dt = e.dataTransfer; const files = dt.files; if (files.length > 0) { await this.startUpload(Array.from(files)); } } async handleFileSelect() { if (!this.fileInput.files.length) return; await this.startUpload(Array.from(this.fileInput.files)); } async startUpload(files) { this.uploadProgressContainer.style.display = 'block'; this.uploadProgressBar.style.width = '0%'; this.uploadProgressBar.className = 'progress-bar bg-primary-opacity-15 text-primary'; this.uploadProgressText.textContent = ''; this.pendingUploads = files; this.pendingUploadIdx = 0; this.overwriteAll = false; this.skipAll = false; await this.uploadFilesSequentially(); } async uploadFilesSequentially() { let completedFiles = 0; let currentFileIndex = 0; const updateProgress = () => { if (!this.pendingUploads || currentFileIndex >= this.pendingUploads.length) { this.uploadProgressBar.style.width = '100%'; this.uploadProgressBar.textContent = '100%'; this.uploadProgressText.textContent = 'Upload complete!'; return; } const progress = Math.round((completedFiles / this.pendingUploads.length) * 100); this.uploadProgressBar.style.width = progress + '%'; this.uploadProgressBar.textContent = progress + '%'; this.uploadProgressText.textContent = `Uploading ${this.pendingUploads[currentFileIndex].name} (${currentFileIndex + 1}/${this.pendingUploads.length})`; }; const processNextFile = async () => { if (currentFileIndex >= this.pendingUploads.length) { // All files processed this.uploadProgressBar.style.width = '100%'; this.uploadProgressBar.textContent = '100%'; this.uploadProgressBar.className = 'progress-bar bg-success-opacity-15 text-success'; this.uploadProgressText.textContent = 'Upload complete!'; // Reset state this.pendingUploads = null; this.pendingUploadIdx = null; this.overwriteAll = false; this.skipAll = false; // Hide progress after delay setTimeout(() => { this.uploadProgressContainer.style.display = 'none'; this.uploadProgressText.textContent = ''; this.uploadProgressBar.className = 'progress-bar bg-primary-opacity-15 text-primary'; }, 3000); // Refresh file list await this.roomManager.fileManager.fetchFiles(); return; } const file = this.pendingUploads[currentFileIndex]; const formData = new FormData(this.uploadForm); if (this.roomManager.currentPath) { formData.append('path', this.roomManager.currentPath); } formData.set('file', file); try { updateProgress(); let uploadFormData = formData; if (this.overwriteAll) { uploadFormData = new FormData(this.uploadForm); if (this.roomManager.currentPath) { uploadFormData.append('path', this.roomManager.currentPath); } uploadFormData.set('file', file); uploadFormData.append('overwrite', 'true'); } const response = await this.uploadFile(uploadFormData); if (response.success) { completedFiles++; currentFileIndex++; updateProgress(); await processNextFile(); } else if (response.error === 'File type not allowed') { this.handleFileTypeError(file); currentFileIndex++; updateProgress(); await processNextFile(); } else if (response.error === 'File exists') { const result = await this.handleFileExists(file, uploadFormData); if (result.continue) { currentFileIndex++; updateProgress(); await processNextFile(); } } } catch (error) { console.error('Upload error:', error); this.uploadProgressText.textContent = `Error uploading ${file.name}`; this.uploadProgressBar.className = 'progress-bar bg-danger-opacity-15 text-danger'; currentFileIndex++; updateProgress(); await processNextFile(); } }; await processNextFile(); } async uploadFile(formData) { const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); const response = await fetch(`/api/rooms/${this.roomManager.roomId}/files/upload`, { method: 'POST', headers: { 'X-CSRFToken': csrfToken }, body: formData }); const result = await response.json(); return { success: response.ok, error: result.error }; } handleFileTypeError(file) { const allowedTypes = [ 'Documents: PDF, DOCX, DOC, TXT, RTF, ODT, MD, CSV', 'Spreadsheets: XLSX, XLS, ODS, XLSM', 'Presentations: PPTX, PPT, ODP', 'Images: JPG, JPEG, PNG, GIF, BMP, SVG, WEBP, TIFF', 'Archives: ZIP, RAR, 7Z, TAR, GZ', 'Code/Text: PY, JS, HTML, CSS, JSON, XML, SQL, SH, BAT', 'Audio: MP3, WAV, OGG, M4A, FLAC', 'Video: MP4, AVI, MOV, WMV, FLV, MKV, WEBM', 'CAD/Design: DWG, DXF, AI, PSD, EPS, INDD', 'Other: EML, MSG, VCF, ICS' ].join('\n'); const uploadError = document.getElementById('uploadError'); const uploadErrorContent = document.getElementById('uploadErrorContent'); const newError = `
File type not allowed: ${file.name}
`; if (uploadErrorContent.innerHTML === '') { uploadErrorContent.innerHTML = newError + `
Allowed file types:
${allowedTypes}
`; } else { uploadErrorContent.innerHTML = newError + uploadErrorContent.innerHTML; } uploadError.style.display = 'block'; this.uploadProgressBar.className = 'progress-bar bg-danger-opacity-15 text-danger'; } async handleFileExists(file, formData) { if (this.overwriteAll) { formData.append('overwrite', 'true'); const response = await this.uploadFile(formData); return { continue: response.success }; } if (this.skipAll) { return { continue: true }; } const result = await this.roomManager.modalManager.showOverwriteModal(file.name); if (result === 'overwrite' || result === 'overwrite_all') { if (result === 'overwrite_all') { this.overwriteAll = true; } formData.append('overwrite', 'true'); const response = await this.uploadFile(formData); return { continue: response.success }; } else if (result === 'skip' || result === 'skip_all') { if (result === 'skip_all') { this.skipAll = true; } return { continue: true }; } return { continue: false }; } }