Add configurable manifest-driven race audio system

This commit is contained in:
larssand
2026-08-28 13:29:27 +02:00
parent 3f3b410869
commit 5f79228941
22 changed files with 1133 additions and 109 deletions

View File

@@ -390,6 +390,7 @@ export function renderDriversView(deps) {
})
.join("");
const editingProfileId = editingDriver ? getDriverProfileId(editingDriver) : "";
const passingSoundOptions = (selected = "") => [`<option value="">${t("audio.pass_default")}</option>`, ...Array.from({ length: 8 }, (_item, index) => { const value = `pass_${String(index + 1).padStart(2, "0")}.wav`; return `<option value="${value}" ${selected === value ? "selected" : ""}>${value}</option>`; })].join("");
const existingClubs = [...new Set(state.drivers.map((driver) => String(driver.club || "").trim()).filter(Boolean))].sort();
const existingCountries = [...new Set([
"Sverige", "Norge", "Finland", "Danmark", "Island", "Tyskland", "Nederländerna", "Belgien", "Frankrike", "Storbritannien", "Polen", "Tjeckien", "Österrike", "Schweiz",
@@ -405,6 +406,7 @@ export function renderDriversView(deps) {
<input name="brand" placeholder="${t("drivers.brand_placeholder")}" />
<input name="transponder" placeholder="${t("drivers.transponder_placeholder")}" />
<input name="speechName" placeholder="${t("drivers.speech_name_placeholder")}" title="${t("drivers.speech_name_hint")}" />
<label>${t("audio.passing_sound")}<select name="passSound">${passingSoundOptions()}</select></label>
<input name="club" list="driverClubOptions" placeholder="${t("drivers.club_placeholder")}" />
<input name="country" list="driverCountryOptions" placeholder="${t("drivers.country_placeholder")}" />
<label>${t("drivers.training_profile")}<select name="profileId" title="${t("drivers.training_profile_hint")}">
@@ -470,6 +472,7 @@ export function renderDriversView(deps) {
<input name="brand" value="${escapeHtml(editingDriver.brand || "")}" placeholder="${t("drivers.brand_placeholder")}" />
<input name="transponder" value="${escapeHtml(editingDriver.transponder || "")}" placeholder="${t("drivers.transponder_placeholder")}" />
<input name="speechName" value="${escapeHtml(editingDriver.speechName || "")}" placeholder="${t("drivers.speech_name_placeholder")}" title="${t("drivers.speech_name_hint")}" />
<label>${t("audio.passing_sound")}<select name="passSound">${passingSoundOptions(editingDriver.passSound)}</select></label>
<input name="club" list="driverClubOptions" value="${escapeHtml(profileGroups.find((profile) => profile.id === editingProfileId)?.club || editingDriver.club || "")}" placeholder="${t("drivers.club_placeholder")}" />
<input name="country" list="driverCountryOptions" value="${escapeHtml(profileGroups.find((profile) => profile.id === editingProfileId)?.country || editingDriver.country || "")}" placeholder="${t("drivers.country_placeholder")}" />
<label>${t("drivers.training_profile")}<select name="profileId" title="${t("drivers.training_profile_hint")}">
@@ -516,6 +519,7 @@ export function renderDriversView(deps) {
brand: String(form.get("brand") || "").trim(),
transponder: String(form.get("transponder") || "").trim(),
speechName: String(form.get("speechName") || "").trim(),
passSound: String(form.get("passSound") || "").trim(),
profileId,
club: String(form.get("club") || linkedProfile?.club || "").trim(),
country: String(form.get("country") || linkedProfile?.country || "").trim(),
@@ -601,6 +605,7 @@ export function renderDriversView(deps) {
const cleanedBrand = String(form.get("brand") || "").trim();
const cleanedTp = String(form.get("transponder") || "").trim();
const cleanedSpeechName = String(form.get("speechName") || "").trim();
const cleanedPassSound = String(form.get("passSound") || "").trim();
const requestedProfileId = String(form.get("profileId") || "").trim();
const cleanedProfileId = requestedProfileId === "__new__" ? uid("profile") : requestedProfileId || editingProfileId;
const cleanedClub = String(form.get("club") || "").trim();
@@ -623,6 +628,7 @@ export function renderDriversView(deps) {
brand: cleanedBrand,
transponder: cleanedTp,
speechName: cleanedSpeechName,
passSound: cleanedPassSound,
profileId: cleanedProfileId,
club: cleanedClub,
country: cleanedCountry,
@@ -669,6 +675,7 @@ export function renderCarsView(deps) {
!carSearch || [car.name, car.transponder, car.brand].map((value) => String(value || "").toLowerCase()).some((value) => value.includes(carSearch))
);
const editingCar = state.cars.find((car) => car.id === selectedCarEditId()) || null;
const passingSoundOptions = (selected = "") => [`<option value="">${t("audio.pass_default")}</option>`, ...Array.from({ length: 8 }, (_item, index) => { const value = `pass_${String(index + 1).padStart(2, "0")}.wav`; return `<option value="${value}" ${selected === value ? "selected" : ""}>${value}</option>`; })].join("");
dom.view.innerHTML = `
<section class="panel">
@@ -677,6 +684,7 @@ export function renderCarsView(deps) {
<input required name="name" placeholder="${t("cars.name_placeholder")}" />
<input name="brand" placeholder="${t("cars.brand_placeholder")}" />
<input required name="transponder" placeholder="${t("cars.transponder_placeholder")}" />
<label>${t("audio.passing_sound")}<select name="passSound">${passingSoundOptions()}</select></label>
<button class="btn btn-primary" type="submit">${t("cars.add")}</button>
</form>
</section>
@@ -719,6 +727,7 @@ export function renderCarsView(deps) {
<input name="name" required value="${escapeHtml(editingCar.name)}" placeholder="${t("cars.name_placeholder")}" />
<input name="brand" value="${escapeHtml(editingCar.brand || "")}" placeholder="${t("cars.brand_placeholder")}" />
<input name="transponder" required value="${escapeHtml(editingCar.transponder || "")}" placeholder="${t("cars.transponder_placeholder")}" />
<label>${t("audio.passing_sound")}<select name="passSound">${passingSoundOptions(editingCar.passSound)}</select></label>
<p class="form-error" id="carEditError" hidden></p>
<div class="actions-inline">
<button class="btn btn-primary" id="carEditSave" type="button">${t("common.save")}</button>
@@ -741,6 +750,7 @@ export function renderCarsView(deps) {
name: String(form.get("name")).trim(),
brand: String(form.get("brand") || "").trim(),
transponder: String(form.get("transponder")).trim(),
passSound: String(form.get("passSound") || "").trim(),
})
);
saveState();
@@ -806,6 +816,7 @@ export function renderCarsView(deps) {
const cleanedName = String(form.get("name") || "").trim();
const cleanedBrand = String(form.get("brand") || "").trim();
const cleanedTp = String(form.get("transponder") || "").trim();
const cleanedPassSound = String(form.get("passSound") || "").trim();
if (!cleanedName) {
setFormError("carEditError", t("validation.required_name"));
return;
@@ -822,6 +833,7 @@ export function renderCarsView(deps) {
name: cleanedName,
brand: cleanedBrand,
transponder: cleanedTp,
passSound: cleanedPassSound,
})
: item
);