import { Directive, HostListener, inject, OnDestroy, OnInit } from '@angular/core'; import { MatLegacyDialog as MatDialog, MatLegacyDialogRef as MatDialogRef } from '@angular/material/legacy-dialog'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { hasFormChanged, IqserEventTarget } from '../utils'; import { ConfirmOptions } from '.'; import { ConfirmationDialogService } from './confirmation-dialog.service'; import { firstValueFrom, Subscription } from 'rxjs'; import { LoadingService } from '../loading'; import { Toaster } from '../services'; const TARGET_NODE = 'mat-dialog-container'; export interface SaveOptions { closeAfterSave?: boolean; addMembers?: boolean; } @Directive() /** * Extend this component when you want to submit the form after pressing enter. * * This could be done by adding properties (submit)="save()" on the form and type="submit" on the save button. * However, some components (e.g. redaction-select, color picker) don't set focus on the input after choosing a value. * Also, other components (e.g. dropdown select) trigger a different action on enter, instead of submit. * * Make sure to remove the (submit)="save()" property from the form and to set type="button" on the save button * (otherwise the save request will be triggered twice). * */ export abstract class BaseDialogComponent implements OnDestroy { form!: UntypedFormGroup; initialFormValue!: Record; protected readonly _formBuilder = inject(UntypedFormBuilder); protected readonly _loadingService = inject(LoadingService); protected readonly _toaster = inject(Toaster); readonly #confirmationDialogService = inject(ConfirmationDialogService); readonly #dialog = inject(MatDialog); #backdropClickSubscription = this._dialogRef.backdropClick().subscribe(() => { this.close(); }); protected constructor(protected readonly _dialogRef: MatDialogRef, private readonly _isInEditMode?: boolean) {} get valid(): boolean { return this.form.valid; } get changed(): boolean { return hasFormChanged(this.form, this.initialFormValue); } get disabled(): boolean { return !this.valid || !this.changed; } abstract save(options?: SaveOptions): void; ngOnDestroy(): void { this.#backdropClickSubscription.unsubscribe(); } close(): void { if (this._isInEditMode && this.changed) { this._openConfirmDialog().then(result => { if (result in ConfirmOptions) { if (result === ConfirmOptions.CONFIRM) { this.save({ closeAfterSave: true }); } else { this._dialogRef.close(); } } }); } else { this._dialogRef.close(); } } @HostListener('window:keydown.Enter', ['$event']) onEnter(event: KeyboardEvent): void { event?.stopImmediatePropagation(); const node = (event.target as IqserEventTarget).localName?.trim()?.toLowerCase(); if (this.valid && !this.disabled && this.changed && node === TARGET_NODE) { this.save(); } } @HostListener('window:keydown.Escape', ['$event']) onEscape(): void { if (this.#dialog.openDialogs.length === 1) { this.close(); } } protected _openConfirmDialog() { const dialogRef = this.#confirmationDialogService.openDialog({ disableConfirm: !this.valid }); return firstValueFrom(dialogRef.afterClosed()); } }