From 90063122531f4fb70f53e77fcc74ea1dcdc348ff Mon Sep 17 00:00:00 2001
From: Pavlo Tkach <3469726+ptkach@users.noreply.github.com>
Date: Fri, 8 Dec 2023 17:52:57 -0500
Subject: [PATCH] Create reusable dialog / bottom sheet component (#2237)
---
console-webapp/src/app/app.module.ts | 8 +-
.../registrar/registrarDetails.component.html | 6 +-
.../registrar/registrarDetails.component.ts | 82 +++----------
.../registrar/registrarsTable.component.html | 4 +-
.../registrar/registrarsTable.component.ts | 10 +-
.../settings/contact/contact.component.html | 3 +
.../app/settings/contact/contact.component.ts | 116 ++++++------------
.../app/settings/contact/contact.service.ts | 2 +-
.../contact/contactDetails.component.html | 4 +-
.../components/dialogBottomSheet.component.ts | 69 +++++++++++
10 files changed, 148 insertions(+), 156 deletions(-)
create mode 100644 console-webapp/src/app/shared/components/dialogBottomSheet.component.ts
diff --git a/console-webapp/src/app/app.module.ts b/console-webapp/src/app/app.module.ts
index 7eeee433f..83aac71a2 100644
--- a/console-webapp/src/app/app.module.ts
+++ b/console-webapp/src/app/app.module.ts
@@ -49,15 +49,14 @@ import { SettingsWidgetComponent } from './home/widgets/settingsWidget.component
import { UserDataService } from './shared/services/userData.service';
import WhoisComponent from './settings/whois/whois.component';
import { SnackBarModule } from './snackbar.module';
-import {
- RegistrarDetailsComponent,
- RegistrarDetailsWrapperComponent,
-} from './registrar/registrarDetails.component';
+import { RegistrarDetailsComponent } from './registrar/registrarDetails.component';
import { DomainListComponent } from './domains/domainList.component';
+import { DialogBottomSheetWrapper } from './shared/components/dialogBottomSheet.component';
@NgModule({
declarations: [
AppComponent,
+ DialogBottomSheetWrapper,
BillingWidgetComponent,
ContactDetailsDialogComponent,
ContactWidgetComponent,
@@ -70,7 +69,6 @@ import { DomainListComponent } from './domains/domainList.component';
PromotionsWidgetComponent,
RegistrarComponent,
RegistrarDetailsComponent,
- RegistrarDetailsWrapperComponent,
RegistrarSelectorComponent,
ResourcesWidgetComponent,
SecurityComponent,
diff --git a/console-webapp/src/app/registrar/registrarDetails.component.html b/console-webapp/src/app/registrar/registrarDetails.component.html
index 118fc8821..c1c9a4df7 100644
--- a/console-webapp/src/app/registrar/registrarDetails.component.html
+++ b/console-webapp/src/app/registrar/registrarDetails.component.html
@@ -1,7 +1,7 @@
-
+
Edit Registrar: {{ registrarInEdit.registrarId }}
-
diff --git a/console-webapp/src/app/registrar/registrarDetails.component.ts b/console-webapp/src/app/registrar/registrarDetails.component.ts
index 9fa1e0f15..47b0aaf2b 100644
--- a/console-webapp/src/app/registrar/registrarDetails.component.ts
+++ b/console-webapp/src/app/registrar/registrarDetails.component.ts
@@ -12,61 +12,38 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, Injector } from '@angular/core';
+import { Component } from '@angular/core';
import { Registrar, RegistrarService } from './registrar.service';
-import { BreakpointObserver } from '@angular/cdk/layout';
-import {
- MAT_BOTTOM_SHEET_DATA,
- MatBottomSheet,
- MatBottomSheetRef,
-} from '@angular/material/bottom-sheet';
-import {
- MAT_DIALOG_DATA,
- MatDialog,
- MatDialogRef,
-} from '@angular/material/dialog';
import { MatChipInputEvent } from '@angular/material/chips';
+import { DialogBottomSheetContent } from '../shared/components/dialogBottomSheet.component';
-const MOBILE_LAYOUT_BREAKPOINT = '(max-width: 599px)';
+type RegistrarDetailsParams = {
+ close: Function;
+ data: {
+ registrar: Registrar;
+ };
+};
@Component({
selector: 'app-registrar-details',
templateUrl: './registrarDetails.component.html',
styleUrls: ['./registrarDetails.component.scss'],
})
-export class RegistrarDetailsComponent {
+export class RegistrarDetailsComponent implements DialogBottomSheetContent {
registrarInEdit!: Registrar;
- private elementRef:
- | MatBottomSheetRef
- | MatDialogRef;
+ params?: RegistrarDetailsParams;
- constructor(
- protected registrarService: RegistrarService,
- private injector: Injector
- ) {
- // We only inject one, either Dialog or Bottom Sheet data
- // so one of the injectors is expected to fail
- try {
- var params = this.injector.get(MAT_DIALOG_DATA);
- this.elementRef = this.injector.get(MatDialogRef);
- } catch (e) {
- var params = this.injector.get(MAT_BOTTOM_SHEET_DATA);
- this.elementRef = this.injector.get(MatBottomSheetRef);
- }
- this.registrarInEdit = JSON.parse(JSON.stringify(params.registrar));
+ constructor(protected registrarService: RegistrarService) {}
+
+ init(params: RegistrarDetailsParams) {
+ this.params = params;
+ this.registrarInEdit = JSON.parse(
+ JSON.stringify(this.params.data.registrar)
+ );
}
- onCancel(e: MouseEvent) {
- if (this.elementRef instanceof MatBottomSheetRef) {
- this.elementRef.dismiss();
- } else if (this.elementRef instanceof MatDialogRef) {
- this.elementRef.close();
- }
- }
-
- saveAndClose(e: MouseEvent) {
- // TODO: Implement save call to API
- this.onCancel(e);
+ saveAndClose() {
+ this.params?.close();
}
addTLD(e: MatChipInputEvent) {
@@ -82,24 +59,3 @@ export class RegistrarDetailsComponent {
);
}
}
-
-@Component({
- selector: 'app-registrar-details-wrapper',
- template: '',
-})
-export class RegistrarDetailsWrapperComponent {
- constructor(
- private dialog: MatDialog,
- private bottomSheet: MatBottomSheet,
- protected breakpointObserver: BreakpointObserver
- ) {}
-
- open(registrar: Registrar) {
- const config = { data: { registrar } };
- if (this.breakpointObserver.isMatched(MOBILE_LAYOUT_BREAKPOINT)) {
- this.bottomSheet.open(RegistrarDetailsComponent, config);
- } else {
- this.dialog.open(RegistrarDetailsComponent, config);
- }
- }
-}
diff --git a/console-webapp/src/app/registrar/registrarsTable.component.html b/console-webapp/src/app/registrar/registrarsTable.component.html
index 746ebd9b1..26feb6cd3 100644
--- a/console-webapp/src/app/registrar/registrarsTable.component.html
+++ b/console-webapp/src/app/registrar/registrarsTable.component.html
@@ -48,7 +48,7 @@
[pageSizeOptions]="[5, 10, 20]"
showFirstLastButtons
>
-
+ >
diff --git a/console-webapp/src/app/registrar/registrarsTable.component.ts b/console-webapp/src/app/registrar/registrarsTable.component.ts
index b86d2da13..05de8f1c5 100644
--- a/console-webapp/src/app/registrar/registrarsTable.component.ts
+++ b/console-webapp/src/app/registrar/registrarsTable.component.ts
@@ -17,7 +17,8 @@ import { Registrar, RegistrarService } from './registrar.service';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
-import { RegistrarDetailsWrapperComponent } from './registrarDetails.component';
+import { RegistrarDetailsComponent } from './registrarDetails.component';
+import { DialogBottomSheetWrapper } from '../shared/components/dialogBottomSheet.component';
@Component({
selector: 'app-registrar',
@@ -82,7 +83,7 @@ export class RegistrarComponent {
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
@ViewChild('registrarDetailsView')
- detailsComponentWrapper!: RegistrarDetailsWrapperComponent;
+ detailsComponentWrapper!: DialogBottomSheetWrapper;
constructor(protected registrarService: RegistrarService) {
this.dataSource = new MatTableDataSource
(
@@ -97,7 +98,10 @@ export class RegistrarComponent {
openDetails(event: MouseEvent, registrar: Registrar) {
event.stopPropagation();
- this.detailsComponentWrapper.open(registrar);
+ this.detailsComponentWrapper.open(
+ RegistrarDetailsComponent,
+ { registrar }
+ );
}
applyFilter(event: Event) {
diff --git a/console-webapp/src/app/settings/contact/contact.component.html b/console-webapp/src/app/settings/contact/contact.component.html
index 963732e71..dac160a51 100644
--- a/console-webapp/src/app/settings/contact/contact.component.html
+++ b/console-webapp/src/app/settings/contact/contact.component.html
@@ -41,4 +41,7 @@
addCreate a Contact
+
diff --git a/console-webapp/src/app/settings/contact/contact.component.ts b/console-webapp/src/app/settings/contact/contact.component.ts
index c672c7b77..2d7bdcb8a 100644
--- a/console-webapp/src/app/settings/contact/contact.component.ts
+++ b/console-webapp/src/app/settings/contact/contact.component.ts
@@ -12,21 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, Inject } from '@angular/core';
-import {
- MatDialog,
- MAT_DIALOG_DATA,
- MatDialogRef,
-} from '@angular/material/dialog';
-import {
- MatBottomSheet,
- MAT_BOTTOM_SHEET_DATA,
- MatBottomSheetRef,
-} from '@angular/material/bottom-sheet';
+import { Component, ViewChild } from '@angular/core';
import { Contact, ContactService } from './contact.service';
-import { BreakpointObserver } from '@angular/cdk/layout';
import { HttpErrorResponse } from '@angular/common/http';
import { MatSnackBar } from '@angular/material/snack-bar';
+import {
+ DialogBottomSheetContent,
+ DialogBottomSheetWrapper,
+} from 'src/app/shared/components/dialogBottomSheet.component';
enum Operations {
DELETE,
@@ -40,7 +33,13 @@ interface GroupedContacts {
contacts: Array;
}
-let isMobile: boolean;
+type ContactDetailsParams = {
+ close: Function;
+ data: {
+ contact: Contact;
+ operation: Operations;
+ };
+};
const contactTypes: Array = [
{ value: 'ADMIN', label: 'Primary contact', contacts: [] },
@@ -52,72 +51,46 @@ const contactTypes: Array = [
{ value: 'WHOIS', label: 'WHOIS-Inquiry contact', contacts: [] },
];
-class ContactDetailsEventsResponder {
- private ref?: MatDialogRef | MatBottomSheetRef;
- constructor() {
- this.onClose = this.onClose.bind(this);
- }
-
- setRef(ref: MatDialogRef | MatBottomSheetRef) {
- this.ref = ref;
- }
-
- onClose() {
- if (this.ref == undefined) {
- throw "Reference to ContactDetailsDialogComponent hasn't been set. ";
- }
- if (this.ref instanceof MatBottomSheetRef) {
- this.ref.dismiss();
- } else if (this.ref instanceof MatDialogRef) {
- this.ref.close();
- }
- }
-}
-
@Component({
selector: 'app-contact-details-dialog',
templateUrl: 'contactDetails.component.html',
styleUrls: ['./contact.component.scss'],
})
-export class ContactDetailsDialogComponent {
- contact: Contact;
+export class ContactDetailsDialogComponent implements DialogBottomSheetContent {
+ contact?: Contact;
contactTypes = contactTypes;
- operation: Operations;
- contactIndex: number;
- onCloseCallback: Function;
+ contactIndex?: number;
+
+ params?: ContactDetailsParams;
constructor(
public contactService: ContactService,
- private _snackBar: MatSnackBar,
- @Inject(isMobile ? MAT_BOTTOM_SHEET_DATA : MAT_DIALOG_DATA)
- public data: {
- onClose: Function;
- contact: Contact;
- operation: Operations;
- }
- ) {
- this.onCloseCallback = data.onClose;
- this.contactIndex = contactService.contacts.findIndex(
- (c) => c === data.contact
+ private _snackBar: MatSnackBar
+ ) {}
+
+ init(params: ContactDetailsParams) {
+ this.params = params;
+ this.contactIndex = this.contactService.contacts.findIndex(
+ (c) => c === params.data.contact
);
- this.contact = JSON.parse(JSON.stringify(data.contact));
- this.operation = data.operation;
+ this.contact = JSON.parse(JSON.stringify(params.data.contact));
}
- onClose(e: MouseEvent) {
- e.preventDefault();
- this.onCloseCallback.call(this);
+ close() {
+ this.params?.close();
}
saveAndClose(e: SubmitEvent) {
e.preventDefault();
+ if (!this.contact || this.contactIndex === undefined) return;
if (!(e.target as HTMLFormElement).checkValidity()) {
return;
}
+ const operation = this.params?.data.operation;
let operationObservable;
- if (this.operation === Operations.ADD) {
+ if (operation === Operations.ADD) {
operationObservable = this.contactService.addContact(this.contact);
- } else if (this.operation === Operations.UPDATE) {
+ } else if (operation === Operations.UPDATE) {
operationObservable = this.contactService.updateContact(
this.contactIndex,
this.contact
@@ -127,7 +100,7 @@ export class ContactDetailsDialogComponent {
}
operationObservable.subscribe({
- complete: this.onCloseCallback.bind(this),
+ complete: () => this.close(),
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error);
},
@@ -143,11 +116,11 @@ export class ContactDetailsDialogComponent {
export default class ContactComponent {
public static PATH = 'contact';
+ @ViewChild('contactDetailsWrapper')
+ detailsComponentWrapper!: DialogBottomSheetWrapper;
+
loading: boolean = false;
constructor(
- private dialog: MatDialog,
- private bottomSheet: MatBottomSheet,
- private breakpointObserver: BreakpointObserver,
public contactService: ContactService,
private _snackBar: MatSnackBar
) {
@@ -195,20 +168,9 @@ export default class ContactComponent {
operation: Operations = Operations.UPDATE
) {
e.preventDefault();
- // TODO: handle orientation change
- isMobile = this.breakpointObserver.isMatched('(max-width: 599px)');
- const responder = new ContactDetailsEventsResponder();
- const config = { data: { onClose: responder.onClose, contact, operation } };
-
- if (isMobile) {
- const bottomSheetRef = this.bottomSheet.open(
- ContactDetailsDialogComponent,
- config
- );
- responder.setRef(bottomSheetRef);
- } else {
- const dialogRef = this.dialog.open(ContactDetailsDialogComponent, config);
- responder.setRef(dialogRef);
- }
+ this.detailsComponentWrapper.open(
+ ContactDetailsDialogComponent,
+ { contact, operation }
+ );
}
}
diff --git a/console-webapp/src/app/settings/contact/contact.service.ts b/console-webapp/src/app/settings/contact/contact.service.ts
index 07f1e15a5..122ded34e 100644
--- a/console-webapp/src/app/settings/contact/contact.service.ts
+++ b/console-webapp/src/app/settings/contact/contact.service.ts
@@ -45,7 +45,7 @@ export class ContactService {
return this.backend
.getContacts(this.registrarService.activeRegistrarId)
.pipe(
- tap((contacts) => {
+ tap((contacts = []) => {
this.contacts = contacts;
})
);
diff --git a/console-webapp/src/app/settings/contact/contactDetails.component.html b/console-webapp/src/app/settings/contact/contactDetails.component.html
index b0ee9912d..7b0248046 100644
--- a/console-webapp/src/app/settings/contact/contactDetails.component.html
+++ b/console-webapp/src/app/settings/contact/contactDetails.component.html
@@ -1,5 +1,5 @@
Contact details
-
+
diff --git a/console-webapp/src/app/shared/components/dialogBottomSheet.component.ts b/console-webapp/src/app/shared/components/dialogBottomSheet.component.ts
new file mode 100644
index 000000000..73ad95afd
--- /dev/null
+++ b/console-webapp/src/app/shared/components/dialogBottomSheet.component.ts
@@ -0,0 +1,69 @@
+// Copyright 2023 The Nomulus Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { BreakpointObserver } from '@angular/cdk/layout';
+import { ComponentType } from '@angular/cdk/portal';
+import { Component } from '@angular/core';
+import {
+ MatBottomSheet,
+ MatBottomSheetRef,
+} from '@angular/material/bottom-sheet';
+import { MatDialog, MatDialogRef } from '@angular/material/dialog';
+
+const MOBILE_LAYOUT_BREAKPOINT = '(max-width: 599px)';
+
+export interface DialogBottomSheetContent {
+ init(data: Object): void;
+}
+
+/**
+ * Wraps up a child component in an Angular Material Dalog for desktop or a Bottom Sheet
+ * component for mobile depending on a screen resolution, with Breaking Point being 599px.
+ * Child component is required to implement @see DialogBottomSheetContent interface
+ */
+@Component({
+ selector: 'app-dialog-bottom-sheet-wrapper',
+ template: '',
+})
+export class DialogBottomSheetWrapper {
+ private elementRef?: MatBottomSheetRef | MatDialogRef
;
+
+ constructor(
+ private dialog: MatDialog,
+ private bottomSheet: MatBottomSheet,
+ protected breakpointObserver: BreakpointObserver
+ ) {}
+
+ open(
+ component: ComponentType,
+ data: any
+ ) {
+ const config = { data, close: () => this.close() };
+ if (this.breakpointObserver.isMatched(MOBILE_LAYOUT_BREAKPOINT)) {
+ this.elementRef = this.bottomSheet.open(component);
+ this.elementRef.instance.init(config);
+ } else {
+ this.elementRef = this.dialog.open(component);
+ this.elementRef.componentInstance.init(config);
+ }
+ }
+
+ close() {
+ if (this.elementRef instanceof MatBottomSheetRef) {
+ this.elementRef.dismiss();
+ } else if (this.elementRef instanceof MatDialogRef) {
+ this.elementRef.close();
+ }
+ }
+}