add tema
This commit is contained in:
49
src/app.js
49
src/app.js
@@ -15,6 +15,8 @@ const NAV_ITEMS = [
|
||||
const SESSION_TYPES = ["open_practice", "free_practice", "practice", "qualification", "heat", "final", "team_race"];
|
||||
const STORAGE_KEY = "rc_timing_control_v1";
|
||||
const DEFAULT_LANGUAGE = "sv";
|
||||
const DEFAULT_THEME = "dark";
|
||||
const AVAILABLE_THEMES = ["dark", "nord", "light"];
|
||||
|
||||
const TRANSLATIONS = {
|
||||
sv: {
|
||||
@@ -41,6 +43,10 @@ const TRANSLATIONS = {
|
||||
"nav.guide": "Guide",
|
||||
"nav.guide_sub": "Dokumentation och uppstart",
|
||||
"ui.language": "Språk",
|
||||
"ui.theme": "Tema",
|
||||
"ui.theme_dark": "Mörk",
|
||||
"ui.theme_nord": "Nord",
|
||||
"ui.theme_light": "Ljus",
|
||||
"brand.title": "JMK RB RaceController",
|
||||
"brand.subtitle": "RC Timing System",
|
||||
"ui.no_active_session": "Ingen aktiv session",
|
||||
@@ -775,6 +781,10 @@ const TRANSLATIONS = {
|
||||
"nav.guide": "Guide",
|
||||
"nav.guide_sub": "Documentation and setup",
|
||||
"ui.language": "Language",
|
||||
"ui.theme": "Theme",
|
||||
"ui.theme_dark": "Dark",
|
||||
"ui.theme_nord": "Nord",
|
||||
"ui.theme_light": "Light",
|
||||
"brand.title": "JMK RB RaceController",
|
||||
"brand.subtitle": "RC Timing System",
|
||||
"ui.no_active_session": "No Active Session",
|
||||
@@ -1554,7 +1564,9 @@ init();
|
||||
async function init() {
|
||||
document.body.classList.toggle("overlay-mode", overlayMode);
|
||||
seedDefaultData();
|
||||
applyTheme();
|
||||
await hydrateFromBackend();
|
||||
applyTheme();
|
||||
await loadAmmcConfigFromBackend();
|
||||
renderNav();
|
||||
renderView();
|
||||
@@ -1594,6 +1606,10 @@ function seedDefaultData() {
|
||||
state.settings.language = DEFAULT_LANGUAGE;
|
||||
}
|
||||
|
||||
if (!AVAILABLE_THEMES.includes(String(state.settings.theme || "").toLowerCase())) {
|
||||
state.settings.theme = DEFAULT_THEME;
|
||||
}
|
||||
|
||||
if (typeof state.settings.audioEnabled !== "boolean") {
|
||||
state.settings.audioEnabled = true;
|
||||
}
|
||||
@@ -1678,6 +1694,7 @@ function loadState() {
|
||||
wsUrl: parsed.settings?.wsUrl || "ws://127.0.0.1:9000",
|
||||
backendUrl: parsed.settings?.backendUrl || getDefaultBackendUrl(),
|
||||
language: parsed.settings?.language || DEFAULT_LANGUAGE,
|
||||
theme: AVAILABLE_THEMES.includes(String(parsed.settings?.theme || "").toLowerCase()) ? String(parsed.settings.theme).toLowerCase() : DEFAULT_THEME,
|
||||
autoReconnect: parsed.settings?.autoReconnect !== false,
|
||||
audioEnabled: parsed.settings?.audioEnabled !== false,
|
||||
passingSoundMode: parsed.settings?.passingSoundMode || "beep",
|
||||
@@ -1720,6 +1737,7 @@ function loadState() {
|
||||
wsUrl: "ws://127.0.0.1:9000",
|
||||
backendUrl: getDefaultBackendUrl(),
|
||||
language: DEFAULT_LANGUAGE,
|
||||
theme: DEFAULT_THEME,
|
||||
autoReconnect: true,
|
||||
audioEnabled: true,
|
||||
passingSoundMode: "beep",
|
||||
@@ -1761,6 +1779,14 @@ function currentLanguage() {
|
||||
return state.settings.language === "en" ? "en" : "sv";
|
||||
}
|
||||
|
||||
function currentTheme() {
|
||||
return AVAILABLE_THEMES.includes(String(state.settings.theme || "").toLowerCase()) ? String(state.settings.theme).toLowerCase() : DEFAULT_THEME;
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
document.body.dataset.theme = currentTheme();
|
||||
}
|
||||
|
||||
function t(key, vars = {}) {
|
||||
const lang = currentLanguage();
|
||||
const dict = TRANSLATIONS[lang] || TRANSLATIONS.en;
|
||||
@@ -1769,6 +1795,23 @@ function t(key, vars = {}) {
|
||||
}
|
||||
|
||||
function setupLanguageControl() {
|
||||
const themeLabel = document.getElementById("themeLabel");
|
||||
if (themeLabel) {
|
||||
themeLabel.textContent = t("ui.theme");
|
||||
}
|
||||
const themeSelect = document.getElementById("themeSelect");
|
||||
if (themeSelect instanceof HTMLSelectElement) {
|
||||
themeSelect.value = currentTheme();
|
||||
Array.from(themeSelect.options).forEach((option) => {
|
||||
option.textContent = t(`ui.theme_${option.value}`);
|
||||
});
|
||||
themeSelect.onchange = () => {
|
||||
state.settings.theme = AVAILABLE_THEMES.includes(themeSelect.value) ? themeSelect.value : DEFAULT_THEME;
|
||||
applyTheme();
|
||||
saveState();
|
||||
};
|
||||
}
|
||||
|
||||
const label = document.getElementById("languageLabel");
|
||||
if (label) {
|
||||
label.textContent = t("ui.language");
|
||||
@@ -1966,6 +2009,7 @@ function applyPersistedState(persisted) {
|
||||
wsUrl: persisted.settings?.wsUrl || state.settings.wsUrl || "ws://127.0.0.1:9000",
|
||||
backendUrl: persisted.settings?.backendUrl || state.settings.backendUrl || getDefaultBackendUrl(),
|
||||
language: persisted.settings?.language || state.settings.language || DEFAULT_LANGUAGE,
|
||||
theme: AVAILABLE_THEMES.includes(String(persisted.settings?.theme || state.settings.theme || DEFAULT_THEME).toLowerCase()) ? String(persisted.settings?.theme || state.settings.theme || DEFAULT_THEME).toLowerCase() : DEFAULT_THEME,
|
||||
autoReconnect: persisted.settings?.autoReconnect !== false,
|
||||
audioEnabled: persisted.settings?.audioEnabled !== false,
|
||||
passingSoundMode: persisted.settings?.passingSoundMode || state.settings.passingSoundMode || "beep",
|
||||
@@ -1987,6 +2031,7 @@ function applyPersistedState(persisted) {
|
||||
? state.settings.racePresets.map((preset) => normalizeStoredRacePreset(preset)).filter((preset) => preset.name)
|
||||
: [],
|
||||
};
|
||||
applyTheme();
|
||||
}
|
||||
|
||||
function normalizeSession(session) {
|
||||
@@ -2405,6 +2450,10 @@ function renderView() {
|
||||
if (languageLabel) {
|
||||
languageLabel.textContent = t("ui.language");
|
||||
}
|
||||
const themeLabel = document.getElementById("themeLabel");
|
||||
if (themeLabel) {
|
||||
themeLabel.textContent = t("ui.theme");
|
||||
}
|
||||
|
||||
switch (currentView) {
|
||||
case "dashboard":
|
||||
|
||||
@@ -13,6 +13,36 @@
|
||||
--shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
body[data-theme="nord"] {
|
||||
--bg: #0b1220;
|
||||
--bg-soft: #131d31;
|
||||
--panel: #182235;
|
||||
--panel-2: #10192b;
|
||||
--line: #31425f;
|
||||
--text: #edf3ff;
|
||||
--muted: #a7b8d8;
|
||||
--accent: #5e81ac;
|
||||
--accent-2: #88c0d0;
|
||||
--ok: #79c9a4;
|
||||
--warn: #f0c674;
|
||||
--shadow: 0 12px 32px rgba(4, 10, 18, 0.42);
|
||||
}
|
||||
|
||||
body[data-theme="light"] {
|
||||
--bg: #eef2f8;
|
||||
--bg-soft: #f7f9fc;
|
||||
--panel: #ffffff;
|
||||
--panel-2: #ecf1f7;
|
||||
--line: #cfd8e6;
|
||||
--text: #152033;
|
||||
--muted: #5f708d;
|
||||
--accent: #d62828;
|
||||
--accent-2: #ef4444;
|
||||
--ok: #1f9d67;
|
||||
--warn: #c98914;
|
||||
--shadow: 0 10px 28px rgba(27, 45, 78, 0.12);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -22,9 +52,9 @@ body {
|
||||
font-family: Barlow, "Segoe UI", sans-serif;
|
||||
color: var(--text);
|
||||
background:
|
||||
radial-gradient(circle at 15% 0%, rgba(225, 6, 0, 0.18), transparent 30%),
|
||||
radial-gradient(circle at 100% 80%, rgba(37, 59, 103, 0.22), transparent 30%),
|
||||
linear-gradient(160deg, #05070b 0%, #090d16 55%, #07090e 100%);
|
||||
radial-gradient(circle at 15% 0%, color-mix(in srgb, var(--accent) 18%, transparent), transparent 30%),
|
||||
radial-gradient(circle at 100% 80%, color-mix(in srgb, var(--accent-2) 16%, transparent), transparent 30%),
|
||||
linear-gradient(160deg, color-mix(in srgb, var(--bg) 90%, #000 10%) 0%, var(--bg-soft) 55%, var(--bg) 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@@ -178,7 +208,7 @@ body {
|
||||
.topbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.lang-wrap {
|
||||
@@ -195,6 +225,14 @@ body {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.theme-wrap {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.theme-select {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.view {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user