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 }}

-
+ Registry Lock: - +
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

-
+

@@ -97,7 +97,7 @@ > - + 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(); + } + } +}