common-ui/src/lib/dialog/base-dialog.component.ts
2022-12-19 15:04:19 +02:00

98 lines
3.6 KiB
TypeScript

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<string, string>;
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<BaseDialogComponent>, 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());
}
}