[PM-9605] Extension AnonLayout wrapper component (#10338)
* setup extension component * setup extension service * update icon based on theme, adjust padding, service injection * override service * add stories * add current-account component * add ConfigService to storybook * use null checks for boolean data - otherwise false values are ignored * update translations * remove router implementation test * remove imports in main.background.ts * add showLogo to template * update icon usage * fix app-current-account storybook style issue
This commit is contained in:
parent
e4ed4a3858
commit
b2a995462f
|
@ -0,0 +1,23 @@
|
||||||
|
import { Observable, Subject } from "rxjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AnonLayoutWrapperDataService,
|
||||||
|
DefaultAnonLayoutWrapperDataService,
|
||||||
|
} from "@bitwarden/auth/angular";
|
||||||
|
|
||||||
|
import { ExtensionAnonLayoutWrapperData } from "./extension-anon-layout-wrapper.component";
|
||||||
|
|
||||||
|
export class ExtensionAnonLayoutWrapperDataService
|
||||||
|
extends DefaultAnonLayoutWrapperDataService
|
||||||
|
implements AnonLayoutWrapperDataService
|
||||||
|
{
|
||||||
|
protected override anonLayoutWrapperDataSubject = new Subject<ExtensionAnonLayoutWrapperData>();
|
||||||
|
|
||||||
|
override setAnonLayoutWrapperData(data: ExtensionAnonLayoutWrapperData): void {
|
||||||
|
this.anonLayoutWrapperDataSubject.next(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
override anonLayoutWrapperData$(): Observable<ExtensionAnonLayoutWrapperData> {
|
||||||
|
return this.anonLayoutWrapperDataSubject.asObservable();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<popup-page>
|
||||||
|
<popup-header
|
||||||
|
slot="header"
|
||||||
|
[background]="'alt'"
|
||||||
|
[showBackButton]="showBackButton"
|
||||||
|
[pageTitle]="''"
|
||||||
|
>
|
||||||
|
<bit-icon *ngIf="showLogo" [icon]="logo"></bit-icon>
|
||||||
|
|
||||||
|
<ng-container slot="end">
|
||||||
|
<app-pop-out></app-pop-out>
|
||||||
|
<app-current-account *ngIf="showAcctSwitcher"></app-current-account>
|
||||||
|
</ng-container>
|
||||||
|
</popup-header>
|
||||||
|
|
||||||
|
<auth-anon-layout
|
||||||
|
[title]="pageTitle"
|
||||||
|
[subtitle]="pageSubtitle"
|
||||||
|
[icon]="pageIcon"
|
||||||
|
[showReadonlyHostname]="showReadonlyHostname"
|
||||||
|
[hideLogo]="true"
|
||||||
|
[decreaseTopPadding]="true"
|
||||||
|
>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
<router-outlet slot="secondary" name="secondary"></router-outlet>
|
||||||
|
<router-outlet slot="environment-selector" name="environment-selector"></router-outlet>
|
||||||
|
</auth-anon-layout>
|
||||||
|
</popup-page>
|
|
@ -0,0 +1,190 @@
|
||||||
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
|
import { ActivatedRoute, Data, NavigationEnd, Router, RouterModule } from "@angular/router";
|
||||||
|
import { Subject, filter, firstValueFrom, switchMap, takeUntil, tap } from "rxjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AnonLayoutComponent,
|
||||||
|
AnonLayoutWrapperData,
|
||||||
|
AnonLayoutWrapperDataService,
|
||||||
|
} from "@bitwarden/auth/angular";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
|
import { Icon, IconModule } from "@bitwarden/components";
|
||||||
|
|
||||||
|
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";
|
||||||
|
import { CurrentAccountComponent } from "../account-switching/current-account.component";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ExtensionBitwardenLogoPrimary,
|
||||||
|
ExtensionBitwardenLogoWhite,
|
||||||
|
} from "./extension-bitwarden-logo.icon";
|
||||||
|
|
||||||
|
export interface ExtensionAnonLayoutWrapperData extends AnonLayoutWrapperData {
|
||||||
|
showAcctSwitcher?: boolean;
|
||||||
|
showBackButton?: boolean;
|
||||||
|
showLogo?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
templateUrl: "extension-anon-layout-wrapper.component.html",
|
||||||
|
imports: [
|
||||||
|
AnonLayoutComponent,
|
||||||
|
CommonModule,
|
||||||
|
CurrentAccountComponent,
|
||||||
|
IconModule,
|
||||||
|
PopOutComponent,
|
||||||
|
PopupPageComponent,
|
||||||
|
PopupHeaderComponent,
|
||||||
|
RouterModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
protected showAcctSwitcher: boolean;
|
||||||
|
protected showBackButton: boolean;
|
||||||
|
protected showLogo: boolean = true;
|
||||||
|
|
||||||
|
protected pageTitle: string;
|
||||||
|
protected pageSubtitle: string;
|
||||||
|
protected pageIcon: Icon;
|
||||||
|
protected showReadonlyHostname: boolean;
|
||||||
|
protected maxWidth: "md" | "3xl";
|
||||||
|
|
||||||
|
protected theme: string;
|
||||||
|
protected logo: Icon;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private extensionAnonLayoutWrapperDataService: AnonLayoutWrapperDataService,
|
||||||
|
private themeStateService: ThemeStateService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
// Set the initial page data on load
|
||||||
|
this.setAnonLayoutWrapperDataFromRouteData(this.route.snapshot.firstChild?.data);
|
||||||
|
|
||||||
|
// Listen for page changes and update the page data appropriately
|
||||||
|
this.listenForPageDataChanges();
|
||||||
|
this.listenForServiceDataChanges();
|
||||||
|
|
||||||
|
this.theme = await firstValueFrom(this.themeStateService.selectedTheme$);
|
||||||
|
|
||||||
|
if (this.theme === "dark") {
|
||||||
|
this.logo = ExtensionBitwardenLogoWhite;
|
||||||
|
} else {
|
||||||
|
this.logo = ExtensionBitwardenLogoPrimary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private listenForPageDataChanges() {
|
||||||
|
this.router.events
|
||||||
|
.pipe(
|
||||||
|
filter((event) => event instanceof NavigationEnd),
|
||||||
|
// reset page data on page changes
|
||||||
|
tap(() => this.resetPageData()),
|
||||||
|
switchMap(() => this.route.firstChild?.data || null),
|
||||||
|
takeUntil(this.destroy$),
|
||||||
|
)
|
||||||
|
.subscribe((firstChildRouteData: Data | null) => {
|
||||||
|
this.setAnonLayoutWrapperDataFromRouteData(firstChildRouteData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setAnonLayoutWrapperDataFromRouteData(firstChildRouteData: Data | null) {
|
||||||
|
if (!firstChildRouteData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstChildRouteData["pageTitle"] !== undefined) {
|
||||||
|
this.pageTitle = this.i18nService.t(firstChildRouteData["pageTitle"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstChildRouteData["pageSubtitle"] !== undefined) {
|
||||||
|
this.pageSubtitle = this.i18nService.t(firstChildRouteData["pageSubtitle"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstChildRouteData["pageIcon"] !== undefined) {
|
||||||
|
this.pageIcon = firstChildRouteData["pageIcon"];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showReadonlyHostname = Boolean(firstChildRouteData["showReadonlyHostname"]);
|
||||||
|
this.maxWidth = firstChildRouteData["maxWidth"];
|
||||||
|
|
||||||
|
if (firstChildRouteData["showAcctSwitcher"] !== undefined) {
|
||||||
|
this.showAcctSwitcher = Boolean(firstChildRouteData["showAcctSwitcher"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstChildRouteData["showBackButton"] !== undefined) {
|
||||||
|
this.showBackButton = Boolean(firstChildRouteData["showBackButton"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstChildRouteData["showLogo"] !== undefined) {
|
||||||
|
this.showLogo = Boolean(firstChildRouteData["showLogo"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private listenForServiceDataChanges() {
|
||||||
|
this.extensionAnonLayoutWrapperDataService
|
||||||
|
.anonLayoutWrapperData$()
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe((data: ExtensionAnonLayoutWrapperData) => {
|
||||||
|
this.setAnonLayoutWrapperData(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setAnonLayoutWrapperData(data: ExtensionAnonLayoutWrapperData) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.pageTitle) {
|
||||||
|
this.pageTitle = this.i18nService.t(data.pageTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.pageSubtitle) {
|
||||||
|
this.pageSubtitle = this.i18nService.t(data.pageSubtitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.pageIcon) {
|
||||||
|
this.pageIcon = data.pageIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.showReadonlyHostname != null) {
|
||||||
|
this.showReadonlyHostname = data.showReadonlyHostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.showAcctSwitcher != null) {
|
||||||
|
this.showAcctSwitcher = data.showAcctSwitcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.showBackButton != null) {
|
||||||
|
this.showBackButton = data.showBackButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.showLogo != null) {
|
||||||
|
this.showLogo = data.showLogo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetPageData() {
|
||||||
|
this.pageTitle = null;
|
||||||
|
this.pageSubtitle = null;
|
||||||
|
this.pageIcon = null;
|
||||||
|
this.showReadonlyHostname = null;
|
||||||
|
this.showAcctSwitcher = null;
|
||||||
|
this.showBackButton = null;
|
||||||
|
this.showLogo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,294 @@
|
||||||
|
import { importProvidersFrom, Component } from "@angular/core";
|
||||||
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
|
import {
|
||||||
|
Meta,
|
||||||
|
StoryObj,
|
||||||
|
applicationConfig,
|
||||||
|
componentWrapperDecorator,
|
||||||
|
moduleMetadata,
|
||||||
|
} from "@storybook/angular";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
|
||||||
|
import { AnonLayoutWrapperDataService, LockIcon } from "@bitwarden/auth/angular";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
|
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
|
||||||
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { ClientType } from "@bitwarden/common/enums";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
|
import {
|
||||||
|
EnvironmentService,
|
||||||
|
Environment,
|
||||||
|
} from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.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 { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
import { ButtonModule, I18nMockService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { RegistrationCheckEmailIcon } from "../../../../../../libs/auth/src/angular/icons/registration-check-email.icon";
|
||||||
|
|
||||||
|
import { ExtensionAnonLayoutWrapperDataService } from "./extension-anon-layout-wrapper-data.service";
|
||||||
|
import {
|
||||||
|
ExtensionAnonLayoutWrapperComponent,
|
||||||
|
ExtensionAnonLayoutWrapperData,
|
||||||
|
} from "./extension-anon-layout-wrapper.component";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Auth/Extension Anon Layout Wrapper",
|
||||||
|
component: ExtensionAnonLayoutWrapperComponent,
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
const decorators = (options: {
|
||||||
|
components: any[];
|
||||||
|
routes: Routes;
|
||||||
|
applicationVersion?: string;
|
||||||
|
clientType?: ClientType;
|
||||||
|
hostName?: string;
|
||||||
|
themeType?: ThemeType;
|
||||||
|
}) => {
|
||||||
|
return [
|
||||||
|
componentWrapperDecorator(
|
||||||
|
/**
|
||||||
|
* Applying a CSS transform makes a `position: fixed` element act like it is `position: relative`
|
||||||
|
* https://github.com/storybookjs/storybook/issues/8011#issue-490251969
|
||||||
|
*/
|
||||||
|
(story) => {
|
||||||
|
return /* HTML */ `<div class="tw-scale-100 ">${story}</div>`;
|
||||||
|
},
|
||||||
|
({ globals }) => {
|
||||||
|
/**
|
||||||
|
* avoid a bug with the way that we render the same component twice in the same iframe and how
|
||||||
|
* that interacts with the router-outlet
|
||||||
|
*/
|
||||||
|
const themeOverride = globals["theme"] === "both" ? "light" : globals["theme"];
|
||||||
|
return { theme: themeOverride };
|
||||||
|
},
|
||||||
|
),
|
||||||
|
moduleMetadata({
|
||||||
|
declarations: options.components,
|
||||||
|
imports: [RouterModule, ButtonModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: AnonLayoutWrapperDataService,
|
||||||
|
useClass: ExtensionAnonLayoutWrapperDataService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AccountService,
|
||||||
|
useValue: {
|
||||||
|
activeAccount$: of({
|
||||||
|
id: "test-user-id" as UserId,
|
||||||
|
name: "Test User 1",
|
||||||
|
email: "test@email.com",
|
||||||
|
emailVerified: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AuthService,
|
||||||
|
useValue: {
|
||||||
|
activeAccountStatus$: of(AuthenticationStatus.Unlocked),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AvatarService,
|
||||||
|
useValue: {
|
||||||
|
avatarColor$: of("#ab134a"),
|
||||||
|
} as Partial<AvatarService>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ConfigService,
|
||||||
|
useValue: {
|
||||||
|
getFeatureFlag: () => true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: EnvironmentService,
|
||||||
|
useValue: {
|
||||||
|
environment$: of({
|
||||||
|
getHostname: () => options.hostName || "storybook.bitwarden.com",
|
||||||
|
} as Partial<Environment>),
|
||||||
|
} as Partial<EnvironmentService>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PlatformUtilsService,
|
||||||
|
useValue: {
|
||||||
|
getApplicationVersion: () =>
|
||||||
|
Promise.resolve(options.applicationVersion || "FAKE_APP_VERSION"),
|
||||||
|
getClientType: () => options.clientType || ClientType.Web,
|
||||||
|
} as Partial<PlatformUtilsService>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ThemeStateService,
|
||||||
|
useValue: {
|
||||||
|
selectedTheme$: of(options.themeType || ThemeType.Light),
|
||||||
|
} as Partial<ThemeStateService>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () => {
|
||||||
|
return new I18nMockService({
|
||||||
|
setAStrongPassword: "Set a strong password",
|
||||||
|
finishCreatingYourAccountBySettingAPassword:
|
||||||
|
"Finish creating your account by setting a password",
|
||||||
|
enterpriseSingleSignOn: "Enterprise single sign-on",
|
||||||
|
checkYourEmail: "Check your email",
|
||||||
|
loading: "Loading",
|
||||||
|
popOutNewWindow: "Pop out to a new window",
|
||||||
|
switchAccounts: "Switch accounts",
|
||||||
|
back: "Back",
|
||||||
|
activeAccount: "Active account",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
applicationConfig({
|
||||||
|
providers: [importProvidersFrom(RouterModule.forRoot(options.routes))],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Story = StoryObj<ExtensionAnonLayoutWrapperComponent>;
|
||||||
|
|
||||||
|
// Default Example
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "bit-default-primary-outlet-example-component",
|
||||||
|
template: "<p>Primary Outlet Example: <br> your primary component goes here</p>",
|
||||||
|
})
|
||||||
|
class DefaultPrimaryOutletExampleComponent {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "bit-default-secondary-outlet-example-component",
|
||||||
|
template: "<p>Secondary Outlet Example: <br> your secondary component goes here</p>",
|
||||||
|
})
|
||||||
|
class DefaultSecondaryOutletExampleComponent {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "bit-default-env-selector-outlet-example-component",
|
||||||
|
template: "<p>Env Selector Outlet Example: <br> your env selector component goes here</p>",
|
||||||
|
})
|
||||||
|
class DefaultEnvSelectorOutletExampleComponent {}
|
||||||
|
|
||||||
|
export const DefaultContentExample: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: "<router-outlet></router-outlet>",
|
||||||
|
}),
|
||||||
|
decorators: decorators({
|
||||||
|
components: [
|
||||||
|
DefaultPrimaryOutletExampleComponent,
|
||||||
|
DefaultSecondaryOutletExampleComponent,
|
||||||
|
DefaultEnvSelectorOutletExampleComponent,
|
||||||
|
],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: "**",
|
||||||
|
redirectTo: "default-example",
|
||||||
|
pathMatch: "full",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: ExtensionAnonLayoutWrapperComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "default-example",
|
||||||
|
data: {},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: DefaultPrimaryOutletExampleComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: DefaultSecondaryOutletExampleComponent,
|
||||||
|
outlet: "secondary",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: DefaultEnvSelectorOutletExampleComponent,
|
||||||
|
outlet: "environment-selector",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dynamic Content Example
|
||||||
|
const initialData: ExtensionAnonLayoutWrapperData = {
|
||||||
|
pageTitle: "setAStrongPassword",
|
||||||
|
pageSubtitle: "finishCreatingYourAccountBySettingAPassword",
|
||||||
|
pageIcon: LockIcon,
|
||||||
|
showAcctSwitcher: true,
|
||||||
|
showBackButton: true,
|
||||||
|
showLogo: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const changedData: ExtensionAnonLayoutWrapperData = {
|
||||||
|
pageTitle: "enterpriseSingleSignOn",
|
||||||
|
pageSubtitle: "checkYourEmail",
|
||||||
|
pageIcon: RegistrationCheckEmailIcon,
|
||||||
|
showAcctSwitcher: false,
|
||||||
|
showBackButton: false,
|
||||||
|
showLogo: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "bit-dynamic-content-example-component",
|
||||||
|
template: `
|
||||||
|
<button type="button" bitButton buttonType="primary" (click)="toggleData()">Toggle Data</button>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class DynamicContentExampleComponent {
|
||||||
|
initialData = true;
|
||||||
|
|
||||||
|
constructor(private extensionAnonLayoutWrapperDataService: AnonLayoutWrapperDataService) {}
|
||||||
|
|
||||||
|
toggleData() {
|
||||||
|
if (this.initialData) {
|
||||||
|
this.extensionAnonLayoutWrapperDataService.setAnonLayoutWrapperData(changedData);
|
||||||
|
} else {
|
||||||
|
this.extensionAnonLayoutWrapperDataService.setAnonLayoutWrapperData(initialData);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialData = !this.initialData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DynamicContentExample: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: "<router-outlet></router-outlet>",
|
||||||
|
}),
|
||||||
|
decorators: decorators({
|
||||||
|
components: [DynamicContentExampleComponent],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: "**",
|
||||||
|
redirectTo: "dynamic-content-example",
|
||||||
|
pathMatch: "full",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: ExtensionAnonLayoutWrapperComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "dynamic-content-example",
|
||||||
|
data: initialData,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: DynamicContentExampleComponent,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
};
|
File diff suppressed because one or more lines are too long
|
@ -15,6 +15,7 @@ import {
|
||||||
CLIENT_TYPE,
|
CLIENT_TYPE,
|
||||||
} from "@bitwarden/angular/services/injection-tokens";
|
} from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||||
|
import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular";
|
||||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||||
|
@ -82,6 +83,7 @@ import { TotpService } from "@bitwarden/common/vault/services/totp.service";
|
||||||
import { DialogService, ToastService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
|
import { ExtensionAnonLayoutWrapperDataService } from "../../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service";
|
||||||
import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service";
|
import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service";
|
||||||
import AutofillService from "../../autofill/services/autofill.service";
|
import AutofillService from "../../autofill/services/autofill.service";
|
||||||
import MainBackground from "../../background/main.background";
|
import MainBackground from "../../background/main.background";
|
||||||
|
@ -521,6 +523,11 @@ const safeProviders: SafeProvider[] = [
|
||||||
useFactory: getBgService<ForegroundTaskSchedulerService>("taskSchedulerService"),
|
useFactory: getBgService<ForegroundTaskSchedulerService>("taskSchedulerService"),
|
||||||
deps: [],
|
deps: [],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: AnonLayoutWrapperDataService,
|
||||||
|
useClass: ExtensionAnonLayoutWrapperDataService,
|
||||||
|
deps: [],
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -106,7 +106,7 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||||
this.pageIcon = data.pageIcon;
|
this.pageIcon = data.pageIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.showReadonlyHostname) {
|
if (data.showReadonlyHostname != null) {
|
||||||
this.showReadonlyHostname = data.showReadonlyHostname;
|
this.showReadonlyHostname = data.showReadonlyHostname;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<main
|
<main
|
||||||
class="tw-flex tw-min-h-screen tw-w-full tw-mx-auto tw-flex-col tw-gap-7 tw-bg-background-alt tw-px-8 tw-pb-4 tw-pt-8 tw-text-main"
|
class="tw-flex tw-min-h-screen tw-w-full tw-mx-auto tw-flex-col tw-gap-7 tw-bg-background-alt tw-px-8 tw-pb-4 tw-text-main"
|
||||||
|
[ngClass]="{ 'tw-pt-0': decreaseTopPadding, 'tw-pt-8': !decreaseTopPadding }"
|
||||||
>
|
>
|
||||||
<bit-icon *ngIf="!hideLogo" [icon]="logo" class="tw-max-w-36"></bit-icon>
|
<bit-icon *ngIf="!hideLogo" [icon]="logo" class="tw-max-w-36"></bit-icon>
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
|
||||||
@Input() showReadonlyHostname: boolean;
|
@Input() showReadonlyHostname: boolean;
|
||||||
@Input() hideLogo: boolean = false;
|
@Input() hideLogo: boolean = false;
|
||||||
@Input() hideFooter: boolean = false;
|
@Input() hideFooter: boolean = false;
|
||||||
|
@Input() decreaseTopPadding: boolean = false;
|
||||||
/**
|
/**
|
||||||
* Max width of the layout content
|
* Max width of the layout content
|
||||||
*
|
*
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { AnonLayoutWrapperDataService } from "./anon-layout-wrapper-data.service
|
||||||
import { AnonLayoutWrapperData } from "./anon-layout-wrapper.component";
|
import { AnonLayoutWrapperData } from "./anon-layout-wrapper.component";
|
||||||
|
|
||||||
export class DefaultAnonLayoutWrapperDataService implements AnonLayoutWrapperDataService {
|
export class DefaultAnonLayoutWrapperDataService implements AnonLayoutWrapperDataService {
|
||||||
private anonLayoutWrapperDataSubject = new Subject<AnonLayoutWrapperData>();
|
protected anonLayoutWrapperDataSubject = new Subject<AnonLayoutWrapperData>();
|
||||||
|
|
||||||
setAnonLayoutWrapperData(data: AnonLayoutWrapperData): void {
|
setAnonLayoutWrapperData(data: AnonLayoutWrapperData): void {
|
||||||
this.anonLayoutWrapperDataSubject.next(data);
|
this.anonLayoutWrapperDataSubject.next(data);
|
||||||
|
|
|
@ -52,3 +52,10 @@ $card-icons-base: "../../src/billing/images/cards/";
|
||||||
.sbdocs-preview pre.prismjs {
|
.sbdocs-preview pre.prismjs {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sb-show-main app-current-account button {
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
padding-inline: 0px;
|
||||||
|
padding-block: 0px;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue