/** * @fileoverview Manages modal dialogs for the room interface. * This file handles: * - Modal initialization and configuration * - File operations (delete, rename, move) * - Folder creation * - File details display * - Overwrite confirmation * - Batch operations */ /** * @class ModalManager * @classdesc Manages all modal dialogs and their interactions in the room interface. * Handles file operations, folder creation, and various confirmation dialogs. */ export class ModalManager { /** * Creates a new ModalManager instance. * @param {RoomManager} roomManager - The parent RoomManager instance */ constructor(roomManager) { this.roomManager = roomManager; // Initialize modals this.deleteModal = new bootstrap.Modal(document.getElementById('deleteConfirmModal')); this.newFolderModal = new bootstrap.Modal(document.getElementById('newFolderModal')); this.renameModal = new bootstrap.Modal(document.getElementById('renameModal')); this.detailsModal = new bootstrap.Modal(document.getElementById('detailsModal')); this.overwriteModal = new bootstrap.Modal(document.getElementById('overwriteConfirmModal'), { backdrop: 'static', keyboard: false }); this.moveModal = new bootstrap.Modal(document.getElementById('moveModal')); this.initializeModals(); } /** * Initializes event listeners for all modals. * Sets up handlers for delete, new folder, rename, and move operations. */ initializeModals() { // Initialize delete modal if (this.roomManager.canDelete) { document.getElementById('confirmDeleteBtn').addEventListener('click', () => { this.roomManager.fileManager.deleteFileConfirmed(); }); } // Initialize new folder modal if (this.roomManager.canUpload) { document.getElementById('newFolderBtn').addEventListener('click', () => { document.getElementById('folderNameInput').value = ''; document.getElementById('folderError').textContent = ''; this.newFolderModal.show(); setTimeout(() => { document.getElementById('folderNameInput').focus(); }, 100); }); document.getElementById('createFolderBtn').addEventListener('click', () => { this.createFolder(); }); } // Initialize rename modal if (this.roomManager.canRename) { document.getElementById('confirmRenameBtn').addEventListener('click', () => { this.renameFile(); }); } // Initialize move modal if (this.roomManager.canMove) { document.getElementById('confirmMoveBtn').addEventListener('click', () => { this.roomManager.fileManager.moveFileConfirmed(); }); } } /** * Shows the delete confirmation modal for a single file. * @param {string} filename - The name of the file to delete * @param {string} [path=''] - The path of the file to delete */ showDeleteModal(filename, path = '') { console.log('[ModalManager] Showing delete modal for:', { filename, path }); const fileNameEl = document.getElementById('deleteFileName'); const labelEl = document.getElementById('deleteConfirmLabel'); const confirmBtn = document.getElementById('confirmDeleteBtn'); if (fileNameEl) fileNameEl.textContent = filename; if (labelEl) labelEl.textContent = 'Move to Trash'; // Store the file info in the FileManager this.roomManager.fileManager.fileToDelete = { name: filename, path: path }; // Set up the confirm button click handler if (confirmBtn) { confirmBtn.onclick = () => { console.log('[ModalManager] Delete confirmed for:', { filename, path }); this.roomManager.fileManager.deleteFile(filename, path); this.deleteModal.hide(); }; } this.deleteModal.show(); } /** * Shows the delete confirmation modal for multiple selected files. * Processes all selected checkboxes and prepares for batch deletion. */ showBatchDeleteModal() { const selectedCheckboxes = document.querySelectorAll('.select-item-checkbox:checked'); if (selectedCheckboxes.length === 0) return; const selectedItems = Array.from(selectedCheckboxes).map(cb => { const idx = parseInt(cb.dataset.index); console.log('[ModalManager] Processing checkbox with idx:', idx); console.log('[ModalManager] Current files:', this.roomManager.fileManager.currentFiles); if (isNaN(idx) || idx < 0 || idx >= this.roomManager.fileManager.currentFiles.length) { console.error('[ModalManager] Invalid index:', idx); return null; } const file = this.roomManager.fileManager.currentFiles[idx]; if (!file) { console.error('[ModalManager] No file found at index:', idx); return null; } console.log('[ModalManager] Found file:', file); return { id: file.id, name: file.name, path: file.path || '', type: file.type }; }).filter(item => item !== null); // Remove any null items if (selectedItems.length === 0) { console.error('[ModalManager] No valid items selected for deletion'); return; } // Update modal content const fileNameEl = document.getElementById('deleteFileName'); const labelEl = document.getElementById('deleteConfirmLabel'); if (fileNameEl) fileNameEl.textContent = `${selectedItems.length} item${selectedItems.length > 1 ? 's' : ''}`; if (labelEl) labelEl.textContent = 'Move to Trash'; // Store the items to delete in the FileManager this.roomManager.fileManager.batchDeleteItems = selectedItems; // Show the modal this.deleteModal.show(); } /** * Shows the rename modal for a file or folder. * @param {string} filename - The current name of the file/folder */ showRenameModal(filename) { document.getElementById('renameError').textContent = ''; const ext = filename.includes('.') ? filename.substring(filename.lastIndexOf('.')) : ''; const isFile = ext && filename.lastIndexOf('.') > 0; const inputGroup = document.getElementById('renameInputGroup'); if (isFile) { const base = filename.substring(0, filename.lastIndexOf('.')); inputGroup.innerHTML = `
${ext}
`; } else { inputGroup.innerHTML = ``; } this.renameModal.show(); setTimeout(() => { document.getElementById('renameInput').focus(); }, 100); } /** * Shows the file details modal. * @param {Object} file - The file object containing details to display */ showDetailsModal(file) { const icon = file.type === 'folder' ? `` : ``; const uploaderPic = file.uploader_profile_pic ? `/uploads/profile_pics/${file.uploader_profile_pic}` : '/static/default-avatar.png'; const detailsHtml = `
${icon}
${file.name}
${file.type === 'folder' ? 'Folder' : 'File'}
Profile Picture ${file.uploaded_by || '-'}
${this.roomManager.viewManager.formatDate(file.modified)}

Room:
Path: ${(file.path ? file.path + '/' : '') + file.name}
Size: ${this.roomManager.viewManager.formatFileSize(file.size)}
Uploaded at: ${file.uploaded_at ? new Date(file.uploaded_at).toLocaleString() : '-'}
`; document.getElementById('detailsModalBody').innerHTML = detailsHtml; this.detailsModal.show(); } /** * Shows the overwrite confirmation modal. * @param {string} filename - The name of the file that would be overwritten * @returns {Promise} A promise that resolves with the user's choice ('skip', 'skip_all', 'overwrite', or 'overwrite_all') */ showOverwriteModal(filename) { return new Promise((resolve) => { const fileNameEl = document.getElementById('overwriteFileName'); if (fileNameEl) fileNameEl.textContent = filename; const skipBtn = document.getElementById('skipOverwriteBtn'); const skipAllBtn = document.getElementById('skipAllOverwriteBtn'); const overwriteBtn = document.getElementById('confirmOverwriteBtn'); const overwriteAllBtn = document.getElementById('confirmAllOverwriteBtn'); const handleResult = (result) => { this.overwriteModal.hide(); resolve(result); }; skipBtn.onclick = () => handleResult('skip'); skipAllBtn.onclick = () => handleResult('skip_all'); overwriteBtn.onclick = () => handleResult('overwrite'); overwriteAllBtn.onclick = () => handleResult('overwrite_all'); this.overwriteModal.show(); }); } /** * Shows the move file modal. * @param {string} fileId - The ID of the file to move * @param {string} path - The current path of the file */ showMoveModal(fileId, path) { console.log('[ModalManager] Showing move modal for file:', { fileId, path }); document.getElementById('moveError').textContent = ''; // Set the file to move in the FileManager this.roomManager.fileManager.fileToMove = fileId; this.roomManager.fileManager.fileToMovePath = path; console.log('[ModalManager] Set fileToMove:', this.roomManager.fileManager.fileToMove); console.log('[ModalManager] Set fileToMovePath:', this.roomManager.fileManager.fileToMovePath); fetch(`/api/rooms/${this.roomManager.roomId}/folders`) .then(r => r.json()) .then(folders => { console.log('[ModalManager] Fetched folders:', folders); const select = document.getElementById('moveTargetFolder'); select.innerHTML = ''; folders.sort((a, b) => { if (!a) return -1; if (!b) return 1; return a.localeCompare(b); }); folders.forEach(folderPath => { if (folderPath === path || (path && folderPath.startsWith(path + '/'))) { return; } const pathParts = folderPath.split('/'); const displayName = pathParts[pathParts.length - 1]; const indent = '    '.repeat(pathParts.length - 1); select.innerHTML += ``; }); console.log('[ModalManager] Showing move modal'); this.moveModal.show(); }) .catch(error => { console.error('[ModalManager] Error fetching folders:', error); document.getElementById('moveError').textContent = 'Failed to load folders. Please try again.'; }); } /** * Creates a new folder in the current path. * @async */ async createFolder() { const folderName = document.getElementById('folderNameInput').value.trim(); if (!folderName) { document.getElementById('folderError').textContent = 'Folder name is required.'; return; } const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); try { const response = await fetch(`/api/rooms/${this.roomManager.roomId}/folders`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, body: JSON.stringify({ name: folderName, path: this.roomManager.currentPath }) }); const result = await response.json(); if (result.success) { this.newFolderModal.hide(); await this.roomManager.fileManager.fetchFiles(); } else { if (result.error === 'A folder with this name exists in the trash') { document.getElementById('folderError').textContent = `Cannot create folder "${folderName}" because this name is currently in the trash. Please restore or permanently delete the trashed item first.`; } else if (result.error === 'A folder with this name already exists in this location') { document.getElementById('folderError').textContent = `A folder named "${folderName}" already exists in this location. Please choose a different name.`; } else { document.getElementById('folderError').textContent = result.error || 'Failed to create folder.'; } } } catch (error) { document.getElementById('folderError').textContent = 'Failed to create folder.'; } } /** * Renames a file or folder. * @async */ async renameFile() { const newName = document.getElementById('renameInput').value.trim(); if (!newName) { document.getElementById('renameError').textContent = 'New name is required.'; return; } const result = await this.roomManager.fileManager.renameFile( this.renameTarget, newName, this.roomManager.currentPath ); if (result.success) { this.renameModal.hide(); await this.roomManager.fileManager.fetchFiles(); } else { document.getElementById('renameError').textContent = result.error || 'Rename failed.'; } } }