1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-27792] Scaffold layout desktop migration (#17658)

Introduces foundational scaffolding for the Bitwarden Desktop application UI migration
This commit is contained in:
Isaac Ivins
2025-12-01 13:04:07 -05:00
committed by GitHub
parent be00be8fd8
commit d05356dbeb
13 changed files with 278 additions and 2 deletions

View File

@@ -14,6 +14,7 @@ import {
} from "@bitwarden/angular/auth/guards";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import {
DevicesIcon,
RegistrationUserAddIcon,
@@ -39,15 +40,19 @@ import {
TwoFactorAuthGuard,
NewDeviceVerificationComponent,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components";
import { LockComponent, ConfirmKeyConnectorDomainComponent } from "@bitwarden/key-management-ui";
import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard";
import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component";
import { VaultV2Component } from "../vault/app/vault/vault-v2.component";
import { VaultComponent } from "../vault/app/vault-v3/vault.component";
import { Fido2PlaceholderComponent } from "./components/fido2placeholder.component";
import { DesktopLayoutComponent } from "./layout/desktop-layout.component";
import { SendComponent } from "./tools/send/send.component";
import { SendV2Component } from "./tools/send-v2/send-v2.component";
/**
* Data properties acceptable for use in route objects in the desktop
@@ -99,7 +104,10 @@ const routes: Routes = [
{
path: "vault",
component: VaultV2Component,
canActivate: [authGuard],
canActivate: [
authGuard,
canAccessFeature(FeatureFlag.DesktopUiMigrationMilestone1, false, "new-vault", false),
],
},
{
path: "send",
@@ -325,6 +333,21 @@ const routes: Routes = [
},
],
},
{
path: "",
component: DesktopLayoutComponent,
canActivate: [authGuard],
children: [
{
path: "new-vault",
component: VaultComponent,
},
{
path: "new-sends",
component: SendV2Component,
},
],
},
];
@NgModule({

View File

@@ -0,0 +1,10 @@
<bit-layout>
<app-side-nav slot="side-nav">
<bit-nav-logo [openIcon]="logo" route="." [label]="'passwordManager' | i18n"></bit-nav-logo>
<bit-nav-item icon="bwi-vault" [text]="'vault' | i18n" route="new-vault"></bit-nav-item>
<bit-nav-item icon="bwi-send" [text]="'send' | i18n" route="new-sends"></bit-nav-item>
</app-side-nav>
<router-outlet></router-outlet>
</bit-layout>

View File

@@ -0,0 +1,61 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { RouterModule } from "@angular/router";
import { mock } from "jest-mock-extended";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { NavigationModule } from "@bitwarden/components";
import { DesktopLayoutComponent } from "./desktop-layout.component";
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: true,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
describe("DesktopLayoutComponent", () => {
let component: DesktopLayoutComponent;
let fixture: ComponentFixture<DesktopLayoutComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DesktopLayoutComponent, RouterModule.forRoot([]), NavigationModule],
providers: [
{
provide: I18nService,
useValue: mock<I18nService>(),
},
],
}).compileComponents();
fixture = TestBed.createComponent(DesktopLayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("creates component", () => {
expect(component).toBeTruthy();
});
it("renders bit-layout component", () => {
const compiled = fixture.nativeElement;
const layoutElement = compiled.querySelector("bit-layout");
expect(layoutElement).toBeTruthy();
});
it("supports content projection for side-nav", () => {
const compiled = fixture.nativeElement;
const ngContent = compiled.querySelectorAll("ng-content");
expect(ngContent).toBeTruthy();
});
});

View File

@@ -0,0 +1,18 @@
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { RouterModule } from "@angular/router";
import { PasswordManagerLogo } from "@bitwarden/assets/svg";
import { LayoutComponent, NavigationModule } from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";
import { DesktopSideNavComponent } from "./desktop-side-nav.component";
@Component({
selector: "app-layout",
imports: [RouterModule, I18nPipe, LayoutComponent, NavigationModule, DesktopSideNavComponent],
templateUrl: "./desktop-layout.component.html",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DesktopLayoutComponent {
protected readonly logo = PasswordManagerLogo;
}

View File

@@ -0,0 +1,3 @@
<bit-side-nav [variant]="variant()">
<ng-content></ng-content>
</bit-side-nav>

View File

@@ -0,0 +1,74 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { mock } from "jest-mock-extended";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { NavigationModule } from "@bitwarden/components";
import { DesktopSideNavComponent } from "./desktop-side-nav.component";
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: true,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
describe("DesktopSideNavComponent", () => {
let component: DesktopSideNavComponent;
let fixture: ComponentFixture<DesktopSideNavComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DesktopSideNavComponent, NavigationModule],
providers: [
{
provide: I18nService,
useValue: mock<I18nService>(),
},
],
}).compileComponents();
fixture = TestBed.createComponent(DesktopSideNavComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("creates component", () => {
expect(component).toBeTruthy();
});
it("renders bit-side-nav component", () => {
const compiled = fixture.nativeElement;
const sideNavElement = compiled.querySelector("bit-side-nav");
expect(sideNavElement).toBeTruthy();
});
it("uses primary variant by default", () => {
expect(component.variant()).toBe("primary");
});
it("accepts variant input", () => {
fixture.componentRef.setInput("variant", "secondary");
fixture.detectChanges();
expect(component.variant()).toBe("secondary");
});
it("passes variant to bit-side-nav", () => {
fixture.componentRef.setInput("variant", "secondary");
fixture.detectChanges();
const compiled = fixture.nativeElement;
const sideNavElement = compiled.querySelector("bit-side-nav");
expect(sideNavElement.getAttribute("ng-reflect-variant")).toBe("secondary");
});
});

View File

@@ -0,0 +1,14 @@
import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component, input } from "@angular/core";
import { NavigationModule, SideNavVariant } from "@bitwarden/components";
@Component({
selector: "app-side-nav",
templateUrl: "desktop-side-nav.component.html",
imports: [CommonModule, NavigationModule],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DesktopSideNavComponent {
readonly variant = input<SideNavVariant>("primary");
}

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { SendV2Component } from "./send-v2.component";
describe("SendV2Component", () => {
let component: SendV2Component;
let fixture: ComponentFixture<SendV2Component>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SendV2Component],
}).compileComponents();
fixture = TestBed.createComponent(SendV2Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("creates component", () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,9 @@
import { Component, ChangeDetectionStrategy } from "@angular/core";
@Component({
selector: "app-send-v2",
imports: [],
template: "<p>Sends V2 Component</p>",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SendV2Component {}

View File

@@ -2228,6 +2228,10 @@
"contactInfo": {
"message": "Contact information"
},
"send": {
"message": "Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"allSends": {
"message": "All Sends",
"description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -2991,7 +2995,8 @@
"message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected."
},
"vault": {
"message": "Vault"
"message": "Vault",
"description": "'Vault' is a noun and refers to the Bitwarden Vault feature."
},
"loginWithMasterPassword": {
"message": "Log in with master password"

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { VaultComponent } from "./vault.component";
describe("VaultComponent", () => {
let component: VaultComponent;
let fixture: ComponentFixture<VaultComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [VaultComponent],
}).compileComponents();
fixture = TestBed.createComponent(VaultComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("creates component", () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,9 @@
import { ChangeDetectionStrategy, Component } from "@angular/core";
@Component({
selector: "app-vault-v3",
imports: [],
template: "<p>Vault V3 Component</p>",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class VaultComponent {}