[PM-2067] Update Folder Add-Edit modal to use the Component Library (#5648)

* Add formGroup to base FolderAddEditComponent

* [web] use DialogService to open the modal

* [web] migrate FolderAddEditComponent use component library

* [desktop] use the formGroup in the template

* [browser] use the formGroup in the template

* [browser & desktop] remove disable on form invalid

* [web] Migrate to async actions

* [web] Strengthen typing for FolderAddEdit dialog

* Show form error instead of error toast

* Move browser folder add edit component to vault

* Remove extra template variables

* Remove inner form

* Remove inner form

* Update apps/web/src/app/vault/individual-vault/folder-add-edit.component.html

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

---------

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
Robyn MacCallum 2023-07-06 09:58:12 -04:00 committed by GitHub
parent db2427e05c
commit b737c70712
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 161 additions and 125 deletions

View File

@ -33,11 +33,11 @@ import { ShareComponent } from "../vault/popup/components/vault/share.component"
import { VaultFilterComponent } from "../vault/popup/components/vault/vault-filter.component";
import { VaultItemsComponent } from "../vault/popup/components/vault/vault-items.component";
import { ViewComponent } from "../vault/popup/components/vault/view.component";
import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component";
import { DebounceNavigationService } from "./services/debounceNavigationService";
import { AutofillComponent } from "./settings/autofill.component";
import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
import { FolderAddEditComponent } from "./settings/folder-add-edit.component";
import { FoldersComponent } from "./settings/folders.component";
import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component";
import { OptionsComponent } from "./settings/options.component";

View File

@ -52,6 +52,7 @@ import { VaultItemsComponent } from "../vault/popup/components/vault/vault-items
import { VaultSelectComponent } from "../vault/popup/components/vault/vault-select.component";
import { ViewCustomFieldsComponent } from "../vault/popup/components/vault/view-custom-fields.component";
import { ViewComponent } from "../vault/popup/components/vault/view.component";
import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component";
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
@ -63,7 +64,6 @@ import { ServicesModule } from "./services/services.module";
import { AboutComponent } from "./settings/about.component";
import { AutofillComponent } from "./settings/autofill.component";
import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
import { FolderAddEditComponent } from "./settings/folder-add-edit.component";
import { FoldersComponent } from "./settings/folders.component";
import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component";
import { OptionsComponent } from "./settings/options.component";

View File

@ -1,4 +1,4 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" [formGroup]="formGroup">
<header>
<div class="left">
<button type="button" routerLink="/folders">{{ "cancel" | i18n }}</button>
@ -18,13 +18,7 @@
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
type="text"
name="Name"
[(ngModel)]="folder.name"
[appAutofocus]="!editMode"
/>
<input id="name" type="text" formControlName="name" [appAutofocus]="!editMode" />
</div>
</div>
</div>

View File

@ -1,4 +1,5 @@
import { Component } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
@ -24,7 +25,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
private router: Router,
private route: ActivatedRoute,
logService: LogService,
dialogService: DialogServiceAbstraction
dialogService: DialogServiceAbstraction,
formBuilder: FormBuilder
) {
super(
folderService,
@ -32,7 +34,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
i18nService,
platformUtilsService,
logService,
dialogService
dialogService,
formBuilder
);
}

View File

@ -1,6 +1,12 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="folderAddEditTitle">
<div class="modal-dialog modal-sm" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<form
#form
class="modal-content"
(ngSubmit)="submit()"
[appApiAction]="formPromise"
[formGroup]="formGroup"
>
<div class="modal-body">
<div class="box">
<h1 class="box-header" id="folderAddEditTitle">
@ -9,13 +15,7 @@
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
type="text"
name="Name"
[(ngModel)]="folder.name"
[appAutofocus]="!editMode"
/>
<input id="name" type="text" formControlName="name" [appAutofocus]="!editMode" />
</div>
</div>
</div>

View File

@ -1,4 +1,5 @@
import { Component } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/vault/components/folder-add-edit.component";
@ -19,7 +20,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
logService: LogService,
dialogService: DialogServiceAbstraction
dialogService: DialogServiceAbstraction,
formBuilder: FormBuilder
) {
super(
folderService,
@ -27,7 +29,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
i18nService,
platformUtilsService,
logService,
dialogService
dialogService,
formBuilder
);
}
}

View File

@ -1,68 +1,32 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="folderAddEditTitle">
<div class="modal-dialog modal-dialog-scrollable modal-sm" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header">
<h1 class="modal-title" id="folderAddEditTitle">{{ title }}</h1>
<form [bitSubmit]="submitAndClose" [formGroup]="formGroup">
<bit-dialog>
<span bitDialogTitle>
{{ title }}
</span>
<span bitDialogContent>
<bit-form-field>
<bit-label>{{ "name" | i18n }}</bit-label>
<input bitInput id="name" formControlName="name" />
</bit-form-field>
</span>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" bitFormButton type="submit">
<span>{{ "save" | i18n }}</span>
</button>
<button bitButton buttonType="secondary" bitDialogClose type="button" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
<div class="tw-m-0 tw-ml-auto">
<button
buttonType="danger"
bitIconButton="bwi-trash"
bitFormButton
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[bitAction]="deleteAndClose"
></button>
</div>
<div class="modal-body">
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
class="form-control"
type="text"
name="Name"
[(ngModel)]="folder.name"
required
appAutofocus
/>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
<div class="ml-auto">
<button
#deleteBtn
type="button"
(click)="delete()"
class="btn btn-outline-danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="$any(deleteBtn).loading"
[appApiAction]="deletePromise"
>
<i
class="bwi bwi-trash bwi-lg bwi-fw"
[hidden]="$any(deleteBtn).loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!$any(deleteBtn).loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
</div>
</div>
</form>
</div>
</div>
</ng-container>
</bit-dialog>
</form>

View File

@ -1,6 +1,8 @@
import { Component } from "@angular/core";
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/vault/components/folder-add-edit.component";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -20,7 +22,10 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
logService: LogService,
dialogService: DialogServiceAbstraction
dialogService: DialogServiceAbstraction,
formBuilder: FormBuilder,
protected dialogRef: DialogRef<FolderAddEditDialogResult>,
@Inject(DIALOG_DATA) params: FolderAddEditDialogParams
) {
super(
folderService,
@ -28,7 +33,81 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
i18nService,
platformUtilsService,
logService,
dialogService
dialogService,
formBuilder
);
params?.folderId ? (this.folderId = params.folderId) : null;
}
deleteAndClose = async () => {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "deleteFolder" },
content: { key: "deleteFolderConfirmation" },
type: SimpleDialogType.WARNING,
});
if (!confirmed) {
return;
}
try {
this.deletePromise = this.folderApiService.delete(this.folder.id);
await this.deletePromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder"));
this.onDeletedFolder.emit(this.folder);
} catch (e) {
this.logService.error(e);
}
this.dialogRef.close(FolderAddEditDialogResult.Deleted);
};
submitAndClose = async () => {
this.folder.name = this.formGroup.controls.name.value;
if (this.folder.name == null || this.folder.name === "") {
this.formGroup.controls.name.markAsTouched();
return;
}
try {
const folder = await this.folderService.encrypt(this.folder);
this.formPromise = this.folderApiService.save(folder);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder")
);
this.onSavedFolder.emit(this.folder);
this.dialogRef.close(FolderAddEditDialogResult.Saved);
} catch (e) {
this.logService.error(e);
}
return;
};
}
export interface FolderAddEditDialogParams {
folderId: string;
}
export enum FolderAddEditDialogResult {
Deleted = "deleted",
Canceled = "canceled",
Saved = "saved",
}
/**
* Strongly typed helper to open a FolderAddEdit dialog
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Optional configuration for the dialog
*/
export function openFolderAddEditDialog(
dialogService: DialogServiceAbstraction,
config?: DialogConfig<FolderAddEditDialogParams>
) {
return dialogService.open<FolderAddEditDialogResult, FolderAddEditDialogParams>(
FolderAddEditComponent,
config
);
}

View File

@ -84,7 +84,7 @@ import {
openBulkShareDialog,
} from "./bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component";
import { CollectionsComponent } from "./collections.component";
import { FolderAddEditComponent } from "./folder-add-edit.component";
import { FolderAddEditDialogResult, openFolderAddEditDialog } from "./folder-add-edit.component";
import { ShareComponent } from "./share.component";
import { VaultFilterComponent } from "./vault-filter/components/vault-filter.component";
import { VaultFilterService } from "./vault-filter/services/abstractions/vault-filter.service";
@ -470,40 +470,25 @@ export class VaultComponent implements OnInit, OnDestroy {
}
addFolder = async (): Promise<void> => {
const [modal] = await this.modalService.openViewRef(
FolderAddEditComponent,
this.folderAddEditModalRef,
(comp) => {
comp.folderId = null;
comp.onSavedFolder.pipe(takeUntil(this.destroy$)).subscribe(() => {
modal.close();
});
}
);
openFolderAddEditDialog(this.dialogService);
};
editFolder = async (folder: FolderFilter): Promise<void> => {
const [modal] = await this.modalService.openViewRef(
FolderAddEditComponent,
this.folderAddEditModalRef,
(comp) => {
comp.folderId = folder.id;
comp.onSavedFolder.pipe(takeUntil(this.destroy$)).subscribe(() => {
modal.close();
});
comp.onDeletedFolder.pipe(takeUntil(this.destroy$)).subscribe(() => {
// Navigate away if we deleted the colletion we were viewing
if (this.filter.folderId === folder.id) {
this.router.navigate([], {
queryParams: { folderId: null },
queryParamsHandling: "merge",
replaceUrl: true,
});
}
modal.close();
});
}
);
const dialog = openFolderAddEditDialog(this.dialogService, {
data: {
folderId: folder.id,
},
});
const result = await lastValueFrom(dialog.closed);
if (result === FolderAddEditDialogResult.Deleted) {
this.router.navigate([], {
queryParams: { folderId: null },
queryParamsHandling: "merge",
replaceUrl: true,
});
}
};
filterSearchText(searchText: string) {

View File

@ -1,4 +1,5 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { Validators, FormBuilder } from "@angular/forms";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -22,13 +23,18 @@ export class FolderAddEditComponent implements OnInit {
deletePromise: Promise<any>;
protected componentName = "";
formGroup = this.formBuilder.group({
name: ["", [Validators.required]],
});
constructor(
protected folderService: FolderService,
protected folderApiService: FolderApiServiceAbstraction,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
private logService: LogService,
protected dialogService: DialogServiceAbstraction
protected logService: LogService,
protected dialogService: DialogServiceAbstraction,
protected formBuilder: FormBuilder
) {}
async ngOnInit() {
@ -36,6 +42,7 @@ export class FolderAddEditComponent implements OnInit {
}
async submit(): Promise<boolean> {
this.folder.name = this.formGroup.controls.name.value;
if (this.folder.name == null || this.folder.name === "") {
this.platformUtilsService.showToast(
"error",
@ -97,5 +104,6 @@ export class FolderAddEditComponent implements OnInit {
} else {
this.title = this.i18nService.t("addFolder");
}
this.formGroup.controls.name.setValue(this.folder.name);
}
}