- Add API endpoint and handler to delete collections - Introduce LoadingSpinner component for async operations - Show loading spinners during file loading and preview rendering - Enhance modal accessibility by removing aria-hidden attribute - Refactor delete functionality to distinguish between collections and files/folders - Remove unused collection definitions from config
		
			
				
	
	
		
			152 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			152 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * Loading Spinner Component
 | |
|  * Displays a loading overlay with spinner for async operations
 | |
|  */
 | |
| 
 | |
| class LoadingSpinner {
 | |
|     /**
 | |
|      * Create a loading spinner for a container
 | |
|      * @param {string|HTMLElement} container - Container element or ID
 | |
|      * @param {string} message - Optional loading message
 | |
|      */
 | |
|     constructor(container, message = 'Loading...') {
 | |
|         this.container = typeof container === 'string'
 | |
|             ? document.getElementById(container)
 | |
|             : container;
 | |
| 
 | |
|         if (!this.container) {
 | |
|             Logger.error('LoadingSpinner: Container not found');
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         this.message = message;
 | |
|         this.overlay = null;
 | |
|         this.isShowing = false;
 | |
|         this.showTime = null; // Track when spinner was shown
 | |
|         this.minDisplayTime = 300; // Minimum time to show spinner (ms)
 | |
| 
 | |
|         // Ensure container has position relative for absolute positioning
 | |
|         const position = window.getComputedStyle(this.container).position;
 | |
|         if (position === 'static') {
 | |
|             this.container.style.position = 'relative';
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Show the loading spinner
 | |
|      * @param {string} message - Optional custom message
 | |
|      */
 | |
|     show(message = null) {
 | |
|         if (this.isShowing) return;
 | |
| 
 | |
|         // Record when spinner was shown
 | |
|         this.showTime = Date.now();
 | |
| 
 | |
|         // Create overlay if it doesn't exist
 | |
|         if (!this.overlay) {
 | |
|             this.overlay = this.createOverlay(message || this.message);
 | |
|             this.container.appendChild(this.overlay);
 | |
|         } else {
 | |
|             // Update message if provided
 | |
|             if (message) {
 | |
|                 const textElement = this.overlay.querySelector('.loading-text');
 | |
|                 if (textElement) {
 | |
|                     textElement.textContent = message;
 | |
|                 }
 | |
|             }
 | |
|             this.overlay.classList.remove('hidden');
 | |
|         }
 | |
| 
 | |
|         this.isShowing = true;
 | |
|         Logger.debug(`Loading spinner shown: ${message || this.message}`);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Hide the loading spinner
 | |
|      * Ensures minimum display time for better UX
 | |
|      */
 | |
|     hide() {
 | |
|         if (!this.isShowing || !this.overlay) return;
 | |
| 
 | |
|         // Calculate how long the spinner has been showing
 | |
|         const elapsed = Date.now() - this.showTime;
 | |
|         const remaining = Math.max(0, this.minDisplayTime - elapsed);
 | |
| 
 | |
|         // If minimum time hasn't elapsed, delay hiding
 | |
|         if (remaining > 0) {
 | |
|             setTimeout(() => {
 | |
|                 this.overlay.classList.add('hidden');
 | |
|                 this.isShowing = false;
 | |
|                 Logger.debug('Loading spinner hidden');
 | |
|             }, remaining);
 | |
|         } else {
 | |
|             this.overlay.classList.add('hidden');
 | |
|             this.isShowing = false;
 | |
|             Logger.debug('Loading spinner hidden');
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Remove the loading spinner from DOM
 | |
|      */
 | |
|     destroy() {
 | |
|         if (this.overlay && this.overlay.parentNode) {
 | |
|             this.overlay.parentNode.removeChild(this.overlay);
 | |
|             this.overlay = null;
 | |
|         }
 | |
|         this.isShowing = false;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create the overlay element
 | |
|      * @param {string} message - Loading message
 | |
|      * @returns {HTMLElement} The overlay element
 | |
|      */
 | |
|     createOverlay(message) {
 | |
|         const overlay = document.createElement('div');
 | |
|         overlay.className = 'loading-overlay';
 | |
| 
 | |
|         const content = document.createElement('div');
 | |
|         content.className = 'loading-content';
 | |
| 
 | |
|         const spinner = document.createElement('div');
 | |
|         spinner.className = 'loading-spinner';
 | |
| 
 | |
|         const text = document.createElement('div');
 | |
|         text.className = 'loading-text';
 | |
|         text.textContent = message;
 | |
| 
 | |
|         content.appendChild(spinner);
 | |
|         content.appendChild(text);
 | |
|         overlay.appendChild(content);
 | |
| 
 | |
|         return overlay;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Update the loading message
 | |
|      * @param {string} message - New message
 | |
|      */
 | |
|     updateMessage(message) {
 | |
|         this.message = message;
 | |
|         if (this.overlay && this.isShowing) {
 | |
|             const textElement = this.overlay.querySelector('.loading-text');
 | |
|             if (textElement) {
 | |
|                 textElement.textContent = message;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if spinner is currently showing
 | |
|      * @returns {boolean} True if showing
 | |
|      */
 | |
|     isVisible() {
 | |
|         return this.isShowing;
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Make LoadingSpinner globally available
 | |
| window.LoadingSpinner = LoadingSpinner;
 | |
| 
 |