Open Practice är nu inlagt.
This commit is contained in:
72
src/app.js
72
src/app.js
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user