ソースを参照

add cache alll previews modal, fix resizing issue

tuanchris 5 ヶ月 前
コミット
28ca6d237c
3 ファイル変更324 行追加28 行削除
  1. 253 26
      static/js/base.js
  2. 14 2
      static/js/index.js
  3. 57 0
      templates/base.html

+ 253 - 26
static/js/base.js

@@ -187,37 +187,53 @@ async function openPlayerPreviewModal() {
 // Setup player preview canvas for modal
 function setupPlayerPreviewCanvas(ctx) {
     const canvas = ctx.canvas;
-    const container = canvas.parentElement;
+    const container = canvas.parentElement; // This is the div with max-w and max-h constraints
     const modal = document.getElementById('playerPreviewModal');
     
     if (!container || !modal) return;
     
-    // Get the modal's available height for the canvas area
-    const modalContent = modal.querySelector('.bg-white');
-    const modalHeader = modal.querySelector('.flex-shrink-0');
-    const modalControls = modal.querySelector('.flex-shrink-0:last-child');
+    // Calculate available viewport space directly
+    const viewportWidth = window.innerWidth;
+    const viewportHeight = window.innerHeight;
     
-    const modalHeight = modalContent.clientHeight;
-    const headerHeight = modalHeader ? modalHeader.clientHeight : 0;
-    const controlsHeight = modalControls ? modalControls.clientHeight : 0;
-    const padding = 32; // 2 * p-4 (16px each)
+    // Calculate maximum canvas size based on viewport and fixed estimates
+    // Modal uses max-w-5xl (1024px) but we want to be responsive to actual viewport
+    const modalMaxWidth = Math.min(1024, viewportWidth * 0.9); // Account for modal padding
+    const modalMaxHeight = viewportHeight * 0.95; // max-h-[95vh]
     
-    // Calculate available height for canvas
-    const availableHeight = modalHeight - headerHeight - controlsHeight - padding;
+    // Reserve space for modal header (~80px) and controls (~200px) and padding
+    const reservedSpace = 320; // Header + controls + padding
+    const availableModalHeight = modalMaxHeight - reservedSpace;
     
-    // Calculate the size (use the smaller of width or height to maintain aspect ratio)
-    const containerWidth = container.clientWidth;
-    const containerHeight = container.clientHeight;
-    const size = Math.min(containerWidth, containerHeight, availableHeight);
+    // Calculate canvas constraints (stay within original 800px max, but be responsive)
+    const maxCanvasSize = Math.min(800, modalMaxWidth - 64, availableModalHeight); // 64px for canvas area padding
+    
+    // Ensure minimum size
+    const finalSize = Math.max(200, maxCanvasSize);
+    
+    // Update container to exact size (override CSS constraints)
+    container.style.width = `${finalSize}px`;
+    container.style.height = `${finalSize}px`;
+    container.style.maxWidth = `${finalSize}px`;
+    container.style.maxHeight = `${finalSize}px`;
+    container.style.minWidth = `${finalSize}px`;
+    container.style.minHeight = `${finalSize}px`;
     
     // Set the internal canvas size for high-DPI rendering
     const pixelRatio = (window.devicePixelRatio || 1) * 2;
-    canvas.width = size * pixelRatio;
-    canvas.height = size * pixelRatio;
-    
-    // Set the display size
-    canvas.style.width = `${size}px`;
-    canvas.style.height = `${size}px`;
+    canvas.width = finalSize * pixelRatio;
+    canvas.height = finalSize * pixelRatio;
+    
+    // Set the display size (canvas fills its container)
+    canvas.style.width = '100%';
+    canvas.style.height = '100%';
+    
+    console.log('Canvas resized:', {
+        viewport: `${viewportWidth}x${viewportHeight}`,
+        modalMaxWidth,
+        availableModalHeight,
+        finalSize: finalSize
+    });
 }
 
 // Get interpolated coordinate at specific progress
@@ -727,14 +743,22 @@ function initializeWebSocket() {
     connectWebSocket();
 }
 
-// Add resize handler for responsive canvas
+// Add resize handler for responsive canvas with debouncing
+let resizeTimeout;
 window.addEventListener('resize', () => {
     const canvas = document.getElementById('playerPreviewCanvas');
     const modal = document.getElementById('playerPreviewModal');
+    
     if (canvas && modal && !modal.classList.contains('hidden')) {
-        const ctx = canvas.getContext('2d');
-        setupPlayerPreviewCanvas(ctx);
-        drawPlayerPreview(ctx, targetProgress);
+        // Clear previous timeout
+        clearTimeout(resizeTimeout);
+        
+        // Debounce resize calls to avoid excessive updates
+        resizeTimeout = setTimeout(() => {
+            const ctx = canvas.getContext('2d');
+            setupPlayerPreviewCanvas(ctx);
+            drawPlayerPreview(ctx, targetProgress);
+        }, 16); // ~60fps update rate
     }
 });
 
@@ -748,4 +772,207 @@ function handleFileChange(newFile) {
             playerPreviewData = null;
         }
     }
-} 
+}
+
+// Cache All Previews Prompt functionality
+let cacheAllInProgress = false;
+
+function shouldShowCacheAllPrompt() {
+    // Check if we've already shown the prompt
+    const promptShown = localStorage.getItem('cacheAllPromptShown');
+    console.log('shouldShowCacheAllPrompt - promptShown:', promptShown);
+    return !promptShown;
+}
+
+function showCacheAllPrompt(forceShow = false) {
+    console.log('showCacheAllPrompt called, forceShow:', forceShow);
+    if (!forceShow && !shouldShowCacheAllPrompt()) {
+        console.log('Cache all prompt already shown, skipping');
+        return;
+    }
+    
+    const modal = document.getElementById('cacheAllPromptModal');
+    if (modal) {
+        console.log('Showing cache all prompt modal');
+        modal.classList.remove('hidden');
+        // Store whether this was forced (manually triggered)
+        modal.dataset.manuallyTriggered = forceShow.toString();
+    } else {
+        console.log('Cache all prompt modal not found');
+    }
+}
+
+function hideCacheAllPrompt() {
+    const modal = document.getElementById('cacheAllPromptModal');
+    if (modal) {
+        modal.classList.add('hidden');
+    }
+}
+
+function markCacheAllPromptAsShown() {
+    localStorage.setItem('cacheAllPromptShown', 'true');
+}
+
+function initializeCacheAllPrompt() {
+    const modal = document.getElementById('cacheAllPromptModal');
+    const skipBtn = document.getElementById('skipCacheAllBtn');
+    const startBtn = document.getElementById('startCacheAllBtn');
+    const closeBtn = document.getElementById('closeCacheAllBtn');
+    
+    if (!modal || !skipBtn || !startBtn || !closeBtn) {
+        return;
+    }
+
+    // Skip button handler
+    skipBtn.addEventListener('click', () => {
+        const wasManuallyTriggered = modal.dataset.manuallyTriggered === 'true';
+        hideCacheAllPrompt();
+        
+        // Only mark as shown if it was automatically shown (not manually triggered)
+        if (!wasManuallyTriggered) {
+            markCacheAllPromptAsShown();
+        }
+    });
+
+    // Close button handler (after completion)
+    closeBtn.addEventListener('click', () => {
+        const wasManuallyTriggered = modal.dataset.manuallyTriggered === 'true';
+        hideCacheAllPrompt();
+        
+        // Always mark as shown after successful completion
+        if (!wasManuallyTriggered) {
+            markCacheAllPromptAsShown();
+        }
+    });
+
+    // Start caching button handler
+    startBtn.addEventListener('click', async () => {
+        if (cacheAllInProgress) {
+            return;
+        }
+
+        cacheAllInProgress = true;
+        
+        // Hide buttons and show progress
+        document.getElementById('cacheAllButtons').classList.add('hidden');
+        document.getElementById('cacheAllProgress').classList.remove('hidden');
+
+        try {
+            await startCacheAllProcess();
+            
+            // Show completion message
+            document.getElementById('cacheAllProgress').classList.add('hidden');
+            document.getElementById('cacheAllComplete').classList.remove('hidden');
+        } catch (error) {
+            console.error('Error caching all previews:', error);
+            
+            // Show error and reset
+            document.getElementById('cacheAllProgressText').textContent = 'Error occurred during caching';
+            setTimeout(() => {
+                hideCacheAllPrompt();
+                markCacheAllPromptAsShown();
+            }, 3000);
+        } finally {
+            cacheAllInProgress = false;
+        }
+    });
+}
+
+async function startCacheAllProcess() {
+    try {
+        // Get list of patterns
+        const response = await fetch('/list_theta_rho_files');
+        const patterns = await response.json();
+        
+        if (!patterns || patterns.length === 0) {
+            throw new Error('No patterns found');
+        }
+
+        const progressBar = document.getElementById('cacheAllProgressBar');
+        const progressText = document.getElementById('cacheAllProgressText');
+        const progressPercentage = document.getElementById('cacheAllProgressPercentage');
+        
+        let completed = 0;
+        const batchSize = 5; // Process in small batches to avoid overwhelming the server
+
+        for (let i = 0; i < patterns.length; i += batchSize) {
+            const batch = patterns.slice(i, i + batchSize);
+            
+            // Update progress text
+            progressText.textContent = `Caching previews... (${Math.min(i + batchSize, patterns.length)}/${patterns.length})`;
+            
+            // Process batch
+            const batchPromises = batch.map(async (pattern) => {
+                try {
+                    const previewResponse = await fetch('/preview_thr', {
+                        method: 'POST',
+                        headers: {
+                            'Content-Type': 'application/json',
+                        },
+                        body: JSON.stringify({ file_name: pattern })
+                    });
+                    
+                    if (previewResponse.ok) {
+                        const data = await previewResponse.json();
+                        if (data.preview_url) {
+                            // Pre-load the image to cache it
+                            return new Promise((resolve) => {
+                                const img = new Image();
+                                img.onload = () => resolve();
+                                img.onerror = () => resolve(); // Continue even if image fails
+                                img.src = data.preview_url;
+                            });
+                        }
+                    }
+                    return Promise.resolve();
+                } catch (error) {
+                    console.warn(`Failed to cache preview for ${pattern}:`, error);
+                    return Promise.resolve(); // Continue with other patterns
+                }
+            });
+
+            await Promise.all(batchPromises);
+            completed += batch.length;
+
+            // Update progress bar
+            const progress = Math.round((completed / patterns.length) * 100);
+            progressBar.style.width = `${progress}%`;
+            progressPercentage.textContent = `${progress}%`;
+
+            // Small delay between batches to prevent overwhelming the server
+            if (i + batchSize < patterns.length) {
+                await new Promise(resolve => setTimeout(resolve, 100));
+            }
+        }
+
+        progressText.textContent = `Completed! Cached ${patterns.length} previews.`;
+        
+    } catch (error) {
+        throw error;
+    }
+}
+
+// Function to be called after initial cache generation completes
+function onInitialCacheComplete() {
+    console.log('onInitialCacheComplete called');
+    // Show the cache all prompt after a short delay
+    setTimeout(() => {
+        console.log('Triggering cache all prompt after delay');
+        showCacheAllPrompt();
+    }, 1000);
+}
+
+// Initialize on DOM load
+document.addEventListener('DOMContentLoaded', () => {
+    initializeCacheAllPrompt();
+});
+
+// Make functions available globally for debugging
+window.onInitialCacheComplete = onInitialCacheComplete;
+window.showCacheAllPrompt = showCacheAllPrompt;
+window.testCacheAllPrompt = function() {
+    console.log('Manual test trigger');
+    // Clear localStorage for testing
+    localStorage.removeItem('cacheAllPromptShown');
+    showCacheAllPrompt();
+}; 

+ 14 - 2
static/js/index.js

@@ -1073,9 +1073,21 @@ document.addEventListener('DOMContentLoaded', async () => {
             });
         }
 
-        // Setup cache all button
+        // Setup cache all button - now triggers the modal
         if (cacheAllButton) {
-            cacheAllButton.addEventListener('click', () => cacheAllPreviews());
+            cacheAllButton.addEventListener('click', () => {
+                // Always show the modal when manually clicked, using forceShow parameter
+                if (typeof showCacheAllPrompt === 'function') {
+                    showCacheAllPrompt(true); // true = forceShow
+                } else {
+                    // Fallback if function not available
+                    const modal = document.getElementById('cacheAllPromptModal');
+                    if (modal) {
+                        modal.classList.remove('hidden');
+                        modal.dataset.manuallyTriggered = 'true';
+                    }
+                }
+            });
         }
 
         // Load patterns on page load

+ 57 - 0
templates/base.html

@@ -594,7 +594,15 @@
         
         function updateCacheProgress(data) {
           if (!data.is_running && data.stage === 'complete') {
+            console.log('Initial cache generation completed');
             hideCacheProgressModal();
+            // Trigger cache all prompt after initial cache generation completes
+            if (typeof onInitialCacheComplete === 'function') {
+              console.log('Calling onInitialCacheComplete()');
+              onInitialCacheComplete();
+            } else {
+              console.log('onInitialCacheComplete function not found');
+            }
             return;
           }
           
@@ -695,5 +703,54 @@
         });
       });
     </script>
+
+    <!-- Cache All Previews Prompt Modal -->
+    <div id="cacheAllPromptModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden p-4">
+        <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md">
+            <div class="p-6">
+                <div class="text-center">
+                    <h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200 mb-2">Cache All Pattern Previews?</h2>
+                    <p class="text-gray-600 dark:text-gray-400 mb-4 max-w-md mx-auto">
+                        Would you like to cache all pattern previews for faster browsing? This will download and store preview images in your browser for instant loading.
+                    </p>
+                    
+                    <div class="bg-amber-50 dark:bg-amber-900 p-3 rounded-lg mb-4 text-sm">
+                        <p class="text-amber-700 dark:text-amber-300">
+                            <strong>Note:</strong> This cache is browser-specific. You'll need to repeat this process for each browser you use.
+                        </p>
+                    </div>
+                    
+                    <!-- Progress section (hidden initially) -->
+                    <div id="cacheAllProgress" class="mb-4 hidden">
+                        <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
+                            <div id="cacheAllProgressBar" class="bg-blue-600 dark:bg-blue-400 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
+                        </div>
+                        <div class="mt-2 flex justify-between text-sm text-gray-500 dark:text-gray-400">
+                            <span id="cacheAllProgressText">Starting...</span>
+                            <span id="cacheAllProgressPercentage">0%</span>
+                        </div>
+                    </div>
+                    
+                    <!-- Buttons -->
+                    <div id="cacheAllButtons" class="flex gap-3 justify-center">
+                        <button id="skipCacheAllBtn" class="px-4 py-2 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 transition-colors">
+                            Skip for now
+                        </button>
+                        <button id="startCacheAllBtn" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">
+                            Cache All Previews
+                        </button>
+                    </div>
+                    
+                    <!-- Completion message (hidden initially) -->
+                    <div id="cacheAllComplete" class="hidden">
+                        <p class="text-green-600 dark:text-green-400 mb-4">✓ All previews cached successfully!</p>
+                        <button id="closeCacheAllBtn" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">
+                            Done
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
   </body>
 </html>