[PM-7713] Refresh Appearance Settings (#10458)
* add v2 of appearance component * swap in new appearance component based on refresh flag * update default theme verbiage
This commit is contained in:
parent
4556e59ee8
commit
d212bb1fd0
|
@ -4164,5 +4164,11 @@
|
|||
},
|
||||
"accountActions": {
|
||||
"message": "Account actions"
|
||||
},
|
||||
"showNumberOfAutofillSuggestions": {
|
||||
"message": "Show number of login autofill suggestions on extension icon"
|
||||
},
|
||||
"systemDefault": {
|
||||
"message": "System default"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,7 @@ import { AddEditV2Component } from "../vault/popup/components/vault-v2/add-edit/
|
|||
import { AssignCollections } from "../vault/popup/components/vault-v2/assign-collections/assign-collections.component";
|
||||
import { AttachmentsV2Component } from "../vault/popup/components/vault-v2/attachments/attachments-v2.component";
|
||||
import { ViewV2Component } from "../vault/popup/components/vault-v2/view-v2/view-v2.component";
|
||||
import { AppearanceV2Component } from "../vault/popup/settings/appearance-v2.component";
|
||||
import { AppearanceComponent } from "../vault/popup/settings/appearance.component";
|
||||
import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component";
|
||||
import { FoldersV2Component } from "../vault/popup/settings/folders-v2.component";
|
||||
|
@ -339,12 +340,11 @@ const routes: Routes = [
|
|||
canActivate: [authGuard],
|
||||
data: { state: "premium" },
|
||||
},
|
||||
{
|
||||
...extensionRefreshSwap(AppearanceComponent, AppearanceV2Component, {
|
||||
path: "appearance",
|
||||
component: AppearanceComponent,
|
||||
canActivate: [authGuard],
|
||||
data: { state: "appearance" },
|
||||
},
|
||||
}),
|
||||
...extensionRefreshSwap(AddEditComponent, AddEditV2Component, {
|
||||
path: "clone-cipher",
|
||||
canActivate: [authGuard],
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<popup-page>
|
||||
<popup-header slot="header" [pageTitle]="'appearance' | i18n" showBackButton>
|
||||
<ng-container slot="end">
|
||||
<app-pop-out></app-pop-out>
|
||||
</ng-container>
|
||||
</popup-header>
|
||||
|
||||
<form [formGroup]="appearanceForm">
|
||||
<bit-card>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "theme" | i18n }}</bit-label>
|
||||
<bit-select formControlName="theme">
|
||||
<bit-option
|
||||
*ngFor="let o of themeOptions"
|
||||
[value]="o.value"
|
||||
[label]="o.name"
|
||||
></bit-option>
|
||||
</bit-select>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-control>
|
||||
<input bitCheckbox formControlName="enableBadgeCounter" type="checkbox" />
|
||||
<bit-label>{{ "showNumberOfAutofillSuggestions" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
|
||||
<bit-form-control disableMargin>
|
||||
<input bitCheckbox formControlName="enableFavicon" type="checkbox" />
|
||||
<bit-label>{{ "enableFavicon" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
</bit-card>
|
||||
</form>
|
||||
</popup-page>
|
|
@ -0,0 +1,110 @@
|
|||
import { Component, Input } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
|
||||
|
||||
import { AppearanceV2Component } from "./appearance-v2.component";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "popup-header",
|
||||
template: `<ng-content></ng-content>`,
|
||||
})
|
||||
class MockPopupHeaderComponent {
|
||||
@Input() pageTitle: string;
|
||||
@Input() backAction: () => void;
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "popup-page",
|
||||
template: `<ng-content></ng-content>`,
|
||||
})
|
||||
class MockPopupPageComponent {}
|
||||
|
||||
describe("AppearanceV2Component", () => {
|
||||
let component: AppearanceV2Component;
|
||||
let fixture: ComponentFixture<AppearanceV2Component>;
|
||||
|
||||
const showFavicons$ = new BehaviorSubject<boolean>(true);
|
||||
const enableBadgeCounter$ = new BehaviorSubject<boolean>(true);
|
||||
const selectedTheme$ = new BehaviorSubject<ThemeType>(ThemeType.Nord);
|
||||
const setSelectedTheme = jest.fn().mockResolvedValue(undefined);
|
||||
const setShowFavicons = jest.fn().mockResolvedValue(undefined);
|
||||
const setEnableBadgeCounter = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
beforeEach(async () => {
|
||||
setSelectedTheme.mockClear();
|
||||
setShowFavicons.mockClear();
|
||||
setEnableBadgeCounter.mockClear();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppearanceV2Component],
|
||||
providers: [
|
||||
{ provide: ConfigService, useValue: mock<ConfigService>() },
|
||||
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
|
||||
{ provide: MessagingService, useValue: mock<MessagingService>() },
|
||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||
{ provide: DomainSettingsService, useValue: { showFavicons$, setShowFavicons } },
|
||||
{
|
||||
provide: BadgeSettingsServiceAbstraction,
|
||||
useValue: { enableBadgeCounter$, setEnableBadgeCounter },
|
||||
},
|
||||
{ provide: ThemeStateService, useValue: { selectedTheme$, setSelectedTheme } },
|
||||
],
|
||||
})
|
||||
.overrideComponent(AppearanceV2Component, {
|
||||
remove: {
|
||||
imports: [PopupHeaderComponent, PopupPageComponent],
|
||||
},
|
||||
add: {
|
||||
imports: [MockPopupHeaderComponent, MockPopupPageComponent],
|
||||
},
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AppearanceV2Component);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("populates the form with the user's current settings", () => {
|
||||
expect(component.appearanceForm.value).toEqual({
|
||||
enableFavicon: true,
|
||||
enableBadgeCounter: true,
|
||||
theme: ThemeType.Nord,
|
||||
});
|
||||
});
|
||||
|
||||
describe("form changes", () => {
|
||||
it("updates the users theme", () => {
|
||||
component.appearanceForm.controls.theme.setValue(ThemeType.Light);
|
||||
|
||||
expect(setSelectedTheme).toHaveBeenCalledWith(ThemeType.Light);
|
||||
});
|
||||
|
||||
it("updates the users favicon setting", () => {
|
||||
component.appearanceForm.controls.enableFavicon.setValue(false);
|
||||
|
||||
expect(setShowFavicons).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("updates the users badge counter setting", () => {
|
||||
component.appearanceForm.controls.enableBadgeCounter.setValue(false);
|
||||
|
||||
expect(setEnableBadgeCounter).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,110 @@
|
|||
import { CommonModule } from "@angular/common";
|
||||
import { Component, DestroyRef, OnInit } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
import { CheckboxModule } from "@bitwarden/components";
|
||||
|
||||
import { CardComponent } from "../../../../../../libs/components/src/card/card.component";
|
||||
import { FormFieldModule } from "../../../../../../libs/components/src/form-field/form-field.module";
|
||||
import { SelectModule } from "../../../../../../libs/components/src/select/select.module";
|
||||
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl: "./appearance-v2.component.html",
|
||||
imports: [
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
PopupPageComponent,
|
||||
PopupHeaderComponent,
|
||||
PopOutComponent,
|
||||
CardComponent,
|
||||
FormFieldModule,
|
||||
SelectModule,
|
||||
ReactiveFormsModule,
|
||||
CheckboxModule,
|
||||
],
|
||||
})
|
||||
export class AppearanceV2Component implements OnInit {
|
||||
appearanceForm = this.formBuilder.group({
|
||||
enableFavicon: false,
|
||||
enableBadgeCounter: true,
|
||||
theme: ThemeType.System,
|
||||
});
|
||||
|
||||
/** Available theme options */
|
||||
themeOptions: { name: string; value: ThemeType }[];
|
||||
|
||||
constructor(
|
||||
private messagingService: MessagingService,
|
||||
private domainSettingsService: DomainSettingsService,
|
||||
private badgeSettingsService: BadgeSettingsServiceAbstraction,
|
||||
private themeStateService: ThemeStateService,
|
||||
private formBuilder: FormBuilder,
|
||||
private destroyRef: DestroyRef,
|
||||
i18nService: I18nService,
|
||||
) {
|
||||
this.themeOptions = [
|
||||
{ name: i18nService.t("systemDefault"), value: ThemeType.System },
|
||||
{ name: i18nService.t("light"), value: ThemeType.Light },
|
||||
{ name: i18nService.t("dark"), value: ThemeType.Dark },
|
||||
{ name: "Nord", value: ThemeType.Nord },
|
||||
{ name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark },
|
||||
];
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const enableFavicon = await firstValueFrom(this.domainSettingsService.showFavicons$);
|
||||
const enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$);
|
||||
const theme = await firstValueFrom(this.themeStateService.selectedTheme$);
|
||||
|
||||
// Set initial values for the form
|
||||
this.appearanceForm.setValue({
|
||||
enableFavicon,
|
||||
enableBadgeCounter,
|
||||
theme,
|
||||
});
|
||||
|
||||
this.appearanceForm.controls.theme.valueChanges
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((newTheme) => {
|
||||
void this.saveTheme(newTheme);
|
||||
});
|
||||
|
||||
this.appearanceForm.controls.enableFavicon.valueChanges
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((enableFavicon) => {
|
||||
void this.updateFavicon(enableFavicon);
|
||||
});
|
||||
|
||||
this.appearanceForm.controls.enableBadgeCounter.valueChanges
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((enableBadgeCounter) => {
|
||||
void this.updateBadgeCounter(enableBadgeCounter);
|
||||
});
|
||||
}
|
||||
|
||||
async updateFavicon(enableFavicon: boolean) {
|
||||
await this.domainSettingsService.setShowFavicons(enableFavicon);
|
||||
}
|
||||
|
||||
async updateBadgeCounter(enableBadgeCounter: boolean) {
|
||||
await this.badgeSettingsService.setEnableBadgeCounter(enableBadgeCounter);
|
||||
this.messagingService.send("bgUpdateContextMenu");
|
||||
}
|
||||
|
||||
async saveTheme(newTheme: ThemeType) {
|
||||
await this.themeStateService.setSelectedTheme(newTheme);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue