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" }, { 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 STORAGE_KEY = "rc_timing_control_v1";
const DEFAULT_LANGUAGE = "sv"; const DEFAULT_LANGUAGE = "sv";
@@ -64,6 +64,7 @@ const TRANSLATIONS = {
"dashboard.connect_decoder": "Anslut Decoder", "dashboard.connect_decoder": "Anslut Decoder",
"dashboard.recent_sessions": "Senaste sessioner", "dashboard.recent_sessions": "Senaste sessioner",
"dashboard.free_practice": "Fri träning", "dashboard.free_practice": "Fri träning",
"dashboard.open_practice": "Open Practice",
"session.none_yet": "Inga sessioner ännu.", "session.none_yet": "Inga sessioner ännu.",
"classes.create": "Skapa klass", "classes.create": "Skapa klass",
"classes.placeholder": "Klassnamn (t.ex. 2WD Buggy)", "classes.placeholder": "Klassnamn (t.ex. 2WD Buggy)",
@@ -168,6 +169,7 @@ const TRANSLATIONS = {
"events.export_heat_sheet": "Exportera heatsheet", "events.export_heat_sheet": "Exportera heatsheet",
"events.pdf_heat_sheet": "PDF 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.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.generate_qualifying": "Skapa kval från practice",
"events.clear_generated_qualifying": "Rensa genererade kval", "events.clear_generated_qualifying": "Rensa genererade kval",
"events.generate_finals": "Skapa finaler från kval", "events.generate_finals": "Skapa finaler från kval",
@@ -373,6 +375,8 @@ const TRANSLATIONS = {
"status.leader": "LEDARE", "status.leader": "LEDARE",
"status.seeded": "SEED", "status.seeded": "SEED",
"status.free_practice": "FREE", "status.free_practice": "FREE",
"status.open_practice": "OPEN",
"session.open_practice": "open practice",
"session.free_practice": "fri träning", "session.free_practice": "fri träning",
"session.practice": "träning", "session.practice": "träning",
"session.qualification": "kval", "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_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_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.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.title": "Overlay",
"overlay.subtitle": "Extern leaderboard-skärm", "overlay.subtitle": "Extern leaderboard-skärm",
"overlay.no_active": "Ingen aktiv session vald.", "overlay.no_active": "Ingen aktiv session vald.",
@@ -511,6 +519,7 @@ const TRANSLATIONS = {
"dashboard.connect_decoder": "Connect Decoder", "dashboard.connect_decoder": "Connect Decoder",
"dashboard.recent_sessions": "Recent Sessions", "dashboard.recent_sessions": "Recent Sessions",
"dashboard.free_practice": "Free Practice", "dashboard.free_practice": "Free Practice",
"dashboard.open_practice": "Open Practice",
"session.none_yet": "No sessions yet.", "session.none_yet": "No sessions yet.",
"classes.create": "Create Class", "classes.create": "Create Class",
"classes.placeholder": "Class name (e.g. 2WD Buggy)", "classes.placeholder": "Class name (e.g. 2WD Buggy)",
@@ -615,6 +624,7 @@ const TRANSLATIONS = {
"events.export_heat_sheet": "Export heat sheet", "events.export_heat_sheet": "Export heat sheet",
"events.pdf_heat_sheet": "PDF 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.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.generate_qualifying": "Generate qualifying from practice",
"events.clear_generated_qualifying": "Clear generated qualifying", "events.clear_generated_qualifying": "Clear generated qualifying",
"events.generate_finals": "Generate finals from qualifying", "events.generate_finals": "Generate finals from qualifying",
@@ -820,6 +830,8 @@ const TRANSLATIONS = {
"status.leader": "LEADER", "status.leader": "LEADER",
"status.seeded": "SEED", "status.seeded": "SEED",
"status.free_practice": "FREE", "status.free_practice": "FREE",
"status.open_practice": "OPEN",
"session.open_practice": "open practice",
"session.free_practice": "free practice", "session.free_practice": "free practice",
"session.practice": "practice", "session.practice": "practice",
"session.qualification": "qualification", "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_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_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.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.title": "Overlay",
"overlay.subtitle": "External leaderboard screen", "overlay.subtitle": "External leaderboard screen",
"overlay.no_active": "No active session selected.", "overlay.no_active": "No active session selected.",
@@ -2307,6 +2323,7 @@ function renderEventManager(eventId) {
</form> </form>
<p class="hint">${t("events.seed_best_laps_hint")}</p> <p class="hint">${t("events.seed_best_laps_hint")}</p>
<p class="hint">${t("events.free_practice_note")}</p> <p class="hint">${t("events.free_practice_note")}</p>
<p class="hint">${t("events.open_practice_note")}</p>
<div class="mt-16"> <div class="mt-16">
${renderTable( ${renderTable(
@@ -3112,7 +3129,13 @@ function renderTiming() {
}</p> }</p>
<p>${t("timing.remaining")}: ${formatCountdown(sessionTiming?.remainingMs ?? 0)}</p> <p>${t("timing.remaining")}: ${formatCountdown(sessionTiming?.remainingMs ?? 0)}</p>
<p>${t("timing.total_passings")}: ${result.passings.length}</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>` : `<p>${t("timing.no_active")}</p>`
} }
${showFinishedBanner ? `<p class="finish-banner">${t("timing.race_finished")}</p>` : ""} ${showFinishedBanner ? `<p class="finish-banner">${t("timing.race_finished")}</p>` : ""}
@@ -3359,6 +3382,17 @@ function renderGuide() {
</div> </div>
</section> </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"> <section class="panel mt-16">
<div class="panel-header"><h3>${t("guide.host_title")}</h3></div> <div class="panel-header"><h3>${t("guide.host_title")}</h3></div>
<div class="panel-body"> <div class="panel-body">
@@ -4283,7 +4317,10 @@ function parseRtcTime(value) {
} }
function resolveCompetitor(session, transponder) { 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") { if (session.mode === "track") {
const matchingAssignments = (session.assignments || []).filter((a) => { const matchingAssignments = (session.assignments || []).filter((a) => {
const car = state.cars.find((c) => c.id === a.carId); 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); const driver = state.drivers.find((d) => d.transponder === transponder);
if (driver) { 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 { return {
key: `ignore_${driver.id}`, key: `ignore_${driver.id}`,
ignore: true, ignore: true,
@@ -4342,7 +4379,7 @@ function resolveCompetitor(session, transponder) {
return { return {
key: `driver_tp_${transponder}`, key: `driver_tp_${transponder}`,
driverId: null, driverId: null,
driverName: isFreePractice ? `TP ${transponder}` : t("common.unknown_driver"), driverName: isOpenPractice ? transponder : isFreePractice ? `TP ${transponder}` : t("common.unknown_driver"),
carId: null, carId: null,
carName: t("common.unknown_car"), carName: t("common.unknown_car"),
}; };
@@ -4355,6 +4392,8 @@ function buildLeaderboard(session) {
const useTargetTieBreak = session.status === "finished"; const useTargetTieBreak = session.status === "finished";
const useSeedRanking = ["practice", "qualification"].includes(sessionType) && Number(session.seedBestLapCount || 0) > 0; const useSeedRanking = ["practice", "qualification"].includes(sessionType) && Number(session.seedBestLapCount || 0) > 0;
const isFreePractice = sessionType === "free_practice"; const isFreePractice = sessionType === "free_practice";
const isOpenPractice = sessionType === "open_practice";
const isRollingPractice = isFreePractice || isOpenPractice;
const rows = Object.values(result.competitors).map((row) => { const rows = Object.values(result.competitors).map((row) => {
const totalElapsedMs = getCompetitorElapsedMs(session, row); const totalElapsedMs = getCompetitorElapsedMs(session, row);
const distanceToTargetMs = Math.abs(targetMs - totalElapsedMs); const distanceToTargetMs = Math.abs(targetMs - totalElapsedMs);
@@ -4371,13 +4410,13 @@ function buildLeaderboard(session) {
previousLapMs, previousLapMs,
lapDeltaMs, lapDeltaMs,
comparisonMs: comparisonMs:
isFreePractice isRollingPractice
? row.bestLapMs || row.lastLapMs || Number.MAX_SAFE_INTEGER ? row.bestLapMs || row.lastLapMs || Number.MAX_SAFE_INTEGER
: useSeedRanking && seedMetric : useSeedRanking && seedMetric
? seedMetric.totalMs ? seedMetric.totalMs
: totalElapsedMs, : totalElapsedMs,
resultDisplay: resultDisplay:
isFreePractice isRollingPractice
? formatLap(row.bestLapMs || row.lastLapMs) ? formatLap(row.bestLapMs || row.lastLapMs)
: useSeedRanking && seedMetric : useSeedRanking && seedMetric
? `${seedMetric.lapCount}/${formatRaceClock(seedMetric.totalMs)}` ? `${seedMetric.lapCount}/${formatRaceClock(seedMetric.totalMs)}`
@@ -4386,7 +4425,7 @@ function buildLeaderboard(session) {
}); });
rows.sort((a, b) => { rows.sort((a, b) => {
if (isFreePractice) { if (isRollingPractice) {
if (a.comparisonMs !== b.comparisonMs) { if (a.comparisonMs !== b.comparisonMs) {
return a.comparisonMs - b.comparisonMs; return a.comparisonMs - b.comparisonMs;
} }
@@ -4427,12 +4466,12 @@ function buildLeaderboard(session) {
} }
return { return {
...row, ...row,
gap: formatLeaderboardGap(row, leader, { useSeedRanking, useTargetTieBreak, isFreePractice }), gap: formatLeaderboardGap(row, leader, { useSeedRanking, useTargetTieBreak, isFreePractice: isRollingPractice }),
leaderGap: formatLeaderboardGap(row, leader, { useSeedRanking, useTargetTieBreak, isFreePractice }), leaderGap: formatLeaderboardGap(row, leader, { useSeedRanking, useTargetTieBreak, isFreePractice: isRollingPractice }),
gapAhead: formatLeaderboardGap(row, rows[index - 1], { gapAhead: formatLeaderboardGap(row, rows[index - 1], {
useSeedRanking, useSeedRanking,
useTargetTieBreak, useTargetTieBreak,
isFreePractice, isFreePractice: isRollingPractice,
selfLabel: t("status.leader"), selfLabel: t("status.leader"),
}), }),
lapDelta: formatLapDelta(row.lapDeltaMs), lapDelta: formatLapDelta(row.lapDeltaMs),
@@ -4788,11 +4827,12 @@ function renderRaceStandingsTable(rows, emptyLabel) {
function getSessionSortWeight(session) { function getSessionSortWeight(session) {
const order = { const order = {
free_practice: 0, open_practice: 0,
practice: 1, free_practice: 1,
qualification: 2, practice: 2,
heat: 3, qualification: 3,
final: 4, heat: 4,
final: 5,
}; };
return order[String(session?.type || "").toLowerCase()] || 99; return order[String(session?.type || "").toLowerCase()] || 99;
} }