синхронизация событий со всеми браузерами, тест 1

This commit is contained in:
2026-05-25 14:32:27 +03:00
parent 998774bf1c
commit 495121a8b7
2 changed files with 58 additions and 11 deletions

12
app.py
View File

@@ -1222,6 +1222,18 @@ def api_clear_events(session_token: str):
match_id = session_row[1]
clear_events(match_id)
# Если события очищены в одном браузере, сбрасываем общий серверный таймер тоже.
# Иначе другой браузер с запущенным таймером может снова подтянуть старое время.
MATCH_CLOCK_STATES[match_id] = {
"currentPeriod": None,
"matchClockSeconds": 0,
"timerRunning": False,
"pausedAccumulatedSeconds": 0,
"serverSavedAt": time.time(),
"sourceClientId": "events_clear",
}
return {"ok": True}

View File

@@ -74,6 +74,10 @@ let lastClockServerSyncAt = 0;
let clockServerSyncInFlight = false;
let applyingRemoteClockState = false;
let clockEditorIsOpen = false;
let eventsServerSyncInFlight = false;
let applyingRemoteEventsState = false;
let lastEventsSnapshot = "";
const formationState = {
@@ -326,7 +330,6 @@ function saveMatchState() {
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
syncClockStateToServer(false);
} catch (e) {
console.warn("Не удалось сохранить состояние матча", e);
}
@@ -860,6 +863,7 @@ function setMatchPeriod(period) {
startTimer();
saveMatchState();
syncClockStateToServer(true);
syncClockStateToServer(true);
updatePeriodButtons();
return;
}
@@ -1107,7 +1111,7 @@ function stepExtraTime(step) {
input.value = value;
}
function clearMatchEvents() {
async function clearMatchEvents() {
if (timerId) {
clearInterval(timerId);
timerId = null;
@@ -1136,8 +1140,8 @@ function clearMatchEvents() {
renderEvents();
saveMatchState();
syncClockStateToServer(true);
clearEventsOnServer();
await clearEventsOnServer();
await syncClockStateToServer(true);
updateCoachCardButtons();
updatePeriodButtons();
triggerVmixTimerAction("clear_match");
@@ -1680,18 +1684,39 @@ function restoreEventsClickOrder(serverEvents, persistedEvents) {
async function loadEventsFromServer() {
if (!SESSION_TOKEN) return;
if (!SESSION_TOKEN || applyingRemoteEventsState || clockEditorIsOpen) return;
if (eventsServerSyncInFlight) return;
eventsServerSyncInFlight = true;
try {
const persisted = getPersistedState();
const persistedEvents = Array.isArray(persisted?.matchEvents) ? persisted.matchEvents : [];
const res = await fetch(`/admin/session/${SESSION_TOKEN}/events`);
const res = await fetch(`/admin/session/${SESSION_TOKEN}/events`, { cache: "no-store" });
if (!res.ok) return;
const data = await res.json();
if (!Array.isArray(data)) return;
const snapshot = JSON.stringify(data.map((event) => ({
id: event.id ?? null,
side: event.side ?? "",
type: event.type ?? "",
player_name: event.player_name ?? "",
minute: event.minute ?? null,
seconds: event.seconds ?? 0,
meta: event.meta ?? null,
player_id: event.player_id ?? null,
player_out_id: event.player_out_id ?? null,
player_in_id: event.player_in_id ?? null
})));
if (snapshot === lastEventsSnapshot) return;
lastEventsSnapshot = snapshot;
applyingRemoteEventsState = true;
matchEvents = data.map((event) => {
const fallback = persistedEvents.find((p) =>
String(p.type || "") === String(event.type || "") &&
@@ -1709,8 +1734,6 @@ async function loadEventsFromServer() {
minute: typeof event.minute === "string" && event.minute.includes("'")
? event.minute
: (fallback?.minute || getEventDisplayMinuteBySeconds(event.seconds || 0, event.type)),
player_id: event.player_id ?? fallback?.player_id ?? null,
player_out_id: event.player_out_id ?? fallback?.player_out_id ?? null,
player_in_id: event.player_in_id ?? fallback?.player_in_id ?? null
@@ -1719,8 +1742,6 @@ async function loadEventsFromServer() {
matchEvents = restoreEventsClickOrder(matchEvents, persistedEvents);
scoreState = { home: 0, away: 0 };
matchEvents.forEach((event) => {
if (["goal", "penalty", "own_goal"].includes(event.type)) {
@@ -1728,6 +1749,12 @@ async function loadEventsFromServer() {
}
});
if (!matchEvents.length) {
activeSubstitution = null;
clearBenchSelection();
restoreOriginalLineupRows();
}
updateScoreUI();
renderEvents();
rebuildLineupsFromEvents();
@@ -1736,9 +1763,17 @@ async function loadEventsFromServer() {
updatePeriodButtons();
} catch (e) {
console.warn("Не удалось загрузить события из БД", e);
} finally {
applyingRemoteEventsState = false;
eventsServerSyncInFlight = false;
}
}
function initEventsStateSync() {
loadEventsFromServer();
setInterval(loadEventsFromServer, 2000);
}
async function sendDirectVmixCommands(commands) {
const items = Array.isArray(commands)
? commands.map(x => String(x || "").trim()).filter(Boolean)
@@ -2530,7 +2565,7 @@ if (!restored) {
saveMatchState();
}
loadEventsFromServer();
initEventsStateSync();
initClockStateSync();
window.addEventListener("beforeunload", saveMatchState);
window.addEventListener("pagehide", saveMatchState);