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" },
|
{ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user