From ece99f8e48bccc04ff7d6c97ffdd07f0da819823 Mon Sep 17 00:00:00 2001 From: lelo Date: Fri, 9 Jan 2026 22:20:45 +0000 Subject: [PATCH] improve image preview --- app.py | 9 ++++-- static/app.js | 86 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/app.py b/app.py index 556e620..c7837b6 100755 --- a/app.py +++ b/app.py @@ -542,8 +542,13 @@ def list_directory_contents(directory, subpath): music_exts = ('.mp3',) image_exts = ('.jpg', '.jpeg', '.png', '.gif', '.bmp') - blocked_filenames = ['Thumbs.db', '*.mrk'] - + blocked_filenames = [ + 'Thumbs.db', + '*.mrk', + '*.arw', '*.cr2', '*.lrf' + ] + + try: with os.scandir(directory) as it: # Sorting by name if required. diff --git a/static/app.js b/static/app.js index b1c07ba..a7450c3 100644 --- a/static/app.js +++ b/static/app.js @@ -3,6 +3,44 @@ let currentMusicFiles = []; // Array of objects with at least { path, index } let currentMusicIndex = -1; // Index of the current music file let currentTrackPath = ""; +// Check thumb availability in parallel; sequentially generate missing ones to avoid concurrent creation. +function ensureFirstFivePreviews() { + const images = Array.from(document.querySelectorAll('.images-grid img')).slice(0, 5); + if (!images.length) return; + + const checks = images.map(img => { + const thumbUrl = img.dataset.thumbUrl || img.src; + const fullUrl = img.dataset.fullUrl; + return fetch(thumbUrl, { method: 'GET', cache: 'no-store' }) + .then(res => ({ + img, + thumbUrl, + fullUrl, + hasThumb: res.ok && res.status !== 204 + })) + .catch(() => ({ img, thumbUrl, fullUrl, hasThumb: false })); + }); + + Promise.all(checks).then(results => { + const missing = results.filter(r => !r.hasThumb && r.fullUrl); + if (!missing.length) return; + generateThumbsSequentially(missing); + }); +} + +async function generateThumbsSequentially(entries) { + for (const { img, thumbUrl, fullUrl } of entries) { + try { + await fetch(fullUrl, { method: 'GET', cache: 'no-store' }); + const cacheBust = `_=${Date.now()}`; + const separator = thumbUrl.includes('?') ? '&' : '?'; + img.src = `${thumbUrl}${separator}${cacheBust}`; + } catch (e) { + // ignore; best effort + } + } +} + // Cache common DOM elements const mainContainer = document.querySelector('main'); const searchContainer = document.querySelector('search'); @@ -61,17 +99,20 @@ function renderContent(data) { }); document.getElementById('breadcrumbs').innerHTML = breadcrumbHTML; + const imageFiles = data.files.filter(file => file.file_type === 'image'); + const nonImageFiles = data.files.filter(file => file.file_type !== 'image'); + // Check for image-only directory (no subdirectories, at least one file, all images) const isImageOnly = data.directories.length === 0 - && data.files.length > 0 - && data.files.every(file => file.file_type === 'image'); + && imageFiles.length > 0 + && nonImageFiles.length === 0; let contentHTML = ''; if (isImageOnly) { // Display thumbnails grid contentHTML += '
'; - data.files.forEach(file => { + imageFiles.forEach(file => { const thumbUrl = `${file.path}?thumbnail=true`; contentHTML += `
@@ -81,6 +122,8 @@ function renderContent(data) { data-file-type="${file.file_type}"> @@ -138,9 +181,9 @@ function renderContent(data) { // Render files (including music and non-image files) currentMusicFiles = []; - if (data.files.length > 0) { + if (nonImageFiles.length > 0) { contentHTML += '
    '; - data.files.forEach((file, idx) => { + nonImageFiles.forEach((file, idx) => { let symbol = '📄'; if (file.file_type === 'music') { symbol = '🔊'; @@ -158,6 +201,32 @@ function renderContent(data) { }); contentHTML += '
'; } + + // Show images in preview grid after the file list + if (imageFiles.length > 0) { + contentHTML += '
'; + imageFiles.forEach(file => { + const thumbUrl = `${file.path}?thumbnail=true`; + contentHTML += ` +
+ + + + + ${file.name} +
+ `; + }); + contentHTML += '
'; + } } // Insert generated content @@ -174,7 +243,7 @@ function renderContent(data) { }); // Attach event listeners for file items (including images) - if (isImageOnly) { + if (isImageOnly || imageFiles.length > 0) { document.querySelectorAll('.image-item').forEach(item => { item.addEventListener('click', function(e) { if (!e.target.closest('a')) { @@ -195,9 +264,8 @@ function renderContent(data) { } // Update gallery images for lightbox or similar - currentGalleryImages = data.files - .filter(f => f.file_type === 'image') - .map(f => f.path); + currentGalleryImages = imageFiles.map(f => f.path); + ensureFirstFivePreviews(); attachEventListeners(); }