branding och namechange
This commit is contained in:
90
src/app.js
90
src/app.js
@@ -30,6 +30,8 @@ import { renderTeamStintLog as renderTeamStintLogHelper, renderSessionChannelAna
|
||||
|
||||
import { getManualCorrectionSummary as getManualCorrectionSummaryLogic, applyCompetitorCorrection as applyCompetitorCorrectionLogic, recalculateCompetitorFromPassings as recalculateCompetitorFromPassingsLogic, invalidateCompetitorLastLap as invalidateCompetitorLastLapLogic, restoreCompetitorLastInvalidLap as restoreCompetitorLastInvalidLapLogic, findPassingByUndoMarker as findPassingByUndoMarkerLogic, undoJudgingAdjustment as undoJudgingAdjustmentLogic, getJudgeFilteredRows as getJudgeFilteredRowsLogic, getJudgeFilteredLog as getJudgeFilteredLogLogic } from "./judging_logic.js";
|
||||
|
||||
const PRODUCT_NAME = "RC RaceCore";
|
||||
|
||||
const renderRaceFormatFieldView = (labelKey, hintKey, controlHtml, options = {}) => renderRaceFormatField(labelKey, hintKey, controlHtml, options, { t });
|
||||
const renderRaceFormatContextCardView = (titleKey, hintKey) => renderRaceFormatContextCard(titleKey, hintKey, { t });
|
||||
const renderManageStatusBadgeView = (status) => renderManageStatusBadge(status, { t });
|
||||
@@ -327,8 +329,8 @@ const TRANSLATIONS = {
|
||||
"ui.theme_dark": "Mörk",
|
||||
"ui.theme_nord": "Nord",
|
||||
"ui.theme_light": "Ljus",
|
||||
"brand.title": "JMK RB RaceController",
|
||||
"brand.subtitle": "RC Timing System",
|
||||
"brand.title": "RC RaceCore",
|
||||
"brand.subtitle": "Live Timing & Race Control",
|
||||
"ui.no_active_session": "Ingen aktiv session",
|
||||
"ui.event": "Event",
|
||||
"ui.decoder_online": "Decoder online",
|
||||
@@ -1074,7 +1076,9 @@ const TRANSLATIONS = {
|
||||
"settings.test_audio": "Testa ljud",
|
||||
"settings.audio_note": "Browsern kräver oftast ett klick först innan ljud/tal tillåts.",
|
||||
"settings.voice_note": "Rösterna kommer från operativsystemet. På Windows ger Microsofts svenska röst oftast bäst uttal. Ange vid behov ett särskilt uttalsnamn under Förare.",
|
||||
"settings.branding": "Klubbinfo / PDF",
|
||||
"settings.branding": "Klubbbranding / UI / PDF",
|
||||
"settings.branding_note": "Klubbnamn, klubbtext och logotyp används i huvudmenyn, overlay, publik träningsprofil och PDF-export.",
|
||||
"settings.save_branding": "Spara branding",
|
||||
"settings.club_name": "Klubbnamn",
|
||||
"settings.club_tagline": "Klubbtext",
|
||||
"settings.pdf_footer": "PDF-footer",
|
||||
@@ -1082,10 +1086,10 @@ const TRANSLATIONS = {
|
||||
"settings.pdf_theme_classic": "Classic",
|
||||
"settings.pdf_theme_minimal": "Minimal",
|
||||
"settings.pdf_theme_motorsport": "Motorsport",
|
||||
"settings.logo": "Logo / overlay",
|
||||
"settings.logo": "Klubblogotyp",
|
||||
"settings.logo_upload": "Ladda logo",
|
||||
"settings.logo_clear": "Rensa logo",
|
||||
"settings.logo_note": "Logon visas i overlay. PDF-export försöker bädda in loggan automatiskt via backend.",
|
||||
"settings.logo_note": "Logotypen visas i huvudmenyn, overlay och publik träningsprofil. PDF-export försöker bädda in den via backend.",
|
||||
"settings.storage": "Lagring",
|
||||
"settings.race_presets": "Klubb-presetar",
|
||||
"settings.race_presets_note": "Exportera eller importera lokala klubb-presetar mellan installationer.",
|
||||
@@ -1320,7 +1324,7 @@ const TRANSLATIONS = {
|
||||
"overlay.subtitle": "Extern leaderboard-skärm",
|
||||
"overlay.no_active": "Ingen aktiv session vald.",
|
||||
"overlay.last_passings": "Senaste passeringar",
|
||||
"overlay.window_title": "JMK RB RaceController Overlay",
|
||||
"overlay.window_title": "RC RaceCore Overlay",
|
||||
"overlay.mode_leaderboard": "Leaderboard",
|
||||
"overlay.mode_speaker": "Speaker",
|
||||
"overlay.mode_results": "Resultat",
|
||||
@@ -1447,8 +1451,8 @@ const TRANSLATIONS = {
|
||||
"ui.theme_dark": "Dark",
|
||||
"ui.theme_nord": "Nord",
|
||||
"ui.theme_light": "Light",
|
||||
"brand.title": "JMK RB RaceController",
|
||||
"brand.subtitle": "RC Timing System",
|
||||
"brand.title": "RC RaceCore",
|
||||
"brand.subtitle": "Live Timing & Race Control",
|
||||
"ui.no_active_session": "No Active Session",
|
||||
"ui.event": "Event",
|
||||
"ui.decoder_online": "Decoder Online",
|
||||
@@ -2194,7 +2198,9 @@ const TRANSLATIONS = {
|
||||
"settings.test_audio": "Test audio",
|
||||
"settings.audio_note": "Browsers usually require a click first before sound/speech is allowed.",
|
||||
"settings.voice_note": "Voices are provided by the operating system. Select a matching local voice and set a separate spoken name under Drivers when pronunciation needs help.",
|
||||
"settings.branding": "Club Info / PDF",
|
||||
"settings.branding": "Club branding / UI / PDF",
|
||||
"settings.branding_note": "Club name, tagline, and logo are used in the main menu, overlays, public training profiles, and PDF exports.",
|
||||
"settings.save_branding": "Save branding",
|
||||
"settings.club_name": "Club name",
|
||||
"settings.club_tagline": "Club tagline",
|
||||
"settings.pdf_footer": "PDF footer",
|
||||
@@ -2202,10 +2208,10 @@ const TRANSLATIONS = {
|
||||
"settings.pdf_theme_classic": "Classic",
|
||||
"settings.pdf_theme_minimal": "Minimal",
|
||||
"settings.pdf_theme_motorsport": "Motorsport",
|
||||
"settings.logo": "Logo / overlay",
|
||||
"settings.logo": "Club logo",
|
||||
"settings.logo_upload": "Upload logo",
|
||||
"settings.logo_clear": "Clear logo",
|
||||
"settings.logo_note": "The logo is shown in overlay. PDF export attempts to embed the logo automatically via the backend.",
|
||||
"settings.logo_note": "The logo is shown in the main menu, overlays, and public training profiles. PDF export attempts to embed it via the backend.",
|
||||
"settings.storage": "Storage",
|
||||
"settings.race_presets": "Club presets",
|
||||
"settings.race_presets_note": "Export or import local club presets between installations.",
|
||||
@@ -2440,7 +2446,7 @@ const TRANSLATIONS = {
|
||||
"overlay.subtitle": "External leaderboard screen",
|
||||
"overlay.no_active": "No active session selected.",
|
||||
"overlay.last_passings": "Recent passings",
|
||||
"overlay.window_title": "JMK RB RaceController Overlay",
|
||||
"overlay.window_title": "RC RaceCore Overlay",
|
||||
"overlay.mode_leaderboard": "Leaderboard",
|
||||
"overlay.mode_speaker": "Speaker",
|
||||
"overlay.mode_results": "Results",
|
||||
@@ -2909,16 +2915,19 @@ function seedDefaultData() {
|
||||
state.settings.speakerFinishCueEnabled = true;
|
||||
}
|
||||
|
||||
if (!state.settings.clubName) {
|
||||
state.settings.clubName = "JMK RB RaceController";
|
||||
const storedClubName = String(state.settings.clubName || "").trim();
|
||||
if (!storedClubName || ["JMK RB RaceController", "RC RaceCore"].includes(storedClubName)) {
|
||||
state.settings.clubName = "JMK RB";
|
||||
}
|
||||
|
||||
if (!state.settings.clubTagline) {
|
||||
state.settings.clubTagline = "RC Timing System";
|
||||
const storedClubTagline = String(state.settings.clubTagline || "").trim();
|
||||
if (!storedClubTagline || storedClubTagline === "RC Timing System") {
|
||||
state.settings.clubTagline = "Live Timing & Race Control";
|
||||
}
|
||||
|
||||
if (!state.settings.pdfFooter) {
|
||||
state.settings.pdfFooter = "Generated by JMK RB RaceController";
|
||||
const storedPdfFooter = String(state.settings.pdfFooter || "").trim();
|
||||
if (!storedPdfFooter || storedPdfFooter === "Generated by JMK RB RaceController") {
|
||||
state.settings.pdfFooter = "Generated by RC RaceCore";
|
||||
}
|
||||
|
||||
if (!state.settings.pdfTheme) {
|
||||
@@ -2996,9 +3005,9 @@ function loadState() {
|
||||
speakerBestLapCueEnabled: parsed.settings?.speakerBestLapCueEnabled !== false,
|
||||
speakerTop3CueEnabled: parsed.settings?.speakerTop3CueEnabled === true,
|
||||
speakerSessionStartCueEnabled: parsed.settings?.speakerSessionStartCueEnabled !== false,
|
||||
clubName: parsed.settings?.clubName || "JMK RB RaceController",
|
||||
clubTagline: parsed.settings?.clubTagline || "RC Timing System",
|
||||
pdfFooter: parsed.settings?.pdfFooter || "Generated by JMK RB RaceController",
|
||||
clubName: parsed.settings?.clubName || "JMK RB",
|
||||
clubTagline: parsed.settings?.clubTagline || "Live Timing & Race Control",
|
||||
pdfFooter: parsed.settings?.pdfFooter || "Generated by RC RaceCore",
|
||||
pdfTheme: parsed.settings?.pdfTheme || "classic",
|
||||
logoDataUrl: parsed.settings?.logoDataUrl || "",
|
||||
racePresets: Array.isArray(parsed.settings?.racePresets)
|
||||
@@ -3062,9 +3071,9 @@ function loadState() {
|
||||
speakerBestLapCueEnabled: true,
|
||||
speakerTop3CueEnabled: false,
|
||||
speakerSessionStartCueEnabled: true,
|
||||
clubName: "JMK RB RaceController",
|
||||
clubTagline: "RC Timing System",
|
||||
pdfFooter: "Generated by JMK RB RaceController",
|
||||
clubName: "JMK RB",
|
||||
clubTagline: "Live Timing & Race Control",
|
||||
pdfFooter: "Generated by RC RaceCore",
|
||||
pdfTheme: "classic",
|
||||
logoDataUrl: "",
|
||||
racePresets: [],
|
||||
@@ -3143,14 +3152,7 @@ function setupLanguageControl() {
|
||||
if (label) {
|
||||
label.textContent = t("ui.language");
|
||||
}
|
||||
const brandTitle = document.getElementById("brandTitle");
|
||||
if (brandTitle) {
|
||||
brandTitle.textContent = t("brand.title");
|
||||
}
|
||||
const brandSubtitle = document.getElementById("brandSubtitle");
|
||||
if (brandSubtitle) {
|
||||
brandSubtitle.textContent = t("brand.subtitle");
|
||||
}
|
||||
updateAppBranding();
|
||||
|
||||
const select = document.getElementById("languageSelect");
|
||||
if (!(select instanceof HTMLSelectElement)) {
|
||||
@@ -3168,6 +3170,27 @@ function setupLanguageControl() {
|
||||
};
|
||||
}
|
||||
|
||||
function updateAppBranding() {
|
||||
const clubName = String(state.settings?.clubName || "").trim() || PRODUCT_NAME;
|
||||
const clubTagline = String(state.settings?.clubTagline || "").trim() || t("brand.subtitle");
|
||||
const logoDataUrl = String(state.settings?.logoDataUrl || "").trim();
|
||||
const hasLogo = /^data:image\/(?:png|jpeg|webp);base64,/i.test(logoDataUrl);
|
||||
const brandTitle = document.getElementById("brandTitle");
|
||||
const brandSubtitle = document.getElementById("brandSubtitle");
|
||||
const brandMark = document.getElementById("brandMark");
|
||||
const brandLogo = document.getElementById("brandLogo");
|
||||
|
||||
if (brandTitle) brandTitle.textContent = clubName;
|
||||
if (brandSubtitle) brandSubtitle.textContent = clubTagline;
|
||||
if (brandMark) brandMark.classList.toggle("has-logo", hasLogo);
|
||||
if (brandLogo instanceof HTMLImageElement) {
|
||||
brandLogo.hidden = !hasLogo;
|
||||
if (hasLogo) brandLogo.src = logoDataUrl;
|
||||
else brandLogo.removeAttribute("src");
|
||||
}
|
||||
document.title = clubName === PRODUCT_NAME ? PRODUCT_NAME : `${clubName} | ${PRODUCT_NAME}`;
|
||||
}
|
||||
|
||||
function buildPersistableState() {
|
||||
syncTrainingLedger();
|
||||
return {
|
||||
@@ -3193,7 +3216,7 @@ function normalizeImportedClass(classItem) {
|
||||
|
||||
function buildExportMeta(exportType) {
|
||||
return {
|
||||
app: "JMK RB RaceController",
|
||||
app: "RC RaceCore",
|
||||
schemaVersion: EXPORT_SCHEMA_VERSION,
|
||||
exportType,
|
||||
exportedAt: new Date().toISOString(),
|
||||
@@ -3571,6 +3594,7 @@ function updateViewUrl(view, driverId = "", classId = "") {
|
||||
}
|
||||
|
||||
function renderView() {
|
||||
updateAppBranding();
|
||||
if (currentView === "settings") settingsDraftActive = false;
|
||||
clearModalEscapeHandler();
|
||||
const navMeta = NAV_ITEMS.find((x) => x.id === currentView);
|
||||
|
||||
@@ -257,7 +257,7 @@ export function resolveEventBranding(event, { normalizeBrandingConfig, state })
|
||||
return {
|
||||
brandName: local.brandName || state.settings.clubName || "JMK RB",
|
||||
brandTagline: local.brandTagline || state.settings.clubTagline || "Live Event",
|
||||
pdfFooter: local.pdfFooter || state.settings.pdfFooter || "Generated by JMK RB RaceController",
|
||||
pdfFooter: local.pdfFooter || state.settings.pdfFooter || "Generated by RC RaceCore",
|
||||
pdfTheme: local.pdfTheme || state.settings.pdfTheme || "classic",
|
||||
logoDataUrl: local.logoDataUrl || state.settings.logoDataUrl || "",
|
||||
};
|
||||
|
||||
@@ -155,7 +155,7 @@ export function renderOverlayPageView(deps) {
|
||||
<span class="pill">${escapeHtml(modeLabel)}</span>
|
||||
</div>
|
||||
<h1>${escapeHtml(active.name)}</h1>
|
||||
<p class="overlay-header-sub">${escapeHtml(branding.brandName || "JMK RB RaceController")}</p>
|
||||
<p class="overlay-header-sub">${escapeHtml(branding.brandName || "RC RaceCore")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay-meta">
|
||||
|
||||
@@ -18,6 +18,11 @@ export function createDefaultAmmcConfigHelper() {
|
||||
};
|
||||
}
|
||||
|
||||
function migrateBrandDefault(value, legacyValues, fallback) {
|
||||
const normalized = String(value || "").trim();
|
||||
return !normalized || legacyValues.includes(normalized) ? fallback : normalized;
|
||||
}
|
||||
|
||||
export function getManagedWsUrlHelper({ ammc, getBackendUrl }, role = "main") {
|
||||
const portKey = role === "split" ? "splitWsPort" : role === "pit_entry" ? "pitEntryWsPort" : role === "pit_exit" ? "pitExitWsPort" : "wsPort";
|
||||
const defaultPort = role === "split" ? 9001 : role === "pit_entry" ? 9002 : role === "pit_exit" ? 9003 : 9000;
|
||||
@@ -131,9 +136,9 @@ export function applyPersistedStateHelper(persisted, deps) {
|
||||
speakerBestLapCueEnabled: persisted.settings?.speakerBestLapCueEnabled !== false,
|
||||
speakerTop3CueEnabled: persisted.settings?.speakerTop3CueEnabled === true,
|
||||
speakerSessionStartCueEnabled: persisted.settings?.speakerSessionStartCueEnabled !== false,
|
||||
clubName: persisted.settings?.clubName || state.settings.clubName || "JMK RB RaceController",
|
||||
clubTagline: persisted.settings?.clubTagline || state.settings.clubTagline || "RC Timing System",
|
||||
pdfFooter: persisted.settings?.pdfFooter || state.settings.pdfFooter || "Generated by JMK RB RaceController",
|
||||
clubName: migrateBrandDefault(persisted.settings?.clubName || state.settings.clubName, ["JMK RB RaceController", "RC RaceCore"], "JMK RB"),
|
||||
clubTagline: migrateBrandDefault(persisted.settings?.clubTagline || state.settings.clubTagline, ["RC Timing System"], "Live Timing & Race Control"),
|
||||
pdfFooter: migrateBrandDefault(persisted.settings?.pdfFooter || state.settings.pdfFooter, ["Generated by JMK RB RaceController"], "Generated by RC RaceCore"),
|
||||
pdfTheme: persisted.settings?.pdfTheme || state.settings.pdfTheme || "classic",
|
||||
logoDataUrl: persisted.settings?.logoDataUrl || state.settings.logoDataUrl || "",
|
||||
raceAudio: normalizeRaceAudioSettings(persisted.settings?.raceAudio || state.settings?.raceAudio),
|
||||
|
||||
@@ -272,15 +272,18 @@ export function renderSettings(deps) {
|
||||
|
||||
<section class="panel mt-16">
|
||||
<div class="panel-header"><h3>${t("settings.branding")}</h3></div>
|
||||
<div class="panel-body">
|
||||
<p class="hint">${t("settings.branding_note")}</p>
|
||||
</div>
|
||||
<div class="panel-body form-grid cols-3 settings-grid">
|
||||
<input name="clubName" form="settingsForm" value="${escapeHtml(state.settings.clubName || "")}" placeholder="${t("settings.club_name")}" />
|
||||
<input name="clubTagline" form="settingsForm" value="${escapeHtml(state.settings.clubTagline || "")}" placeholder="${t("settings.club_tagline")}" />
|
||||
<input name="pdfFooter" form="settingsForm" value="${escapeHtml(state.settings.pdfFooter || "")}" placeholder="${t("settings.pdf_footer")}" />
|
||||
<select name="pdfTheme" form="settingsForm">
|
||||
<label><span>${t("settings.club_name")}</span><input name="clubName" form="settingsForm" value="${escapeHtml(state.settings.clubName || "")}" placeholder="JMK RB" /></label>
|
||||
<label><span>${t("settings.club_tagline")}</span><input name="clubTagline" form="settingsForm" value="${escapeHtml(state.settings.clubTagline || "")}" placeholder="Live Timing & Race Control" /></label>
|
||||
<label><span>${t("settings.pdf_footer")}</span><input name="pdfFooter" form="settingsForm" value="${escapeHtml(state.settings.pdfFooter || "")}" placeholder="Generated by RC RaceCore" /></label>
|
||||
<label><span>${t("settings.pdf_theme")}</span><select name="pdfTheme" form="settingsForm">
|
||||
<option value="classic" ${state.settings.pdfTheme === "classic" ? "selected" : ""}>${t("settings.pdf_theme_classic")}</option>
|
||||
<option value="minimal" ${state.settings.pdfTheme === "minimal" ? "selected" : ""}>${t("settings.pdf_theme_minimal")}</option>
|
||||
<option value="motorsport" ${state.settings.pdfTheme === "motorsport" ? "selected" : ""}>${t("settings.pdf_theme_motorsport")}</option>
|
||||
</select>
|
||||
</select></label>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h4>${t("settings.logo")}</h4>
|
||||
@@ -288,6 +291,7 @@ export function renderSettings(deps) {
|
||||
<div class="actions">
|
||||
<input id="logoUpload" type="file" accept="image/*" />
|
||||
<button id="clearLogo" class="btn" type="button">${t("settings.logo_clear")}</button>
|
||||
<button class="btn btn-primary" type="submit" form="settingsForm">${t("settings.save_branding")}</button>
|
||||
</div>
|
||||
${
|
||||
state.settings.logoDataUrl
|
||||
@@ -567,9 +571,9 @@ export function renderSettings(deps) {
|
||||
state.settings.speakerBestLapCueEnabled = form.get("speakerBestLapCueEnabled") === "on";
|
||||
state.settings.speakerTop3CueEnabled = form.get("speakerTop3CueEnabled") === "on";
|
||||
state.settings.speakerSessionStartCueEnabled = form.get("speakerSessionStartCueEnabled") === "on";
|
||||
state.settings.clubName = String(form.get("clubName") || "").trim() || "JMK RB RaceController";
|
||||
state.settings.clubTagline = String(form.get("clubTagline") || "").trim() || "RC Timing System";
|
||||
state.settings.pdfFooter = String(form.get("pdfFooter") || "").trim() || "Generated by JMK RB RaceController";
|
||||
state.settings.clubName = String(form.get("clubName") || "").trim() || "JMK RB";
|
||||
state.settings.clubTagline = String(form.get("clubTagline") || "").trim() || "Live Timing & Race Control";
|
||||
state.settings.pdfFooter = String(form.get("pdfFooter") || "").trim() || "Generated by RC RaceCore";
|
||||
state.settings.pdfTheme = ["classic", "minimal", "motorsport"].includes(String(form.get("pdfTheme") || "classic"))
|
||||
? String(form.get("pdfTheme"))
|
||||
: "classic";
|
||||
|
||||
@@ -122,6 +122,7 @@ body {
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
overflow: hidden;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 10px;
|
||||
@@ -3216,3 +3217,20 @@ select:focus {
|
||||
.audio-event-table td:first-child { min-width: 170px; }
|
||||
.race-control-cues .btn { flex: 1 1 140px; }
|
||||
}
|
||||
|
||||
.brand-mark.has-logo {
|
||||
padding: 3px;
|
||||
background: var(--surface-card);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
.brand-mark.has-logo > span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user