add replay session
This commit is contained in:
@@ -1,3 +1,89 @@
|
||||
import {
|
||||
advanceSessionReplay,
|
||||
buildSessionReplaySnapshot,
|
||||
loadSessionReplay,
|
||||
pauseSessionReplay,
|
||||
playSessionReplay,
|
||||
resetSessionReplay,
|
||||
setSessionReplaySpeed,
|
||||
} from "./session_replay.js";
|
||||
|
||||
function stopSessionReplayTimer(runtime) {
|
||||
if (runtime?.timer) clearInterval(runtime.timer);
|
||||
if (runtime) runtime.timer = null;
|
||||
}
|
||||
|
||||
function renderSessionReplayContent(runtime, { t, escapeHtml, formatLap, formatElapsedClock }) {
|
||||
const snapshot = buildSessionReplaySnapshot(runtime);
|
||||
const rows = snapshot.leaderboard.slice(0, 12);
|
||||
return `
|
||||
<div class="session-replay-progress">
|
||||
<progress max="100" value="${Math.round(snapshot.progress * 100)}"></progress>
|
||||
<strong>${formatElapsedClock(snapshot.elapsedMs)} / ${formatElapsedClock(snapshot.durationMs)}</strong>
|
||||
<span class="pill">${escapeHtml(t(`timing.replay_status_${snapshot.status}`))}</span>
|
||||
</div>
|
||||
${snapshot.latestPassing
|
||||
? `<p class="session-replay-latest"><span>${t("timing.replay_latest")}</span><strong>${escapeHtml(snapshot.latestPassing.driverName)}</strong><small>${formatLap(snapshot.latestPassing.lapMs)}</small></p>`
|
||||
: `<p class="hint">${t("timing.replay_no_visible_passings")}</p>`}
|
||||
${rows.length
|
||||
? `<div class="table-wrap"><table><thead><tr><th>${t("table.pos")}</th><th>${t("table.driver")}</th><th>${t("table.laps")}</th><th>${t("table.last_lap")}</th><th>${t("table.best_lap")}</th></tr></thead><tbody>
|
||||
${rows.map((row, index) => `<tr><td><span class="pos-pill">${index + 1}</span></td><td><strong>${escapeHtml(row.driverName)}</strong><small class="table-subnote">${escapeHtml(row.carName || row.transponder)}</small></td><td>${row.laps}</td><td>${formatLap(row.lastLapMs)}</td><td class="best">${formatLap(row.bestLapMs)}</td></tr>`).join("")}
|
||||
</tbody></table></div>`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
function updateSessionReplayPanel(runtime, deps) {
|
||||
const content = document.querySelector("[data-session-replay-content]");
|
||||
if (content) content.innerHTML = renderSessionReplayContent(runtime, deps);
|
||||
}
|
||||
|
||||
function startSessionReplayTimer(runtime, deps) {
|
||||
stopSessionReplayTimer(runtime);
|
||||
runtime.timer = setInterval(() => {
|
||||
advanceSessionReplay(runtime);
|
||||
updateSessionReplayPanel(runtime, deps);
|
||||
if (runtime.status !== "running") stopSessionReplayTimer(runtime);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function renderSessionReplayPanel(runtime, deps) {
|
||||
const { state, t, escapeHtml, getEventName, formatLap, formatElapsedClock } = deps;
|
||||
if (runtime.status === "running") advanceSessionReplay(runtime);
|
||||
const replayable = state.sessions.filter((session) => state.resultsBySession?.[session.id]?.passings?.length);
|
||||
const selectedId = runtime.sessionId || replayable[0]?.id || "";
|
||||
return `
|
||||
<section class="panel mt-16 session-replay-panel">
|
||||
<div class="panel-header">
|
||||
<div><h3>${t("timing.session_replay")}</h3><p class="hint">${t("timing.session_replay_hint")}</p></div>
|
||||
<span class="pill">${t("timing.replay_isolated")}</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="session-replay-controls">
|
||||
<select id="sessionReplaySelect">
|
||||
<option value="">${t("timing.replay_select")}</option>
|
||||
${replayable.map((session) => `<option value="${escapeHtml(session.id)}" ${session.id === selectedId ? "selected" : ""}>${escapeHtml(getEventName(session.eventId))} · ${escapeHtml(session.name)} · ${state.resultsBySession[session.id].passings.length}</option>`).join("")}
|
||||
</select>
|
||||
<button id="sessionReplayLoad" class="btn" type="button" ${selectedId ? "" : "disabled"}>${t("timing.replay_load")}</button>
|
||||
<button id="sessionReplayPlay" class="btn btn-primary" type="button" ${runtime.passings.length ? "" : "disabled"}>${t("timing.replay_play")}</button>
|
||||
<button id="sessionReplayPause" class="btn" type="button" ${runtime.status === "running" ? "" : "disabled"}>${t("timing.replay_pause")}</button>
|
||||
<button id="sessionReplayReset" class="btn" type="button" ${runtime.passings.length ? "" : "disabled"}>${t("timing.replay_reset")}</button>
|
||||
<label>${t("timing.replay_speed")}
|
||||
<select id="sessionReplaySpeed">
|
||||
${[1, 2, 5, 10].map((speed) => `<option value="${speed}" ${runtime.speed === speed ? "selected" : ""}>${speed}x</option>`).join("")}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div data-session-replay-content>
|
||||
${runtime.passings.length
|
||||
? renderSessionReplayContent(runtime, { t, escapeHtml, formatLap, formatElapsedClock })
|
||||
: `<p class="hint">${t(replayable.length ? "timing.replay_load_hint" : "timing.replay_empty")}</p>`}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSpeakerToggle(settingKey, labelKey, { state, t }) {
|
||||
return `
|
||||
<label class="check-card">
|
||||
@@ -267,7 +353,7 @@ export function renderTimingView(deps) {
|
||||
normalizeDriver, normalizeCar, processDecoderMessage, getSelectedLeaderboardKey, setSelectedLeaderboardKey,
|
||||
getQuickAddDraft, setQuickAddDraft, getPassingValidationLabel, isCountedPassing, getCompetitorPassings,
|
||||
getManualCorrectionSummary, formatTeamActiveMemberLabel, formatRaceClock, renderSessionChannelAnalysis,
|
||||
setLastFinishAnnouncementSessionId, lastOverlayLeaderKeyBySession, lastOverlayTop3BySession, overlayEvents, setOverlayEvents,
|
||||
setLastFinishAnnouncementSessionId, lastOverlayLeaderKeyBySession, lastOverlayTop3BySession, overlayEvents, setOverlayEvents, sessionReplay,
|
||||
} = deps;
|
||||
|
||||
const helperDeps = {
|
||||
@@ -393,6 +479,8 @@ export function renderTimingView(deps) {
|
||||
|
||||
${active ? renderQuickAddPanel(active, helperDeps) : ""}
|
||||
|
||||
${renderSessionReplayPanel(sessionReplay, { state, t, escapeHtml, getEventName, formatLap, formatElapsedClock })}
|
||||
|
||||
<section class="panel mt-16">
|
||||
<div class="panel-header"><h3>${t("timing.speaker_panel")}</h3></div>
|
||||
<div class="panel-body">
|
||||
@@ -462,6 +550,36 @@ export function renderTimingView(deps) {
|
||||
|
||||
document.getElementById("timingDisconnect")?.addEventListener("click", disconnectDecoder);
|
||||
|
||||
const replayRenderDeps = { t, escapeHtml, formatLap, formatElapsedClock };
|
||||
document.getElementById("sessionReplayLoad")?.addEventListener("click", () => {
|
||||
const sessionId = String(document.getElementById("sessionReplaySelect")?.value || "");
|
||||
const sourceSession = state.sessions.find((session) => session.id === sessionId);
|
||||
if (!sourceSession) return;
|
||||
stopSessionReplayTimer(sessionReplay);
|
||||
loadSessionReplay(sessionReplay, sourceSession, state.resultsBySession[sourceSession.id]);
|
||||
renderView();
|
||||
});
|
||||
document.getElementById("sessionReplayPlay")?.addEventListener("click", () => {
|
||||
if (!playSessionReplay(sessionReplay)) return;
|
||||
renderView();
|
||||
startSessionReplayTimer(sessionReplay, replayRenderDeps);
|
||||
});
|
||||
document.getElementById("sessionReplayPause")?.addEventListener("click", () => {
|
||||
pauseSessionReplay(sessionReplay);
|
||||
stopSessionReplayTimer(sessionReplay);
|
||||
renderView();
|
||||
});
|
||||
document.getElementById("sessionReplayReset")?.addEventListener("click", () => {
|
||||
stopSessionReplayTimer(sessionReplay);
|
||||
resetSessionReplay(sessionReplay);
|
||||
renderView();
|
||||
});
|
||||
document.getElementById("sessionReplaySpeed")?.addEventListener("change", (event) => {
|
||||
setSessionReplaySpeed(sessionReplay, Number(event.currentTarget?.value || 1));
|
||||
updateSessionReplayPanel(sessionReplay, replayRenderDeps);
|
||||
});
|
||||
|
||||
|
||||
leaderboard.forEach((row) => {
|
||||
document.getElementById(`leaderboard-detail-${row.key}`)?.addEventListener("click", () => {
|
||||
setSelectedLeaderboardKey(row.key);
|
||||
|
||||
Reference in New Issue
Block a user