From 946f41835f756b312c3daa72e94f35cd54d963fb Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Wed, 12 Nov 2025 12:31:50 +0100 Subject: [PATCH] impr: randomize themes based on system theme (@fehmer) (#7075) closes #7069 --- frontend/src/html/pages/settings.html | 5 ++- .../src/ts/controllers/theme-controller.ts | 33 +++++++++++++------ packages/schemas/src/configs.ts | 1 + 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/frontend/src/html/pages/settings.html b/frontend/src/html/pages/settings.html index 8b232477b..945469999 100644 --- a/frontend/src/html/pages/settings.html +++ b/frontend/src/html/pages/settings.html @@ -1469,7 +1469,9 @@ random themes are not saved to your config. If set to 'favorite' only favorite themes will be randomized. If set to 'light' or 'dark', only presets with light or dark background colors will be randomized, - respectively. If set to 'custom', custom themes will be randomized. + respectively. If set to 'auto' dark or light themes are used, depending + on your system theme. If set to 'custom', custom themes will be + randomized.
@@ -1477,6 +1479,7 @@ +
diff --git a/frontend/src/ts/controllers/theme-controller.ts b/frontend/src/ts/controllers/theme-controller.ts index 16690c41f..340b18cb6 100644 --- a/frontend/src/ts/controllers/theme-controller.ts +++ b/frontend/src/ts/controllers/theme-controller.ts @@ -11,7 +11,7 @@ import * as Notifications from "../elements/notifications"; import * as Loader from "../elements/loader"; import { debounce } from "throttle-debounce"; import { ThemeName } from "@monkeytype/schemas/configs"; -import { ThemesList } from "../constants/themes"; +import { themes, ThemesList } from "../constants/themes"; import fileStorage from "../utils/file-storage"; export let randomTheme: ThemeName | string | null = null; @@ -307,7 +307,7 @@ async function changeThemeList(): Promise { themesList = themes .filter((t) => isColorDark(t.bgColor)) .map((t) => t.name); - } else if (Config.randomTheme === "on") { + } else if (Config.randomTheme === "on" || Config.randomTheme === "auto") { themesList = themes.map((t) => { return t.name; }); @@ -323,14 +323,23 @@ export async function randomizeTheme(): Promise { await changeThemeList(); if (themesList.length === 0) return; } - randomTheme = themesList[randomThemeIndex] as string; - randomThemeIndex++; - if (randomThemeIndex >= themesList.length) { - Arrays.shuffle(themesList); - randomThemeIndex = 0; + let filter = (_: string): boolean => true; + if (Config.randomTheme === "auto") { + filter = prefersColorSchemeDark() ? isColorDark : isColorLight; } + let nextTheme = null; + do { + randomTheme = themesList[randomThemeIndex] as string; + nextTheme = themes[themesList[randomThemeIndex] as ThemeName]; + randomThemeIndex++; + if (randomThemeIndex >= themesList.length) { + Arrays.shuffle(themesList); + randomThemeIndex = 0; + } + } while (!filter(nextTheme.bgColor)); + let colorsOverride: string[] | undefined; if (Config.randomTheme === "custom") { @@ -484,7 +493,7 @@ ConfigEvent.subscribe(async (eventKey, eventValue, nosave) => { await clearRandom(); await clearPreview(false); if (Config.autoSwitchTheme) { - if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) { + if (prefersColorSchemeDark()) { await set(Config.themeDark, true); } else { await set(Config.themeLight, true); @@ -523,7 +532,7 @@ ConfigEvent.subscribe(async (eventKey, eventValue, nosave) => { if (eventKey === "customBackgroundSize") applyCustomBackgroundSize(); if (eventKey === "autoSwitchTheme") { if (eventValue as boolean) { - if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) { + if (prefersColorSchemeDark()) { await set(Config.themeDark, true); } else { await set(Config.themeLight, true); @@ -535,7 +544,7 @@ ConfigEvent.subscribe(async (eventKey, eventValue, nosave) => { if ( eventKey === "themeLight" && Config.autoSwitchTheme && - !window.matchMedia?.("(prefers-color-scheme: dark)").matches && + !prefersColorSchemeDark() && !nosave ) { await set(Config.themeLight, true); @@ -569,3 +578,7 @@ window.addEventListener("customBackgroundFailed", () => { { duration: 5 } ); }); + +function prefersColorSchemeDark(): boolean { + return window.matchMedia?.("(prefers-color-scheme: dark)").matches; +} diff --git a/packages/schemas/src/configs.ts b/packages/schemas/src/configs.ts index c5f6ebbe7..020a17007 100644 --- a/packages/schemas/src/configs.ts +++ b/packages/schemas/src/configs.ts @@ -68,6 +68,7 @@ export const RandomThemeSchema = z.enum([ "light", "dark", "custom", + "auto", ]); export type RandomTheme = z.infer;