/**
* @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'}
${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-CSRFToken': 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.';
}
}
}