1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 23:33:31 +00:00

[PM-5539] Migrate ThemingService (#8219)

* Update ThemingService

* Finish ThemingService

* Lint

* More Tests & Docs

* Refactor to ThemeStateService

* Rename File

* Fix Import

* Remove `type` added to imports

* Update InitServices

* Fix Test

* Remove Unreferenced Code

* Remove Unneeded Null Check

* Add Ticket Link

* Add Back THEMING_DISK

* Fix Desktop

* Create SYSTEM_THEME_OBSERVABLE

* Fix Browser Injection

* Update Desktop Manual Access

* Fix Default Theme

* Update Test
This commit is contained in:
Justin Baur
2024-03-13 10:25:39 -05:00
committed by GitHub
parent 531ae3184f
commit e6fe0d1d13
38 changed files with 396 additions and 222 deletions

View File

@@ -8,6 +8,7 @@ import { DomainSettingsService } from "@bitwarden/common/autofill/services/domai
import { UserNotificationSettingsService } from "@bitwarden/common/autofill/services/user-notification-settings.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
@@ -51,6 +52,7 @@ describe("NotificationBackground", () => {
const domainSettingsService = mock<DomainSettingsService>();
const environmentService = mock<EnvironmentService>();
const logService = mock<LogService>();
const themeStateService = mock<ThemeStateService>();
beforeEach(() => {
notificationBackground = new NotificationBackground(
@@ -64,6 +66,7 @@ describe("NotificationBackground", () => {
domainSettingsService,
environmentService,
logService,
themeStateService,
);
});

View File

@@ -11,6 +11,7 @@ import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
@@ -77,6 +78,7 @@ export default class NotificationBackground {
private domainSettingsService: DomainSettingsService,
private environmentService: EnvironmentService,
private logService: LogService,
private themeStateService: ThemeStateService,
) {}
async init() {
@@ -165,7 +167,7 @@ export default class NotificationBackground {
const notificationType = notificationQueueMessage.type;
const typeData: Record<string, any> = {
isVaultLocked: notificationQueueMessage.wasVaultLocked,
theme: await this.stateService.getTheme(),
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
};
switch (notificationType) {

View File

@@ -1,4 +1,5 @@
import { mock, mockReset } from "jest-mock-extended";
import { of } from "rxjs";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
@@ -10,6 +11,7 @@ import { AutofillSettingsService } from "@bitwarden/common/autofill/services/aut
import { ThemeType } from "@bitwarden/common/platform/enums";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
import { I18nService } from "@bitwarden/common/platform/services/i18n.service";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { SettingsService } from "@bitwarden/common/services/settings.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
@@ -53,6 +55,7 @@ describe("OverlayBackground", () => {
const autofillSettingsService = mock<AutofillSettingsService>();
const i18nService = mock<I18nService>();
const platformUtilsService = mock<BrowserPlatformUtilsService>();
const themeStateService = mock<ThemeStateService>();
const initOverlayElementPorts = async (options = { initList: true, initButton: true }) => {
const { initList, initButton } = options;
if (initButton) {
@@ -79,12 +82,15 @@ describe("OverlayBackground", () => {
autofillSettingsService,
i18nService,
platformUtilsService,
themeStateService,
);
jest
.spyOn(overlayBackground as any, "getOverlayVisibility")
.mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus);
themeStateService.selectedTheme$ = of(ThemeType.Light);
void overlayBackground.init();
});
@@ -993,7 +999,7 @@ describe("OverlayBackground", () => {
});
it("gets the system theme", async () => {
jest.spyOn(overlayBackground["stateService"], "getTheme").mockResolvedValue(ThemeType.System);
themeStateService.selectedTheme$ = of(ThemeType.System);
await initOverlayElementPorts({ initList: true, initButton: false });
await flushPromises();

View File

@@ -11,6 +11,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { buildCipherIcon } from "@bitwarden/common/vault/icon/build-cipher-icon";
@@ -96,6 +97,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private themeStateService: ThemeStateService,
) {
this.iconsServerUrl = this.environmentService.getIconsUrl();
}
@@ -695,7 +697,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
command: `initAutofillOverlay${isOverlayListPort ? "List" : "Button"}`,
authStatus: await this.getAuthStatus(),
styleSheetUrl: chrome.runtime.getURL(`overlay/${isOverlayListPort ? "list" : "button"}.css`),
theme: await this.stateService.getTheme(),
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
translations: this.getTranslations(),
ciphers: isOverlayListPort ? this.getOverlayCipherData() : null,
});

View File

@@ -116,6 +116,7 @@ import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
/* eslint-enable import/no-restricted-paths */
import { DefaultThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
import { ApiService } from "@bitwarden/common/services/api.service";
import { AuditService } from "@bitwarden/common/services/audit.service";
@@ -450,6 +451,9 @@ export default class MainBackground {
async () => this.biometricUnlock(),
self,
);
const themeStateService = new DefaultThemeStateService(this.globalStateProvider);
this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider);
this.cryptoService = new BrowserCryptoService(
this.keyGenerationService,
@@ -858,6 +862,7 @@ export default class MainBackground {
this.domainSettingsService,
this.environmentService,
this.logService,
themeStateService,
);
this.overlayBackground = new OverlayBackground(
this.cipherService,
@@ -869,6 +874,7 @@ export default class MainBackground {
this.autofillSettingsService,
this.i18nService,
this.platformUtilsService,
themeStateService,
);
this.filelessImporterBackground = new FilelessImporterBackground(
this.configService,

View File

@@ -1,4 +1,5 @@
import { Injectable } from "@angular/core";
import { DOCUMENT } from "@angular/common";
import { Inject, Injectable } from "@angular/core";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -19,6 +20,7 @@ export class InitService {
private logService: LogServiceAbstraction,
private themingService: AbstractThemingService,
private configService: ConfigService,
@Inject(DOCUMENT) private document: Document,
) {}
init() {
@@ -34,7 +36,7 @@ export class InitService {
}
const htmlEl = window.document.documentElement;
await this.themingService.monitorThemeChanges();
this.themingService.applyThemeChangesTo(this.document);
htmlEl.classList.add("locale_" + this.i18nService.translationLocale);
// Workaround for slow performance on external monitors on Chrome + MacOS

View File

@@ -3,13 +3,13 @@ import { DomSanitizer } from "@angular/platform-browser";
import { ToastrService } from "ngx-toastr";
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards";
import { ThemingService } from "@bitwarden/angular/platform/services/theming/theming.service";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service";
import {
MEMORY_STORAGE,
SECURE_STORAGE,
OBSERVABLE_DISK_STORAGE,
OBSERVABLE_MEMORY_STORAGE,
SYSTEM_THEME_OBSERVABLE,
} from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import {
@@ -488,11 +488,8 @@ function getBgService<T>(service: keyof MainBackground) {
deps: [StateServiceAbstraction],
},
{
provide: AbstractThemingService,
useFactory: (
stateService: StateServiceAbstraction,
platformUtilsService: PlatformUtilsService,
) => {
provide: SYSTEM_THEME_OBSERVABLE,
useFactory: (platformUtilsService: PlatformUtilsService) => {
// Safari doesn't properly handle the (prefers-color-scheme) media query in the popup window, it always returns light.
// In Safari, we have to use the background page instead, which comes with limitations like not dynamically changing the extension theme when the system theme is changed.
let windowContext = window;
@@ -501,9 +498,9 @@ function getBgService<T>(service: keyof MainBackground) {
windowContext = backgroundWindow;
}
return new ThemingService(stateService, windowContext, document);
return AngularThemingService.createSystemThemeFromWindow(windowContext);
},
deps: [StateServiceAbstraction, PlatformUtilsService],
deps: [PlatformUtilsService],
},
{
provide: ConfigService,

View File

@@ -1,7 +1,6 @@
import { Component, OnInit } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
@@ -16,6 +15,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { enableAccountSwitching } from "../../platform/flags";
@@ -57,7 +57,7 @@ export class OptionsComponent implements OnInit {
private domainSettingsService: DomainSettingsService,
private badgeSettingsService: BadgeSettingsServiceAbstraction,
i18nService: I18nService,
private themingService: AbstractThemingService,
private themeStateService: ThemeStateService,
private settingsService: SettingsService,
private vaultSettingsService: VaultSettingsService,
) {
@@ -125,7 +125,7 @@ export class OptionsComponent implements OnInit {
this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$);
this.theme = await this.stateService.getTheme();
this.theme = await firstValueFrom(this.themeStateService.selectedTheme$);
const defaultUriMatch = await firstValueFrom(
this.domainSettingsService.defaultUriMatchStrategy$,
@@ -186,7 +186,7 @@ export class OptionsComponent implements OnInit {
}
async saveTheme() {
await this.themingService.updateConfiguredTheme(this.theme);
await this.themeStateService.setSelectedTheme(this.theme);
}
async saveClearClipboard() {

View File

@@ -3,7 +3,6 @@ import { FormBuilder } from "@angular/forms";
import { BehaviorSubject, firstValueFrom, Observable, Subject } from "rxjs";
import { concatMap, debounceTime, filter, map, switchMap, takeUntil, tap } from "rxjs/operators";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
@@ -21,6 +20,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { ThemeType, KeySuffixOptions } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { DialogService } from "@bitwarden/components";
import { SetPinComponent } from "../../auth/components/set-pin.component";
@@ -116,7 +116,7 @@ export class SettingsComponent implements OnInit {
private messagingService: MessagingService,
private cryptoService: CryptoService,
private modalService: ModalService,
private themingService: AbstractThemingService,
private themeStateService: ThemeStateService,
private settingsService: SettingsService,
private dialogService: DialogService,
private userVerificationService: UserVerificationServiceAbstraction,
@@ -263,7 +263,7 @@ export class SettingsComponent implements OnInit {
await this.stateService.getEnableBrowserIntegrationFingerprint(),
enableDuckDuckGoBrowserIntegration:
await this.stateService.getEnableDuckDuckGoBrowserIntegration(),
theme: await this.stateService.getTheme(),
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
locale: await firstValueFrom(this.i18nService.locale$),
};
this.form.setValue(initialValues, { emitEvent: false });
@@ -557,7 +557,7 @@ export class SettingsComponent implements OnInit {
}
async saveTheme() {
await this.themingService.updateConfiguredTheme(this.form.value.theme);
await this.themeStateService.setSelectedTheme(this.form.value.theme);
}
async saveMinOnCopyToClipboard() {

View File

@@ -1,12 +0,0 @@
import { ThemingService } from "@bitwarden/angular/platform/services/theming/theming.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
export class DesktopThemingService extends ThemingService {
protected async getSystemTheme(): Promise<ThemeType> {
return await ipc.platform.getSystemTheme();
}
protected monitorSystemThemeChanges(): void {
ipc.platform.onSystemThemeUpdated((theme: ThemeType) => this.updateSystemTheme(theme));
}
}

View File

@@ -1,3 +1,4 @@
import { DOCUMENT } from "@angular/common";
import { Inject, Injectable } from "@angular/core";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
@@ -38,6 +39,7 @@ export class InitService {
private themingService: AbstractThemingService,
private encryptService: EncryptService,
private configService: ConfigService,
@Inject(DOCUMENT) private document: Document,
) {}
init() {
@@ -58,7 +60,7 @@ export class InitService {
setTimeout(() => this.notificationsService.init(), 3000);
const htmlEl = this.win.document.documentElement;
htmlEl.classList.add("os_" + this.platformUtilsService.getDeviceString());
await this.themingService.monitorThemeChanges();
this.themingService.applyThemeChangesTo(this.document);
let installAction = null;
const installedVersion = await this.stateService.getInstalledVersion();
const currentVersion = await this.platformUtilsService.getApplicationVersion();

View File

@@ -1,8 +1,5 @@
import { DOCUMENT } from "@angular/common";
import { APP_INITIALIZER, InjectionToken, NgModule } from "@angular/core";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
import {
SECURE_STORAGE,
STATE_FACTORY,
@@ -13,7 +10,7 @@ import {
OBSERVABLE_MEMORY_STORAGE,
OBSERVABLE_DISK_STORAGE,
WINDOW,
SafeInjectionToken,
SYSTEM_THEME_OBSERVABLE,
} from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
@@ -63,13 +60,13 @@ import { ElectronRendererSecureStorageService } from "../../platform/services/el
import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service";
import { ElectronStateService } from "../../platform/services/electron-state.service";
import { I18nRendererService } from "../../platform/services/i18n.renderer.service";
import { fromIpcSystemTheme } from "../../platform/utils/from-ipc-system-theme";
import { EncryptedMessageHandlerService } from "../../services/encrypted-message-handler.service";
import { NativeMessageHandlerService } from "../../services/native-message-handler.service";
import { NativeMessagingService } from "../../services/native-messaging.service";
import { SearchBarService } from "../layout/search/search-bar.service";
import { DesktopFileDownloadService } from "./desktop-file-download.service";
import { DesktopThemingService } from "./desktop-theming.service";
import { InitService } from "./init.service";
import { RendererCryptoFunctionService } from "./renderer-crypto-function.service";
@@ -151,11 +148,10 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
provide: FileDownloadService,
useClass: DesktopFileDownloadService,
},
safeProvider({
provide: AbstractThemingService,
useClass: DesktopThemingService,
deps: [StateServiceAbstraction, WINDOW, DOCUMENT as SafeInjectionToken<Document>],
}),
{
provide: SYSTEM_THEME_OBSERVABLE,
useFactory: () => fromIpcSystemTheme(),
},
{
provide: EncryptedMessageHandlerService,
deps: [

View File

@@ -246,8 +246,7 @@ export class WindowMain {
// Retrieve the background color
// Resolves background color missmatch when starting the application.
async getBackgroundColor(): Promise<string> {
const data: { theme?: string } = await this.storageService.get("global");
let theme = data?.theme;
let theme = await this.storageService.get("global_theming_selection");
if (theme == null || theme === "system") {
theme = nativeTheme.shouldUseDarkColors ? "dark" : "light";

View File

@@ -0,0 +1,15 @@
import { defer, fromEventPattern, merge } from "rxjs";
import { ThemeType } from "@bitwarden/common/platform/enums";
/**
* @returns An observable watching the system theme via IPC channels
*/
export const fromIpcSystemTheme = () => {
return merge(
defer(() => ipc.platform.getSystemTheme()),
fromEventPattern<ThemeType>((handler) =>
ipc.platform.onSystemThemeUpdated((theme) => handler(theme)),
),
);
};

View File

@@ -23,6 +23,7 @@ import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/comm
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
@@ -32,6 +33,10 @@ import { StorageServiceProvider } from "@bitwarden/common/platform/services/stor
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
/* eslint-enable import/no-restricted-paths -- Implementation for memory storage */
import {
DefaultThemeStateService,
ThemeStateService,
} from "@bitwarden/common/platform/theming/theme-state.service";
import { PolicyListService } from "../admin-console/core/policy-list.service";
import { HtmlStorageService } from "../core/html-storage.service";
@@ -133,6 +138,13 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service";
OBSERVABLE_DISK_LOCAL_STORAGE,
],
},
{
provide: ThemeStateService,
useFactory: (globalStateProvider: GlobalStateProvider) =>
// Web chooses to have Light as the default theme
new DefaultThemeStateService(globalStateProvider, ThemeType.Light),
deps: [GlobalStateProvider],
},
],
})
export class CoreModule {

View File

@@ -1,3 +1,4 @@
import { DOCUMENT } from "@angular/common";
import { Inject, Injectable } from "@angular/core";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
@@ -35,6 +36,7 @@ export class InitService {
private themingService: AbstractThemingService,
private encryptService: EncryptService,
private configService: ConfigService,
@Inject(DOCUMENT) private document: Document,
) {}
init() {
@@ -55,7 +57,7 @@ export class InitService {
this.twoFactorService.init();
const htmlEl = this.win.document.documentElement;
htmlEl.classList.add("locale_" + this.i18nService.translationLocale);
await this.themingService.monitorThemeChanges();
this.themingService.applyThemeChangesTo(this.document);
const containerService = new ContainerService(this.cryptoService, this.encryptService);
containerService.attachToGlobal(this.win);

View File

@@ -1,7 +1,5 @@
import { ThemeType } from "@bitwarden/common/platform/enums";
import { GlobalState as BaseGlobalState } from "@bitwarden/common/platform/models/domain/global-state";
export class GlobalState extends BaseGlobalState {
theme?: ThemeType = ThemeType.Light;
rememberEmail = true;
}

View File

@@ -2,7 +2,6 @@ import { Component, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { concatMap, filter, firstValueFrom, map, Observable, Subject, takeUntil, tap } from "rxjs";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@@ -14,6 +13,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { DialogService } from "@bitwarden/components";
@Component({
@@ -35,7 +35,6 @@ export class PreferencesComponent implements OnInit {
themeOptions: any[];
private startingLocale: string;
private startingTheme: ThemeType;
private destroy$ = new Subject<void>();
form = this.formBuilder.group({
@@ -54,7 +53,7 @@ export class PreferencesComponent implements OnInit {
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
private platformUtilsService: PlatformUtilsService,
private messagingService: MessagingService,
private themingService: AbstractThemingService,
private themeStateService: ThemeStateService,
private settingsService: SettingsService,
private dialogService: DialogService,
) {
@@ -141,11 +140,10 @@ export class PreferencesComponent implements OnInit {
this.vaultTimeoutSettingsService.vaultTimeoutAction$(),
),
enableFavicons: !(await this.settingsService.getDisableFavicon()),
theme: await this.stateService.getTheme(),
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
locale: (await firstValueFrom(this.i18nService.locale$)) ?? null,
};
this.startingLocale = initialFormValues.locale;
this.startingTheme = initialFormValues.theme;
this.form.setValue(initialFormValues, { emitEvent: false });
}
@@ -165,10 +163,7 @@ export class PreferencesComponent implements OnInit {
values.vaultTimeoutAction,
);
await this.settingsService.setDisableFavicon(!values.enableFavicons);
if (values.theme !== this.startingTheme) {
await this.themingService.updateConfiguredTheme(values.theme);
this.startingTheme = values.theme;
}
await this.themeStateService.setSelectedTheme(values.theme);
await this.i18nService.setLocale(values.locale);
if (values.locale !== this.startingLocale) {
window.location.reload();

View File

@@ -1,24 +0,0 @@
// Set theme on page load
// This is done outside the Angular app to avoid a flash of unthemed content before it loads
// The defaultTheme is also set in the html itself to make sure that some theming is always applied
(function () {
const defaultTheme = "light";
const htmlEl = document.documentElement;
let theme = defaultTheme;
const globalState = window.localStorage.getItem("global");
if (globalState != null) {
const globalStateJson = JSON.parse(globalState);
if (globalStateJson != null && globalStateJson.theme != null) {
if (globalStateJson.theme.indexOf("system") > -1) {
theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
} else if (globalStateJson.theme.indexOf("dark") > -1) {
theme = "dark";
}
}
if (!htmlEl.classList.contains("theme_" + theme)) {
htmlEl.classList.remove("theme_" + defaultTheme);
htmlEl.classList.add("theme_" + theme);
}
}
})();

37
apps/web/src/theme.ts Normal file
View File

@@ -0,0 +1,37 @@
// Set theme on page load
// This is done outside the Angular app to avoid a flash of unthemed content before it loads
const setTheme = () => {
const getLegacyTheme = (): string | null => {
// MANUAL-STATE-ACCESS: Calling global to get setting before migration
// has had a chance to run, can be remove in the future.
// Tracking Issue: https://bitwarden.atlassian.net/browse/PM-6676
const globalState = window.localStorage.getItem("global");
const parsedGlobalState = JSON.parse(globalState) as { theme?: string } | null;
return parsedGlobalState ? parsedGlobalState.theme : null;
};
const defaultTheme = "light";
const htmlEl = document.documentElement;
let theme = defaultTheme;
// First try the new state providers location, then the legacy location
const themeFromState =
window.localStorage.getItem("global_theming_selection") ?? getLegacyTheme();
if (themeFromState) {
if (themeFromState.indexOf("system") > -1) {
theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
} else if (themeFromState.indexOf("dark") > -1) {
theme = "dark";
}
}
if (!htmlEl.classList.contains("theme_" + theme)) {
// The defaultTheme is also set in the html itself to make sure that some theming is always applied
htmlEl.classList.remove("theme_" + defaultTheme);
htmlEl.classList.add("theme_" + theme);
}
};
setTheme();

View File

@@ -26,7 +26,12 @@
"strictTemplates": true,
"preserveWhitespaces": true
},
"files": ["src/polyfills.ts", "src/main.ts", "../../bitwarden_license/bit-web/src/main.ts"],
"files": [
"src/polyfills.ts",
"src/main.ts",
"../../bitwarden_license/bit-web/src/main.ts",
"src/theme.ts"
],
"include": [
"src/connectors/*.ts",
"src/**/*.stories.ts",

View File

@@ -323,7 +323,7 @@ const webpackConfig = {
"connectors/sso": "./src/connectors/sso.ts",
"connectors/captcha": "./src/connectors/captcha.ts",
"connectors/duo-redirect": "./src/connectors/duo-redirect.ts",
theme_head: "./src/theme.js",
theme_head: "./src/theme.ts",
},
optimization: {
splitChunks: {