499 lines
14 KiB
JavaScript
499 lines
14 KiB
JavaScript
// 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();
|
|
}
|
|
})();
|