diff --git a/index.html b/index.html
index 9cf0952..ecbdb27 100644
--- a/index.html
+++ b/index.html
@@ -32,7 +32,7 @@
diff --git a/src/app.js b/src/app.js
index 5813376..2dcd257 100644
--- a/src/app.js
+++ b/src/app.js
@@ -626,7 +626,9 @@ async function generateImages() {
failed: 0
};
state.pendingBatches.push(batch);
- renderGallery(); // Show loading placeholders
+
+ // Add loading placeholders without full re-render
+ addLoadingPlaceholders(batch, imageCount);
showToast(`Queued ${imageCount} image(s) for generation`, 'success');
@@ -649,18 +651,21 @@ async function generateImages() {
};
state.images.unshift(imageData);
batch.completed++;
- renderGallery();
+
+ // Remove one placeholder and add the new image
+ removeOnePlaceholder(batchId);
+ prependImageCard(imageData, 0);
// Save to IndexedDB in background
ImagenDB.saveImage(imageData).catch(e => console.error('Failed to save to IndexedDB:', e));
} else {
batch.failed++;
- renderGallery();
+ removeOnePlaceholder(batchId);
}
} catch (error) {
console.error('Failed to generate image:', error);
batch.failed++;
- renderGallery();
+ removeOnePlaceholder(batchId);
}
};
@@ -678,7 +683,6 @@ async function generateImages() {
if (batchIndex !== -1) {
state.pendingBatches.splice(batchIndex, 1);
}
- renderGallery();
if (batch.completed > 0) {
showToast(`${batch.completed} image(s) generated!`, 'success');
@@ -953,6 +957,176 @@ function renderGallery() {
});
}
+// ===== Incremental Gallery Updates =====
+function addLoadingPlaceholders(batch, count) {
+ // Hide empty state if showing
+ elements.galleryEmpty.style.display = 'none';
+
+ for (let i = 0; i < count; i++) {
+ const placeholder = createPlaceholderElement(batch);
+ elements.gallery.insertBefore(placeholder, elements.gallery.firstChild);
+ }
+}
+
+function createPlaceholderElement(batch) {
+ const placeholder = document.createElement('div');
+ placeholder.className = 'image-card loading-placeholder';
+ placeholder.dataset.batchId = batch.id;
+ const truncatedPrompt = batch.prompt.length > 60 ? batch.prompt.substring(0, 60) + '...' : batch.prompt;
+ placeholder.innerHTML = `
+
+
+
${escapeHtml(truncatedPrompt)}
+
+ ${escapeHtml(batch.modelName)}
+
+
+ Pending
+
+
+
+ `;
+ return placeholder;
+}
+
+function removeOnePlaceholder(batchId) {
+ const placeholder = elements.gallery.querySelector(`.loading-placeholder[data-batch-id="${batchId}"]`);
+ if (placeholder) {
+ placeholder.remove();
+ }
+
+ // Show empty state if gallery is now empty
+ if (elements.gallery.children.length === 0 ||
+ (elements.gallery.children.length === 1 && elements.gallery.contains(elements.galleryEmpty))) {
+ elements.galleryEmpty.style.display = 'flex';
+ if (!elements.gallery.contains(elements.galleryEmpty)) {
+ elements.gallery.appendChild(elements.galleryEmpty);
+ }
+ }
+}
+
+function prependImageCard(image, index) {
+ const card = createImageCardElement(image, index);
+
+ // Insert after any remaining placeholders
+ const firstNonPlaceholder = elements.gallery.querySelector('.image-card:not(.loading-placeholder)');
+ if (firstNonPlaceholder) {
+ elements.gallery.insertBefore(card, firstNonPlaceholder);
+ } else {
+ elements.gallery.appendChild(card);
+ }
+
+ // Update indices on existing cards since we prepended
+ updateCardIndices();
+}
+
+function createImageCardElement(image, index) {
+ const card = document.createElement('div');
+ card.className = 'image-card';
+ card.dataset.imageId = image.id;
+
+ const safeUrl = sanitizeImageUrl(image.url);
+ const safePrompt = escapeHtml(image.prompt);
+
+ card.innerHTML = `
+
+
+
+
+
+
+
+
+

+
+
${safePrompt}
+
+ ${escapeHtml(image.modelName || image.model)}
+ ${escapeHtml(image.quality || image.size)}
+ ${escapeHtml(image.aspectRatio)}
+
+
+ `;
+
+ // Attach event handlers
+ attachImageCardHandlers(card, image);
+
+ return card;
+}
+
+function attachImageCardHandlers(card, image) {
+ const imageId = image.id;
+
+ card.querySelector('.image-card-download').addEventListener('click', (e) => {
+ e.stopPropagation();
+ const idx = state.images.findIndex(img => img.id === imageId);
+ if (idx !== -1) downloadImageByIndex(idx);
+ });
+
+ card.querySelector('.image-card-delete').addEventListener('click', (e) => {
+ e.stopPropagation();
+ const idx = state.images.findIndex(img => img.id === imageId);
+ if (idx !== -1) deleteImage(idx);
+ });
+
+ card.querySelector('.image-card-reference').addEventListener('click', (e) => {
+ e.stopPropagation();
+ const idx = state.images.findIndex(img => img.id === imageId);
+ if (idx !== -1) addImageAsReference(idx);
+ });
+
+ card.querySelector('.image-card-recreate').addEventListener('click', (e) => {
+ e.stopPropagation();
+ const idx = state.images.findIndex(img => img.id === imageId);
+ if (idx !== -1) recreateImageByIndex(idx);
+ });
+
+ card.addEventListener('click', () => {
+ const idx = state.images.findIndex(img => img.id === imageId);
+ if (idx !== -1) openModal(state.images[idx]);
+ });
+}
+
+function updateCardIndices() {
+ // No longer needed since we use image IDs instead of indices
+}
+
async function deleteImage(index) {
const imageToDelete = state.images[index];
state.images.splice(index, 1);
@@ -963,7 +1137,20 @@ async function deleteImage(index) {
console.warn('Could not delete from IndexedDB:', e);
}
- renderGallery();
+ // Remove card from DOM without full re-render
+ const card = elements.gallery.querySelector(`.image-card[data-image-id="${imageToDelete.id}"]`);
+ if (card) {
+ card.remove();
+ }
+
+ // Show empty state if gallery is now empty
+ if (state.images.length === 0 && state.pendingBatches.length === 0) {
+ elements.galleryEmpty.style.display = 'flex';
+ if (!elements.gallery.contains(elements.galleryEmpty)) {
+ elements.gallery.appendChild(elements.galleryEmpty);
+ }
+ }
+
showToast('Image deleted', 'success');
}