Open Practice är nu inlagt.

This commit is contained in:
larssand
2026-03-14 12:25:11 +01:00
parent 86fef324d1
commit 24cf7d9522

View File

@@ -11,7 +11,7 @@ const NAV_ITEMS = [
{ id: "guide", titleKey: "nav.guide", subtitleKey: "nav.guide_sub" },
];
const SESSION_TYPES = ["free_practice", "practice", "qualification", "heat", "final"];
const SESSION_TYPES = ["open_practice", "free_practice", "practice", "qualification", "heat", "final"];
const STORAGE_KEY = "rc_timing_control_v1";
const DEFAULT_LANGUAGE = "sv";
@@ -64,6 +64,7 @@ const TRANSLATIONS = {
"dashboard.connect_decoder": "Anslut Decoder",
"dashboard.recent_sessions": "Senaste sessioner",
"dashboard.free_practice": "Fri träning",
"dashboard.open_practice": "Open Practice",
"session.none_yet": "Inga sessioner ännu.",
"classes.create": "Skapa klass",
"classes.placeholder": "Klassnamn (t.ex. 2WD Buggy)",
@@ -168,6 +169,7 @@ const TRANSLATIONS = {
"events.export_heat_sheet": "Exportera heatsheet",
"events.pdf_heat_sheet": "PDF heatsheet",
"events.free_practice_note": "Free Practice visar löpande varvtider och används inte för seedning.",
"events.open_practice_note": "Open Practice visar alla inkommande transpondrar löpande. Om ingen förare matchar visas bara transpondern.",
"events.generate_qualifying": "Skapa kval från practice",
"events.clear_generated_qualifying": "Rensa genererade kval",
"events.generate_finals": "Skapa finaler från kval",
@@ -373,6 +375,8 @@ const TRANSLATIONS = {
"status.leader": "LEDARE",
"status.seeded": "SEED",
"status.free_practice": "FREE",
"status.open_practice": "OPEN",
"session.open_practice": "open practice",
"session.free_practice": "fri träning",
"session.practice": "träning",
"session.qualification": "kval",
@@ -421,6 +425,10 @@ const TRANSLATIONS = {
"guide.free_practice_1": "Använd sessionstypen fri träning när du bara vill visa löpande varvtider.",
"guide.free_practice_2": "Free Practice påverkar inte seedning till kval eller finaler.",
"guide.free_practice_3": "Leaderboarden visar varv, senaste varv, bästa varv, gap till framförvarande och eget delta mot föregående varv.",
"guide.open_practice_title": "Open Practice",
"guide.open_practice_1": "Använd Open Practice när du vill att systemet bara ska lista alla transpondrar som kommer in.",
"guide.open_practice_2": "Om transpondern inte matchar en registrerad förare visas transpondernumret som namn.",
"guide.open_practice_3": "Open Practice påverkar inte seedning, kval eller finaler.",
"overlay.title": "Overlay",
"overlay.subtitle": "Extern leaderboard-skärm",
"overlay.no_active": "Ingen aktiv session vald.",
@@ -511,6 +519,7 @@ const TRANSLATIONS = {
"dashboard.connect_decoder": "Connect Decoder",
"dashboard.recent_sessions": "Recent Sessions",
"dashboard.free_practice": "Free Practice",
"dashboard.open_practice": "Open Practice",
"session.none_yet": "No sessions yet.",
"classes.create": "Create Class",
"classes.placeholder": "Class name (e.g. 2WD Buggy)",
@@ -615,6 +624,7 @@ const TRANSLATIONS = {
"events.export_heat_sheet": "Export heat sheet",
"events.pdf_heat_sheet": "PDF heat sheet",
"events.free_practice_note": "Free Practice shows rolling lap times and is not used for seeding.",
"events.open_practice_note": "Open Practice shows all incoming transponders live. If no driver matches, only the transponder is shown.",
"events.generate_qualifying": "Generate qualifying from practice",
"events.clear_generated_qualifying": "Clear generated qualifying",
"events.generate_finals": "Generate finals from qualifying",
@@ -820,6 +830,8 @@ const TRANSLATIONS = {
"status.leader": "LEADER",
"status.seeded": "SEED",
"status.free_practice": "FREE",
"status.open_practice": "OPEN",
"session.open_practice": "open practice",
"session.free_practice": "free practice",
"session.practice": "practice",
"session.qualification": "qualification",
@@ -868,6 +880,10 @@ const TRANSLATIONS = {
"guide.free_practice_1": "Use the free practice session type when you only want to show live lap times.",
"guide.free_practice_2": "Free Practice does not affect seeding for qualifying or finals.",
"guide.free_practice_3": "The leaderboard shows laps, last lap, best lap, gap to the car ahead and your own delta versus the previous lap.",
"guide.open_practice_title": "Open Practice",
"guide.open_practice_1": "Use Open Practice when you want the system to simply list every transponder that comes in.",
"guide.open_practice_2": "If the transponder does not match a registered driver, the transponder number is shown as the name.",
"guide.open_practice_3": "Open Practice does not affect seeding, qualifying or finals.",
"overlay.title": "Overlay",
"overlay.subtitle": "External leaderboard screen",
"overlay.no_active": "No active session selected.",
@@ -2307,6 +2323,7 @@ function renderEventManager(eventId) {
</form>
<p class="hint">${t("events.seed_best_laps_hint")}</p>
<p class="hint">${t("events.free_practice_note")}</p>
<p class="hint">${t("events.open_practice_note")}</p>
<div class="mt-16">
${renderTable(
@@ -3112,7 +3129,13 @@ function renderTiming() {
}</p>
<p>${t("timing.remaining")}: ${formatCountdown(sessionTiming?.remainingMs ?? 0)}</p>
<p>${t("timing.total_passings")}: ${result.passings.length}</p>
${active.type === "free_practice" ? `<p class="hint">${t("events.free_practice_note")}</p>` : ""}`
${
active.type === "free_practice"
? `<p class="hint">${t("events.free_practice_note")}</p>`
: active.type === "open_practice"
? `<p class="hint">${t("events.open_practice_note")}</p>`
: ""
}`
: `<p>${t("timing.no_active")}</p>`
}
${showFinishedBanner ? `<p class="finish-banner">${t("timing.race_finished")}</p>` : ""}
@@ -3359,6 +3382,17 @@ function renderGuide() {
</div>
</section>
<section class="panel mt-16">
<div class="panel-header"><h3>${t("guide.open_practice_title")}</h3></div>
<div class="panel-body">
<ul>
<li>${t("guide.open_practice_1")}</li>
<li>${t("guide.open_practice_2")}</li>
<li>${t("guide.open_practice_3")}</li>
</ul>
</div>
</section>
<section class="panel mt-16">
<div class="panel-header"><h3>${t("guide.host_title")}</h3></div>
<div class="panel-body">
@@ -4283,7 +4317,10 @@ function parseRtcTime(value) {
}
function resolveCompetitor(session, transponder) {
const isFreePractice = String(session?.type || "").toLowerCase() === "free_practice";
const sessionType = String(session?.type || "").toLowerCase();
const isOpenPractice = sessionType === "open_practice";
const isFreePractice = sessionType === "free_practice";
const isOpenMonitoringSession = isOpenPractice || isFreePractice;
if (session.mode === "track") {
const matchingAssignments = (session.assignments || []).filter((a) => {
const car = state.cars.find((c) => c.id === a.carId);
@@ -4324,7 +4361,7 @@ function resolveCompetitor(session, transponder) {
const driver = state.drivers.find((d) => d.transponder === transponder);
if (driver) {
if (!isFreePractice && Array.isArray(session.driverIds) && session.driverIds.length && !session.driverIds.includes(driver.id)) {
if (!isOpenMonitoringSession && Array.isArray(session.driverIds) && session.driverIds.length && !session.driverIds.includes(driver.id)) {
return {
key: `ignore_${driver.id}`,
ignore: true,
@@ -4342,7 +4379,7 @@ function resolveCompetitor(session, transponder) {
return {
key: `driver_tp_${transponder}`,
driverId: null,
driverName: isFreePractice ? `TP ${transponder}` : t("common.unknown_driver"),
driverName: isOpenPractice ? transponder : isFreePractice ? `TP ${transponder}` : t("common.unknown_driver"),
carId: null,
carName: t("common.unknown_car"),
};
@@ -4355,6 +4392,8 @@ function buildLeaderboard(session) {
const useTargetTieBreak = session.status === "finished";
const useSeedRanking = ["practice", "qualification"].includes(sessionType) && Number(session.seedBestLapCount || 0) > 0;
const isFreePractice = sessionType === "free_practice";
const isOpenPractice = sessionType === "open_practice";
const isRollingPractice = isFreePractice || isOpenPractice;
const rows = Object.values(result.competitors).map((row) => {
const totalElapsedMs = getCompetitorElapsedMs(session, row);
const distanceToTargetMs = Math.abs(targetMs - totalElapsedMs);
@@ -4371,13 +4410,13 @@ function buildLeaderboard(session) {
previousLapMs,
lapDeltaMs,
comparisonMs:
isFreePractice
isRollingPractice
? row.bestLapMs || row.lastLapMs || Number.MAX_SAFE_INTEGER
: useSeedRanking && seedMetric
? seedMetric.totalMs
: totalElapsedMs,
resultDisplay:
isFreePractice
isRollingPractice
? formatLap(row.bestLapMs || row.lastLapMs)
: useSeedRanking && seedMetric
? `${seedMetric.lapCount}/${formatRaceClock(seedMetric.totalMs)}`
@@ -4386,7 +4425,7 @@ function buildLeaderboard(session) {
});
rows.sort((a, b) => {
if (isFreePractice) {
if (isRollingPractice) {
if (a.comparisonMs !== b.comparisonMs) {
return a.comparisonMs - b.comparisonMs;
}
@@ -4427,12 +4466,12 @@ function buildLeaderboard(session) {
}
return {
...row,
gap: formatLeaderboardGap(row, leader, { useSeedRanking, useTargetTieBreak, isFreePractice }),
leaderGap: formatLeaderboardGap(row, leader, { useSeedRanking, useTargetTieBreak, isFreePractice }),
gap: formatLeaderboardGap(row, leader, { useSeedRanking, useTargetTieBreak, isFreePractice: isRollingPractice }),
leaderGap: formatLeaderboardGap(row, leader, { useSeedRanking, useTargetTieBreak, isFreePractice: isRollingPractice }),
gapAhead: formatLeaderboardGap(row, rows[index - 1], {
useSeedRanking,
useTargetTieBreak,
isFreePractice,
isFreePractice: isRollingPractice,
selfLabel: t("status.leader"),
}),
lapDelta: formatLapDelta(row.lapDeltaMs),
@@ -4788,11 +4827,12 @@ function renderRaceStandingsTable(rows, emptyLabel) {
function getSessionSortWeight(session) {
const order = {
free_practice: 0,
practice: 1,
qualification: 2,
heat: 3,
final: 4,
open_practice: 0,
free_practice: 1,
practice: 2,
qualification: 3,
heat: 4,
final: 5,
};
return order[String(session?.type || "").toLowerCase()] || 99;
}