import { BehaviorSubject, filter, fromEvent, Observable } from "rxjs"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; import { Theme } from "./theme"; import { ThemeBuilder } from "./theme-builder"; import { AbstractThemingService } from "./theming.service.abstraction"; export class ThemingService implements AbstractThemingService { private _theme = new BehaviorSubject(null); theme$: Observable = this._theme.pipe(filter((x) => x !== null)); constructor( private stateService: StateService, private window: Window, private document: Document, ) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.monitorThemeChanges(); } async monitorThemeChanges(): Promise { this._theme.next( new ThemeBuilder(await this.stateService.getTheme(), await this.getSystemTheme()), ); this.monitorConfiguredThemeChanges(); this.monitorSystemThemeChanges(); } updateSystemTheme(systemTheme: ThemeType): void { this._theme.next(this._theme.getValue().updateSystemTheme(systemTheme)); } async updateConfiguredTheme(theme: ThemeType): Promise { await this.stateService.setTheme(theme); this._theme.next(this._theme.getValue().updateConfiguredTheme(theme)); } protected monitorConfiguredThemeChanges(): void { this.theme$.subscribe((theme: Theme) => { this.document.documentElement.classList.remove( "theme_" + ThemeType.Light, "theme_" + ThemeType.Dark, "theme_" + ThemeType.Nord, "theme_" + ThemeType.SolarizedDark, ); this.document.documentElement.classList.add("theme_" + theme.effectiveTheme); }); } // We use a media match query for monitoring the system theme on web and browser, but this doesn't work for electron apps on Linux. // In desktop we override these methods to track systemTheme with the electron renderer instead, which works for all OSs. protected async getSystemTheme(): Promise { return this.window.matchMedia("(prefers-color-scheme: dark)").matches ? ThemeType.Dark : ThemeType.Light; } protected monitorSystemThemeChanges(): void { fromEvent( window.matchMedia("(prefers-color-scheme: dark)"), "change", ).subscribe((event) => { this.updateSystemTheme(event.matches ? ThemeType.Dark : ThemeType.Light); }); } }