Fix up gallery loader a bit.
This commit is contained in:
+26
-48
@@ -7,7 +7,8 @@
|
||||
import { getThumbnailSize } from "./config.js";
|
||||
import { formatFileSize } from "../reports/reportGenerator.js";
|
||||
import { actions, selectors } from "./stateManager.js";
|
||||
import { copyImageToClipboard, generateThumbnail } from "./imageUtils.js";
|
||||
import { copyImageToClipboard } from "./imageUtils.js";
|
||||
import { getLazyLoader } from "./lazyImageLoader.js";
|
||||
import { showFullImage } from "./imageViewer.js";
|
||||
|
||||
/**
|
||||
@@ -52,58 +53,35 @@ export function createImageItem(image, showImageContextMenu, showMetadata) {
|
||||
background: #333;
|
||||
`;
|
||||
|
||||
// Set thumbnail source with error handling
|
||||
// Set thumbnail source with lazy loader and error handling
|
||||
const thumbnailSizeConfig = getThumbnailSize(selectors.thumbnailSize());
|
||||
|
||||
// Load thumbnail using centralized loader
|
||||
const loadThumbnailUrl = async () => {
|
||||
try {
|
||||
const sizeName = thumbnailSizeConfig.width <= 120 ? 'small' :
|
||||
thumbnailSizeConfig.width <= 200 ? 'medium' : 'large';
|
||||
return await generateThumbnail(image, sizeName);
|
||||
} catch (error) {
|
||||
console.error('Error generating thumbnail:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const sizeName = thumbnailSizeConfig.width <= 120 ? 'small' :
|
||||
thumbnailSizeConfig.width <= 200 ? 'medium' : 'large';
|
||||
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.style.cssText = `
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #333;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
`;
|
||||
errorDiv.textContent = 'Thumbnail failed';
|
||||
errorDiv.style.display = 'none';
|
||||
item.appendChild(errorDiv);
|
||||
|
||||
loadThumbnailUrl().then(thumbnailUrl => {
|
||||
if (thumbnailUrl) {
|
||||
thumbnail.src = thumbnailUrl;
|
||||
} else {
|
||||
thumbnail.style.display = 'none';
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.style.cssText = `
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #333;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
`;
|
||||
errorDiv.textContent = 'Thumbnail failed';
|
||||
item.appendChild(errorDiv);
|
||||
}
|
||||
});
|
||||
|
||||
thumbnail.addEventListener('error', () => {
|
||||
thumbnail.style.display = 'none';
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.style.cssText = `
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #333;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
`;
|
||||
errorDiv.textContent = '❌ Thumbnail failed';
|
||||
item.insertBefore(errorDiv, thumbnail.nextSibling);
|
||||
errorDiv.style.display = 'flex';
|
||||
});
|
||||
|
||||
const lazyLoader = getLazyLoader();
|
||||
lazyLoader.observe(thumbnail, image, sizeName);
|
||||
|
||||
// Create overlay with filename and info
|
||||
const overlay = document.createElement('div');
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
|
||||
import { loadThumbnail } from './imageLoader.js';
|
||||
|
||||
function getImagePath(imageInput) {
|
||||
if (!imageInput) return null;
|
||||
if (typeof imageInput === 'string') return imageInput;
|
||||
return imageInput.path || imageInput.relative_path || imageInput.name || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for lazy loading and retry behavior
|
||||
*/
|
||||
@@ -79,11 +85,17 @@ export class LazyImageLoader {
|
||||
* @param {string} imagePath - Path to the image
|
||||
* @param {string} thumbnailSize - Size of thumbnail to load
|
||||
*/
|
||||
observe(img, imagePath, thumbnailSize = 'large') {
|
||||
observe(img, imageInput, thumbnailSize = 'large') {
|
||||
if (!this.observer) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
const imagePath = getImagePath(imageInput);
|
||||
if (!imagePath) {
|
||||
console.warn('[LazyImageLoader] No image path available for lazy load', imageInput);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store metadata on element
|
||||
img.dataset.imagePath = imagePath;
|
||||
img.dataset.thumbnailSize = thumbnailSize;
|
||||
@@ -149,7 +161,7 @@ export class LazyImageLoader {
|
||||
|
||||
try {
|
||||
// Attempt to load thumbnail
|
||||
const thumbnailUrl = await loadThumbnail({ path: imagePath }, thumbnailSize);
|
||||
const thumbnailUrl = await loadThumbnail(imagePath, thumbnailSize);
|
||||
|
||||
// Set the image source
|
||||
img.src = thumbnailUrl;
|
||||
|
||||
@@ -311,7 +311,27 @@ function setupGalleryEventHandlers(folderAndControls, unused, grid, metadata, he
|
||||
|
||||
// Show loading indicator in grid with immediate count update
|
||||
grid.imageCountSpan.textContent = '(Loading...)';
|
||||
let progressStarted = false;
|
||||
|
||||
const ensureProgressContainer = () => {
|
||||
if (!progressContainer || !grid.gridContainer.contains(progressContainer)) {
|
||||
if (progressContainer && progressContainer.parentNode) {
|
||||
progressContainer.remove();
|
||||
}
|
||||
if (!progressStarted) {
|
||||
grid.gridContainer.innerHTML = '';
|
||||
}
|
||||
progressContainer = document.createElement('div');
|
||||
progressContainer.className = 'gallery-loading-progress';
|
||||
if (grid.gridContainer.firstChild) {
|
||||
grid.gridContainer.insertBefore(progressContainer, grid.gridContainer.firstChild);
|
||||
} else {
|
||||
grid.gridContainer.appendChild(progressContainer);
|
||||
}
|
||||
progressStarted = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Create a progress update function
|
||||
const updateProgress = async (current, total, message = '') => {
|
||||
// Don't update if aborted
|
||||
@@ -319,12 +339,7 @@ function setupGalleryEventHandlers(folderAndControls, unused, grid, metadata, he
|
||||
return;
|
||||
}
|
||||
|
||||
if (!progressContainer) {
|
||||
grid.gridContainer.innerHTML = '';
|
||||
progressContainer = document.createElement('div');
|
||||
progressContainer.className = 'gallery-loading-progress';
|
||||
grid.gridContainer.appendChild(progressContainer);
|
||||
}
|
||||
ensureProgressContainer();
|
||||
|
||||
const percentage = total > 0 ? Math.round((current / total) * 100) : 0;
|
||||
|
||||
@@ -337,6 +352,7 @@ function setupGalleryEventHandlers(folderAndControls, unused, grid, metadata, he
|
||||
const fill = progressContainer.querySelector('.gallery-progress-bar__fill');
|
||||
if (fill) {
|
||||
fill.style.setProperty('--fill-width', `${percentage}%`);
|
||||
fill.style.width = `${percentage}%`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -396,6 +412,15 @@ function setupGalleryEventHandlers(folderAndControls, unused, grid, metadata, he
|
||||
renderImageGrid(currentImages, currentFolders, loadImagesWrapper)
|
||||
.catch(err => console.warn('Gallery: Final re-render after metadata completion failed:', err));
|
||||
}
|
||||
|
||||
// Remove any remaining loading banner after metadata completes
|
||||
if (progressContainer && progressContainer.parentNode) {
|
||||
setTimeout(() => {
|
||||
if (progressContainer && progressContainer.parentNode) {
|
||||
progressContainer.remove();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -422,14 +447,14 @@ function setupGalleryEventHandlers(folderAndControls, unused, grid, metadata, he
|
||||
// Update UI immediately with actual counts
|
||||
grid.imageCountSpan.textContent = `${images.length} images / ${folders.length} folders`;
|
||||
|
||||
// Clear the progress container if it exists
|
||||
// Render image grid with both images and folders immediately
|
||||
await renderImageGrid(images, folders, loadImagesWrapper);
|
||||
|
||||
// Remove the gallery load progress banner after initial grid render
|
||||
if (progressContainer && progressContainer.parentNode) {
|
||||
progressContainer.remove();
|
||||
}
|
||||
|
||||
// Render image grid with both images and folders immediately
|
||||
await renderImageGrid(images, folders, loadImagesWrapper);
|
||||
|
||||
// Auto-show metadata for the first image if available
|
||||
if (images && images.length > 0) {
|
||||
showImageMetadata(images[0]);
|
||||
@@ -485,7 +510,11 @@ function setupGalleryEventHandlers(folderAndControls, unused, grid, metadata, he
|
||||
|
||||
// Only clear if not doing incremental update
|
||||
if (!incremental) {
|
||||
const loadingProgress = grid.gridContainer.querySelector('.gallery-loading-progress');
|
||||
grid.gridContainer.innerHTML = '';
|
||||
if (loadingProgress) {
|
||||
grid.gridContainer.appendChild(loadingProgress);
|
||||
}
|
||||
}
|
||||
|
||||
if (!images || (images.length === 0 && folders.length === 0)) {
|
||||
@@ -611,6 +640,7 @@ function setupGalleryEventHandlers(folderAndControls, unused, grid, metadata, he
|
||||
const batchSize = 20; // Process 20 images at a time
|
||||
const totalImages = images.length;
|
||||
let processedCount = 0;
|
||||
let renderedCount = incremental ? grid.gridContainer.querySelectorAll('.gallery-image-item').length : 0;
|
||||
|
||||
// Find or create progress container
|
||||
let progressContainer = grid.gridContainer.querySelector('.gallery-progress-container');
|
||||
@@ -640,13 +670,9 @@ function setupGalleryEventHandlers(folderAndControls, unused, grid, metadata, he
|
||||
const fill = progressContainer.querySelector('.gallery-thumbnail-progress__fill');
|
||||
if (fill) {
|
||||
fill.style.setProperty('--fill-width', `${percentage}%`);
|
||||
fill.style.width = `${percentage}%`;
|
||||
}
|
||||
};
|
||||
|
||||
await updateProgress();
|
||||
|
||||
// Track which images have been rendered (by index)
|
||||
let renderedCount = incremental ? grid.gridContainer.querySelectorAll('.gallery-image-item').length : 0;
|
||||
}
|
||||
|
||||
// For large folders, show skeleton loaders first for better perceived performance
|
||||
const skeletonThreshold = 100; // Show skeletons for folders with 100+ images
|
||||
|
||||
Reference in New Issue
Block a user