diff --git a/backend/api/schemas/config-schema.js b/backend/api/schemas/config-schema.js index d6ae15482..27b00962d 100644 --- a/backend/api/schemas/config-schema.js +++ b/backend/api/schemas/config-schema.js @@ -13,6 +13,9 @@ const CARET_STYLES = [ const CONFIG_SCHEMA = joi.object({ theme: joi.string(), + themeLight: joi.string(), + themeDark: joi.string(), + autoSwitchTheme: joi.boolean(), customTheme: joi.boolean(), customThemeColors: joi .array() diff --git a/frontend/src/scripts/config.ts b/frontend/src/scripts/config.ts index 061d808ab..2642a597f 100644 --- a/frontend/src/scripts/config.ts +++ b/frontend/src/scripts/config.ts @@ -24,6 +24,9 @@ let loadDone: (...stuff: any[]) => any; const defaultConfig: MonkeyTypes.Config = { theme: "serika_dark", + themeLight: "serika", + themeDark: "serika_dark", + autoSwitchTheme: false, customTheme: false, customThemeColors: [ "#323437", @@ -1036,6 +1039,13 @@ export function setIndicateTypos( ConfigEvent.dispatch("indicateTypos", config.indicateTypos); } +export function setAutoSwitchTheme(boolean: boolean, nosave?: boolean): void { + boolean = boolean ?? defaultConfig.autoSwitchTheme; + config.autoSwitchTheme = boolean; + if (!nosave) saveToLocalStorage(); + ConfigEvent.dispatch("autoSwitchTheme", config.autoSwitchTheme); +} + export function setCustomTheme(boolean: boolean, nosave?: boolean): void { if (boolean !== undefined) config.customTheme = boolean; if (!nosave) saveToLocalStorage(); @@ -1049,6 +1059,18 @@ export function setTheme(name: string, nosave?: boolean): void { ConfigEvent.dispatch("theme", config.theme); } +export function setThemeLight(name: string, nosave?: boolean): void { + config.themeLight = name; + if (!nosave) saveToLocalStorage(); + ConfigEvent.dispatch("themeLight", config.themeLight, nosave); +} + +export function setThemeDark(name: string, nosave?: boolean): void { + config.themeDark = name; + if (!nosave) saveToLocalStorage(); + ConfigEvent.dispatch("themeDark", config.themeDark, nosave); +} + function setThemes( theme: string, customState: boolean, @@ -1379,6 +1401,9 @@ export function apply(configObj: MonkeyTypes.Config | null | "null"): void { ); if (configObj && configObj !== null) { setCustomThemeColors(configObj.customThemeColors, true); + setThemeLight(configObj.themeLight, true); + setThemeDark(configObj.themeDark, true); + setAutoSwitchTheme(configObj.autoSwitchTheme, true); setThemes(configObj.theme, configObj.customTheme, true); // setTheme(configObj.theme, true); // setCustomTheme(configObj.customTheme, true, true); diff --git a/frontend/src/scripts/controllers/theme-controller.ts b/frontend/src/scripts/controllers/theme-controller.ts index 421db73ac..0c83c73c7 100644 --- a/frontend/src/scripts/controllers/theme-controller.ts +++ b/frontend/src/scripts/controllers/theme-controller.ts @@ -224,7 +224,18 @@ export function applyCustomBackground(): void { } } -ConfigEvent.subscribe((eventKey, eventValue) => { +window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", (event) => { + if (!Config.autoSwitchTheme || Config.customTheme) return; + if (event.matches) { + set(Config.themeDark); + } else { + set(Config.themeLight); + } + }); + +ConfigEvent.subscribe((eventKey, eventValue, nosave) => { if (eventKey === "customTheme") eventValue ? set("custom") : set(Config.theme); if (eventKey === "theme") { @@ -236,10 +247,55 @@ ConfigEvent.subscribe((eventKey, eventValue) => { if (eventValue) { set("custom"); } else { - set(Config.theme); + if (Config.autoSwitchTheme) { + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + set(Config.themeDark); + } else { + set(Config.themeLight); + } + } else { + set(Config.theme); + } } } if (eventKey === "randomTheme" && eventValue === "off") clearRandom(); if (eventKey === "customBackground") applyCustomBackground(); if (eventKey === "customBackgroundSize") applyCustomBackgroundSize(); + if (eventKey === "autoSwitchTheme") { + if (eventValue) { + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + set(Config.themeDark); + } else { + set(Config.themeLight); + } + } else { + set(Config.theme); + } + } + if ( + eventKey === "themeLight" && + Config.autoSwitchTheme && + !( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) && + !nosave + ) { + set(Config.themeLight); + } + if ( + eventKey === "themeDark" && + Config.autoSwitchTheme && + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches && + !nosave + ) { + set(Config.themeDark); + } }); diff --git a/frontend/src/scripts/pages/settings.ts b/frontend/src/scripts/pages/settings.ts index cae7cbcb3..fd55c00ac 100644 --- a/frontend/src/scripts/pages/settings.ts +++ b/frontend/src/scripts/pages/settings.ts @@ -209,6 +209,11 @@ async function initGroups(): Promise { UpdateConfig.setStartGraphsAtZero, "button" ); + groups["autoSwitchTheme"] = new SettingsGroup( + "autoSwitchTheme", + UpdateConfig.setAutoSwitchTheme, + "button" + ); groups["randomTheme"] = new SettingsGroup( "randomTheme", UpdateConfig.setRandomTheme, @@ -377,9 +382,7 @@ export function reset(): void { $(".pageSettings .section.themes .favThemes.buttons").empty(); $(".pageSettings .section.themes .allThemes.buttons").empty(); $(".pageSettings .section.languageGroups .buttons").empty(); - $(".pageSettings .section.layout select").empty().select2("destroy"); - $(".pageSettings .section.keymapLayout select").empty().select2("destroy"); - $(".pageSettings .section.language select").empty().select2("destroy"); + $(".pageSettings select").empty().select2("destroy"); $(".pageSettings .section.funbox .buttons").empty(); $(".pageSettings .section.fontFamily .buttons").empty(); } @@ -404,7 +407,9 @@ export async function fillSettingsPage(): Promise { langComboBox += ``; languageEl.append(langComboBox); }); - languageEl.select2(); + languageEl.select2({ + width: "100%", + }); const layoutEl = $(".pageSettings .section.layout select").empty(); layoutEl.append(``); @@ -413,7 +418,9 @@ export async function fillSettingsPage(): Promise { `` ); }); - layoutEl.select2(); + layoutEl.select2({ + width: "100%", + }); const keymapEl = $(".pageSettings .section.keymapLayout select").empty(); keymapEl.append(``); @@ -424,7 +431,37 @@ export async function fillSettingsPage(): Promise { ); } }); - keymapEl.select2(); + keymapEl.select2({ + width: "100%", + }); + + const themeEl1 = $( + ".pageSettings .section.autoSwitchThemeInputs select.light" + ).empty(); + const themeEl2 = $( + ".pageSettings .section.autoSwitchThemeInputs select.dark" + ).empty(); + for (const theme of await Misc.getThemesList()) { + themeEl1.append( + `` + ); + themeEl2.append( + `` + ); + } + themeEl1.select2({ + width: "100%", + }); + themeEl2.select2({ + width: "100%", + }); + + $(`.pageSettings .section.autoSwitchThemeInputs select.light`) + .val(Config.themeLight) + .trigger("change.select2"); + $(`.pageSettings .section.autoSwitchThemeInputs select.dark`) + .val(Config.themeDark) + .trigger("change.select2"); const funboxEl = $(".pageSettings .section.funbox .buttons").empty(); funboxEl.append(`
none
`); @@ -680,6 +717,12 @@ export function update(): void { $(".pageSettings .section.minBurst input.customMinBurst").val( Config.minBurstCustomSpeed ); + + if (Config.autoSwitchTheme) { + $(".pageSettings .section.autoSwitchThemeInputs").removeClass("hidden"); + } else { + $(".pageSettings .section.autoSwitchThemeInputs").addClass("hidden"); + } } function toggleSettingsGroup(groupName: string): void { @@ -954,6 +997,28 @@ $(".quickNav .links a").on("click", (e) => { isOpen && toggleSettingsGroup(settingsGroup); }); +$(document).on( + "change", + `.pageSettings .section.autoSwitchThemeInputs select.light`, + (e) => { + const target = $(e.currentTarget); + if (target.hasClass("disabled") || target.hasClass("no-auto-handle")) + return; + UpdateConfig.setThemeLight(target.val() as string); + } +); + +$(document).on( + "change", + `.pageSettings .section.autoSwitchThemeInputs select.dark`, + (e) => { + const target = $(e.currentTarget); + if (target.hasClass("disabled") || target.hasClass("no-auto-handle")) + return; + UpdateConfig.setThemeDark(target.val() as string); + } +); + let configEventDisabled = false; export function setEventDisabled(value: boolean): void { configEventDisabled = value; diff --git a/frontend/src/scripts/types/types.d.ts b/frontend/src/scripts/types/types.d.ts index b8a0316ee..6243e4999 100644 --- a/frontend/src/scripts/types/types.d.ts +++ b/frontend/src/scripts/types/types.d.ts @@ -229,6 +229,9 @@ declare namespace MonkeyTypes { interface Config { theme: string; + themeLight: string; + themeDark: string; + autoSwitchTheme: boolean; customTheme: boolean; customThemeColors: string[]; favThemes: string[]; diff --git a/frontend/src/styles/settings.scss b/frontend/src/styles/settings.scss index 86ef1fb57..4260367b2 100644 --- a/frontend/src/styles/settings.scss +++ b/frontend/src/styles/settings.scss @@ -85,6 +85,15 @@ } } + &.autoSwitchThemeInputs { + grid-template-areas: unset; + grid-template-columns: 1fr 3fr 1fr 3fr; + gap: 1rem; + select { + width: 100%; + } + } + &.themes .tabContainer [tabcontent="custom"] { label.button:first-child { color: var(--text-color); diff --git a/frontend/src/styles/z_media-queries.scss b/frontend/src/styles/z_media-queries.scss index a3cda17ca..1b989d868 100644 --- a/frontend/src/styles/z_media-queries.scss +++ b/frontend/src/styles/z_media-queries.scss @@ -119,6 +119,11 @@ padding: 0.25rem 0; } } + .pageSettings { + .section.autoSwitchThemeInputs { + grid-template-columns: 1fr 3fr; + } + } .pageAccount { .group.personalBestTables { .tables { diff --git a/frontend/static/index.html b/frontend/static/index.html index 9683bb9e6..34cd4ba87 100644 --- a/frontend/static/index.html +++ b/frontend/static/index.html @@ -3513,6 +3513,38 @@ +
+

auto switch theme

+
+ Enabling this will automatically switch the theme between + light and dark depending on the system theme (this will not + override custom theme). +
+
+
+ off +
+
+ on +
+
+
+
+
light
+
+
dark
+
+

randomize theme