293 lines
No EOL
9.3 KiB
JavaScript
293 lines
No EOL
9.3 KiB
JavaScript
// ===============================
|
||
// AVVIO
|
||
// ===============================
|
||
console.log("main.js avviato");
|
||
|
||
// ===============================
|
||
// UTILS AUTH + SYNC HEADER UI
|
||
// ===============================
|
||
function isAuthenticated() {
|
||
// Fonte verità: presenza del token in localStorage
|
||
return !!localStorage.getItem("token");
|
||
}
|
||
|
||
/**
|
||
* Sincronizza l’UI dell’header in base allo stato auth:
|
||
* - Aggiunge una classe sul body (CSS-friendly)
|
||
* - Mostra/Nasconde il bottone logout (hotfix inline, se vuoi puoi affidarti solo al CSS)
|
||
*/
|
||
function syncHeaderAuthUI() {
|
||
const authed = isAuthenticated();
|
||
document.body.classList.toggle('authenticated', authed);
|
||
|
||
const logoutBtn = document.getElementById('logoutBtn');
|
||
if (logoutBtn) {
|
||
// Hotfix immediato: forza la visibilità anche via inline style
|
||
// (puoi rimuovere queste due righe se preferisci usare solo la regola CSS body.authenticated #logoutBtn)
|
||
logoutBtn.style.display = authed ? 'inline-flex' : 'none';
|
||
}
|
||
}
|
||
|
||
// ===============================
|
||
// PATCH: misura l'altezza reale dell'header e aggiorna --header-h
|
||
// (serve per far partire la mappa subito sotto l’header, anche su mobile)
|
||
// ===============================
|
||
(function () {
|
||
const root = document.documentElement;
|
||
const header = document.querySelector('header');
|
||
|
||
function setHeaderHeight() {
|
||
const h = header ? Math.round(header.getBoundingClientRect().height) : 60;
|
||
root.style.setProperty('--header-h', h + 'px');
|
||
}
|
||
|
||
setHeaderHeight();
|
||
|
||
if (window.ResizeObserver && header) {
|
||
const ro = new ResizeObserver(setHeaderHeight);
|
||
ro.observe(header);
|
||
} else {
|
||
window.addEventListener('resize', setHeaderHeight);
|
||
window.addEventListener('orientationchange', setHeaderHeight);
|
||
}
|
||
|
||
window.addEventListener('load', setHeaderHeight);
|
||
})();
|
||
|
||
// ===============================
|
||
// PATCH: quando si apre la mappa (#globalMap.open) invalida le dimensioni Leaflet
|
||
// (utile perché prima era display:none; invalidateSize evita “tagli” o tile sfasati)
|
||
// ===============================
|
||
(function () {
|
||
const mapEl = document.getElementById('globalMap');
|
||
if (!mapEl) return;
|
||
|
||
function invalidateWhenOpen() {
|
||
if (!mapEl.classList.contains('open')) return;
|
||
// Aspetta un tick così il layout è aggiornato
|
||
setTimeout(() => {
|
||
try {
|
||
// In mapGlobal.js imposta: window.leafletMapInstance = window.globalMap;
|
||
window.leafletMapInstance?.invalidateSize();
|
||
} catch (e) {
|
||
console.warn('invalidateSize non eseguito:', e);
|
||
}
|
||
}, 0);
|
||
}
|
||
|
||
// 1) Osserva il cambio classe (quando aggiungi .open)
|
||
const mo = new MutationObserver((mutations) => {
|
||
if (mutations.some(m => m.attributeName === 'class')) {
|
||
invalidateWhenOpen();
|
||
}
|
||
});
|
||
mo.observe(mapEl, { attributes: true, attributeFilter: ['class'] });
|
||
|
||
// 2) Fallback: se usi il bottone #openMapBtn per aprire/chiudere
|
||
document.getElementById('openMapBtn')?.addEventListener('click', () => {
|
||
setTimeout(invalidateWhenOpen, 0);
|
||
});
|
||
})();
|
||
|
||
// ===============================
|
||
// MENU ⋮ (toggle apri/chiudi con lo stesso bottone, senza global conflicts)
|
||
// ===============================
|
||
(() => {
|
||
const optBtn = document.getElementById("optionsBtn");
|
||
const optSheet = document.getElementById("optionsSheet");
|
||
const overlayEl= document.getElementById("sheetOverlay");
|
||
|
||
if (!optBtn || !optSheet) return;
|
||
|
||
function openOptionsSheet() {
|
||
try { window.closeBottomSheet?.(); } catch {}
|
||
optSheet.classList.add("open");
|
||
overlayEl?.classList.add("open");
|
||
// ARIA (facoltativo)
|
||
optBtn.setAttribute("aria-expanded", "true");
|
||
optSheet.setAttribute("aria-hidden", "false");
|
||
}
|
||
|
||
function closeOptionsSheet() {
|
||
optSheet.classList.remove("open");
|
||
overlayEl?.classList.remove("open");
|
||
// ARIA (facoltativo)
|
||
optBtn.setAttribute("aria-expanded", "false");
|
||
optSheet.setAttribute("aria-hidden", "true");
|
||
}
|
||
|
||
function toggleOptionsSheet(e) {
|
||
e?.preventDefault();
|
||
e?.stopPropagation();
|
||
if (optSheet.classList.contains("open")) closeOptionsSheet();
|
||
else openOptionsSheet();
|
||
}
|
||
|
||
// Click sul bottone: toggle (fase di cattura per battere eventuali altri handler)
|
||
optBtn.addEventListener("click", toggleOptionsSheet, { capture: true });
|
||
|
||
// Chiudi clic overlay
|
||
overlayEl?.addEventListener("click", (e) => {
|
||
e.stopPropagation();
|
||
closeOptionsSheet();
|
||
});
|
||
|
||
// Chiudi con ESC
|
||
document.addEventListener("keydown", (e) => {
|
||
if (e.key === "Escape" && optSheet.classList.contains("open")) {
|
||
closeOptionsSheet();
|
||
}
|
||
});
|
||
|
||
// Evita chiusure involontarie per click interni
|
||
optSheet.addEventListener("click", (e) => e.stopPropagation());
|
||
|
||
// Espone una close per usarla altrove (es. dopo la scelta)
|
||
window.closeOptionsSheet = closeOptionsSheet;
|
||
})();
|
||
|
||
// ===============================
|
||
// LOGIN AUTOMATICO SU INDEX
|
||
// ===============================
|
||
document.addEventListener("DOMContentLoaded", async () => {
|
||
// Allinea subito l’UI in base al token eventualmente già presente
|
||
syncHeaderAuthUI();
|
||
|
||
try {
|
||
// 1) Carica config
|
||
const cfgRes = await fetch('/config');
|
||
const cfg = await cfgRes.json();
|
||
window.BASE_URL = cfg.baseUrl;
|
||
|
||
// 2) Recupera token salvato
|
||
const savedToken = localStorage.getItem("token");
|
||
|
||
// Se non c'è token → mostra login
|
||
if (!savedToken) {
|
||
document.getElementById("loginModal").style.display = "flex";
|
||
return;
|
||
}
|
||
|
||
// 3) Verifica token
|
||
const ping = await fetch(`${window.BASE_URL}/photos`, {
|
||
headers: { "Authorization": "Bearer " + savedToken }
|
||
});
|
||
|
||
if (!ping.ok) {
|
||
// Token invalido → cancella e mostra login
|
||
localStorage.removeItem("token");
|
||
syncHeaderAuthUI(); // riallinea header subito
|
||
document.getElementById("loginModal").style.display = "flex";
|
||
return;
|
||
}
|
||
|
||
// 4) Token valido → salva e carica gallery
|
||
window.token = savedToken;
|
||
syncHeaderAuthUI(); // <— mostra il logout senza refresh
|
||
loadPhotos();
|
||
|
||
} catch (err) {
|
||
console.error("Errore autenticazione:", err);
|
||
document.getElementById("loginModal").style.display = "flex";
|
||
}
|
||
});
|
||
|
||
// ===============================
|
||
// VARIABILI GLOBALI
|
||
// ===============================
|
||
let currentSort = "desc";
|
||
let currentGroup = "auto";
|
||
let currentFilter = null;
|
||
|
||
window.currentSort = currentSort;
|
||
window.currentGroup = currentGroup;
|
||
window.currentFilter = currentFilter;
|
||
|
||
// ===============================
|
||
// BOTTONI OPZIONI
|
||
// ===============================
|
||
document.querySelectorAll("#optionsSheet .sheet-btn").forEach(btn => {
|
||
btn.addEventListener("click", () => {
|
||
if (btn.dataset.sort) window.currentSort = currentSort = btn.dataset.sort;
|
||
if (btn.dataset.group) window.currentGroup = currentGroup = btn.dataset.group;
|
||
if (btn.dataset.filter) window.currentFilter = currentFilter = btn.dataset.filter;
|
||
|
||
// Chiudi sheet e overlay dopo la scelta (usa l’API esposta sopra)
|
||
window.closeOptionsSheet?.();
|
||
|
||
refreshGallery();
|
||
});
|
||
});
|
||
|
||
// ===============================
|
||
// REFRESH GALLERY
|
||
// ===============================
|
||
function refreshGallery() {
|
||
console.log("Aggiornamento galleria...");
|
||
|
||
const data = Array.isArray(window.photosData) ? window.photosData : [];
|
||
let photos = [...data];
|
||
|
||
if (typeof applyFilters === 'function') photos = applyFilters(photos);
|
||
if (typeof sortByDate === 'function') photos = sortByDate(photos, currentSort);
|
||
|
||
let sections = [{ label: 'Tutte', photos }];
|
||
if (typeof groupByDate === 'function') sections = groupByDate(photos, currentGroup);
|
||
|
||
if (typeof renderGallery === 'function') {
|
||
renderGallery(sections);
|
||
}
|
||
}
|
||
|
||
window.refreshGallery = refreshGallery;
|
||
|
||
// ===============================
|
||
// SETTINGS (⚙️) — apre admin.html
|
||
// ===============================
|
||
const settingsBtn = document.getElementById('settingsBtn');
|
||
settingsBtn?.addEventListener('click', () => {
|
||
window.location.href = "admin.html";
|
||
});
|
||
|
||
// ===============================
|
||
// LOGIN SUBMIT
|
||
// ===============================
|
||
document.getElementById("loginSubmit").addEventListener("click", async () => {
|
||
const email = document.getElementById("loginEmail").value;
|
||
const password = document.getElementById("loginPassword").value;
|
||
const errorEl = document.getElementById("loginError");
|
||
|
||
errorEl.textContent = "";
|
||
|
||
try {
|
||
const res = await fetch(`${window.BASE_URL}/auth/login`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ email, password })
|
||
});
|
||
|
||
if (!res.ok) {
|
||
errorEl.textContent = "Utente o password errati";
|
||
return;
|
||
}
|
||
|
||
const data = await res.json();
|
||
const token = data.token;
|
||
|
||
// Salva token
|
||
localStorage.setItem("token", token);
|
||
window.token = token;
|
||
|
||
// Chiudi login
|
||
const loginModalEl = document.getElementById("loginModal");
|
||
if (loginModalEl) loginModalEl.style.display = "none";
|
||
|
||
// Riallinea UI header subito (mostra logout) e carica gallery
|
||
syncHeaderAuthUI();
|
||
loadPhotos();
|
||
|
||
} catch (err) {
|
||
console.error("Errore login:", err);
|
||
errorEl.textContent = "Errore di connessione al server";
|
||
}
|
||
}); |