From 26d9a9caa6febecf891dddfbae79bd16354ddfca Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Wed, 30 Apr 2025 13:20:44 +0800 Subject: [PATCH] refactor: streamline example images download functionality and UI updates --- py/routes/misc_routes.py | 68 +------ static/css/components/download-modal.css | 8 - static/css/components/modal.css | 84 ++++++-- static/css/components/progress-panel.css | 5 +- static/js/managers/ExampleImagesManager.js | 216 ++++++++++++--------- templates/components/modals.html | 21 +- templates/components/progress_panel.html | 31 ++- 7 files changed, 235 insertions(+), 198 deletions(-) diff --git a/py/routes/misc_routes.py b/py/routes/misc_routes.py index bc561529..588df51b 100644 --- a/py/routes/misc_routes.py +++ b/py/routes/misc_routes.py @@ -48,9 +48,6 @@ class MiscRoutes: app.router.add_post('/api/pause-example-images', MiscRoutes.pause_example_images) app.router.add_post('/api/resume-example-images', MiscRoutes.resume_example_images) - # Folder browser route - app.router.add_post('/api/browse-folder', MiscRoutes.browse_folder) - @staticmethod async def update_settings(request): """Update application settings""" @@ -67,17 +64,10 @@ class MiscRoutes: 'error': f"Path does not exist: {value}" }) - # Add static route for example images path if it changed + # Path changed - server restart required for new path to take effect old_path = settings.get('example_images_path') if old_path != value: - # We need to add the new static route - # Note: we can't remove old routes in aiohttp, but we can create a new one - app = request.app - try: - app.router.add_static('/example_images_static', value) - logger.info(f"Added static route for example images: /example_images_static -> {value}") - except Exception as e: - logger.warning(f"Could not add static route for example images: {str(e)}") + logger.info(f"Example images path changed to {value} - server restart required") # Save to settings settings.set(key, value) @@ -174,7 +164,7 @@ class MiscRoutes: output_dir = data.get('output_dir') optimize = data.get('optimize', True) model_types = data.get('model_types', ['lora', 'checkpoint']) - delay = float(data.get('delay', 1.0)) + delay = float(data.get('delay', 0.2)) if not output_dir: return web.json_response({ @@ -494,55 +484,3 @@ class MiscRoutes: # Set download status to not downloading is_downloading = False - - @staticmethod - async def browse_folder(request): - """Open a folder browser dialog and return the selected path - - Expects a JSON body with: - { - "initial_dir": "string" // Optional initial directory - } - """ - try: - # Parse the request body - data = await request.json() - initial_dir = data.get('initial_dir', '') - - # If initial_dir doesn't exist, use a default path - if initial_dir and not os.path.isdir(initial_dir): - initial_dir = '' - - # Create a hidden root window for the dialog - root = tk.Tk() - root.withdraw() # Hide the root window - - # Show the folder selection dialog - folder_path = filedialog.askdirectory( - initialdir=initial_dir, - title="Select folder for example images" - ) - - # Destroy the root window - root.destroy() - - if folder_path: - # Convert to proper path format for the OS - folder_path = os.path.normpath(folder_path) - - return web.json_response({ - 'success': True, - 'folder': folder_path - }) - else: - return web.json_response({ - 'success': False, - 'error': 'No folder selected' - }) - - except Exception as e: - logger.error(f"Failed to browse folder: {e}", exc_info=True) - return web.json_response({ - 'success': False, - 'error': str(e) - }, status=500) diff --git a/static/css/components/download-modal.css b/static/css/components/download-modal.css index e220a104..80f5b1c3 100644 --- a/static/css/components/download-modal.css +++ b/static/css/components/download-modal.css @@ -190,14 +190,6 @@ border-color: var(--lora-border); } -/* Add disabled button styles */ -.primary-btn.disabled { - background-color: var(--border-color); - color: var(--text-color); - opacity: 0.7; - cursor: not-allowed; -} - /* Enhance the local badge to make it more noticeable */ .version-item.exists-locally { background: oklch(var(--lora-accent) / 0.05); diff --git a/static/css/components/modal.css b/static/css/components/modal.css index 36ce62bb..e51ba923 100644 --- a/static/css/components/modal.css +++ b/static/css/components/modal.css @@ -502,7 +502,7 @@ input:checked + .toggle-slider:before { gap: var(--space-2); } -.download-buttons .primary-btn { +.primary-btn { display: flex; align-items: center; gap: 8px; @@ -516,30 +516,80 @@ input:checked + .toggle-slider:before { font-size: 0.95em; } -.download-buttons .primary-btn:hover { - background-color: oklch(var(--lora-accent) / 0.9); +.primary-btn:hover { + background-color: oklch(from var(--lora-accent) l c h / 85%); + color: var(--lora-text); } -.path-browser-control { +/* Secondary button styles */ +.secondary-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background-color: var(--card-bg); + color: var(--text-color); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-sm); + cursor: pointer; + transition: all 0.2s; + font-size: 0.95em; +} + +.secondary-btn:hover { + background-color: var(--border-color); + color: var(--text-color); +} + +/* Disabled button styles */ +.primary-btn.disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: var(--lora-accent); + color: var(--lora-text); + pointer-events: none; +} + +.secondary-btn.disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} + +/* Dark theme specific button adjustments */ +[data-theme="dark"] .primary-btn:hover { + background-color: oklch(from var(--lora-accent) l c h / 75%); +} + +[data-theme="dark"] .secondary-btn { + background-color: var(--lora-surface); +} + +[data-theme="dark"] .secondary-btn:hover { + background-color: oklch(35% 0.02 256 / 0.98); +} + +.primary-btn.disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.path-control { display: flex; gap: 8px; + align-items: center; + width: 100%; } -.path-browser-control input[type="text"] { +.path-control input[type="text"] { flex: 1; -} - -.browse-btn { - white-space: nowrap; - padding: 6px 12px; - background-color: var(--button-bg); + padding: 6px 10px; + border-radius: var(--border-radius-xs); border: 1px solid var(--border-color); - border-radius: 4px; - cursor: pointer; -} - -.browse-btn:hover { - background-color: var(--button-hover-bg); + background-color: var(--lora-surface); + color: var(--text-color); + font-size: 0.95em; + height: 32px; } .primary-btn.disabled { diff --git a/static/css/components/progress-panel.css b/static/css/components/progress-panel.css index 11d085c7..2b24ff34 100644 --- a/static/css/components/progress-panel.css +++ b/static/css/components/progress-panel.css @@ -88,7 +88,8 @@ color: var(--text-color); } -.progress-container { +/* Use specific selectors to avoid conflicts with loading.css */ +.progress-panel .progress-container { width: 100%; background-color: var(--lora-border); border-radius: 4px; @@ -96,7 +97,7 @@ height: var(--space-1); } -.progress-bar { +.progress-panel .progress-bar { width: 0%; height: 100%; background-color: var(--lora-accent); diff --git a/static/js/managers/ExampleImagesManager.js b/static/js/managers/ExampleImagesManager.js index 1ac36a66..19ca8795 100644 --- a/static/js/managers/ExampleImagesManager.js +++ b/static/js/managers/ExampleImagesManager.js @@ -8,10 +8,14 @@ class ExampleImagesManager { this.isPaused = false; this.progressUpdateInterval = null; this.startTime = null; - this.progressPanel = document.getElementById('exampleImagesProgress'); + this.progressPanel = null; // Wait for DOM before initializing event listeners - document.addEventListener('DOMContentLoaded', () => this.initEventListeners()); + document.addEventListener('DOMContentLoaded', () => { + this.initEventListeners(); + // Initialize progress panel reference + this.progressPanel = document.getElementById('exampleImagesProgress'); + }); // Initialize download path field this.initializePathOptions(); @@ -22,14 +26,9 @@ class ExampleImagesManager { // Initialize event listeners for buttons initEventListeners() { - const startBtn = document.getElementById('startExampleDownloadBtn'); - if (startBtn) { - startBtn.onclick = () => this.startDownload(); - } - - const resumeBtn = document.getElementById('resumeExampleDownloadBtn'); - if (resumeBtn) { - resumeBtn.onclick = () => this.resumeDownload(); + const downloadBtn = document.getElementById('exampleImagesDownloadBtn'); + if (downloadBtn) { + downloadBtn.onclick = () => this.handleDownloadButton(); } } @@ -49,12 +48,6 @@ class ExampleImagesManager { this.updateDownloadButtonState(false); } - // Add event listener to the browse button - const browseButton = document.getElementById('browseExampleImagesPath'); - if (browseButton) { - browseButton.addEventListener('click', () => this.browseFolderDialog()); - } - // Add event listener to validate path input pathInput.addEventListener('input', async () => { const hasPath = pathInput.value.trim() !== ''; @@ -98,47 +91,29 @@ class ExampleImagesManager { // Method to update download button state updateDownloadButtonState(enabled) { - const startBtn = document.getElementById('startExampleDownloadBtn'); - if (startBtn) { + const downloadBtn = document.getElementById('exampleImagesDownloadBtn'); + if (downloadBtn) { if (enabled) { - startBtn.classList.remove('disabled'); - startBtn.disabled = false; + downloadBtn.classList.remove('disabled'); + downloadBtn.disabled = false; } else { - startBtn.classList.add('disabled'); - startBtn.disabled = true; + downloadBtn.classList.add('disabled'); + downloadBtn.disabled = true; } } } - // Method to open folder browser dialog - async browseFolderDialog() { - try { - const response = await fetch('/api/browse-folder', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - initial_dir: getStorageItem('example_images_path', '') - }) - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const data = await response.json(); - - if (data.success && data.folder) { - const pathInput = document.getElementById('exampleImagesPath'); - pathInput.value = data.folder; - setStorageItem('example_images_path', data.folder); - this.updateDownloadButtonState(true); - showToast('Example images path updated successfully', 'success'); - } - } catch (error) { - console.error('Failed to browse folder:', error); - showToast('Failed to browse folder. Please ensure the server supports this feature.', 'error'); + // Method to handle download button click based on current state + async handleDownloadButton() { + if (this.isDownloading && this.isPaused) { + // If download is paused, resume it + this.resumeDownload(); + } else if (!this.isDownloading) { + // If no download in progress, start a new one + this.startDownload(); + } else { + // If download is in progress, show info toast + showToast('Download already in progress', 'info'); } } @@ -151,13 +126,27 @@ class ExampleImagesManager { this.isDownloading = data.is_downloading; this.isPaused = data.status.status === 'paused'; + // Update download button text based on status + this.updateDownloadButtonText(); + if (this.isDownloading) { - this.updateUI(data.status); - this.showProgressPanel(); + // Ensure progress panel exists before updating UI + if (!this.progressPanel) { + this.progressPanel = document.getElementById('exampleImagesProgress'); + } - // Start the progress update interval if downloading - if (!this.progressUpdateInterval) { - this.startProgressUpdates(); + if (this.progressPanel) { + this.updateUI(data.status); + this.showProgressPanel(); + + // Start the progress update interval if downloading + if (!this.progressUpdateInterval) { + this.startProgressUpdates(); + } + } else { + console.warn('Progress panel not found, will retry on next update'); + // Set a shorter timeout to try again + setTimeout(() => this.checkDownloadStatus(), 500); } } } @@ -166,6 +155,18 @@ class ExampleImagesManager { } } + // Update download button text based on current state + updateDownloadButtonText() { + const btnTextElement = document.getElementById('exampleDownloadBtnText'); + if (btnTextElement) { + if (this.isDownloading && this.isPaused) { + btnTextElement.textContent = "Resume"; + } else if (!this.isDownloading) { + btnTextElement.textContent = "Download"; + } + } + } + async startDownload() { if (this.isDownloading) { showToast('Download already in progress', 'warning'); @@ -176,7 +177,7 @@ class ExampleImagesManager { const outputDir = document.getElementById('exampleImagesPath').value || ''; if (!outputDir) { - showToast('Please select a download location first', 'warning'); + showToast('Please enter a download location first', 'warning'); return; } @@ -203,11 +204,9 @@ class ExampleImagesManager { this.updateUI(data.status); this.showProgressPanel(); this.startProgressUpdates(); + this.updateDownloadButtonText(); showToast('Example images download started', 'success'); - // Hide the start button, show resume button - document.getElementById('startExampleDownloadBtn').style.display = 'none'; - // Close settings modal modalManager.closeModal('settingsModal'); } else { @@ -236,10 +235,8 @@ class ExampleImagesManager { document.getElementById('downloadStatusText').textContent = 'Paused'; document.getElementById('pauseExampleDownloadBtn').innerHTML = ''; document.getElementById('pauseExampleDownloadBtn').onclick = () => this.resumeDownload(); + this.updateDownloadButtonText(); showToast('Download paused', 'info'); - - // Show resume button in settings too - document.getElementById('resumeExampleDownloadBtn').style.display = 'block'; } else { showToast(data.error || 'Failed to pause download', 'error'); } @@ -266,10 +263,8 @@ class ExampleImagesManager { document.getElementById('downloadStatusText').textContent = 'Downloading'; document.getElementById('pauseExampleDownloadBtn').innerHTML = ''; document.getElementById('pauseExampleDownloadBtn').onclick = () => this.pauseDownload(); + this.updateDownloadButtonText(); showToast('Download resumed', 'success'); - - // Hide resume button in settings - document.getElementById('resumeExampleDownloadBtn').style.display = 'none'; } else { showToast(data.error || 'Failed to resume download', 'error'); } @@ -298,6 +293,10 @@ class ExampleImagesManager { if (data.success) { this.isDownloading = data.is_downloading; + this.isPaused = data.status.status === 'paused'; + + // Update download button text + this.updateDownloadButtonText(); if (this.isDownloading) { this.updateUI(data.status); @@ -313,10 +312,6 @@ class ExampleImagesManager { } else if (data.status.status === 'error') { showToast('Example images download failed', 'error'); } - - // Reset UI - document.getElementById('startExampleDownloadBtn').style.display = 'block'; - document.getElementById('resumeExampleDownloadBtn').style.display = 'none'; } } } catch (error) { @@ -325,21 +320,38 @@ class ExampleImagesManager { } updateUI(status) { + // Ensure progress panel exists + if (!this.progressPanel) { + this.progressPanel = document.getElementById('exampleImagesProgress'); + if (!this.progressPanel) { + console.error('Progress panel element not found in DOM'); + return; + } + } + // Update status text const statusText = document.getElementById('downloadStatusText'); - statusText.textContent = this.getStatusText(status.status); + if (statusText) { + statusText.textContent = this.getStatusText(status.status); + } // Update progress counts and bar const progressCounts = document.getElementById('downloadProgressCounts'); - progressCounts.textContent = `${status.completed}/${status.total}`; + if (progressCounts) { + progressCounts.textContent = `${status.completed}/${status.total}`; + } const progressBar = document.getElementById('downloadProgressBar'); - const progressPercent = status.total > 0 ? (status.completed / status.total) * 100 : 0; - progressBar.style.width = `${progressPercent}%`; + if (progressBar) { + const progressPercent = status.total > 0 ? (status.completed / status.total) * 100 : 0; + progressBar.style.width = `${progressPercent}%`; + } // Update current model const currentModel = document.getElementById('currentModelName'); - currentModel.textContent = status.current_model || '-'; + if (currentModel) { + currentModel.textContent = status.current_model || '-'; + } // Update time stats this.updateTimeStats(status); @@ -348,14 +360,21 @@ class ExampleImagesManager { this.updateErrors(status); // Update pause/resume button - if (status.status === 'paused') { - document.getElementById('pauseExampleDownloadBtn').innerHTML = ''; - document.getElementById('pauseExampleDownloadBtn').onclick = () => this.resumeDownload(); - document.getElementById('resumeExampleDownloadBtn').style.display = 'block'; - } else { - document.getElementById('pauseExampleDownloadBtn').innerHTML = ''; - document.getElementById('pauseExampleDownloadBtn').onclick = () => this.pauseDownload(); - document.getElementById('resumeExampleDownloadBtn').style.display = 'none'; + const pauseBtn = document.getElementById('pauseExampleDownloadBtn'); + const resumeBtn = document.getElementById('resumeExampleDownloadBtn'); + + if (pauseBtn) { + if (status.status === 'paused') { + pauseBtn.innerHTML = ''; + pauseBtn.onclick = () => this.resumeDownload(); + } else { + pauseBtn.innerHTML = ''; + pauseBtn.onclick = () => this.pauseDownload(); + } + } + + if (resumeBtn) { + resumeBtn.style.display = status.status === 'paused' ? 'block' : 'none'; } } @@ -363,6 +382,8 @@ class ExampleImagesManager { const elapsedTime = document.getElementById('elapsedTime'); const remainingTime = document.getElementById('remainingTime'); + if (!elapsedTime || !remainingTime) return; + // Calculate elapsed time let elapsed; if (status.start_time) { @@ -389,6 +410,8 @@ class ExampleImagesManager { const errorContainer = document.getElementById('downloadErrorContainer'); const errorList = document.getElementById('downloadErrors'); + if (!errorContainer || !errorList) return; + if (status.errors && status.errors.length > 0) { // Show only the last 3 errors const recentErrors = status.errors.slice(-3); @@ -425,22 +448,41 @@ class ExampleImagesManager { } showProgressPanel() { + // Ensure progress panel exists + if (!this.progressPanel) { + this.progressPanel = document.getElementById('exampleImagesProgress'); + if (!this.progressPanel) { + console.error('Progress panel element not found in DOM'); + return; + } + } this.progressPanel.classList.add('visible'); } hideProgressPanel() { + if (!this.progressPanel) { + this.progressPanel = document.getElementById('exampleImagesProgress'); + if (!this.progressPanel) return; + } this.progressPanel.classList.remove('visible'); } toggleProgressPanel() { + if (!this.progressPanel) { + this.progressPanel = document.getElementById('exampleImagesProgress'); + if (!this.progressPanel) return; + } + this.progressPanel.classList.toggle('collapsed'); // Update icon const icon = document.querySelector('#collapseProgressBtn i'); - if (this.progressPanel.classList.contains('collapsed')) { - icon.className = 'fas fa-chevron-up'; - } else { - icon.className = 'fas fa-chevron-down'; + if (icon) { + if (this.progressPanel.classList.contains('collapsed')) { + icon.className = 'fas fa-chevron-up'; + } else { + icon.className = 'fas fa-chevron-down'; + } } } } diff --git a/templates/components/modals.html b/templates/components/modals.html index 3d17faa8..e3f1946e 100644 --- a/templates/components/modals.html +++ b/templates/components/modals.html @@ -135,15 +135,15 @@
-
- -
- Choose where to save example images downloaded from Civitai + Enter the folder path where example images from Civitai will be saved
@@ -164,17 +164,6 @@ Optimize example images to reduce file size and improve loading speed - -
-
- - -
-
diff --git a/templates/components/progress_panel.html b/templates/components/progress_panel.html index 7fd23739..78553814 100644 --- a/templates/components/progress_panel.html +++ b/templates/components/progress_panel.html @@ -5,10 +5,10 @@ Example Images Download
- -
@@ -45,4 +45,29 @@
- \ No newline at end of file + + + \ No newline at end of file