mirror of
https://github.com/bitwarden/browser
synced 2026-02-22 20:34:04 +00:00
conditionally render app-header in portal/slot
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { TemplatePortal } from "@angular/cdk/portal";
|
||||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
@@ -9,6 +10,8 @@ import {
|
||||
Type,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
TemplateRef,
|
||||
AfterViewInit,
|
||||
} from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { Router } from "@angular/router";
|
||||
@@ -84,6 +87,7 @@ import { PremiumComponent } from "../billing/app/accounts/premium.component";
|
||||
import { MenuAccount, MenuUpdateRequest } from "../main/menu/menu.updater";
|
||||
|
||||
import { SettingsComponent } from "./accounts/settings.component";
|
||||
import { DesktopHeaderService } from "./layout/desktop-header.service";
|
||||
import { ExportDesktopComponent } from "./tools/export/export-desktop.component";
|
||||
import { CredentialGeneratorComponent } from "./tools/generator/credential-generator.component";
|
||||
import { ImportDesktopComponent } from "./tools/import/import-desktop.component";
|
||||
@@ -104,7 +108,12 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
||||
<ng-template #exportVault></ng-template>
|
||||
<ng-template #appGenerator></ng-template>
|
||||
<ng-template #loginApproval></ng-template>
|
||||
<app-header></app-header>
|
||||
<ng-template #headerPortal>
|
||||
<app-header></app-header>
|
||||
</ng-template>
|
||||
@if (!headerInPortal()) {
|
||||
<app-header></app-header>
|
||||
}
|
||||
|
||||
<div id="container">
|
||||
<div class="loading" *ngIf="loading">
|
||||
@@ -117,7 +126,7 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
||||
`,
|
||||
standalone: false,
|
||||
})
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
|
||||
@@ -140,9 +149,15 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@ViewChild("loginApproval", { read: ViewContainerRef, static: true })
|
||||
loginApprovalModalRef: ViewContainerRef;
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@ViewChild("headerPortal", { read: TemplateRef, static: true })
|
||||
headerPortalRef: TemplateRef<unknown>;
|
||||
|
||||
loading = false;
|
||||
|
||||
protected headerInPortal = this.desktopHeaderService.isAttached;
|
||||
|
||||
private lastActivity: Date = null;
|
||||
private modal: ModalRef = null;
|
||||
private idleTimer: number = null;
|
||||
@@ -197,6 +212,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private readonly tokenService: TokenService,
|
||||
private desktopAutotypeDefaultSettingPolicy: DesktopAutotypeDefaultSettingPolicy,
|
||||
private readonly lockService: LockService,
|
||||
private readonly desktopHeaderService: DesktopHeaderService,
|
||||
private readonly viewContainerRef: ViewContainerRef,
|
||||
) {
|
||||
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||
|
||||
@@ -204,6 +221,11 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
this.destroyRef.onDestroy(() => langSubscription.unsubscribe());
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
const headerPortal = new TemplatePortal(this.headerPortalRef, this.viewContainerRef);
|
||||
this.desktopHeaderService.setHeader(headerPortal);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.accountService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((account) => {
|
||||
this.activeUserId = account?.id;
|
||||
@@ -519,6 +541,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.desktopHeaderService.clearHeader();
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
|
||||
26
apps/desktop/src/app/layout/desktop-header.service.ts
Normal file
26
apps/desktop/src/app/layout/desktop-header.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Portal } from "@angular/cdk/portal";
|
||||
import { Injectable, signal } from "@angular/core";
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class DesktopHeaderService {
|
||||
private readonly _portal = signal<Portal<unknown> | undefined>(undefined);
|
||||
private readonly _isAttached = signal(false);
|
||||
|
||||
/** The portal to display in the desktop header slot */
|
||||
portal = this._portal.asReadonly();
|
||||
|
||||
/** Whether the header is currently attached to a portal outlet */
|
||||
isAttached = this._isAttached.asReadonly();
|
||||
|
||||
setHeader(portal: Portal<unknown>) {
|
||||
this._portal.set(portal);
|
||||
}
|
||||
|
||||
clearHeader() {
|
||||
this._portal.set(undefined);
|
||||
}
|
||||
|
||||
setAttached(attached: boolean) {
|
||||
this._isAttached.set(attached);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,11 @@
|
||||
<bit-layout>
|
||||
<ng-template
|
||||
[cdkPortalOutlet]="headerPortal()"
|
||||
(attached)="onPortalAttached()"
|
||||
(detached)="onPortalDetached()"
|
||||
slot="desktop-header"
|
||||
></ng-template>
|
||||
|
||||
<app-side-nav slot="side-nav">
|
||||
<bit-nav-logo [openIcon]="logo" route="." [label]="'passwordManager' | i18n"></bit-nav-logo>
|
||||
|
||||
|
||||
@@ -1,18 +1,41 @@
|
||||
import { ChangeDetectionStrategy, Component } from "@angular/core";
|
||||
import { PortalModule } from "@angular/cdk/portal";
|
||||
import { ChangeDetectionStrategy, Component, inject, OnDestroy } 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 { DesktopHeaderService } from "./desktop-header.service";
|
||||
import { DesktopSideNavComponent } from "./desktop-side-nav.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-layout",
|
||||
imports: [RouterModule, I18nPipe, LayoutComponent, NavigationModule, DesktopSideNavComponent],
|
||||
imports: [
|
||||
RouterModule,
|
||||
I18nPipe,
|
||||
LayoutComponent,
|
||||
NavigationModule,
|
||||
DesktopSideNavComponent,
|
||||
PortalModule,
|
||||
],
|
||||
templateUrl: "./desktop-layout.component.html",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DesktopLayoutComponent {
|
||||
export class DesktopLayoutComponent implements OnDestroy {
|
||||
protected readonly logo = PasswordManagerLogo;
|
||||
protected readonly desktopHeaderService = inject(DesktopHeaderService);
|
||||
protected readonly headerPortal = this.desktopHeaderService.portal;
|
||||
|
||||
protected onPortalAttached() {
|
||||
this.desktopHeaderService.setAttached(true);
|
||||
}
|
||||
|
||||
protected onPortalDetached() {
|
||||
this.desktopHeaderService.setAttached(false);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.desktopHeaderService.setAttached(false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user