374 lines
No EOL
9.7 KiB
JavaScript
374 lines
No EOL
9.7 KiB
JavaScript
// ===============================
|
||
// 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; |