nya displaykort och race-rader i overlayn
This commit is contained in:
177
src/app.js
177
src/app.js
@@ -440,6 +440,8 @@ const TRANSLATIONS = {
|
||||
"overlay.mode_results": "Resultat",
|
||||
"overlay.fastest_lap": "Snabbaste varv",
|
||||
"overlay.fullscreen": "Fullscreen",
|
||||
"overlay.leaderboard_live": "Live leaderboard",
|
||||
"overlay.rotating_panel": "Displaypanel",
|
||||
"overlay.event_markers": "Eventmarkörer",
|
||||
"guide.host_title": "Hur Managed AMMC körs",
|
||||
"guide.host_1": "1. AMMC körs alltid på samma maskin som `npm start` eller `node server.js` körs på.",
|
||||
@@ -898,6 +900,8 @@ const TRANSLATIONS = {
|
||||
"overlay.mode_results": "Results",
|
||||
"overlay.fastest_lap": "Fastest Lap",
|
||||
"overlay.fullscreen": "Fullscreen",
|
||||
"overlay.leaderboard_live": "Live leaderboard",
|
||||
"overlay.rotating_panel": "Display panel",
|
||||
"overlay.event_markers": "Event markers",
|
||||
"guide.host_title": "How Managed AMMC Runs",
|
||||
"guide.host_1": "1. AMMC always runs on the same machine where `npm start` or `node server.js` is running.",
|
||||
@@ -949,6 +953,8 @@ let baselineAppVersion = "";
|
||||
let selectedLeaderboardKey = null;
|
||||
let selectedGridSessionId = null;
|
||||
let overlaySyncTimer = null;
|
||||
let overlayRotationTimer = null;
|
||||
let overlayRotationIndex = 0;
|
||||
let overlayEvents = [];
|
||||
let lastOverlayLeaderKeyBySession = {};
|
||||
let lastOverlayTop3BySession = {};
|
||||
@@ -994,6 +1000,7 @@ async function init() {
|
||||
startAppVersionPolling();
|
||||
if (overlayMode) {
|
||||
startOverlaySync();
|
||||
startOverlayRotation();
|
||||
if (state.settings.wsUrl) {
|
||||
connectDecoder();
|
||||
}
|
||||
@@ -1545,6 +1552,16 @@ function startOverlaySync() {
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function startOverlayRotation() {
|
||||
clearInterval(overlayRotationTimer);
|
||||
overlayRotationTimer = setInterval(() => {
|
||||
overlayRotationIndex = (overlayRotationIndex + 1) % 3;
|
||||
if (currentView === "overlay" && overlayViewMode === "leaderboard") {
|
||||
renderView();
|
||||
}
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
function renderNav() {
|
||||
if (overlayMode) {
|
||||
dom.nav.innerHTML = "";
|
||||
@@ -3473,6 +3490,8 @@ function renderOverlay() {
|
||||
const fastestRow =
|
||||
[...leaderboard].filter((row) => Number.isFinite(row.bestLapMs)).sort((left, right) => left.bestLapMs - right.bestLapMs)[0] || null;
|
||||
const modeLabel = getOverlayModeLabel(overlayViewMode);
|
||||
const rotatingPanels = buildOverlayPanels(active, recent);
|
||||
const activePanel = rotatingPanels.length ? rotatingPanels[overlayRotationIndex % rotatingPanels.length] : null;
|
||||
|
||||
dom.view.innerHTML = `
|
||||
<section class="overlay-shell">
|
||||
@@ -3565,6 +3584,13 @@ function renderOverlay() {
|
||||
: `
|
||||
<section class="overlay-board">
|
||||
<div class="overlay-table-wrap overlay-display-wrap">
|
||||
<section class="overlay-fastest-banner">
|
||||
<div>
|
||||
<span>${t("overlay.fastest_lap")}</span>
|
||||
<strong>${formatLap(fastestRow?.bestLapMs)}</strong>
|
||||
</div>
|
||||
<div class="overlay-fastest-driver">${escapeHtml(fastestRow?.driverName || "-")}</div>
|
||||
</section>
|
||||
<section class="overlay-stats-row">
|
||||
<article class="overlay-stat-card">
|
||||
<span>${t("overlay.fastest_lap")}</span>
|
||||
@@ -3582,47 +3608,15 @@ function renderOverlay() {
|
||||
<small>${sessionTiming?.untimed ? t("timing.elapsed") : t("timing.remaining")}</small>
|
||||
</article>
|
||||
</section>
|
||||
${renderOverlayLeaderboard(leaderboard)}
|
||||
<div class="overlay-leaderboard-card">
|
||||
<div class="overlay-section-head">
|
||||
<h3>${t("overlay.leaderboard_live")}</h3>
|
||||
</div>
|
||||
${renderOverlayLeaderboard(leaderboard)}
|
||||
</div>
|
||||
</div>
|
||||
<aside class="overlay-side">
|
||||
<section class="overlay-side-card">
|
||||
<h3>${t("events.position_grid")}</h3>
|
||||
${normalizeStartMode(active.startMode) === "position" ? renderPositionGrid(active) : `<p>${t("events.na")}</p>`}
|
||||
</section>
|
||||
<section class="overlay-side-card">
|
||||
<h3>${t("overlay.last_passings")}</h3>
|
||||
${
|
||||
recent.length
|
||||
? recent
|
||||
.map(
|
||||
(passing) => `
|
||||
<div class="overlay-passing">
|
||||
<strong>${escapeHtml(passing.driverName || t("common.unknown_driver"))}</strong>
|
||||
<span>${new Date(passing.timestamp).toLocaleTimeString()}</span>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("")
|
||||
: `<p>${t("timing.no_passings")}</p>`
|
||||
}
|
||||
</section>
|
||||
<section class="overlay-side-card">
|
||||
<h3>${t("overlay.event_markers")}</h3>
|
||||
${
|
||||
overlayEvents.length
|
||||
? overlayEvents
|
||||
.map(
|
||||
(item) => `
|
||||
<div class="overlay-passing">
|
||||
<strong>${escapeHtml(item.label)}</strong>
|
||||
<span>${new Date(item.ts).toLocaleTimeString()}</span>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("")
|
||||
: `<p>${t("timing.no_passings")}</p>`
|
||||
}
|
||||
</section>
|
||||
${activePanel ? renderOverlaySidePanel(activePanel) : `<section class="overlay-side-card"><p>${t("timing.no_passings")}</p></section>`}
|
||||
</aside>
|
||||
</section>
|
||||
`
|
||||
@@ -3648,6 +3642,58 @@ function renderOverlay() {
|
||||
});
|
||||
}
|
||||
|
||||
function buildOverlayPanels(active, recent) {
|
||||
return [
|
||||
{
|
||||
title: t("overlay.last_passings"),
|
||||
content: recent.length
|
||||
? recent
|
||||
.map(
|
||||
(passing) => `
|
||||
<div class="overlay-passing">
|
||||
<strong>${escapeHtml(passing.driverName || passing.transponder || t("common.unknown_driver"))}</strong>
|
||||
<span>${new Date(passing.timestamp).toLocaleTimeString()}</span>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("")
|
||||
: `<p>${t("timing.no_passings")}</p>`,
|
||||
},
|
||||
{
|
||||
title: t("overlay.event_markers"),
|
||||
content: overlayEvents.length
|
||||
? overlayEvents
|
||||
.map(
|
||||
(item) => `
|
||||
<div class="overlay-passing">
|
||||
<strong>${escapeHtml(item.label)}</strong>
|
||||
<span>${new Date(item.ts).toLocaleTimeString()}</span>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("")
|
||||
: `<p>${t("timing.no_passings")}</p>`,
|
||||
},
|
||||
{
|
||||
title: t("events.position_grid"),
|
||||
content:
|
||||
active && normalizeStartMode(active.startMode) === "position" ? renderPositionGrid(active) : `<p>${t("events.na")}</p>`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function renderOverlaySidePanel(panel) {
|
||||
return `
|
||||
<section class="overlay-side-card overlay-rotating-card">
|
||||
<div class="overlay-section-head">
|
||||
<h3>${escapeHtml(panel.title)}</h3>
|
||||
<span class="pill">${t("overlay.rotating_panel")}</span>
|
||||
</div>
|
||||
${panel.content}
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderLeaderboardModal(session, row) {
|
||||
const passings = getCompetitorPassings(session, row);
|
||||
return `
|
||||
@@ -3737,23 +3783,42 @@ function renderOverlayLeaderboard(rows) {
|
||||
return `<p>${t("timing.no_laps")}</p>`;
|
||||
}
|
||||
|
||||
return renderTable(
|
||||
[t("table.pos"), t("table.driver"), t("table.laps"), t("table.result"), t("table.best_lap"), t("table.ahead_gap"), t("table.own_delta")],
|
||||
rows.map((row, idx) => {
|
||||
const posClass = idx === 0 ? "pos-1" : idx === 1 ? "pos-2" : idx === 2 ? "pos-3" : "";
|
||||
return `
|
||||
<tr>
|
||||
<td><span class="pos-pill ${posClass}">${idx + 1}</span></td>
|
||||
<td>${escapeHtml(row.driverName)}</td>
|
||||
<td>${row.laps}</td>
|
||||
<td>${escapeHtml(row.resultDisplay)}</td>
|
||||
<td class="best">${formatLap(row.bestLapMs)}</td>
|
||||
<td>${escapeHtml(row.gapAhead || "-")}</td>
|
||||
<td>${escapeHtml(row.lapDelta || "-")}</td>
|
||||
</tr>
|
||||
`;
|
||||
})
|
||||
);
|
||||
return `
|
||||
<div class="overlay-race-list">
|
||||
${rows
|
||||
.map((row, idx) => {
|
||||
const posClass = idx === 0 ? "pos-1" : idx === 1 ? "pos-2" : idx === 2 ? "pos-3" : "";
|
||||
return `
|
||||
<article class="overlay-race-row ${idx === 0 ? "overlay-race-row-leader" : ""}">
|
||||
<div class="overlay-race-pos">
|
||||
<span class="pos-pill ${posClass}">${idx + 1}</span>
|
||||
</div>
|
||||
<div class="overlay-race-driver">
|
||||
<strong>${escapeHtml(row.driverName)}</strong>
|
||||
<span>${escapeHtml(row.transponder || "-")}</span>
|
||||
</div>
|
||||
<div class="overlay-race-metric">
|
||||
<label>${t("table.result")}</label>
|
||||
<strong>${escapeHtml(row.resultDisplay)}</strong>
|
||||
</div>
|
||||
<div class="overlay-race-metric">
|
||||
<label>${t("table.ahead_gap")}</label>
|
||||
<strong>${escapeHtml(row.gapAhead || "-")}</strong>
|
||||
</div>
|
||||
<div class="overlay-race-metric">
|
||||
<label>${t("table.own_delta")}</label>
|
||||
<strong>${escapeHtml(row.lapDelta || "-")}</strong>
|
||||
</div>
|
||||
<div class="overlay-race-best">
|
||||
<label>${t("table.best_lap")}</label>
|
||||
<strong>${formatLap(row.bestLapMs)}</strong>
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
})
|
||||
.join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderRecentPassings(session) {
|
||||
|
||||
Reference in New Issue
Block a user