fix: mtime filter + lightbox navigation (arrows, swipe, keyboard)
This commit is contained in:
@@ -66,15 +66,26 @@ async def admin_get_photos(
|
|||||||
photos = [p for p in all_photos if _is_image(p)]
|
photos = [p for p in all_photos if _is_image(p)]
|
||||||
|
|
||||||
# Enrichit chaque photo avec mtime depuis le disque
|
# Enrichit chaque photo avec mtime depuis le disque
|
||||||
|
# Indexe par stem ET par nom complet pour couvrir les deux formats d'id
|
||||||
mtime_index: dict[str, float] = {}
|
mtime_index: dict[str, float] = {}
|
||||||
if media_dir.exists():
|
for media_dir_candidate in [
|
||||||
for f in media_dir.iterdir():
|
media_dir,
|
||||||
mtime_index[f.stem] = f.stat().st_mtime
|
Path("/home/pi/photobooth-data/media/processed_full"),
|
||||||
|
Path("/home/pi/photobooth-data/media"),
|
||||||
|
]:
|
||||||
|
if media_dir_candidate.exists():
|
||||||
|
for f in media_dir_candidate.iterdir():
|
||||||
|
if f.is_file():
|
||||||
|
mt = f.stat().st_mtime
|
||||||
|
mtime_index[f.name] = mt # uuid.jpg
|
||||||
|
mtime_index[f.stem] = mt # uuid
|
||||||
|
break
|
||||||
|
|
||||||
for p in photos:
|
for p in photos:
|
||||||
pid = _get_id(p)
|
pid = _get_id(p)
|
||||||
p["photo_id"] = pid
|
p["photo_id"] = pid
|
||||||
mt = mtime_index.get(pid, 0.0)
|
stem = pid.rsplit(".", 1)[0] if "." in pid else pid
|
||||||
|
mt = mtime_index.get(pid) or mtime_index.get(stem) or 0.0
|
||||||
p["mtime"] = mt
|
p["mtime"] = mt
|
||||||
p["date_iso"] = datetime.fromtimestamp(mt).strftime("%Y-%m-%d") if mt else ""
|
p["date_iso"] = datetime.fromtimestamp(mt).strftime("%Y-%m-%d") if mt else ""
|
||||||
p["date_label"] = datetime.fromtimestamp(mt).strftime("%d/%m/%Y %H:%M") if mt else ""
|
p["date_label"] = datetime.fromtimestamp(mt).strftime("%d/%m/%Y %H:%M") if mt else ""
|
||||||
|
|||||||
@@ -106,16 +106,31 @@
|
|||||||
display: none; position: fixed; inset: 0; z-index: 1000;
|
display: none; position: fixed; inset: 0; z-index: 1000;
|
||||||
background: rgba(0,0,0,.92); backdrop-filter: blur(8px);
|
background: rgba(0,0,0,.92); backdrop-filter: blur(8px);
|
||||||
align-items: center; justify-content: center; padding: 1.5rem;
|
align-items: center; justify-content: center; padding: 1.5rem;
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
.lightbox.open { display: flex; }
|
.lightbox.open { display: flex; }
|
||||||
|
|
||||||
|
.lb-nav-btn {
|
||||||
|
flex-shrink: 0; width: 44px; height: 44px; border-radius: 50%;
|
||||||
|
border: 1px solid rgba(255,255,255,.2); background: rgba(255,255,255,.08);
|
||||||
|
color: #fff; font-size: 1.3rem; cursor: pointer; display: flex;
|
||||||
|
align-items: center; justify-content: center; transition: background .15s;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.lb-nav-btn:hover:not(:disabled) { background: rgba(255,255,255,.18); }
|
||||||
|
.lb-nav-btn:disabled { opacity: .2; cursor: default; }
|
||||||
|
|
||||||
.lb-modal {
|
.lb-modal {
|
||||||
background: var(--surface); border: 1px solid var(--border);
|
background: var(--surface); border: 1px solid var(--border);
|
||||||
border-radius: 16px; overflow: hidden;
|
border-radius: 16px; overflow: hidden;
|
||||||
max-width: min(900px, 95vw); width: 100%;
|
max-width: min(860px, calc(95vw - 110px)); width: 100%;
|
||||||
display: flex; flex-direction: column; max-height: 95vh;
|
display: flex; flex-direction: column; max-height: 95vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lb-counter {
|
||||||
|
font-size: .75rem; color: var(--text-muted); white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.lb-header {
|
.lb-header {
|
||||||
display: flex; align-items: center; justify-content: space-between;
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
padding: .75rem 1.1rem; border-bottom: 1px solid var(--border);
|
padding: .75rem 1.1rem; border-bottom: 1px solid var(--border);
|
||||||
@@ -247,6 +262,7 @@
|
|||||||
|
|
||||||
<!-- ── Lightbox ─────────────────────────────────────────────────────────────── -->
|
<!-- ── Lightbox ─────────────────────────────────────────────────────────────── -->
|
||||||
<div class="lightbox" id="lightbox" onclick="lbBackdropClose(event)">
|
<div class="lightbox" id="lightbox" onclick="lbBackdropClose(event)">
|
||||||
|
<button class="lb-nav-btn" id="lb-prev" onclick="lbNavigate(-1);event.stopPropagation()">‹</button>
|
||||||
<div class="lb-modal">
|
<div class="lb-modal">
|
||||||
|
|
||||||
<!-- En-tête -->
|
<!-- En-tête -->
|
||||||
@@ -255,6 +271,7 @@
|
|||||||
<div class="lb-date" id="lb-date">—</div>
|
<div class="lb-date" id="lb-date">—</div>
|
||||||
<div class="lb-id" id="lb-id"></div>
|
<div class="lb-id" id="lb-id"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<span class="lb-counter" id="lb-counter"></span>
|
||||||
<button class="lb-close-btn" onclick="closeLightbox()">✕</button>
|
<button class="lb-close-btn" onclick="closeLightbox()">✕</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -294,6 +311,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<button class="lb-nav-btn" id="lb-next" onclick="lbNavigate(1);event.stopPropagation()">›</button>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -302,6 +320,8 @@
|
|||||||
let currentPage = 1;
|
let currentPage = 1;
|
||||||
let totalPages = 1;
|
let totalPages = 1;
|
||||||
let allPhotos = [];
|
let allPhotos = [];
|
||||||
|
let displayedPhotos = []; // photos actuellement dans la grille (après filtre statut)
|
||||||
|
let currentIndex = -1; // index dans displayedPhotos
|
||||||
let currentPhotoId = null;
|
let currentPhotoId = null;
|
||||||
let currentPhotoData = null;
|
let currentPhotoData = null;
|
||||||
|
|
||||||
@@ -411,15 +431,17 @@ function updateFilterSummary(total) {
|
|||||||
// Rendu grille
|
// Rendu grille
|
||||||
// ════════════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════════════
|
||||||
function renderGrid(photos) {
|
function renderGrid(photos) {
|
||||||
|
displayedPhotos = photos || [];
|
||||||
const grid = document.getElementById('photo-grid');
|
const grid = document.getElementById('photo-grid');
|
||||||
|
|
||||||
if (!photos || !photos.length) {
|
if (!displayedPhotos.length) {
|
||||||
grid.innerHTML = `<div style="color:var(--text-muted);grid-column:1/-1;text-align:center;padding:2rem">
|
grid.innerHTML = `<div style="color:var(--text-muted);grid-column:1/-1;text-align:center;padding:2rem">
|
||||||
📷 Aucune photo pour ces filtres</div>`;
|
📷 Aucune photo pour ces filtres</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
grid.innerHTML = photos.map(p => {
|
// On utilise l'index (pas le JSON sérialisé) pour éviter les problèmes de quotes
|
||||||
|
grid.innerHTML = displayedPhotos.map((p, idx) => {
|
||||||
const pid = p.photo_id || p.id || '';
|
const pid = p.photo_id || p.id || '';
|
||||||
const hasPending = p.print_pending > 0;
|
const hasPending = p.print_pending > 0;
|
||||||
const hasPrinting = p.print_printing > 0;
|
const hasPrinting = p.print_printing > 0;
|
||||||
@@ -433,7 +455,7 @@ function renderGrid(photos) {
|
|||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="photo-card ${hasPending || hasPrinting ? 'has-print' : ''}" id="card-${pid}"
|
<div class="photo-card ${hasPending || hasPrinting ? 'has-print' : ''}" id="card-${pid}"
|
||||||
onclick="openLightbox(${JSON.stringify(p).replace(/'/g, ''')})">
|
onclick="openLightboxIdx(${idx})">
|
||||||
<img src="${p.thumb_url}" loading="lazy" alt="">
|
<img src="${p.thumb_url}" loading="lazy" alt="">
|
||||||
${badge}${dateLabel}
|
${badge}${dateLabel}
|
||||||
<div class="card-overlay">
|
<div class="card-overlay">
|
||||||
@@ -450,7 +472,10 @@ function renderGrid(photos) {
|
|||||||
// ════════════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════════════
|
||||||
// Lightbox
|
// Lightbox
|
||||||
// ════════════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════════════
|
||||||
function openLightbox(photo) {
|
function openLightboxIdx(idx) {
|
||||||
|
if (idx < 0 || idx >= displayedPhotos.length) return;
|
||||||
|
currentIndex = idx;
|
||||||
|
const photo = displayedPhotos[idx];
|
||||||
currentPhotoData = photo;
|
currentPhotoData = photo;
|
||||||
currentPhotoId = photo.photo_id || photo.id || '';
|
currentPhotoId = photo.photo_id || photo.id || '';
|
||||||
|
|
||||||
@@ -458,11 +483,20 @@ function openLightbox(photo) {
|
|||||||
document.getElementById('lb-date').textContent = photo.date_label || '—';
|
document.getElementById('lb-date').textContent = photo.date_label || '—';
|
||||||
document.getElementById('lb-id').textContent = currentPhotoId;
|
document.getElementById('lb-id').textContent = currentPhotoId;
|
||||||
document.getElementById('lb-btn-dl').href = photo.download_url || photo.full_url;
|
document.getElementById('lb-btn-dl').href = photo.download_url || photo.full_url;
|
||||||
|
document.getElementById('lb-counter').textContent = `${idx + 1} / ${displayedPhotos.length}`;
|
||||||
|
|
||||||
|
// Boutons nav
|
||||||
|
document.getElementById('lb-prev').disabled = idx <= 0;
|
||||||
|
document.getElementById('lb-next').disabled = idx >= displayedPhotos.length - 1;
|
||||||
|
|
||||||
_updateLbPrintPanel(photo);
|
_updateLbPrintPanel(photo);
|
||||||
document.getElementById('lightbox').classList.add('open');
|
document.getElementById('lightbox').classList.add('open');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lbNavigate(dir) {
|
||||||
|
openLightboxIdx(currentIndex + dir);
|
||||||
|
}
|
||||||
|
|
||||||
function _updateLbPrintPanel(photo) {
|
function _updateLbPrintPanel(photo) {
|
||||||
const panel = document.getElementById('lb-print-panel');
|
const panel = document.getElementById('lb-print-panel');
|
||||||
const list = document.getElementById('lb-queue-list');
|
const list = document.getElementById('lb-queue-list');
|
||||||
@@ -496,12 +530,31 @@ function closeLightbox() {
|
|||||||
document.getElementById('lightbox').classList.remove('open');
|
document.getElementById('lightbox').classList.remove('open');
|
||||||
document.getElementById('lb-img').src = '';
|
document.getElementById('lb-img').src = '';
|
||||||
currentPhotoId = currentPhotoData = null;
|
currentPhotoId = currentPhotoData = null;
|
||||||
|
currentIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function lbBackdropClose(e) {
|
function lbBackdropClose(e) {
|
||||||
if (e.target === document.getElementById('lightbox')) closeLightbox();
|
if (e.target === document.getElementById('lightbox')) closeLightbox();
|
||||||
}
|
}
|
||||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeLightbox(); });
|
|
||||||
|
// Clavier : Échap + flèches
|
||||||
|
document.addEventListener('keydown', e => {
|
||||||
|
const lb = document.getElementById('lightbox');
|
||||||
|
if (!lb.classList.contains('open')) return;
|
||||||
|
if (e.key === 'Escape') { closeLightbox(); }
|
||||||
|
else if (e.key === 'ArrowLeft') { lbNavigate(-1); }
|
||||||
|
else if (e.key === 'ArrowRight') { lbNavigate(1); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Swipe tactile
|
||||||
|
let _touchX = 0;
|
||||||
|
document.getElementById('lightbox').addEventListener('touchstart', e => {
|
||||||
|
_touchX = e.touches[0].clientX;
|
||||||
|
}, { passive: true });
|
||||||
|
document.getElementById('lightbox').addEventListener('touchend', e => {
|
||||||
|
const dx = e.changedTouches[0].clientX - _touchX;
|
||||||
|
if (Math.abs(dx) > 50) lbNavigate(dx < 0 ? 1 : -1);
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════════════
|
||||||
// Actions impression
|
// Actions impression
|
||||||
@@ -586,15 +639,23 @@ async function _refreshPhotoData(pid) {
|
|||||||
try {
|
try {
|
||||||
const data = await api('GET', `/admin/api/gallery/photos?${_buildParams(currentPage)}`);
|
const data = await api('GET', `/admin/api/gallery/photos?${_buildParams(currentPage)}`);
|
||||||
allPhotos = data.photos || [];
|
allPhotos = data.photos || [];
|
||||||
const photo = allPhotos.find(p => (p.photo_id || p.id) === pid);
|
|
||||||
if (photo && currentPhotoId === pid) {
|
|
||||||
currentPhotoData = photo;
|
|
||||||
_updateLbPrintPanel(photo);
|
|
||||||
}
|
|
||||||
let photos = allPhotos;
|
let photos = allPhotos;
|
||||||
if (statusFilter === 'pending') photos = allPhotos.filter(p => p.print_pending > 0 || p.print_printing > 0);
|
if (statusFilter === 'pending') photos = allPhotos.filter(p => p.print_pending > 0 || p.print_printing > 0);
|
||||||
renderGrid(photos);
|
renderGrid(photos);
|
||||||
document.getElementById('pending-count').textContent = allPhotos.filter(p => p.print_pending > 0).length;
|
document.getElementById('pending-count').textContent = allPhotos.filter(p => p.print_pending > 0).length;
|
||||||
|
|
||||||
|
// Si lightbox ouverte sur cette photo, mettre à jour le panel impression
|
||||||
|
if (currentPhotoId === pid) {
|
||||||
|
const newIdx = displayedPhotos.findIndex(p => (p.photo_id || p.id) === pid);
|
||||||
|
if (newIdx >= 0) {
|
||||||
|
currentIndex = newIdx;
|
||||||
|
currentPhotoData = displayedPhotos[newIdx];
|
||||||
|
_updateLbPrintPanel(currentPhotoData);
|
||||||
|
document.getElementById('lb-prev').disabled = newIdx <= 0;
|
||||||
|
document.getElementById('lb-next').disabled = newIdx >= displayedPhotos.length - 1;
|
||||||
|
document.getElementById('lb-counter').textContent = `${newIdx + 1} / ${displayedPhotos.length}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user