1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

Move SM header into web (#6976)

This commit is contained in:
Oscar Hinton
2023-12-11 18:22:37 +01:00
committed by GitHub
parent 29841605fb
commit a40643d9d6
15 changed files with 68 additions and 51 deletions

View File

@@ -1,6 +1,6 @@
<sm-header [title]="organizationName">
<app-header [title]="organizationName">
<sm-new-menu></sm-new-menu>
</sm-header>
</app-header>
<div *ngIf="!loading && view$ | async as view; else spinner">
<sm-onboarding [title]="'getStarted' | i18n" *ngIf="showOnboarding" (dismiss)="hideOnboarding()">

View File

@@ -1,4 +1,4 @@
<sm-header *ngIf="project$ | async as project" [title]="project.name" icon="bwi-collection">
<app-header *ngIf="project$ | async as project" [title]="project.name" icon="bwi-collection">
<bit-breadcrumbs slot="breadcrumbs">
<bit-breadcrumb [route]="['..']" icon="bwi-angle-left">{{ "projects" | i18n }}</bit-breadcrumb>
</bit-breadcrumbs>
@@ -20,5 +20,5 @@
>
{{ "editProject" | i18n }}
</button>
</sm-header>
</app-header>
<router-outlet></router-outlet>

View File

@@ -1,11 +1,11 @@
<sm-header>
<app-header>
<bit-search
[placeholder]="'searchProjects' | i18n"
[(ngModel)]="search"
class="tw-w-80"
></bit-search>
<sm-new-menu></sm-new-menu>
</sm-header>
</app-header>
<sm-projects-list
(newProjectEvent)="openNewProjectDialog()"
(editProjectEvent)="openEditProject($event)"

View File

@@ -1,11 +1,11 @@
<sm-header>
<app-header>
<bit-search
[placeholder]="'searchSecrets' | i18n"
[(ngModel)]="search"
class="tw-w-80"
></bit-search>
<sm-new-menu></sm-new-menu>
</sm-header>
</app-header>
<sm-secrets-list
(deleteSecretsEvent)="openDeleteSecret($event)"
(newSecretEvent)="openNewSecretDialog()"

View File

@@ -1,4 +1,4 @@
<sm-header
<app-header
*ngIf="serviceAccount$ | async as serviceAccount"
[title]="serviceAccount.name"
icon="bwi-wrench"
@@ -25,5 +25,5 @@
<i class="bwi bwi-plus" aria-hidden="true"></i>
{{ "newAccessToken" | i18n }}
</button>
</sm-header>
</app-header>
<router-outlet></router-outlet>

View File

@@ -1,11 +1,11 @@
<sm-header>
<app-header>
<bit-search
[placeholder]="'searchServiceAccounts' | i18n"
[(ngModel)]="search"
class="tw-w-80"
></bit-search>
<sm-new-menu></sm-new-menu>
</sm-header>
</app-header>
<sm-service-accounts-list
[serviceAccounts]="serviceAccounts$ | async"
(newServiceAccountEvent)="openNewServiceAccountDialog()"

View File

@@ -1,4 +1,4 @@
<sm-header></sm-header>
<app-header></app-header>
<form [formGroup]="formGroup" [bitSubmit]="submit">
<div class="tw-my-4 tw-max-w-xl">

View File

@@ -1,4 +1,4 @@
<sm-header></sm-header>
<app-header></app-header>
<form [formGroup]="formGroup" [bitSubmit]="submit" class="tw-max-w-xl">
<bit-form-field>

View File

@@ -1,106 +0,0 @@
<header
*ngIf="routeData$ | async as routeData"
class="-tw-m-6 tw-mb-3 tw-flex tw-flex-col tw-p-6"
[ngClass]="{
'tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 tw-bg-background-alt tw-pb-0':
tabsContainer.childElementCount !== 0
}"
>
<div class="tw-flex">
<div class="tw-flex tw-min-w-0 tw-flex-1 tw-flex-col tw-gap-2">
<ng-content select="[slot=breadcrumbs]"></ng-content>
<div #titleContainer [ngClass]="{ 'tw-hidden': titleContainer.childElementCount === 0 }">
<ng-content select="[slot=title]"></ng-content>
</div>
<h1
*ngIf="titleContainer.childElementCount === 0"
bitTypography="h1"
noMargin
class="tw-m-0 tw-mr-2 tw-truncate tw-leading-10"
[title]="title || (routeData.titleId | i18n)"
>
<i *ngIf="icon" class="bwi {{ icon }}" aria-hidden="true"></i>
{{ title || (routeData.titleId | i18n) }}
</h1>
</div>
<div class="tw-ml-auto tw-flex tw-flex-col tw-gap-4">
<div class="tw-flex tw-min-w-max tw-items-center tw-justify-end tw-gap-2">
<ng-content></ng-content>
<product-switcher></product-switcher>
<ng-container *ngIf="account$ | async as account">
<button
type="button"
[bitMenuTriggerFor]="accountMenu"
class="tw-border-0 tw-bg-transparent tw-p-0"
>
<dynamic-avatar [id]="account.userId" [text]="account | userName"></dynamic-avatar>
</button>
<bit-menu #accountMenu>
<div class="tw-flex tw-min-w-52 tw-max-w-72 tw-flex-col">
<div
class="tw-flex tw-items-center tw-px-4 tw-py-1 tw-leading-tight tw-text-info"
appStopProp
>
<dynamic-avatar [id]="account.userId" [text]="account | userName"></dynamic-avatar>
<div class="tw-ml-2 tw-block tw-overflow-hidden tw-whitespace-nowrap">
<span>{{ "loggedInAs" | i18n }}</span>
<small class="tw-block tw-overflow-hidden tw-whitespace-nowrap tw-text-muted">
{{ account | userName }}
</small>
</div>
</div>
<ng-container *ngIf="selfHosted">
<bit-menu-divider></bit-menu-divider>
<span class="tw-break-all tw-px-4 tw-py-1 tw-text-left tw-text-muted">
{{ hostname }}
</span>
</ng-container>
<bit-menu-divider></bit-menu-divider>
<a bitMenuItem routerLink="/settings/account">
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
{{ "accountSettings" | i18n }}
</a>
<a bitMenuItem href="https://bitwarden.com/help/" target="_blank" rel="noopener">
<i class="bwi bwi-fw bwi-question-circle" aria-hidden="true"></i>
{{ "getHelp" | i18n }}
</a>
<a bitMenuItem href="https://bitwarden.com/download/" target="_blank" rel="noopener">
<i class="bwi bwi-fw bwi-download" aria-hidden="true"></i>
{{ "getApps" | i18n }}
</a>
<bit-menu-divider></bit-menu-divider>
<button *ngIf="canLock$ | async" bitMenuItem type="button" (click)="lock()">
<i class="bwi bwi-fw bwi-lock" aria-hidden="true"></i>
{{ "lockNow" | i18n }}
</button>
<button bitMenuItem type="button" (click)="logout()">
<i class="bwi bwi-fw bwi-sign-out" aria-hidden="true"></i>
{{ "logOut" | i18n }}
</button>
</div>
</bit-menu>
</ng-container>
</div>
<div
class="tw-ml-auto"
#contentContainer
[ngClass]="{ 'tw-hidden': contentContainer.childElementCount === 0 }"
>
<ng-content select="[slot=secondary]"></ng-content>
</div>
</div>
</div>
<div
#tabsContainer
class="-tw-mx-4 -tw-mb-px"
[ngClass]="{ 'tw-hidden': tabsContainer.childElementCount === 0 }"
>
<ng-content select="[slot=tabs]"></ng-content>
</div>
</header>

View File

@@ -1,70 +0,0 @@
import { Component, Input } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { combineLatest, map, Observable } from "rxjs";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { AccountProfile } from "@bitwarden/common/platform/models/domain/account";
@Component({
selector: "sm-header",
templateUrl: "./header.component.html",
})
export class HeaderComponent {
/**
* Custom title that overrides the route data `titleId`
*/
@Input() title: string;
/**
* Icon to show before the title
*/
@Input() icon: string;
protected routeData$: Observable<{ titleId: string }>;
protected account$: Observable<AccountProfile>;
protected canLock$: Observable<boolean>;
protected selfHosted: boolean;
protected hostname = location.hostname;
constructor(
private route: ActivatedRoute,
private stateService: StateService,
private platformUtilsService: PlatformUtilsService,
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
private messagingService: MessagingService,
) {
this.routeData$ = this.route.data.pipe(
map((params) => {
return {
titleId: params.titleId,
};
}),
);
this.selfHosted = this.platformUtilsService.isSelfHost();
this.account$ = combineLatest([
this.stateService.activeAccount$,
this.stateService.accounts$,
]).pipe(
map(([activeAccount, accounts]) => {
return accounts[activeAccount]?.profile;
}),
);
this.canLock$ = this.vaultTimeoutSettingsService
.availableVaultTimeoutActions$()
.pipe(map((actions) => actions.includes(VaultTimeoutAction.Lock)));
}
protected lock() {
this.messagingService.send("lockVault");
}
protected logout() {
this.messagingService.send("logout");
}
}

View File

@@ -1,216 +0,0 @@
import { Component, Injectable, importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import {
Meta,
Story,
moduleMetadata,
applicationConfig,
componentWrapperDecorator,
} from "@storybook/angular";
import { BehaviorSubject, combineLatest, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import {
AvatarModule,
BreadcrumbsModule,
ButtonModule,
IconButtonModule,
IconModule,
MenuModule,
NavigationModule,
TabsModule,
TypographyModule,
InputModule,
} from "@bitwarden/components";
import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/app/core/tests";
import { HeaderComponent } from "./header.component";
@Injectable({
providedIn: "root",
})
class MockStateService {
activeAccount$ = new BehaviorSubject("1").asObservable();
accounts$ = new BehaviorSubject({ "1": { profile: { name: "Foo" } } }).asObservable();
}
class MockMessagingService implements MessagingService {
send(subscriber: string, arg?: any) {
alert(subscriber);
}
}
class MockVaultTimeoutService {
availableVaultTimeoutActions$() {
return new BehaviorSubject([VaultTimeoutAction.Lock]).asObservable();
}
}
class MockPlatformUtilsService {
isSelfHost() {
return false;
}
}
@Component({
selector: "product-switcher",
template: `<button bitIconButton="bwi-filter"></button>`,
})
class MockProductSwitcher {}
@Component({
selector: "dynamic-avatar",
template: `<bit-avatar [text]="name$ | async"></bit-avatar>`,
})
class MockDynamicAvatar {
protected name$ = combineLatest([
this.stateService.accounts$,
this.stateService.activeAccount$,
]).pipe(
map(
([accounts, activeAccount]) => accounts[activeAccount as keyof typeof accounts].profile.name,
),
);
constructor(private stateService: MockStateService) {}
}
export default {
title: "Web/Header",
component: HeaderComponent,
decorators: [
componentWrapperDecorator(
(story) => `<div class="tw-min-h-screen tw-flex-1 tw-p-6 tw-text-main">${story}</div>`,
),
moduleMetadata({
imports: [
JslibModule,
RouterModule,
AvatarModule,
BreadcrumbsModule,
ButtonModule,
IconButtonModule,
IconModule,
InputModule,
MenuModule,
TabsModule,
TypographyModule,
NavigationModule,
],
declarations: [HeaderComponent, MockProductSwitcher, MockDynamicAvatar],
providers: [
{ provide: StateService, useClass: MockStateService },
{ provide: PlatformUtilsService, useClass: MockPlatformUtilsService },
{ provide: VaultTimeoutSettingsService, useClass: MockVaultTimeoutService },
{
provide: MessagingService,
useFactory: () => {
return new MockMessagingService();
},
},
],
}),
applicationConfig({
providers: [
importProvidersFrom(RouterModule.forRoot([], { useHash: true })),
importProvidersFrom(PreloadedEnglishI18nModule),
],
}),
],
} as Meta;
export const KitchenSink: Story = (args) => ({
props: args,
template: `
<sm-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug">
<bit-breadcrumbs slot="breadcrumbs">
<bit-breadcrumb>Foo</bit-breadcrumb>
<bit-breadcrumb>Bar</bit-breadcrumb>
</bit-breadcrumbs>
<input
bitInput
placeholder="Ask Jeeves"
type="text"
/>
<button bitButton buttonType="primary">New</button>
<button bitButton slot="secondary">Click Me 🎉</button>
<bit-tab-nav-bar slot="tabs">
<bit-tab-link route="">Foo</bit-tab-link>
<bit-tab-link route="#bar">Bar</bit-tab-link>
</bit-tab-nav-bar>
</sm-header>
`,
});
export const Basic: Story = (args) => ({
props: args,
template: `
<sm-header title="Foobar" icon="bwi-bug"></sm-header>
`,
});
export const WithLongTitle: Story = (args) => ({
props: args,
template: `
<sm-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug"></sm-header>
`,
});
export const WithBreadcrumbs: Story = (args) => ({
props: args,
template: `
<sm-header title="Foobar" icon="bwi-bug" class="tw-text-main">
<bit-breadcrumbs slot="breadcrumbs">
<bit-breadcrumb>Foo</bit-breadcrumb>
<bit-breadcrumb>Bar</bit-breadcrumb>
</bit-breadcrumbs>
</sm-header>
`,
});
export const WithSearch: Story = (args) => ({
props: args,
template: `
<sm-header title="Foobar" icon="bwi-bug" class="tw-text-main">
<input
bitInput
placeholder="Ask Jeeves"
type="text"
/>
</sm-header>
`,
});
export const WithSecondaryContent: Story = (args) => ({
props: args,
template: `
<sm-header title="Foobar" icon="bwi-bug" class="tw-text-main">
<button bitButton slot="secondary">Click Me 🎉</button>
</sm-header>
`,
});
export const WithTabs: Story = (args) => ({
props: args,
template: `
<sm-header title="Foobar" icon="bwi-bug" class="tw-text-main">
<bit-tab-nav-bar slot="tabs">
<bit-tab-link route="">Foo</bit-tab-link>
<bit-tab-link route="#bar">Bar</bit-tab-link>
</bit-tab-nav-bar>
</sm-header>
`,
});
export const WithCustomTitleComponent: Story = (args) => ({
props: args,
template: `
<sm-header title="Foobar" icon="bwi-bug" class="tw-text-main">
<h1 slot="title" class="tw-text-3xl tw-font-semibold" style="font-family: 'Comic Sans MS'">Bitwarden</h1>
</sm-header>
`,
});

View File

@@ -1,6 +1,6 @@
<sm-header [title]="organizationName$ | async">
<app-header [title]="organizationName$ | async">
<sm-new-menu></sm-new-menu>
</sm-header>
</app-header>
<bit-no-items [icon]="NoAccess">
<ng-container slot="title">{{ "organizationIsDisabled" | i18n }}</ng-container>
<ng-container slot="description">{{ "secretsAccessSuspended" | i18n }}</ng-container>

View File

@@ -8,6 +8,7 @@ import {
} from "@bitwarden/components";
import { CoreOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/core";
import { DynamicAvatarComponent } from "@bitwarden/web-vault/app/components/dynamic-avatar.component";
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
import { ProductSwitcherModule } from "@bitwarden/web-vault/app/layouts/product-switcher/product-switcher.module";
import { SharedModule } from "@bitwarden/web-vault/app/shared";
@@ -15,7 +16,6 @@ import { AccessPolicySelectorComponent } from "./access-policies/access-policy-s
import { AccessSelectorComponent } from "./access-policies/access-selector.component";
import { BulkConfirmationDialogComponent } from "./dialogs/bulk-confirmation-dialog.component";
import { BulkStatusDialogComponent } from "./dialogs/bulk-status-dialog.component";
import { HeaderComponent } from "./header.component";
import { NewMenuComponent } from "./new-menu.component";
import { OrgSuspendedComponent } from "./org-suspended.component";
import { ProjectsListComponent } from "./projects-list.component";
@@ -31,16 +31,16 @@ import { SecretsListComponent } from "./secrets-list.component";
SelectModule,
DynamicAvatarComponent,
SearchModule,
HeaderModule,
],
exports: [
SharedModule,
NoItemsModule,
AccessSelectorComponent,
AccessPolicySelectorComponent,
BulkStatusDialogComponent,
AccessSelectorComponent,
BulkConfirmationDialogComponent,
HeaderComponent,
BulkStatusDialogComponent,
HeaderModule,
NewMenuComponent,
NoItemsModule,
ProjectsListComponent,
SearchModule,
SecretsListComponent,
@@ -48,15 +48,15 @@ import { SecretsListComponent } from "./secrets-list.component";
SharedModule,
],
declarations: [
BulkStatusDialogComponent,
AccessPolicySelectorComponent,
AccessSelectorComponent,
BulkConfirmationDialogComponent,
HeaderComponent,
BulkStatusDialogComponent,
BulkStatusDialogComponent,
NewMenuComponent,
OrgSuspendedComponent,
ProjectsListComponent,
SecretsListComponent,
AccessSelectorComponent,
OrgSuspendedComponent,
AccessPolicySelectorComponent,
],
providers: [],
bootstrap: [],

View File

@@ -1,6 +1,6 @@
<sm-header>
<app-header>
<sm-new-menu></sm-new-menu>
</sm-header>
</app-header>
<bit-callout type="warning" [title]="'warning' | i18n">{{
"trashCleanupWarning" | i18n
}}</bit-callout>