gallery_server_client_s190js/public/js/gallery.js
2026-10-26 11:00:14 +01:00

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();
}
})();