372 lines
16 KiB
JavaScript
372 lines
16 KiB
JavaScript
/**
|
|
* @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 = `<div class="input-group"><input type="text" id="renameInput" class="form-control" value="${base}" autocomplete="off" /><span class="input-group-text">${ext}</span></div>`;
|
|
} else {
|
|
inputGroup.innerHTML = `<input type="text" id="renameInput" class="form-control" value="${filename}" autocomplete="off" />`;
|
|
}
|
|
|
|
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'
|
|
? `<i class='fas fa-folder' style='font-size:2.2rem;color:var(--primary-color);'></i>`
|
|
: `<i class='fas fa-file-alt' style='font-size:2.2rem;color:var(--secondary-color);'></i>`;
|
|
|
|
const uploaderPic = file.uploader_profile_pic
|
|
? `/uploads/profile_pics/${file.uploader_profile_pic}`
|
|
: '/static/default-avatar.png';
|
|
|
|
const detailsHtml = `
|
|
<div class='d-flex align-items-center gap-3 mb-3'>
|
|
<div>${icon}</div>
|
|
<div style='min-width:0;'>
|
|
<div class='fw-bold' style='font-size:1.1rem;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;' title='${file.name}'>${file.name}</div>
|
|
<div class='text-muted small'>${file.type === 'folder' ? 'Folder' : 'File'}</div>
|
|
</div>
|
|
</div>
|
|
<div class='mb-2 d-flex align-items-center gap-2'>
|
|
<img src='${uploaderPic}' alt='Profile Picture' class='rounded-circle border' style='width:28px;height:28px;object-fit:cover;'>
|
|
<span class='fw-semibold' style='font-size:0.98rem;'>${file.uploaded_by || '-'}</span>
|
|
</div>
|
|
<div class='mb-2 text-muted' style='font-size:0.92rem;'><i class='far fa-clock me-1'></i>${this.roomManager.viewManager.formatDate(file.modified)}</div>
|
|
<hr style='margin:0.7rem 0 0.5rem 0; border-color:var(--border-light);'>
|
|
<div style='font-size:0.91rem;color:var(--text-muted);'><strong style='color:var(--primary-color);'>Room:</strong> <button class='btn btn-sm' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='window.location.href=\"/rooms/${this.roomManager.roomId}\"'><i class='fas fa-door-open me-1'></i>${this.roomManager.roomName}</button></div>
|
|
<div style='font-size:0.91rem;color:var(--text-muted);'><strong style='color:var(--primary-color);'>Path:</strong> <span style='word-break:break-all;'>${(file.path ? file.path + '/' : '') + file.name}</span></div>
|
|
<div style='font-size:0.91rem;color:var(--text-muted);'><strong style='color:var(--primary-color);'>Size:</strong> ${this.roomManager.viewManager.formatFileSize(file.size)}</div>
|
|
<div style='font-size:0.91rem;color:var(--text-muted);'><strong style='color:var(--primary-color);'>Uploaded at:</strong> ${file.uploaded_at ? new Date(file.uploaded_at).toLocaleString() : '-'}</div>
|
|
`;
|
|
|
|
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<string>} 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 = '<option value="">Root Folder</option>';
|
|
|
|
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 += `<option value="${folderPath}">${indent}${displayName}</option>`;
|
|
});
|
|
|
|
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.';
|
|
}
|
|
}
|
|
}
|