mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-22 02:21:34 +01:00
[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:
parent
db2427e05c
commit
b737c70712
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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>
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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">×</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>
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user