// public/js/gallery.js // Coordinatore della galleria: caricamento progressivo delle foto. (() => { "use strict"; const ACTIVITY_EXTENSIONS = ["fit", "tcx", "gpx"]; const PAGE_SIZE = 40; let photoItems = []; let nextCursor = null; let hasMorePhotos = true; let loadingPhotos = false; let photoRequestId = 0; // Stato separato per la paginazione della galleria Attività. let activityItems = []; let nextActivityCursor = null; let hasMoreActivities = true; let loadingActivities = false; let activityRequestId = 0; function isAuthenticated() { return ( document.body.classList.contains("authenticated") && window.AppAuth && typeof window.AppAuth.isLoggedIn === "function" && window.AppAuth.isLoggedIn() ); } function filterByMode(items) { if (window.state?.mode === "photos") { return items.filter(item => item.mime_type?.startsWith("image/") || item.mime_type?.startsWith("video/") ); } return items.filter(item => { const format = String( item.format || item.file_extension || item.mime_type || "" ).toLowerCase(); const name = String( item.name || item.file_name || item.path || "" ).toLowerCase(); return ACTIVITY_EXTENSIONS.some(extension => format === extension || format.includes(extension) || name.endsWith(`.${extension}`) ); }); } function getPhotoQuery(cursor) { const params = new URLSearchParams(); params.set("limit", String(PAGE_SIZE)); params.set("sort", window.currentSort || "desc"); const showDeleted = localStorage.getItem("showDeleted") === "1"; if (showDeleted) params.set("showDeleted", "1"); const search = String(window.currentSearch || "").trim(); const year = String(window.currentYear || "").trim(); const month = String(window.currentMonth || "").trim(); const folder = String(window.currentFolder || "").trim(); const filter = String(window.currentFilter || ""); if (search) params.set("search", search); if (year) params.set("year", year); if (month) params.set("month", month); if (folder) params.set("folder", folder); if (filter && filter !== "all") params.set("filter", filter); if (cursor) params.set("cursor", cursor); return params; } function renderPhotoItems(append = false) { const filtered = filterByMode(photoItems); const sections = window.GalleryUtils.groupByDate( filtered, window.currentGroup || "auto" ); window.GalleryRenderer.renderGallery(sections, append); } async function loadNextPhotoPage(requestId, firstPage = false) { if ( loadingPhotos || !hasMorePhotos || requestId !== photoRequestId || !isAuthenticated() ) { return; } loadingPhotos = true; try { const token = localStorage.getItem("token"); const params = getPhotoQuery(firstPage ? null : nextCursor); const response = await fetch(`/photos/gallery?${params.toString()}`, { headers: { Authorization: "Bearer " + token } }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (requestId !== photoRequestId) return; if (!Array.isArray(data.items)) { throw new Error("Risposta gallery senza array items"); } const existingIds = new Set( photoItems.map(item => String(item.id)) ); const newlyAddedItems = []; for (const item of data.items) { const id = String(item.id); if (!existingIds.has(id)) { photoItems.push(item); newlyAddedItems.push(item); existingIds.add(id); } } // Aggiorna l'indice della mappa soltanto con le nuove foto. if (newlyAddedItems.length && window.state?.mode === "photos") { window.MapSpatialIndex?.append?.(newlyAddedItems); if ( window.MapEngine?.isReady?.() && typeof window.renderPhotoMapLayer === "function" ) { window.renderPhotoMapLayer(); } } nextCursor = data.nextCursor || null; hasMorePhotos = Boolean(data.hasMore && nextCursor); renderPhotoItems(!firstPage); console.log( `[gallery] paginazione: caricati ${data.items.length}, ` + `totale in sessione ${photoItems.length}, altri=${hasMorePhotos}` ); } catch (error) { console.error("[gallery] errore caricamento pagina:", error); } finally { if (requestId === photoRequestId) { loadingPhotos = false; } // Se la prima pagina non riempie lo schermo, continua a caricare // finché lo scorrimento diventa possibile. if ( requestId === photoRequestId && hasMorePhotos && !loadingPhotos && window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 500 ) { loadNextPhotoPage(requestId); } } } function filterActivityItems(items) { const query = String(window.currentSearch || "") .trim() .toLocaleLowerCase("it-IT"); const year = String(window.currentYear || "").trim(); const month = String(window.currentMonth || "").trim(); const folder = String(window.currentFolder || "").trim(); return items.filter(item => { if (folder && String(item.cartella || "") !== folder) return false; if (query) { const text = [ item.name, item.file_name, item.path, item.cartella, item.display_name, item.route_name, item.title, item.description, item.sport, item.activity_type ].filter(value => value != null).join(" ") .toLocaleLowerCase("it-IT"); if (!text.includes(query)) return false; } if (year || month) { if (!item.taken_at) return false; const date = new Date(item.taken_at); if (Number.isNaN(date.getTime())) return false; if (year && date.getFullYear() !== Number(year)) return false; if (month && date.getMonth() + 1 !== Number(month)) return false; } return true; }); } function renderActivityItems(append = false) { const showDeleted = localStorage.getItem("showDeleted") === "1"; const visible = showDeleted ? activityItems : activityItems.filter(item => !item.deleted_at); const filtered = filterActivityItems(visible); const sorted = window.GalleryUtils.sortByDate( filtered, window.currentSort || "desc" ); const sections = window.GalleryUtils.groupByDate( sorted, window.currentGroup || "auto" ); window.GalleryRenderer.renderGallery(sections, append); } async function loadNextActivityPage(requestId, firstPage = false) { if ( loadingActivities || !hasMoreActivities || requestId !== activityRequestId || !isAuthenticated() || window.state?.mode === "photos" ) return; loadingActivities = true; try { const token = localStorage.getItem("token"); const params = new URLSearchParams(); params.set("limit", String(PAGE_SIZE)); params.set("sort", window.currentSort || "desc"); if (localStorage.getItem("showDeleted") === "1") { params.set("showDeleted", "1"); } if (!firstPage && nextActivityCursor) { params.set("cursor", nextActivityCursor); } const response = await fetch( `/activities/gallery?${params.toString()}`, { headers: { Authorization: "Bearer " + token } } ); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (requestId !== activityRequestId) return; if (!Array.isArray(data.items)) { throw new Error("Risposta attività senza array items"); } const existingIds = new Set( activityItems.map(item => String(item.id)) ); for (const item of data.items) { const id = String(item.id); if (!existingIds.has(id)) { activityItems.push(item); existingIds.add(id); } } nextActivityCursor = data.nextCursor || null; hasMoreActivities = Boolean(data.hasMore && nextActivityCursor); renderActivityItems(!firstPage); console.log( `[gallery attività] pagina=${data.items.length}, ` + `totale=${activityItems.length}, altri=${hasMoreActivities}` ); } catch (error) { console.error("[gallery attività] errore caricamento pagina:", error); } finally { if (requestId === activityRequestId) loadingActivities = false; // Continua se la pagina non basta ancora a riempire lo schermo. if ( requestId === activityRequestId && hasMoreActivities && !loadingActivities && window.state?.mode !== "photos" && window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 500 ) { loadNextActivityPage(requestId); } } } function refreshGallery() { if (!isAuthenticated()) { console.warn("[refreshGallery] autenticazione non completata"); return; } if (window.state?.mode !== "photos") { activityRequestId++; const requestId = activityRequestId; activityItems = []; nextActivityCursor = null; hasMoreActivities = true; loadingActivities = false; const gallery = document.getElementById("gallery"); if (gallery) { gallery.replaceChildren(); const message = document.createElement("p"); message.className = "gallery-loading"; message.textContent = "Caricamento attività…"; gallery.appendChild(message); } loadNextActivityPage(requestId, true); return; } photoRequestId++; const requestId = photoRequestId; photoItems = []; nextCursor = null; hasMorePhotos = true; loadingPhotos = false; const gallery = document.getElementById("gallery"); if (gallery) { gallery.replaceChildren(); const message = document.createElement("p"); message.className = "gallery-loading"; message.textContent = "Caricamento foto…"; gallery.appendChild(message); } loadNextPhotoPage(requestId, true); } function initializeGallery() { if (!isAuthenticated()) { console.warn("[gallery] inizializzazione bloccata: autenticazione"); return; } document.addEventListener("showDeletedChange", (event) => { if (!isAuthenticated()) return; const enabled = Boolean(event.detail?.enabled); localStorage.setItem("showDeleted", enabled ? "1" : "0"); window.refreshMapPhotoIndex?.(); refreshGallery(); }); // Non caricare tutta la cache locale quando siamo nella modalità foto. // La modalità attività conserva il caricamento locale precedente. if (window.state?.mode !== "photos") { window.loadLocalState?.(); } window.addEventListener("scroll", () => { const nearBottom = window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 700; if (!nearBottom) return; if (window.state?.mode === "photos") { if (hasMorePhotos && !loadingPhotos) { loadNextPhotoPage(photoRequestId); } } else if (hasMoreActivities && !loadingActivities) { loadNextActivityPage(activityRequestId); } }, { passive: true }); refreshGallery(); } // Aggiorna gli elementi sincronizzati senza azzerare la paginazione. window.applySyncedGalleryItems = function (mode, items, removedId = null) { const syncMode = mode === "activities" ? "activities" : "photos"; if (window.state?.mode !== syncMode) return false; const target = syncMode === "photos" ? photoItems : activityItems; if (removedId != null) { const id = String(removedId); const index = target.findIndex( existing => String(existing.id) === id ); if (index >= 0) target.splice(index, 1); if (syncMode === "photos") { window.MapSpatialIndex?.remove?.(id); if (window.MapEngine?.isReady?.()) { window.renderPhotoMapLayer?.(); } } const savedScrollY = window.scrollY; if (syncMode === "photos") renderPhotoItems(false); else renderActivityItems(false); window.requestAnimationFrame(() => window.scrollTo(0, savedScrollY)); return true; } if (!Array.isArray(items) || items.length === 0) return false; const addedPhotos = []; for (const item of items) { if (!item || item.id == null) continue; const id = String(item.id); const index = target.findIndex( existing => String(existing.id) === id ); if (index >= 0) { target[index] = item; } else { target.push(item); if (syncMode === "photos") addedPhotos.push(item); } } if (syncMode === "photos") { photoItems = window.GalleryUtils.sortByDate( photoItems, window.currentSort || "desc" ); if (addedPhotos.length) { window.MapSpatialIndex?.append?.(addedPhotos); if (window.MapEngine?.isReady?.() && typeof window.renderPhotoMapLayer === "function") { window.renderPhotoMapLayer(); } } } const savedScrollY = window.scrollY; if (syncMode === "photos") { renderPhotoItems(false); } else { renderActivityItems(false); } window.requestAnimationFrame(() => { window.scrollTo(0, savedScrollY); }); return true; }; window.refreshGallery = refreshGallery; // Espone un caricamento aggiuntivo per futuri collegamenti alla navigazione // del dettaglio, senza cambiare per ora modal.js. window.loadNextGalleryPhotoPage = () => loadNextPhotoPage(photoRequestId); window.loadNextGalleryActivityPage = () => loadNextActivityPage(activityRequestId); if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initializeGallery, { once: true }); } else { initializeGallery(); } })();