Add multi-decoder split and pit timing support

This commit is contained in:
larssand
2026-08-25 19:45:40 +02:00
parent 6f02e955a5
commit 8ea8b3a84e
20 changed files with 876 additions and 147 deletions

View File

@@ -132,6 +132,8 @@ function renderLeaderboardModal(session, row, deps) {
<p>${t("timing.total_time")}: ${escapeHtml(row.resultDisplay)}</p>
<p>${t("table.best_lap")}: ${formatLap(row.bestLapMs)}</p>
<p>${t("table.last_lap")}: ${formatLap(row.lastLapMs)}</p>
<p>${t("table.split")}: ${formatLap(row.lastSplitMs)}${Number.isFinite(row.lastSplitDeltaMs) ? ` (${row.lastSplitDeltaMs >= 0 ? "+" : ""}${formatLap(Math.abs(row.lastSplitDeltaMs))})` : ""}</p>
<p>${t("table.pit")}: ${row.pitActive ? t("timing.pit_active") : `${Number(row.pitStops || 0)} / ${formatLap(row.totalPitMs)}`}</p>
<p>${t("table.own_delta")}: ${escapeHtml(row.lapDelta || "-")}</p>
</div>
<div class="panel-body">
@@ -192,6 +194,8 @@ function renderLeaderboard(rows, deps) {
t("table.leader_gap"),
t("table.ahead_gap"),
t("table.own_delta"),
t("table.split"),
t("table.pit"),
"",
],
rows.map((row, idx) => {
@@ -214,6 +218,8 @@ function renderLeaderboard(rows, deps) {
<td>${escapeHtml(row.leaderGap || row.gap || "-")}</td>
<td>${escapeHtml(row.gapAhead || "-")}</td>
<td>${escapeHtml(row.lapDelta || "-")}</td>
<td>${formatLap(row.lastSplitMs)}</td>
<td>${row.pitActive ? `<strong>${t("timing.pit_active")}</strong>` : `${Number(row.pitStops || 0)} / ${formatLap(row.totalPitMs)}`}</td>
<td><button id="leaderboard-detail-${row.key}" class="btn btn-mini">${t("timing.details")}</button></td>
</tr>
`;
@@ -297,8 +303,16 @@ export function renderTimingView(deps) {
<div class="panel-header"><h3>${t("timing.decoder_connection")}</h3></div>
<div class="panel-body timing-top-grid">
<div class="timing-compact-card">
<label class="timing-compact-label" for="timingWsUrl">${t("settings.decoder")}</label>
<label class="timing-compact-label" for="timingWsUrl">${t("timing.main_decoder")}</label>
<input id="timingWsUrl" value="${escapeHtml(state.settings.wsUrl)}" placeholder="ws://127.0.0.1:9000" />
<label class="toggle"><input id="timingSplitEnabled" type="checkbox" ${state.settings.splitDecoderEnabled ? "checked" : ""} /><span>${t("settings.enable_split_decoder")}</span></label>
<label for="timingSplitWsUrl"><small>${t("timing.split_decoder")} WebSocket</small></label>
<input id="timingSplitWsUrl" value="${escapeHtml(state.settings.splitWsUrl || "")}" placeholder="ws://127.0.0.1:9001" />
<label class="toggle"><input id="timingPitEnabled" type="checkbox" ${state.settings.pitDecoderEnabled ? "checked" : ""} /><span>${t("settings.enable_pit_decoders")}</span></label>
<label for="timingPitEntryWsUrl"><small>${t("timing.pit_entry_decoder")} WebSocket</small></label>
<input id="timingPitEntryWsUrl" value="${escapeHtml(state.settings.pitEntryWsUrl || "")}" placeholder="ws://127.0.0.1:9002" />
<label for="timingPitExitWsUrl"><small>${t("timing.pit_exit_decoder")} WebSocket</small></label>
<input id="timingPitExitWsUrl" value="${escapeHtml(state.settings.pitExitWsUrl || "")}" placeholder="ws://127.0.0.1:9003" />
<div class="actions">
<button id="timingConnect" class="btn btn-primary">${t("timing.connect")}</button>
<button id="timingDisconnect" class="btn">${t("timing.disconnect")}</button>
@@ -307,9 +321,12 @@ export function renderTimingView(deps) {
</div>
<div class="timing-compact-card">
<span class="timing-compact-label">${t("timing.status")}</span>
<strong>${state.decoder.connected ? t("timing.connected") : t("timing.disconnected")}</strong>
<strong>${t("timing.main_decoder")}: ${state.decoder.connected ? t("timing.connected") : t("timing.disconnected")}</strong>
<small>${t("timing.last_message")}: ${state.decoder.lastMessageAt ? new Date(state.decoder.lastMessageAt).toLocaleString() : "-"}</small>
${state.settings.splitDecoderEnabled ? `<strong>${t("timing.split_decoder")}: ${state.decoder.splitConnected ? t("timing.connected") : t("timing.disconnected")}</strong><small>${t("timing.last_message")}: ${state.decoder.splitLastMessageAt ? new Date(state.decoder.splitLastMessageAt).toLocaleString() : "-"}</small>` : ""}
${state.settings.pitDecoderEnabled ? `<strong>${t("timing.pit_entry_decoder")}: ${state.decoder.pitEntryConnected ? t("timing.connected") : t("timing.disconnected")}</strong><small>${t("timing.last_message")}: ${state.decoder.pitEntryLastMessageAt ? new Date(state.decoder.pitEntryLastMessageAt).toLocaleString() : "-"}</small><strong>${t("timing.pit_exit_decoder")}: ${state.decoder.pitExitConnected ? t("timing.connected") : t("timing.disconnected")}</strong><small>${t("timing.last_message")}: ${state.decoder.pitExitLastMessageAt ? new Date(state.decoder.pitExitLastMessageAt).toLocaleString() : "-"}</small>` : ""}
<p class="error">${escapeHtml(state.decoder.lastError || "")}</p>
<p class="error">${escapeHtml(state.decoder.splitLastError || state.decoder.pitEntryLastError || state.decoder.pitExitLastError || "")}</p>
</div>
</div>
</section>
@@ -406,8 +423,13 @@ export function renderTimingView(deps) {
const input = document.getElementById("timingWsUrl");
if (input && input.value.trim()) {
state.settings.wsUrl = input.value.trim();
saveState();
}
state.settings.splitDecoderEnabled = Boolean(document.getElementById("timingSplitEnabled")?.checked);
state.settings.splitWsUrl = String(document.getElementById("timingSplitWsUrl")?.value || "").trim();
state.settings.pitDecoderEnabled = Boolean(document.getElementById("timingPitEnabled")?.checked);
state.settings.pitEntryWsUrl = String(document.getElementById("timingPitEntryWsUrl")?.value || "").trim();
state.settings.pitExitWsUrl = String(document.getElementById("timingPitExitWsUrl")?.value || "").trim();
saveState();
connectDecoder();
});