Refactor Persistent Queue UI for improved image handling and gallery view
This commit is contained in:
+11
-1
@@ -15,7 +15,17 @@
|
||||
.pqueue-status-success{color:var(--green-500)}
|
||||
.pqueue-status-error{color:var(--red-500)}
|
||||
.pqueue-empty{opacity:.7;font-size:.9rem;padding:.25rem 0}
|
||||
.pqueue-thumbs-grid{display:grid;grid-template-columns:repeat(2, 96px);gap:6px}
|
||||
.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-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}
|
||||
.pqueue-gallery-img{max-width:92vw;max-height:92vh;border-radius:6px;box-shadow:0 8px 32px rgba(0,0,0,.6)}
|
||||
.pqueue-gallery-counter{position:absolute;left:50%;transform:translateX(-50%);bottom:-28px;color:#fff;font-size:.85rem;opacity:.9}
|
||||
.pqueue-gallery-close{position:absolute;top:-36px;right:0;background:transparent;border:none;color:#fff;font-size:24px;cursor:pointer}
|
||||
.pqueue-gallery-nav{position:absolute;top:50%;transform:translateY(-50%);background:rgba(0,0,0,0.5);border:none;color:#fff;font-size:24px;cursor:pointer;width:36px;height:36px;border-radius:18px}
|
||||
.pqueue-gallery-prev{left:-44px}
|
||||
.pqueue-gallery-next{right:-44px}
|
||||
.pqueue-progress-wrap{width:100%;height:8px;background:#1f2a1f;border-radius:4px;overflow:hidden;margin-top:6px}
|
||||
.pqueue-progress-bar{height:100%;width:0;background:#22c55e;transition:width .3s ease}
|
||||
+124
-35
@@ -232,37 +232,54 @@
|
||||
|
||||
buildResultThumbs(row) {
|
||||
try {
|
||||
const wrap = UI.el('div', { class: 'flex gap-1 flex-wrap' });
|
||||
let outputs = row.outputs ?? {};
|
||||
if (typeof outputs === 'string') {
|
||||
try { outputs = JSON.parse(outputs); } catch(e) { outputs = {}; }
|
||||
}
|
||||
const images = [];
|
||||
Object.values(outputs).forEach(v => {
|
||||
if (v && typeof v === 'object') {
|
||||
const imgs = v.images ?? v.ui?.images ?? [];
|
||||
if (Array.isArray(imgs)) imgs.forEach(i => images.push(i));
|
||||
} else if (Array.isArray(v)) {
|
||||
v.forEach(i => { if (i?.filename || i?.name) images.push(i); });
|
||||
}
|
||||
});
|
||||
images.slice(0, 4).forEach(i => {
|
||||
const filename = i.filename ?? i.name ?? '';
|
||||
const type = i.type ?? 'output';
|
||||
const subfolder = i.subfolder ?? '';
|
||||
const url = new URL('/view', window.location.origin);
|
||||
url.searchParams.set('filename', filename);
|
||||
url.searchParams.set('type', type);
|
||||
if (subfolder) url.searchParams.set('subfolder', subfolder);
|
||||
url.searchParams.set('preview', 'webp;50');
|
||||
const img = UI.el('img', { src: url.href, class: 'pqueue-thumb', title: filename });
|
||||
img.onclick = () => UI.openGallery(filename, type, subfolder);
|
||||
wrap.appendChild(img);
|
||||
});
|
||||
return wrap;
|
||||
const container = UI.el('div', { class: 'flex gap-1 flex-wrap' });
|
||||
const images = UI.extractImages(row);
|
||||
if (!images.length) return container;
|
||||
const first = images[0];
|
||||
const url = new URL('/view', window.location.origin);
|
||||
url.searchParams.set('filename', first.filename);
|
||||
url.searchParams.set('type', first.type);
|
||||
if (first.subfolder) url.searchParams.set('subfolder', first.subfolder);
|
||||
url.searchParams.set('preview', 'webp;50');
|
||||
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);
|
||||
}
|
||||
thumbWrap.onclick = () => UI.openGallery(images, 0);
|
||||
container.appendChild(thumbWrap);
|
||||
return container;
|
||||
} catch(e) { return UI.el('span', {}); }
|
||||
},
|
||||
|
||||
// Collect normalized image descriptors for a row
|
||||
extractImages(row) {
|
||||
try {
|
||||
let outputs = row.outputs ?? {};
|
||||
if (typeof outputs === 'string') {
|
||||
try { outputs = JSON.parse(outputs); } catch(e) { outputs = {}; }
|
||||
}
|
||||
const images = [];
|
||||
Object.values(outputs).forEach(v => {
|
||||
if (v && typeof v === 'object') {
|
||||
const imgs = v.images ?? v.ui?.images ?? [];
|
||||
if (Array.isArray(imgs)) imgs.forEach(i => {
|
||||
const filename = i.filename ?? i.name;
|
||||
if (filename) images.push({ filename, type: i.type ?? 'output', subfolder: i.subfolder ?? '' });
|
||||
});
|
||||
} else if (Array.isArray(v)) {
|
||||
v.forEach(i => {
|
||||
const filename = i?.filename ?? i?.name;
|
||||
if (filename) images.push({ filename, type: i.type ?? 'output', subfolder: i.subfolder ?? '' });
|
||||
});
|
||||
}
|
||||
});
|
||||
return images;
|
||||
} catch(e) { return []; }
|
||||
},
|
||||
|
||||
buildDuration(row) {
|
||||
try {
|
||||
const dur = Number(row.duration_seconds);
|
||||
@@ -280,13 +297,85 @@
|
||||
} catch(e) { return ''; }
|
||||
},
|
||||
|
||||
openGallery(filename, type, subfolder) {
|
||||
const url = new URL('/view', window.location.origin);
|
||||
url.searchParams.set('filename', filename);
|
||||
url.searchParams.set('type', type);
|
||||
if (subfolder) url.searchParams.set('subfolder', subfolder);
|
||||
window.open(url.href, '_blank');
|
||||
},
|
||||
// Lightbox-style gallery for image batches
|
||||
openGallery(images, startIndex = 0) {
|
||||
try {
|
||||
UI.ensureGalleryElements();
|
||||
UI._galleryState.images = images || [];
|
||||
UI._galleryState.index = Math.max(0, Math.min(startIndex, UI._galleryState.images.length - 1));
|
||||
UI._galleryOverlay.style.display = 'flex';
|
||||
UI.showGalleryImage(UI._galleryState.index);
|
||||
UI._bindGalleryKeys(true);
|
||||
} catch (e) { /* noop */ }
|
||||
},
|
||||
|
||||
closeGallery() {
|
||||
if (UI._galleryOverlay) UI._galleryOverlay.style.display = 'none';
|
||||
UI._bindGalleryKeys(false);
|
||||
},
|
||||
|
||||
showGalleryImage(index) {
|
||||
const imgs = UI._galleryState.images || [];
|
||||
if (!imgs.length) return;
|
||||
UI._galleryState.index = (index + imgs.length) % imgs.length;
|
||||
const imgDesc = imgs[UI._galleryState.index];
|
||||
const url = new URL('/view', window.location.origin);
|
||||
url.searchParams.set('filename', imgDesc.filename);
|
||||
url.searchParams.set('type', imgDesc.type);
|
||||
if (imgDesc.subfolder) url.searchParams.set('subfolder', imgDesc.subfolder);
|
||||
UI._galleryImg.src = url.href;
|
||||
UI._galleryCounter.textContent = `${UI._galleryState.index + 1} / ${imgs.length}`;
|
||||
},
|
||||
|
||||
nextImage() { UI.showGalleryImage(UI._galleryState.index + 1); },
|
||||
prevImage() { UI.showGalleryImage(UI._galleryState.index - 1); },
|
||||
|
||||
_bindGalleryKeys(enabled) {
|
||||
const handler = (e) => {
|
||||
if (UI._galleryOverlay?.style.display !== 'flex') return;
|
||||
if (e.key === 'Escape') UI.closeGallery();
|
||||
else if (e.key === 'ArrowRight') UI.nextImage();
|
||||
else if (e.key === 'ArrowLeft') UI.prevImage();
|
||||
};
|
||||
if (enabled) {
|
||||
UI._galleryKeyHandler = handler;
|
||||
document.addEventListener('keydown', handler);
|
||||
} else if (UI._galleryKeyHandler) {
|
||||
document.removeEventListener('keydown', UI._galleryKeyHandler);
|
||||
UI._galleryKeyHandler = null;
|
||||
}
|
||||
},
|
||||
|
||||
ensureGalleryElements() {
|
||||
if (UI._galleryOverlay) return;
|
||||
UI._galleryState = { images: [], index: 0 };
|
||||
const overlay = UI.el('div', { class: 'pqueue-gallery-overlay' });
|
||||
const box = UI.el('div', { class: 'pqueue-gallery' });
|
||||
const closeBtn = UI.el('button', { class: 'pqueue-gallery-close', title: 'Close' });
|
||||
closeBtn.textContent = '×';
|
||||
const img = UI.el('img', { class: 'pqueue-gallery-img' });
|
||||
const counter = UI.el('div', { class: 'pqueue-gallery-counter' });
|
||||
const prev = UI.el('button', { class: 'pqueue-gallery-nav pqueue-gallery-prev', title: 'Previous' });
|
||||
prev.textContent = '‹';
|
||||
const next = UI.el('button', { class: 'pqueue-gallery-nav pqueue-gallery-next', title: 'Next' });
|
||||
next.textContent = '›';
|
||||
|
||||
closeBtn.onclick = () => UI.closeGallery();
|
||||
prev.onclick = () => UI.prevImage();
|
||||
next.onclick = () => UI.nextImage();
|
||||
overlay.onclick = (e) => { if (e.target === overlay) UI.closeGallery(); };
|
||||
|
||||
box.appendChild(closeBtn);
|
||||
box.appendChild(img);
|
||||
box.appendChild(counter);
|
||||
box.appendChild(prev);
|
||||
box.appendChild(next);
|
||||
overlay.appendChild(box);
|
||||
document.body.appendChild(overlay);
|
||||
UI._galleryOverlay = overlay;
|
||||
UI._galleryImg = img;
|
||||
UI._galleryCounter = counter;
|
||||
},
|
||||
|
||||
ensureStyles() {
|
||||
if (document.getElementById('pqueue-styles')) return;
|
||||
|
||||
Reference in New Issue
Block a user