- Extract UI components into separate JS files - Centralize configuration values in config.js - Introduce a dedicated logger module - Improve file tree drag-and-drop and undo functionality - Refactor modal handling to a single manager - Add URL routing support for SPA navigation - Implement view mode for read-only access
		
			
				
	
	
		
			170 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * Unified Modal Manager
 | |
|  * Handles showing and hiding a Bootstrap modal for confirmations and prompts.
 | |
|  * Uses a single reusable modal element to prevent double-opening issues.
 | |
|  */
 | |
| class ModalManager {
 | |
|     constructor(modalId) {
 | |
|         this.modalElement = document.getElementById(modalId);
 | |
|         if (!this.modalElement) {
 | |
|             console.error(`Modal element with id "${modalId}" not found`);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         this.modal = new bootstrap.Modal(this.modalElement, {
 | |
|             backdrop: 'static',
 | |
|             keyboard: true
 | |
|         });
 | |
| 
 | |
|         this.messageElement = this.modalElement.querySelector('#confirmationMessage');
 | |
|         this.inputElement = this.modalElement.querySelector('#confirmationInput');
 | |
|         this.confirmButton = this.modalElement.querySelector('#confirmButton');
 | |
|         this.cancelButton = this.modalElement.querySelector('[data-bs-dismiss="modal"]');
 | |
|         this.titleElement = this.modalElement.querySelector('.modal-title');
 | |
|         this.currentResolver = null;
 | |
|         this.isShowing = false;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Show a confirmation dialog
 | |
|      * @param {string} message - The message to display
 | |
|      * @param {string} title - The dialog title
 | |
|      * @param {boolean} isDangerous - Whether this is a dangerous action (shows red button)
 | |
|      * @returns {Promise<boolean>} - Resolves to true if confirmed, false/null if cancelled
 | |
|      */
 | |
|     confirm(message, title = 'Confirmation', isDangerous = false) {
 | |
|         return new Promise((resolve) => {
 | |
|             // Prevent double-opening
 | |
|             if (this.isShowing) {
 | |
|                 console.warn('Modal is already showing, ignoring duplicate request');
 | |
|                 resolve(null);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             this.isShowing = true;
 | |
|             this.currentResolver = resolve;
 | |
|             this.titleElement.textContent = title;
 | |
|             this.messageElement.textContent = message;
 | |
|             this.inputElement.style.display = 'none';
 | |
| 
 | |
|             // Update button styling based on danger level
 | |
|             if (isDangerous) {
 | |
|                 this.confirmButton.className = 'btn btn-danger';
 | |
|                 this.confirmButton.textContent = 'Delete';
 | |
|             } else {
 | |
|                 this.confirmButton.className = 'btn btn-primary';
 | |
|                 this.confirmButton.textContent = 'OK';
 | |
|             }
 | |
| 
 | |
|             // Set up event handlers
 | |
|             this.confirmButton.onclick = (e) => {
 | |
|                 e.preventDefault();
 | |
|                 e.stopPropagation();
 | |
|                 this._handleConfirm(false);
 | |
|             };
 | |
| 
 | |
|             // Handle modal hidden event for cleanup
 | |
|             this.modalElement.addEventListener('hidden.bs.modal', () => {
 | |
|                 if (this.currentResolver) {
 | |
|                     this._handleCancel();
 | |
|                 }
 | |
|             }, { once: true });
 | |
| 
 | |
|             this.modal.show();
 | |
| 
 | |
|             // Focus confirm button after modal is shown
 | |
|             this.modalElement.addEventListener('shown.bs.modal', () => {
 | |
|                 this.confirmButton.focus();
 | |
|             }, { once: true });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Show a prompt dialog (input dialog)
 | |
|      * @param {string} message - The message/label to display
 | |
|      * @param {string} defaultValue - The default input value
 | |
|      * @param {string} title - The dialog title
 | |
|      * @returns {Promise<string|null>} - Resolves to input value if confirmed, null if cancelled
 | |
|      */
 | |
|     prompt(message, defaultValue = '', title = 'Input') {
 | |
|         return new Promise((resolve) => {
 | |
|             // Prevent double-opening
 | |
|             if (this.isShowing) {
 | |
|                 console.warn('Modal is already showing, ignoring duplicate request');
 | |
|                 resolve(null);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             this.isShowing = true;
 | |
|             this.currentResolver = resolve;
 | |
|             this.titleElement.textContent = title;
 | |
|             this.messageElement.textContent = message;
 | |
|             this.inputElement.style.display = 'block';
 | |
|             this.inputElement.value = defaultValue;
 | |
| 
 | |
|             // Reset button to primary style for prompts
 | |
|             this.confirmButton.className = 'btn btn-primary';
 | |
|             this.confirmButton.textContent = 'OK';
 | |
| 
 | |
|             // Set up event handlers
 | |
|             this.confirmButton.onclick = (e) => {
 | |
|                 e.preventDefault();
 | |
|                 e.stopPropagation();
 | |
|                 this._handleConfirm(true);
 | |
|             };
 | |
| 
 | |
|             // Handle Enter key in input
 | |
|             this.inputElement.onkeydown = (e) => {
 | |
|                 if (e.key === 'Enter') {
 | |
|                     e.preventDefault();
 | |
|                     this._handleConfirm(true);
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|             // Handle modal hidden event for cleanup
 | |
|             this.modalElement.addEventListener('hidden.bs.modal', () => {
 | |
|                 if (this.currentResolver) {
 | |
|                     this._handleCancel();
 | |
|                 }
 | |
|             }, { once: true });
 | |
| 
 | |
|             this.modal.show();
 | |
| 
 | |
|             // Focus and select input after modal is shown
 | |
|             this.modalElement.addEventListener('shown.bs.modal', () => {
 | |
|                 this.inputElement.focus();
 | |
|                 this.inputElement.select();
 | |
|             }, { once: true });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     _handleConfirm(isPrompt) {
 | |
|         if (this.currentResolver) {
 | |
|             const value = isPrompt ? this.inputElement.value.trim() : true;
 | |
|             const resolver = this.currentResolver;
 | |
|             this._cleanup();
 | |
|             resolver(value);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     _handleCancel() {
 | |
|         if (this.currentResolver) {
 | |
|             const resolver = this.currentResolver;
 | |
|             this._cleanup();
 | |
|             resolver(null);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     _cleanup() {
 | |
|         this.confirmButton.onclick = null;
 | |
|         this.inputElement.onkeydown = null;
 | |
|         this.currentResolver = null;
 | |
|         this.isShowing = false;
 | |
|         this.modal.hide();
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Make it globally available
 | |
| window.ConfirmationManager = new ModalManager('confirmationModal');
 | |
| window.ModalManager = window.ConfirmationManager; // Alias for clarity
 |