/** * Progress Bar Components * Centralized progress bar implementations for various UI contexts */ import { createDialog } from './dialogManager.js'; /** * Create a basic progress bar component * @param {string} [labelText='Progress...'] - Label text for the progress bar * @returns {Object} Progress bar component with container and control methods */ export function createProgressBar(labelText = 'Progress...') { const container = document.createElement('div'); container.style.cssText = ` margin-top: 10px; display: none; `; const label = document.createElement('div'); label.style.cssText = ` color: #fff; font-size: 12px; margin-bottom: 5px; `; label.textContent = labelText; const barOuter = document.createElement('div'); barOuter.style.cssText = ` width: 100%; height: 20px; background: #333; border: 1px solid #555; border-radius: 4px; overflow: hidden; `; const barInner = document.createElement('div'); barInner.style.cssText = ` height: 100%; background: linear-gradient(90deg, #9C27B0, #E91E63); width: 0%; transition: width 0.3s ease; position: relative; `; const text = document.createElement('div'); text.style.cssText = ` position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 11px; font-weight: bold; text-shadow: 1px 1px 2px rgba(0,0,0,0.7); `; text.textContent = '0%'; barInner.appendChild(text); barOuter.appendChild(barInner); container.appendChild(label); container.appendChild(barOuter); return { container, show: () => container.style.display = 'block', hide: () => container.style.display = 'none', update: (percent) => { const clamped = Math.max(0, Math.min(100, percent)); barInner.style.width = `${clamped}%`; text.textContent = `${Math.round(clamped)}%`; }, setLabel: (newLabel) => label.textContent = newLabel }; } /** * Create a progress dialog * @param {string} [title='Progress'] - Dialog title * @param {string} [initialMessage='Processing...'] - Initial progress message * @returns {Object} - Dialog object with updateProgress method */ export function createProgressDialog(title = 'Progress', initialMessage = 'Processing...') { const progressContainer = document.createElement('div'); progressContainer.style.cssText = ` padding: 30px; min-width: 400px; text-align: center; `; const messageElement = document.createElement('div'); messageElement.style.cssText = ` margin-bottom: 20px; color: #fff; font-size: 14px; min-height: 20px; `; messageElement.textContent = initialMessage; const progressBarContainer = document.createElement('div'); progressBarContainer.style.cssText = ` width: 100%; height: 20px; background: #444; border-radius: 10px; overflow: hidden; margin-bottom: 15px; position: relative; `; const progressBar = document.createElement('div'); progressBar.style.cssText = ` height: 100%; background: linear-gradient(90deg, #4CAF50, #66BB6A); width: 0%; transition: width 0.3s ease; border-radius: 10px; `; const progressText = document.createElement('div'); progressText.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #fff; font-size: 12px; font-weight: bold; text-shadow: 1px 1px 2px rgba(0,0,0,0.7); `; progressText.textContent = '0%'; progressBarContainer.appendChild(progressBar); progressBarContainer.appendChild(progressText); progressContainer.appendChild(messageElement); progressContainer.appendChild(progressBarContainer); const dialog = createDialog({ title, content: progressContainer, width: '450px', height: 'auto', showFooter: false, closable: false // Prevent manual closing during progress }); // Add updateProgress method to dialog dialog.updateProgress = (percentage, message) => { const clampedPercentage = Math.max(0, Math.min(100, percentage)); progressBar.style.width = `${clampedPercentage}%`; progressText.textContent = `${Math.round(clampedPercentage)}%`; if (message) { messageElement.textContent = message; } // Add pulse effect for active progress if (clampedPercentage < 100) { progressBar.style.animation = 'progressPulse 2s ease-in-out infinite'; } else { progressBar.style.animation = 'none'; progressBar.style.background = '#4CAF50'; messageElement.style.color = '#4CAF50'; } }; // Add CSS animation for progress bar pulse effect if (!document.getElementById('progress-bar-styles')) { const style = document.createElement('style'); style.id = 'progress-bar-styles'; style.textContent = ` @keyframes progressPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } } `; document.head.appendChild(style); } return dialog; } /** * Create an inline progress bar component for embedding in UI * @param {Object} [options={}] - Configuration options * @param {string} [options.title] - Progress title * @param {string} [options.initialMessage] - Initial progress message * @param {boolean} [options.showPercentage] - Whether to show percentage text * @returns {Object} - Progress component with container and control methods */ export function createInlineProgressBar(options = {}) { const { title = 'Progress', initialMessage = 'Processing...', showPercentage = true } = options; const container = document.createElement('div'); container.style.cssText = ` background: #2a2a2a; border: 1px solid #555; border-radius: 8px; padding: 15px; margin: 10px 0; display: none; transition: all 0.3s ease; `; const titleElement = document.createElement('div'); titleElement.style.cssText = ` color: #fff; font-weight: bold; margin-bottom: 8px; font-size: 13px; `; titleElement.textContent = title; const messageElement = document.createElement('div'); messageElement.style.cssText = ` color: #ccc; font-size: 12px; margin-bottom: 12px; min-height: 16px; `; messageElement.textContent = initialMessage; const progressBarContainer = document.createElement('div'); progressBarContainer.style.cssText = ` width: 100%; height: 16px; background: #444; border-radius: 8px; overflow: hidden; position: relative; `; const progressBar = document.createElement('div'); progressBar.style.cssText = ` height: 100%; background: linear-gradient(90deg, #4CAF50, #66BB6A); width: 0%; transition: width 0.3s ease; border-radius: 8px; `; let progressText = null; if (showPercentage) { progressText = document.createElement('div'); progressText.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #fff; font-size: 10px; font-weight: bold; text-shadow: 1px 1px 2px rgba(0,0,0,0.8); pointer-events: none; `; progressText.textContent = '0%'; progressBarContainer.appendChild(progressText); } progressBarContainer.appendChild(progressBar); container.appendChild(titleElement); container.appendChild(messageElement); container.appendChild(progressBarContainer); // Add CSS animation for progress bar pulse effect if not already added if (!document.getElementById('inline-progress-styles')) { const style = document.createElement('style'); style.id = 'inline-progress-styles'; style.textContent = ` @keyframes inlineProgressPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.8; } } .inline-progress-active { animation: inlineProgressPulse 2s ease-in-out infinite; } .inline-progress-complete { background: #4CAF50 !important; animation: none !important; } `; document.head.appendChild(style); } // Control methods const component = { container, show() { container.style.display = 'block'; }, hide() { container.style.display = 'none'; }, updateProgress(percentage, message) { const clampedPercentage = Math.max(0, Math.min(100, percentage)); progressBar.style.width = `${clampedPercentage}%`; if (progressText) { progressText.textContent = `${Math.round(clampedPercentage)}%`; } if (message) { messageElement.textContent = message; } // Visual feedback for progress state if (clampedPercentage < 100) { progressBar.classList.add('inline-progress-active'); progressBar.classList.remove('inline-progress-complete'); } else { progressBar.classList.remove('inline-progress-active'); progressBar.classList.add('inline-progress-complete'); } }, updateTitle(newTitle) { titleElement.textContent = newTitle; }, updateMessage(message) { messageElement.textContent = message; }, reset() { progressBar.style.width = '0%'; if (progressText) { progressText.textContent = '0%'; } messageElement.textContent = initialMessage; progressBar.classList.remove('inline-progress-active', 'inline-progress-complete'); } }; return component; } /** * Create a dataset-specific progress dialog with image preview * @param {string} [title='Generating Descriptions'] - Dialog title * @returns {Object} - Dialog with progress tracking and preview elements */ export function createDatasetProgressDialog(title = 'Generating Descriptions') { const progressOverlay = document.createElement('div'); progressOverlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 10001; display: flex; align-items: center; justify-content: center; `; const progressDialog = document.createElement('div'); progressDialog.style.cssText = ` background: #2d2d2d; border-radius: 8px; padding: 20px; min-width: 500px; max-width: 700px; border: 1px solid #555; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); `; const progressTitle = document.createElement('h3'); progressTitle.textContent = title; progressTitle.style.cssText = ` color: #fff; margin: 0 0 15px 0; font-size: 16px; `; const progressText = document.createElement('div'); progressText.style.cssText = ` color: #fff; margin-bottom: 10px; font-size: 14px; `; progressText.textContent = 'Processing...'; const progressBar = document.createElement('div'); progressBar.style.cssText = ` width: 100%; height: 20px; background: #555; border-radius: 10px; overflow: hidden; margin-bottom: 15px; `; const progressFill = document.createElement('div'); progressFill.style.cssText = ` width: 0%; height: 100%; background: #4CAF50; transition: width 0.3s; `; progressBar.appendChild(progressFill); const statusText = document.createElement('div'); statusText.style.cssText = ` color: #aaa; font-size: 12px; margin-bottom: 15px; font-family: monospace; `; // Preview container for image and text const previewContainer = document.createElement('div'); previewContainer.style.cssText = ` background: #1a1a1a; border-radius: 4px; padding: 10px; margin-bottom: 15px; max-height: 300px; overflow-y: auto; `; const previewLabel = document.createElement('div'); previewLabel.style.cssText = ` color: #888; font-size: 11px; margin-bottom: 8px; text-transform: uppercase; `; previewLabel.textContent = 'Last Generated'; const imagePreview = document.createElement('img'); imagePreview.style.cssText = ` max-width: 100%; max-height: 150px; object-fit: contain; display: block; margin-bottom: 10px; border-radius: 4px; `; const textPreview = document.createElement('div'); textPreview.style.cssText = ` color: #ccc; font-size: 12px; line-height: 1.5; word-wrap: break-word; `; previewContainer.appendChild(previewLabel); previewContainer.appendChild(imagePreview); previewContainer.appendChild(textPreview); const cancelBtn = document.createElement('button'); cancelBtn.textContent = 'Cancel'; cancelBtn.style.cssText = ` background: #f44336; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 13px; width: 100%; `; progressDialog.appendChild(progressTitle); progressDialog.appendChild(progressText); progressDialog.appendChild(progressBar); progressDialog.appendChild(statusText); progressDialog.appendChild(previewContainer); progressDialog.appendChild(cancelBtn); progressOverlay.appendChild(progressDialog); // Close method const close = () => { if (progressOverlay.parentNode) { progressOverlay.parentNode.removeChild(progressOverlay); } }; // Show method const show = () => { document.body.appendChild(progressOverlay); }; return { dialog: progressOverlay, elements: { titleText: progressTitle, progressText, progressBar, progressFill, statusText, imagePreview, textPreview, cancelBtn }, show, close }; } /** * Create an inline HTML progress indicator (for quick inline use) * @param {number} current - Current progress value * @param {number} total - Total value * @param {string} [message=''] - Progress message * @returns {string} HTML string for progress display */ export function createInlineProgressHTML(current, total, message = '') { const percentage = total > 0 ? Math.round((current / total) * 100) : 0; return `