Feat: Enhance Persistent Queue UI with status-based thumbnail placeholders
This commit is contained in:
@@ -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
@@ -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', {}); }
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user