photo_server_json_con_aves22/public/js/modal.js
2026-04-18 20:14:42 +02:00

374 lines
No EOL
9.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ===============================
// MODALE FOTO/VIDEO — Navigazione + Info Panel + Preload
// ===============================
const modal = document.getElementById("modal");
const modalClose = document.getElementById("modalClose");
const modalPrev = document.getElementById("modalPrev");
const modalNext = document.getElementById("modalNext");
window.currentPhoto = null;
window.modalList = [];
window.modalIndex = 0;
// ===============================
// INFO PANEL — Stato + Toggle
// ===============================
let infoOpen = false;
function getInfoPanel() {
return document.getElementById("infoPanel");
}
function isInfoOpen() {
return infoOpen;
}
function openInfo(photo) {
try {
if (typeof window.openInfoPanel === "function") {
window.openInfoPanel(photo);
} else if (typeof window.toggleInfoPanel === "function") {
window.toggleInfoPanel(photo);
}
} catch {}
infoOpen = true;
const panel = getInfoPanel();
panel?.classList.add("open");
panel?.setAttribute("aria-hidden", "false");
panel?.setAttribute("data-open", "1");
document.getElementById("modalInfoBtn")?.classList.add("active");
}
function closeInfo() {
try {
if (typeof window.closeInfoPanel === "function") {
window.closeInfoPanel();
} else if (typeof window.toggleInfoPanel === "function") {
window.toggleInfoPanel();
}
} catch {}
infoOpen = false;
const panel = getInfoPanel();
panel?.classList.remove("open");
panel?.setAttribute("aria-hidden", "true");
panel?.setAttribute("data-open", "0");
document.getElementById("modalInfoBtn")?.classList.remove("active");
}
function toggleInfo(photo) {
if (infoOpen) closeInfo();
else openInfo(photo);
}
// ===============================
// MIME / MEDIA HELPERS
// ===============================
function isProbablyVideo(photo, srcOriginal) {
const mime = String(photo?.mime_type || "").toLowerCase();
if (mime.startsWith("video/")) return true;
return /\.(mp4|m4v|webm|mov|qt|avi|mkv)$/i.test(String(srcOriginal || ""));
}
function guessVideoMime(photo, srcOriginal) {
const t = String(photo?.mime_type || "").toLowerCase();
if (t && t !== "application/octet-stream") return t;
const src = String(srcOriginal || "");
if (/\.(mp4|m4v)$/i.test(src)) return "video/mp4";
if (/\.(webm)$/i.test(src)) return "video/webm";
if (/\.(mov|qt)$/i.test(src)) return "video/quicktime";
if (/\.(avi)$/i.test(src)) return "video/x-msvideo";
if (/\.(mkv)$/i.test(src)) return "video/x-matroska";
return "";
}
function createVideoElement(srcOriginal, srcPreview, photo) {
const video = document.createElement("video");
video.controls = true;
video.playsInline = true;
video.setAttribute("webkit-playsinline", "");
video.preload = "metadata";
video.poster = srcPreview || "";
video.style.maxWidth = "100%";
video.style.maxHeight = "100%";
video.style.objectFit = "contain";
const source = document.createElement("source");
source.src = srcOriginal;
const type = guessVideoMime(photo, srcOriginal);
if (type) source.type = type;
video.appendChild(source);
video.addEventListener("loadedmetadata", () => {
try { video.currentTime = 0.001; } catch {}
});
video.addEventListener("error", () => {
const code = video.error?.code;
const msg = document.createElement("div");
msg.style.padding = "12px";
msg.style.color = "#fff";
msg.style.background = "rgba(0,0,0,0.6)";
msg.style.borderRadius = "8px";
msg.innerHTML = `
<strong>Impossibile riprodurre questo video nel browser.</strong>
${code === 4 ? "Formato/codec non supportato (es. HEVC/H.265)." : "Errore durante il caricamento."}
<br><br>
<ul style="margin:6px 0 0 18px">
<li><a href="${srcOriginal}" target="_blank" rel="noopener" style="color:#fff;text-decoration:underline">Apri in nuova scheda</a></li>
<li>Prova Safari (supporta HEVC) o converti in MP4 (H.264 + AAC)</li>
</ul>
`;
document.getElementById("modalMediaContainer")?.appendChild(msg);
});
video.addEventListener("click", e => e.stopPropagation());
return video;
}
function createImageElement(srcOriginal, srcPreview) {
const img = document.createElement("img");
img.src = srcPreview || srcOriginal || "";
img.style.maxWidth = "100%";
img.style.maxHeight = "100%";
img.style.objectFit = "contain";
if (srcPreview && srcOriginal && srcPreview !== srcOriginal) {
const full = new Image();
full.src = srcOriginal;
full.onload = () => { img.src = srcOriginal; };
}
return img;
}
// ===============================
// URL ASSOLUTI
// ===============================
function mediaUrlsFromPhoto(photo) {
if (!photo) return { original: "", preview: "" };
if (window.PATH_FULL) {
return {
original: photo.path,
preview: photo.thub2 || photo.thub1 || photo.path
};
}
const original = toAbsoluteUrl(
photo.path,
photo.name,
"original",
photo.cartella
);
const preview = toAbsoluteUrl(
photo.thub2 || photo.thub1 || photo.path,
photo.name,
"thumbs",
photo.cartella
);
return { original, preview };
}
// ===============================
// PRELOAD ±N
// ===============================
function preloadNeighbors(N = 3) {
const list = window.modalList || [];
const idx = window.modalIndex || 0;
for (let offset = 1; offset <= N; offset++) {
const iPrev = idx - offset;
const iNext = idx + offset;
[iPrev, iNext].forEach(i => {
const p = list[i];
if (!p) return;
const { original, preview } = mediaUrlsFromPhoto(p);
const isVideo = isProbablyVideo(p, original);
const src = isVideo ? (preview || original) : original;
if (!src) return;
const img = new Image();
img.src = src;
});
}
}
// ===============================
// SET CONTENUTO MODAL
// ===============================
function setModalContent(photo) {
const container = document.getElementById("modalMediaContainer");
container.innerHTML = "";
window.currentPhoto = photo;
const { original, preview } = mediaUrlsFromPhoto(photo);
const isVideo = isProbablyVideo(photo, original);
if (isVideo) {
container.appendChild(createVideoElement(original, preview, photo));
} else {
container.appendChild(createImageElement(original, preview));
}
const infoBtn = document.createElement("button");
infoBtn.id = "modalInfoBtn";
infoBtn.className = "modal-info-btn";
infoBtn.type = "button";
infoBtn.setAttribute("aria-label", "Dettagli");
infoBtn.textContent = "";
infoBtn.addEventListener("click", e => {
e.stopPropagation();
toggleInfo(photo);
});
container.appendChild(infoBtn);
}
// ===============================
// OPEN / CLOSE MODAL
// ===============================
function openModal(photo) {
window.closeBottomSheet?.();
setModalContent(photo);
modal.classList.add("open");
modal.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
}
function closeModal() {
if (isInfoOpen()) closeInfo();
const v = document.querySelector("#modal video");
if (v) {
try { v.pause(); } catch {}
v.removeAttribute("src");
while (v.firstChild) v.removeChild(v.firstChild);
try { v.load(); } catch {}
}
document.getElementById("modalMediaContainer").innerHTML = "";
modal.classList.remove("open");
modal.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
modalPrev?.classList.add("hidden");
modalNext?.classList.add("hidden");
}
modalClose?.addEventListener("click", e => {
e.stopPropagation();
closeModal();
});
modal.addEventListener("click", e => {
if (e.target === modal) closeModal();
});
// ===============================
// NAVIGAZIONE
// ===============================
function openAt(i) {
const list = window.modalList || [];
if (!list[i]) return;
window.modalIndex = i;
const photo = list[i];
if (isInfoOpen()) openInfo(photo);
openModal(photo);
preloadNeighbors(3);
updateArrows();
}
window.openModalFromList = function(list, index) {
window.modalList = Array.isArray(list) ? list : [];
window.modalIndex = Math.max(0, Math.min(index || 0, window.modalList.length - 1));
openAt(window.modalIndex);
};
function showPrev() {
if (window.modalIndex > 0) openAt(window.modalIndex - 1);
}
function showNext() {
if (window.modalIndex < (window.modalList.length - 1)) openAt(window.modalIndex + 1);
}
document.addEventListener("keydown", e => {
if (!modal.classList.contains("open")) return;
if (e.key === "ArrowLeft") { e.preventDefault(); showPrev(); }
if (e.key === "ArrowRight") { e.preventDefault(); showNext(); }
});
modal.addEventListener("click", e => {
if (!modal.classList.contains("open")) return;
if (e.target.closest(".modal-info-btn, .modal-close, .modal-nav-btn")) return;
if (e.target === modal) return;
const rect = modal.getBoundingClientRect();
const x = e.clientX - rect.left;
const side = x / rect.width;
if (side < 0.25) showPrev();
else if (side > 0.75) showNext();
});
// ===============================
// FRECCE
// ===============================
function updateArrows() {
if (!modalPrev || !modalNext) return;
const len = (window.modalList || []).length;
const i = window.modalIndex || 0;
const show = len > 1;
modalPrev.classList.toggle("hidden", !show);
modalNext.classList.toggle("hidden", !show);
modalPrev.classList.toggle("disabled", i <= 0);
modalNext.classList.toggle("disabled", i >= len - 1);
}
modalPrev?.addEventListener("click", e => {
e.stopPropagation();
showPrev();
updateArrows();
});
modalNext?.addEventListener("click", e => {
e.stopPropagation();
showNext();
updateArrows();
});
// EXPORT
window.openModal = openModal;
window.closeModal = closeModal;