синхронизация событий со всеми браузерами, тест 1
This commit is contained in:
12
app.py
12
app.py
@@ -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}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user