Blocca l'invio di numeri carta dall'API server, usa Checkout anche per i cambi piano e nasconde correttamente il placeholder video in regia. Co-authored-by: Cursor <cursoragent@cursor.com>
288 lines
8.7 KiB
JavaScript
288 lines
8.7 KiB
JavaScript
(function () {
|
|
const root = document.getElementById("regia-app");
|
|
if (!root) return;
|
|
|
|
const cfg = {
|
|
token: root.dataset.token,
|
|
statusUrl: root.dataset.statusUrl,
|
|
scoreUrl: root.dataset.scoreUrl,
|
|
pauseUrl: root.dataset.pauseUrl,
|
|
stopUrl: root.dataset.stopUrl,
|
|
cableUrl: root.dataset.cableUrl,
|
|
hlsUrl: root.dataset.hlsUrl,
|
|
homeName: root.dataset.homeName,
|
|
awayName: root.dataset.awayName,
|
|
shareUrl: root.dataset.shareUrl,
|
|
shareTitle: root.dataset.shareTitle,
|
|
streamClosed: root.dataset.streamClosed === "true"
|
|
};
|
|
|
|
const els = {
|
|
badge: document.getElementById("regia-badge"),
|
|
setsLine: document.getElementById("sets-line"),
|
|
scoreLine: document.getElementById("score-line"),
|
|
partialsLine: document.getElementById("partials-line"),
|
|
homePoints: document.getElementById("home-points"),
|
|
toast: document.getElementById("regia-toast"),
|
|
modal: document.getElementById("regia-modal"),
|
|
modalTitle: document.getElementById("modal-title"),
|
|
modalBody: document.getElementById("modal-body"),
|
|
modalConfirm: document.getElementById("modal-confirm"),
|
|
modalCancel: document.getElementById("modal-cancel"),
|
|
preview: document.getElementById("regia-preview"),
|
|
previewPlaceholder: document.getElementById("regia-preview-placeholder")
|
|
};
|
|
|
|
let pendingAction = null;
|
|
let hls = null;
|
|
let previewStarted = false;
|
|
|
|
function toast(msg) {
|
|
els.toast.textContent = msg;
|
|
els.toast.classList.add("visible");
|
|
setTimeout(() => els.toast.classList.remove("visible"), 2800);
|
|
}
|
|
|
|
function formatPartials(partials) {
|
|
if (!partials || !partials.length) return "";
|
|
return partials
|
|
.map((p) => `Set ${p.set || p["set"]} ${p.home || p["home"]}-${p.away || p["away"]}`)
|
|
.join(" · ");
|
|
}
|
|
|
|
function applyScorePayload(data) {
|
|
const s = data.score;
|
|
if (!s) return;
|
|
|
|
const homePts = s.home_points ?? s.homePoints;
|
|
const awayPts = s.away_points ?? s.awayPoints;
|
|
const homeSets = s.home_sets ?? s.homeSets;
|
|
const awaySets = s.away_sets ?? s.awaySets;
|
|
const currentSet = s.current_set ?? s.currentSet;
|
|
|
|
const awayEl = document.getElementById("away-points");
|
|
els.homePoints.textContent = homePts;
|
|
if (awayEl) awayEl.textContent = awayPts;
|
|
els.setsLine.textContent = `Set ${currentSet} · Set vinti ${homeSets}-${awaySets}`;
|
|
els.scoreLine.textContent = `${cfg.homeName} ${homePts} - ${awayPts} ${cfg.awayName}`;
|
|
|
|
const partialsText = formatPartials(s.set_partials || s.setPartials);
|
|
if (partialsText) {
|
|
els.partialsLine.textContent = `Parziali: ${partialsText}`;
|
|
els.partialsLine.hidden = false;
|
|
}
|
|
}
|
|
|
|
function setBadge(data) {
|
|
if (data.stream_closed) {
|
|
els.badge.textContent = "Terminata";
|
|
els.badge.className = "regia-badge regia-badge--ended";
|
|
return;
|
|
}
|
|
if (data.on_air) {
|
|
els.badge.textContent = "In onda";
|
|
els.badge.className = "regia-badge regia-badge--live";
|
|
} else {
|
|
els.badge.textContent = "In attesa";
|
|
els.badge.className = "regia-badge regia-badge--wait";
|
|
}
|
|
}
|
|
|
|
async function postScore(action) {
|
|
const res = await fetch(cfg.scoreUrl, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
body: JSON.stringify({ cmd: action })
|
|
});
|
|
if (!res.ok) throw new Error("Errore aggiornamento punteggio");
|
|
return res.json();
|
|
}
|
|
|
|
async function handleAction(action) {
|
|
try {
|
|
const data = await postScore(action);
|
|
applyScorePayload(data);
|
|
setBadge(data);
|
|
|
|
if (data.set_won && action !== "close_set") {
|
|
const winner = data.winner === "home" ? cfg.homeName : cfg.awayName;
|
|
openModal("Set vinto", `${winner} ha vinto il set. Chiudere il set?`, () => postScore("close_set"));
|
|
} else if (data.match_won) {
|
|
const winner = data.winner === "home" ? cfg.homeName : cfg.awayName;
|
|
openModal("Partita vinta", `${winner} ha vinto la partita. Chiudere la diretta?`, () => stopStream());
|
|
}
|
|
} catch (e) {
|
|
toast(e.message || "Errore");
|
|
}
|
|
}
|
|
|
|
function openModal(title, body, onConfirm) {
|
|
els.modalTitle.textContent = title;
|
|
els.modalBody.textContent = body;
|
|
els.modal.classList.add("open");
|
|
pendingAction = onConfirm;
|
|
}
|
|
|
|
function closeModal() {
|
|
els.modal.classList.remove("open");
|
|
pendingAction = null;
|
|
}
|
|
|
|
els.modalConfirm.addEventListener("click", async () => {
|
|
const fn = pendingAction;
|
|
closeModal();
|
|
if (fn) {
|
|
try {
|
|
const data = await fn();
|
|
if (data) {
|
|
applyScorePayload(data);
|
|
setBadge(data);
|
|
}
|
|
} catch (e) {
|
|
toast(e.message || "Errore");
|
|
}
|
|
}
|
|
});
|
|
|
|
els.modalCancel.addEventListener("click", closeModal);
|
|
|
|
root.querySelectorAll("[data-action]").forEach((btn) => {
|
|
btn.addEventListener("click", () => {
|
|
const action = btn.dataset.action;
|
|
if (action === "close_set") {
|
|
openModal("Chiudi set", "Confermi la chiusura del set corrente?", () => postScore("close_set"));
|
|
} else {
|
|
handleAction(action);
|
|
}
|
|
});
|
|
});
|
|
|
|
function syncPreviewFromStatus(data) {
|
|
if (data.stream_closed) {
|
|
cfg.streamClosed = true;
|
|
stopPreview();
|
|
return;
|
|
}
|
|
|
|
if (data.on_air) {
|
|
if (!previewStarted) startPreview();
|
|
else if (els.preview && els.preview.readyState >= 2) hidePreviewPlaceholder();
|
|
} else {
|
|
destroyPreview();
|
|
showPreviewPlaceholder("Anteprima in attesa del segnale…");
|
|
}
|
|
}
|
|
|
|
async function pollStatus() {
|
|
try {
|
|
const res = await fetch(cfg.statusUrl, { headers: { Accept: "application/json" } });
|
|
if (!res.ok) return;
|
|
const data = await res.json();
|
|
applyScorePayload(data);
|
|
setBadge(data);
|
|
syncPreviewFromStatus(data);
|
|
} catch (_) {}
|
|
}
|
|
|
|
function bindPreviewEvents() {
|
|
if (!els.preview) return;
|
|
["playing", "loadeddata", "canplay"].forEach((ev) => {
|
|
els.preview.addEventListener(ev, hidePreviewPlaceholder);
|
|
});
|
|
}
|
|
|
|
function startPreview() {
|
|
if (cfg.streamClosed || !els.preview || !cfg.hlsUrl || previewStarted) return;
|
|
|
|
previewStarted = true;
|
|
const url = hlsUrlFresh();
|
|
|
|
if (window.Hls && Hls.isSupported()) {
|
|
hls = new Hls({ lowLatencyMode: true, maxBufferLength: 8 });
|
|
hls.loadSource(url);
|
|
hls.attachMedia(els.preview);
|
|
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
els.preview.play().catch(() => {});
|
|
});
|
|
hls.on(Hls.Events.ERROR, (_, err) => {
|
|
if (err.fatal) {
|
|
destroyPreview();
|
|
setTimeout(() => {
|
|
if (!cfg.streamClosed) startPreview();
|
|
}, 2500);
|
|
}
|
|
});
|
|
} else if (els.preview.canPlayType("application/vnd.apple.mpegurl")) {
|
|
els.preview.src = url;
|
|
els.preview.addEventListener(
|
|
"loadedmetadata",
|
|
() => els.preview.play().catch(() => {}),
|
|
{ once: true }
|
|
);
|
|
}
|
|
}
|
|
|
|
function stopPreview() {
|
|
destroyPreview();
|
|
if (els.preview) els.preview.style.display = "none";
|
|
showPreviewPlaceholder("Diretta terminata");
|
|
}
|
|
|
|
async function pauseStream() {
|
|
await fetch(cfg.pauseUrl, { method: "POST", headers: { Accept: "application/json" } });
|
|
toast("Trasmissione in pausa");
|
|
}
|
|
|
|
async function stopStream() {
|
|
const ok = confirm("Chiudere definitivamente la diretta?");
|
|
if (!ok) return null;
|
|
const res = await fetch(cfg.stopUrl, { method: "POST", headers: { Accept: "application/json" } });
|
|
if (!res.ok) throw new Error("Errore chiusura");
|
|
const data = await res.json();
|
|
cfg.streamClosed = true;
|
|
stopPreview();
|
|
toast("Diretta chiusa");
|
|
return data;
|
|
}
|
|
|
|
document.getElementById("btn-pause")?.addEventListener("click", () => pauseStream().catch((e) => toast(e.message)));
|
|
document.getElementById("btn-stop")?.addEventListener("click", () => stopStream().catch((e) => toast(e.message)));
|
|
|
|
async function shareLink() {
|
|
const payload = {
|
|
title: cfg.shareTitle,
|
|
text: "Apri questo link per gestire il punteggio della diretta:",
|
|
url: cfg.shareUrl
|
|
};
|
|
if (navigator.share) {
|
|
try {
|
|
await navigator.share(payload);
|
|
return;
|
|
} catch (err) {
|
|
if (err.name === "AbortError") return;
|
|
}
|
|
}
|
|
await copyLink();
|
|
}
|
|
|
|
async function copyLink() {
|
|
try {
|
|
await navigator.clipboard.writeText(cfg.shareUrl);
|
|
toast("Link copiato");
|
|
} catch (_) {
|
|
prompt("Copia il link:", cfg.shareUrl);
|
|
}
|
|
}
|
|
|
|
document.getElementById("btn-share")?.addEventListener("click", () => shareLink());
|
|
document.getElementById("btn-copy")?.addEventListener("click", () => copyLink());
|
|
|
|
bindPreviewEvents();
|
|
if (!cfg.streamClosed && cfg.hlsUrl) {
|
|
pollStatus();
|
|
} else if (cfg.streamClosed) {
|
|
stopPreview();
|
|
}
|
|
setInterval(pollStatus, 4000);
|
|
})();
|