Feat: Enhance Persistent Queue UI with status-based thumbnail placeholders

This commit is contained in:
Simon Cordt
2025-09-24 11:30:26 +02:00
parent b461eedfcc
commit 470f1514d5
2 changed files with 77 additions and 19 deletions
+11
View File
@@ -18,6 +18,17 @@
.pqueue-thumbs-grid{display:grid;grid-template-columns:repeat(1, 96px);gap:6px}
.pqueue-thumb{width:96px;height:96px;object-fit:cover;border-radius:4px;cursor:pointer}
.pqueue-thumb-wrap{position:relative;width:96px;height:96px}
.pqueue-thumb-wrap-placeholder{background:var(--surface-200,#212121);display:flex;align-items:center;justify-content:center;border-radius:4px;border:1px dashed var(--border-color,#4b4b4b);padding:6px;box-sizing:border-box;cursor:default}
.pqueue-thumb-wrap-placeholder--failed{border-color:var(--red-400,#f87171);background:rgba(248,113,113,0.08)}
.dark-theme .pqueue-thumb-wrap-placeholder--failed{background:rgba(248,113,113,0.12)}
.pqueue-thumb-wrap-placeholder--interrupted{border-color:var(--yellow-400,#facc15);background:rgba(250,204,21,0.08)}
.dark-theme .pqueue-thumb-wrap-placeholder--interrupted{background:rgba(250,204,21,0.12)}
.dark-theme .pqueue-thumb-wrap-placeholder{background:rgba(255,255,255,0.05)}
.pqueue-thumb-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.35rem;text-align:center;color:var(--text-color-secondary,#9ca3af);font-size:.76rem;line-height:1.2;padding:.25rem}
.pqueue-thumb-placeholder--failed{color:var(--red-400,#f87171)}
.pqueue-thumb-placeholder--interrupted{color:var(--yellow-500,#eab308)}
.pqueue-thumb-placeholder>i{font-size:1.25rem;opacity:.85}
.pqueue-thumb-placeholder-text{display:block;max-width:82px;word-break:break-word}
.pqueue-batch-badge{position:absolute;right:4px;bottom:4px;padding:2px 6px;border-radius:10px;background:rgba(0,0,0,0.7);color:#fff;font-size:.75rem;line-height:1}
.pqueue-gallery-overlay{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,0.65);z-index:100000}
.pqueue-gallery{position:relative;max-width:92vw;max-height:92vh}
+66 -19
View File
@@ -228,41 +228,88 @@
UI.updateProgressBarsFromState();
},
getThumbPlaceholderMeta(row) {
const status = String(row?.status ?? '').toLowerCase();
if (['interrupted', 'cancelled', 'canceled', 'stopped'].includes(status)) {
return { text: 'Interrupted', iconClass: 'pi-stop-circle', variant: 'interrupted' };
}
if (['error', 'failed', 'failure'].includes(status)) {
return { text: 'Failed', iconClass: 'pi-times-circle', variant: 'failed' };
}
return { text: 'Preview unavailable', iconClass: 'pi-image-off', variant: 'empty' };
},
createThumbPlaceholder(options = {}) {
const { text, iconClass, variant } = options ?? {};
const classes = ['pqueue-thumb-placeholder'];
if (variant) classes.push(`pqueue-thumb-placeholder--${variant}`);
const placeholder = UI.el('div', { class: classes.join(' ') });
const icon = UI.el('i', { class: `pi ${iconClass || 'pi-image-off'}` });
const label = UI.el('span', { class: 'pqueue-thumb-placeholder-text', text: text || 'Preview unavailable' });
placeholder.appendChild(icon);
placeholder.appendChild(label);
return placeholder;
},
buildResultThumbs(row) {
try {
const container = UI.el('div', { class: 'flex gap-1 flex-wrap' });
const placeholderMeta = UI.getThumbPlaceholderMeta(row);
const ensurePlaceholder = () => {
const wrapClasses = ['pqueue-thumb-wrap', 'pqueue-thumb-wrap-placeholder'];
if (placeholderMeta?.variant) wrapClasses.push(`pqueue-thumb-wrap-placeholder--${placeholderMeta.variant}`);
const wrap = UI.el('div', { class: wrapClasses.join(' ') });
wrap.appendChild(UI.createThumbPlaceholder(placeholderMeta));
container.appendChild(wrap);
};
const attachFallbackHandler = (thumbWrap, img, images = []) => {
img.addEventListener('error', () => {
thumbWrap.classList.add('pqueue-thumb-wrap-placeholder');
if (placeholderMeta?.variant) thumbWrap.classList.add(`pqueue-thumb-wrap-placeholder--${placeholderMeta.variant}`);
thumbWrap.replaceChildren(UI.createThumbPlaceholder(placeholderMeta));
if (!images.length) thumbWrap.onclick = null;
}, { once: true });
};
// Prefer stored DB thumbnail if available; bust cache with timestamp to avoid race
if (row.id) {
const galleryImages = UI.extractImages(row);
const url = new URL(`/api/pqueue/history/thumb/${row.id}`, window.location.origin);
url.searchParams.set('_', String(Date.now()));
const thumbWrap = UI.el('div', { class: 'pqueue-thumb-wrap' });
const img = UI.el('img', { src: url.href, class: 'pqueue-thumb', title: `history-${row.id}` });
attachFallbackHandler(thumbWrap, img, galleryImages);
thumbWrap.appendChild(img);
if (UI.countImages(row) > 1) {
const badge = UI.el('div', { class: 'pqueue-batch-badge', text: `${UI.countImages(row)}` });
const imgCount = UI.countImages(row);
if (imgCount > 1) {
const badge = UI.el('div', { class: 'pqueue-batch-badge', text: `${imgCount}` });
thumbWrap.appendChild(badge);
}
thumbWrap.onclick = () => {
const images = UI.extractImages(row);
if (images.length) UI.openGallery(images, 0, row.prompt_id);
};
if (galleryImages.length) {
thumbWrap.onclick = () => UI.openGallery(galleryImages, 0, row.prompt_id);
} else {
thumbWrap.onclick = null;
}
container.appendChild(thumbWrap);
return container;
}
// Fallback to live preview route if DB thumb missing
const images = UI.extractImages(row);
if (!images.length) return container;
const first = images[0];
const url = UI.buildPreviewUrl(first, row.prompt_id);
const thumbWrap = UI.el('div', { class: 'pqueue-thumb-wrap' });
const img = UI.el('img', { src: url.href, class: 'pqueue-thumb', title: first.filename });
thumbWrap.appendChild(img);
if (images.length > 1) {
const badge = UI.el('div', { class: 'pqueue-batch-badge', text: `${images.length}` });
thumbWrap.appendChild(badge);
if (!container.children.length) {
const images = UI.extractImages(row);
if (images.length) {
const first = images[0];
const url = UI.buildPreviewUrl(first, row.prompt_id);
const thumbWrap = UI.el('div', { class: 'pqueue-thumb-wrap' });
const img = UI.el('img', { src: url.href, class: 'pqueue-thumb', title: first.filename });
attachFallbackHandler(thumbWrap, img, images);
thumbWrap.appendChild(img);
if (images.length > 1) {
const badge = UI.el('div', { class: 'pqueue-batch-badge', text: `${images.length}` });
thumbWrap.appendChild(badge);
}
thumbWrap.onclick = () => UI.openGallery(images, 0, row.prompt_id);
container.appendChild(thumbWrap);
}
}
thumbWrap.onclick = () => UI.openGallery(images, 0, row.prompt_id);
container.appendChild(thumbWrap);
if (!container.children.length) ensurePlaceholder();
return container;
} catch(e) { return UI.el('span', {}); }
},