feat: desktop lightbox — click to open, arrows navigate, J/S/X tag, Esc close
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,9 @@
|
|||||||
@keydown.x.window="tagFocused('skip')"
|
@keydown.x.window="tagFocused('skip')"
|
||||||
@keydown.space.prevent.window="tagFocused('skip')"
|
@keydown.space.prevent.window="tagFocused('skip')"
|
||||||
@keydown.left.prevent.window="navigate(-1)"
|
@keydown.left.prevent.window="navigate(-1)"
|
||||||
@keydown.right.prevent.window="navigate(1)">
|
@keydown.right.prevent.window="navigate(1)"
|
||||||
|
@keydown.escape.window="closeLightbox()"
|
||||||
|
@keydown.enter.window="focused && openLightbox(focused)">
|
||||||
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h1 class="text-xl font-bold">Triage</h1>
|
<h1 class="text-xl font-bold">Triage</h1>
|
||||||
@@ -42,7 +44,7 @@
|
|||||||
data-asset-id="{{ photo.id }}"
|
data-asset-id="{{ photo.id }}"
|
||||||
data-tag="{{ photo.tag }}"
|
data-tag="{{ photo.tag }}"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click="select($el)"
|
@click="openLightbox($el)"
|
||||||
@focus="select($el)">
|
@focus="select($el)">
|
||||||
<img src="/proxy/thumb/{{ photo.id }}"
|
<img src="/proxy/thumb/{{ photo.id }}"
|
||||||
class="w-full aspect-square object-cover" loading="lazy" alt="">
|
class="w-full aspect-square object-cover" loading="lazy" alt="">
|
||||||
@@ -63,6 +65,25 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# ── Lightbox overlay (desktop) ── #}
|
||||||
|
<div id="lb" class="fixed inset-0 z-50 bg-black/95 flex items-center justify-center" style="display:none">
|
||||||
|
<button class="absolute top-4 right-4 btn btn-circle btn-sm btn-ghost text-white opacity-60 hover:opacity-100 text-lg"
|
||||||
|
@click="closeLightbox()">✕</button>
|
||||||
|
<button class="absolute left-3 top-1/2 -translate-y-1/2 btn btn-circle btn-ghost text-white text-4xl opacity-60 hover:opacity-100"
|
||||||
|
@click="navigate(-1)">‹</button>
|
||||||
|
<button class="absolute right-3 top-1/2 -translate-y-1/2 btn btn-circle btn-ghost text-white text-4xl opacity-60 hover:opacity-100"
|
||||||
|
@click="navigate(1)">›</button>
|
||||||
|
<div class="flex flex-col items-center gap-3 px-16 max-w-full">
|
||||||
|
<img id="lb-img" src="" class="max-h-[82vh] max-w-[88vw] object-contain rounded-lg shadow-2xl" alt="">
|
||||||
|
<div class="flex items-center gap-4 text-white/60 text-sm">
|
||||||
|
<span id="lb-date"></span>
|
||||||
|
<span id="lb-filename" class="opacity-40"></span>
|
||||||
|
<span id="lb-tag-badge" class="badge badge-sm"></span>
|
||||||
|
<span class="opacity-30 text-xs">J journal · S story · X skip · ← → navigate · Esc close</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{# ── Mobile card UI (hidden on desktop) ── #}
|
{# ── Mobile card UI (hidden on desktop) ── #}
|
||||||
<div id="mobile-view" style="display:none">
|
<div id="mobile-view" style="display:none">
|
||||||
{# Progress bar #}
|
{# Progress bar #}
|
||||||
@@ -141,6 +162,8 @@ function triageApp(albumId) {
|
|||||||
return {
|
return {
|
||||||
focused: null,
|
focused: null,
|
||||||
|
|
||||||
|
lightboxOpen: false,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
const first = document.querySelector('.photo-card');
|
const first = document.querySelector('.photo-card');
|
||||||
if (first) this.select(first);
|
if (first) this.select(first);
|
||||||
@@ -153,6 +176,44 @@ function triageApp(albumId) {
|
|||||||
el.classList.add('ring-4', 'ring-white', 'ring-offset-2', 'z-10');
|
el.classList.add('ring-4', 'ring-white', 'ring-offset-2', 'z-10');
|
||||||
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
}
|
}
|
||||||
|
if (this.lightboxOpen) this.updateLightbox();
|
||||||
|
},
|
||||||
|
|
||||||
|
openLightbox(el) {
|
||||||
|
this.select(el);
|
||||||
|
this.lightboxOpen = true;
|
||||||
|
document.getElementById('lb').style.display = '';
|
||||||
|
this.updateLightbox();
|
||||||
|
},
|
||||||
|
|
||||||
|
closeLightbox() {
|
||||||
|
if (!this.lightboxOpen) return;
|
||||||
|
this.lightboxOpen = false;
|
||||||
|
document.getElementById('lb').style.display = 'none';
|
||||||
|
},
|
||||||
|
|
||||||
|
updateLightbox() {
|
||||||
|
const el = this.focused;
|
||||||
|
if (!el) return;
|
||||||
|
const assetId = el.dataset.assetId;
|
||||||
|
const tag = el.dataset.tag;
|
||||||
|
document.getElementById('lb-img').src = `/proxy/thumb/${assetId}`;
|
||||||
|
const timeEl = el.querySelector('div');
|
||||||
|
document.getElementById('lb-date').textContent = timeEl ? timeEl.textContent.trim() : '';
|
||||||
|
document.getElementById('lb-filename').textContent = el.dataset.filename || '';
|
||||||
|
const badgeEl = document.getElementById('lb-tag-badge');
|
||||||
|
const MAP = {
|
||||||
|
journal: ['badge-success', 'Journal'],
|
||||||
|
story: ['badge-info', 'Story'],
|
||||||
|
skip: ['badge-ghost opacity-60', 'Skip'],
|
||||||
|
};
|
||||||
|
if (MAP[tag]) {
|
||||||
|
badgeEl.className = `badge badge-sm ${MAP[tag][0]}`;
|
||||||
|
badgeEl.textContent = MAP[tag][1];
|
||||||
|
} else {
|
||||||
|
badgeEl.className = 'badge badge-sm badge-outline opacity-30';
|
||||||
|
badgeEl.textContent = 'Untagged';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
navigate(dir) {
|
navigate(dir) {
|
||||||
@@ -187,6 +248,7 @@ function triageApp(albumId) {
|
|||||||
}
|
}
|
||||||
updateBadge(el, tag);
|
updateBadge(el, tag);
|
||||||
this.updateCount();
|
this.updateCount();
|
||||||
|
if (this.lightboxOpen) this.updateLightbox();
|
||||||
},
|
},
|
||||||
|
|
||||||
updateCount() {
|
updateCount() {
|
||||||
|
|||||||
Reference in New Issue
Block a user