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:
@@ -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({
|
||||
|
||||
10
apps/desktop/src/app/layout/desktop-layout.component.html
Normal file
10
apps/desktop/src/app/layout/desktop-layout.component.html
Normal 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>
|
||||
61
apps/desktop/src/app/layout/desktop-layout.component.spec.ts
Normal file
61
apps/desktop/src/app/layout/desktop-layout.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
18
apps/desktop/src/app/layout/desktop-layout.component.ts
Normal file
18
apps/desktop/src/app/layout/desktop-layout.component.ts
Normal 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;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<bit-side-nav [variant]="variant()">
|
||||
<ng-content></ng-content>
|
||||
</bit-side-nav>
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
14
apps/desktop/src/app/layout/desktop-side-nav.component.ts
Normal file
14
apps/desktop/src/app/layout/desktop-side-nav.component.ts
Normal 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");
|
||||
}
|
||||
22
apps/desktop/src/app/tools/send-v2/send-v2.component.spec.ts
Normal file
22
apps/desktop/src/app/tools/send-v2/send-v2.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
9
apps/desktop/src/app/tools/send-v2/send-v2.component.ts
Normal file
9
apps/desktop/src/app/tools/send-v2/send-v2.component.ts
Normal 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 {}
|
||||
@@ -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"
|
||||
|
||||
22
apps/desktop/src/vault/app/vault-v3/vault.component.spec.ts
Normal file
22
apps/desktop/src/vault/app/vault-v3/vault.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
9
apps/desktop/src/vault/app/vault-v3/vault.component.ts
Normal file
9
apps/desktop/src/vault/app/vault-v3/vault.component.ts
Normal 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 {}
|
||||
Reference in New Issue
Block a user