1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-13 19:51:37 +01:00

[PM-13667] Add button to open credential history on web (#12100)

* Create CredentialGeneratorHistoryDialogComponent to be re-used on web and desktop

* Add button to open credential histpry on web

* Add button to open credential history on desktop (#12101)

- Register route to open new CredentialGeneratorHistoryDialogComponent when FeatureFlag/GeneratorToolsModernization is enabled
- Add button to credential generator
- Add missing keys to en/messages.json

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>

---------

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
Daniel James Smith 2024-11-22 18:29:30 +01:00 committed by GitHub
parent 7eb18b8e1a
commit 03aa4fd4d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 203 additions and 11 deletions

View File

@ -62,6 +62,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
import { DialogService, ToastOptions, ToastService } from "@bitwarden/components";
import { CredentialGeneratorHistoryDialogComponent } from "@bitwarden/generator-components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { KeyService, BiometricStateService } from "@bitwarden/key-management";
@ -324,10 +325,7 @@ export class AppComponent implements OnInit, OnDestroy {
await this.deleteAccount();
break;
case "openPasswordHistory":
await this.openModal<PasswordGeneratorHistoryComponent>(
PasswordGeneratorHistoryComponent,
this.passwordHistoryRef,
);
await this.openGeneratorHistory();
break;
case "showToast":
this.toastService._showToast(message);
@ -558,6 +556,21 @@ export class AppComponent implements OnInit, OnDestroy {
});
}
async openGeneratorHistory() {
const isGeneratorSwapEnabled = await this.configService.getFeatureFlag(
FeatureFlag.GeneratorToolsModernization,
);
if (isGeneratorSwapEnabled) {
await this.dialogService.open(CredentialGeneratorHistoryDialogComponent);
return;
}
await this.openModal<PasswordGeneratorHistoryComponent>(
PasswordGeneratorHistoryComponent,
this.passwordHistoryRef,
);
}
private async updateAppMenu() {
let updateRequest: MenuUpdateRequest;
const stateAccounts = await firstValueFrom(this.accountService.accounts$);

View File

@ -2,6 +2,18 @@
<span bitDialogTitle>{{ "generator" | i18n }}</span>
<ng-container bitDialogContent>
<tools-credential-generator />
<bit-item>
<button
type="button"
bitLink
bit-item-content
aria-haspopup="true"
(click)="openHistoryDialog()"
>
{{ "generatorHistory" | i18n }}
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</button>
</bit-item>
</ng-container>
<ng-container bitDialogFooter>
<button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose>

View File

@ -1,13 +1,37 @@
import { Component } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ButtonModule, DialogModule } from "@bitwarden/components";
import { GeneratorModule } from "@bitwarden/generator-components";
import {
ButtonModule,
DialogModule,
DialogService,
ItemModule,
LinkModule,
} from "@bitwarden/components";
import {
CredentialGeneratorHistoryDialogComponent,
GeneratorModule,
} from "@bitwarden/generator-components";
@Component({
standalone: true,
selector: "credential-generator",
templateUrl: "credential-generator.component.html",
imports: [DialogModule, ButtonModule, JslibModule, GeneratorModule],
imports: [
DialogModule,
ButtonModule,
JslibModule,
GeneratorModule,
ItemModule,
ButtonModule,
LinkModule,
],
})
export class CredentialGeneratorComponent {}
export class CredentialGeneratorComponent {
constructor(private dialogService: DialogService) {}
openHistoryDialog = () => {
// open history dialog
this.dialogService.open(CredentialGeneratorHistoryDialogComponent);
};
}

View File

@ -1371,6 +1371,15 @@
"passwordHistory": {
"message": "Password history"
},
"generatorHistory": {
"message": "Generator history"
},
"clearGeneratorHistoryTitle": {
"message": "Clear generator history"
},
"cleargGeneratorHistoryDescription": {
"message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?"
},
"clear": {
"message": "Clear",
"description": "To clear something out. example: To clear browser history."
@ -1378,6 +1387,15 @@
"noPasswordsInList": {
"message": "There are no passwords to list."
},
"clearHistory": {
"message": "Clear history"
},
"nothingToShow": {
"message": "Nothing to show"
},
"nothingGeneratedRecently": {
"message": "You haven't generated anything recently"
},
"undo": {
"message": "Undo"
},

View File

@ -2,4 +2,16 @@
<bit-container>
<tools-credential-generator />
<bit-item>
<button
type="button"
bitLink
bit-item-content
aria-haspopup="true"
(click)="openHistoryDialog()"
>
{{ "generatorHistory" | i18n }}
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</button>
</bit-item>
</bit-container>

View File

@ -1,6 +1,10 @@
import { Component } from "@angular/core";
import { GeneratorModule } from "@bitwarden/generator-components";
import { ButtonModule, DialogService, ItemModule, LinkModule } from "@bitwarden/components";
import {
CredentialGeneratorHistoryDialogComponent,
GeneratorModule,
} from "@bitwarden/generator-components";
import { HeaderModule } from "../../layouts/header/header.module";
import { SharedModule } from "../../shared";
@ -9,6 +13,12 @@ import { SharedModule } from "../../shared";
standalone: true,
selector: "credential-generator",
templateUrl: "credential-generator.component.html",
imports: [SharedModule, HeaderModule, GeneratorModule],
imports: [SharedModule, HeaderModule, GeneratorModule, ItemModule, ButtonModule, LinkModule],
})
export class CredentialGeneratorComponent {}
export class CredentialGeneratorComponent {
constructor(private dialogService: DialogService) {}
openHistoryDialog = () => {
this.dialogService.open(CredentialGeneratorHistoryDialogComponent);
};
}

View File

@ -1642,9 +1642,27 @@
"passwordHistory": {
"message": "Password history"
},
"generatorHistory": {
"message": "Generator history"
},
"clearGeneratorHistoryTitle": {
"message": "Clear generator history"
},
"cleargGeneratorHistoryDescription": {
"message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?"
},
"noPasswordsInList": {
"message": "There are no passwords to list."
},
"clearHistory": {
"message": "Clear history"
},
"nothingToShow": {
"message": "Nothing to show"
},
"nothingGeneratedRecently": {
"message": "You haven't generated anything recently"
},
"clear": {
"message": "Clear",
"description": "To clear something out. Example: To clear browser history."

View File

@ -0,0 +1,18 @@
<bit-dialog #dialog background="alt">
<span bitDialogTitle>{{ "generatorHistory" | i18n }}</span>
<ng-container bitDialogContent>
<bit-empty-credential-history *ngIf="!(hasHistory$ | async)" style="display: contents" />
<bit-credential-generator-history *ngIf="hasHistory$ | async" />
</ng-container>
<ng-container bitDialogFooter>
<button
[disabled]="!(hasHistory$ | async)"
bitButton
type="submit"
buttonType="primary"
(click)="clear()"
>
{{ "clearHistory" | i18n }}
</button>
</ng-container>
</bit-dialog>

View File

@ -0,0 +1,66 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { BehaviorSubject, distinctUntilChanged, firstValueFrom, map, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserId } from "@bitwarden/common/types/guid";
import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components";
import { GeneratorHistoryService } from "@bitwarden/generator-history";
import { CredentialGeneratorHistoryComponent as CredentialGeneratorHistoryToolsComponent } from "./credential-generator-history.component";
import { EmptyCredentialHistoryComponent } from "./empty-credential-history.component";
@Component({
templateUrl: "credential-generator-history-dialog.component.html",
standalone: true,
imports: [
ButtonModule,
CommonModule,
JslibModule,
DialogModule,
CredentialGeneratorHistoryToolsComponent,
EmptyCredentialHistoryComponent,
],
})
export class CredentialGeneratorHistoryDialogComponent {
protected readonly hasHistory$ = new BehaviorSubject<boolean>(false);
protected readonly userId$ = new BehaviorSubject<UserId>(null);
constructor(
private accountService: AccountService,
private history: GeneratorHistoryService,
private dialogService: DialogService,
) {
this.accountService.activeAccount$
.pipe(
takeUntilDestroyed(),
map(({ id }) => id),
distinctUntilChanged(),
)
.subscribe(this.userId$);
this.userId$
.pipe(
takeUntilDestroyed(),
switchMap((id) => id && this.history.credentials$(id)),
map((credentials) => credentials.length > 0),
)
.subscribe(this.hasHistory$);
}
clear = async () => {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "clearGeneratorHistoryTitle" },
content: { key: "cleargGeneratorHistoryDescription" },
type: "warning",
acceptButtonText: { key: "clearHistory" },
cancelButtonText: { key: "cancel" },
});
if (confirmed) {
await this.history.clear(await firstValueFrom(this.userId$));
}
};
}

View File

@ -1,3 +1,4 @@
export { CredentialGeneratorHistoryComponent } from "./credential-generator-history.component";
export { CredentialGeneratorHistoryDialogComponent } from "./credential-generator-history-dialog.component";
export { EmptyCredentialHistoryComponent } from "./empty-credential-history.component";
export { GeneratorModule } from "./generator.module";