refactor: Modularize UI components and utilities
- 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
This commit is contained in:
		| @@ -1,68 +1,169 @@ | ||||
| /** | ||||
|  * Confirmation Modal Manager | ||||
|  * 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 Confirmation { | ||||
| class ModalManager { | ||||
|     constructor(modalId) { | ||||
|         this.modalElement = document.getElementById(modalId); | ||||
|         this.modal = new bootstrap.Modal(this.modalElement); | ||||
|         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(message, title, showInput = false, defaultValue = '') { | ||||
|     /** | ||||
|      * 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'; | ||||
|  | ||||
|             if (showInput) { | ||||
|                 this.inputElement.style.display = 'block'; | ||||
|                 this.inputElement.value = defaultValue; | ||||
|                 this.inputElement.focus(); | ||||
|             // Update button styling based on danger level | ||||
|             if (isDangerous) { | ||||
|                 this.confirmButton.className = 'btn btn-danger'; | ||||
|                 this.confirmButton.textContent = 'Delete'; | ||||
|             } else { | ||||
|                 this.inputElement.style.display = 'none'; | ||||
|                 this.confirmButton.className = 'btn btn-primary'; | ||||
|                 this.confirmButton.textContent = 'OK'; | ||||
|             } | ||||
|  | ||||
|             this.confirmButton.onclick = () => this._handleConfirm(showInput); | ||||
|             this.modalElement.addEventListener('hidden.bs.modal', () => this._handleCancel(), { once: true }); | ||||
|              | ||||
|             // 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 : true; | ||||
|             this.currentResolver(value); | ||||
|             const value = isPrompt ? this.inputElement.value.trim() : true; | ||||
|             const resolver = this.currentResolver; | ||||
|             this._cleanup(); | ||||
|             resolver(value); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     _handleCancel() { | ||||
|         if (this.currentResolver) { | ||||
|             this.currentResolver(null); // Resolve with null for cancellation | ||||
|             const resolver = this.currentResolver; | ||||
|             this._cleanup(); | ||||
|             resolver(null); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     _cleanup() { | ||||
|         this.confirmButton.onclick = null; | ||||
|         this.modal.hide(); | ||||
|         this.inputElement.onkeydown = null; | ||||
|         this.currentResolver = null; | ||||
|     } | ||||
|  | ||||
|     confirm(message, title = 'Confirmation') { | ||||
|         return this._show(message, title, false); | ||||
|     } | ||||
|  | ||||
|     prompt(message, defaultValue = '', title = 'Prompt') { | ||||
|         return this._show(message, title, true, defaultValue); | ||||
|         this.isShowing = false; | ||||
|         this.modal.hide(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Make it globally available | ||||
| window.ConfirmationManager = new Confirmation('confirmationModal'); | ||||
| window.ConfirmationManager = new ModalManager('confirmationModal'); | ||||
| window.ModalManager = window.ConfirmationManager; // Alias for clarity | ||||
|   | ||||
		Reference in New Issue
	
	Block a user