updated PendingChangesGuard and used it for user profile and notifications preferences screens

This commit is contained in:
Valentin 2022-01-23 16:00:30 +02:00
parent d30c270d72
commit 3c697c643c
9 changed files with 55 additions and 60 deletions

View File

@ -1,40 +1,32 @@
import { CanDeactivate } from '@angular/router';
import { Directive, HostListener, Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { Injectable } from '@angular/core';
import { map, Observable } from 'rxjs';
import { ConfirmationDialogService } from '../../../../../libs/common-ui/src/lib/dialog/confirmation-dialog.service';
import { FormGroup } from '@angular/forms';
import { ConfirmOptions } from '../../../../../libs/common-ui/src';
export interface ComponentCanDeactivate {
hasChanges: boolean;
}
@Directive()
export abstract class ComponentHasChanges implements ComponentCanDeactivate {
abstract hasChanges: boolean;
protected constructor(protected _translateService: TranslateService) {}
@HostListener('window:beforeunload', ['$event'])
unloadNotification($event: any) {
if (this.hasChanges) {
// This message will be displayed in IE/Edge
$event.returnValue = this._translateService.instant('pending-changes-guard');
}
}
changed: boolean;
form: FormGroup;
save: () => Promise<void>;
}
@Injectable({ providedIn: 'root' })
export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate> {
constructor(private readonly _translateService: TranslateService) {}
constructor(private _dialogService: ConfirmationDialogService) {}
canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
// if there are no pending changes, just allow deactivation; else confirm first
return !component.hasChanges
? true
: // NOTE: this warning message will only be shown when navigating elsewhere
// within your angular app;
// when navigating away from your angular app,
// the browser will show a generic warning message
// see http://stackoverflow.com/a/42207299/7307355
confirm(this._translateService.instant('pending-changes-guard'));
if (component.changed) {
const dialogRef = this._dialogService.openDialog({ disableConfirm: component.form.invalid });
return dialogRef.afterClosed().pipe(
map(result => {
if (result === ConfirmOptions.CONFIRM) {
component.save();
}
return !!result;
}),
);
}
return true;
}
}

View File

@ -1,4 +1,4 @@
<form (submit)="save()" [formGroup]="formGroup">
<form (submit)="save()" [formGroup]="form">
<div class="dialog-content">
<div *ngFor="let category of notificationCategories">
<div class="iqser-input-group header w-full">
@ -36,7 +36,7 @@
</div>
<div class="dialog-actions">
<button [disabled]="formGroup.invalid" color="primary" mat-flat-button type="submit">
<button [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
{{ 'user-profile-screen.actions.save' | translate }}
</button>
</div>

View File

@ -10,6 +10,7 @@ import {
NotificationGroupsKeys,
NotificationGroupsValues,
} from '@red/domain';
import { BaseFormComponent } from '../../../../../../../../../libs/common-ui/src/lib/form/base-form.component';
@Component({
selector: 'redaction-notifications-screen',
@ -17,57 +18,57 @@ import {
styleUrls: ['./notifications-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NotificationsScreenComponent implements OnInit {
export class NotificationsScreenComponent extends BaseFormComponent implements OnInit {
readonly emailNotificationScheduleTypes = EmailNotificationScheduleTypesValues;
readonly notificationCategories = NotificationCategoriesValues;
readonly notificationGroupsKeys = NotificationGroupsKeys;
readonly notificationGroupsValues = NotificationGroupsValues;
readonly translations = notificationsTranslations;
readonly formGroup: FormGroup = this._getForm();
constructor(
private readonly _toaster: Toaster,
private readonly _formBuilder: FormBuilder,
private readonly _loadingService: LoadingService,
private readonly _notificationPreferencesService: NotificationPreferencesService,
) {}
) {
super();
}
async ngOnInit(): Promise<void> {
await this._initializeForm();
}
isCategoryActive(category: string) {
return this.formGroup.get(`${category}Enabled`).value;
return this.form.get(`${category}Enabled`).value;
}
setEmailNotificationType(type: string) {
this.formGroup.get('emailNotificationType').setValue(type);
this.form.get('emailNotificationType').setValue(type);
}
getEmailNotificationType() {
return this.formGroup.get('emailNotificationType').value;
return this.form.get('emailNotificationType').value;
}
isPreferenceChecked(category: string, preference: string) {
return this.formGroup.get(category).value.includes(preference);
return this.form.get(category).value.includes(preference);
}
addRemovePreference(checked: boolean, category: string, preference: string) {
const preferences = this.formGroup.get(category).value;
const preferences = this.form.get(category).value;
if (checked) {
preferences.push(preference);
} else {
const indexOfPreference = preferences.indexOf(preference);
preferences.splice(indexOfPreference, 1);
}
this.formGroup.get(category).setValue(preferences);
this.form.get(category).setValue(preferences);
}
async save() {
this._loadingService.start();
try {
await this._notificationPreferencesService.update(this.formGroup.value).toPromise();
await this._notificationPreferencesService.update(this.form.value).toPromise();
} catch (e) {
this._toaster.error(_('notifications-screen.error.generic'));
}
@ -87,8 +88,10 @@ export class NotificationsScreenComponent implements OnInit {
private async _initializeForm() {
this._loadingService.start();
this.form = this._getForm();
const notificationPreferences = await this._notificationPreferencesService.get().toPromise();
this.formGroup.patchValue(notificationPreferences);
this.form.patchValue(notificationPreferences);
this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue()));
this._loadingService.stop();
}

View File

@ -3,8 +3,9 @@ import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component';
import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard';
const routes = [{ path: '', component: NotificationsScreenComponent }];
const routes = [{ path: '', component: NotificationsScreenComponent, canDeactivate: [PendingChangesGuard] }];
@NgModule({
declarations: [NotificationsScreenComponent],

View File

@ -9,6 +9,8 @@ import { PermissionsService } from '../../../../../services/permissions.service'
import { UserService } from '../../../../../services/user.service';
import { ConfigService } from '../../../../../services/config.service';
import { LanguageService } from '../../../../../i18n/language.service';
import { ConfirmationDialogService } from '../../../../../../../../../libs/common-ui/src/lib/dialog/confirmation-dialog.service';
import { BaseFormComponent } from '../../../../../../../../../libs/common-ui/src/lib/form/base-form.component';
@Component({
selector: 'redaction-user-profile-screen',
@ -16,8 +18,7 @@ import { LanguageService } from '../../../../../i18n/language.service';
styleUrls: ['./user-profile-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserProfileScreenComponent implements OnInit {
readonly form: FormGroup = this._getForm();
export class UserProfileScreenComponent extends BaseFormComponent implements OnInit {
changePasswordUrl: SafeResourceUrl;
translations = languagesTranslations;
@ -30,11 +31,12 @@ export class UserProfileScreenComponent implements OnInit {
private readonly _configService: ConfigService,
private readonly _languageService: LanguageService,
private readonly _domSanitizer: DomSanitizer,
private readonly _translateService: TranslateService,
private readonly _loadingService: LoadingService,
private readonly _dialogService: ConfirmationDialogService,
protected readonly _translateService: TranslateService,
) {
super();
this._loadingService.start();
this.changePasswordUrl = this._domSanitizer.bypassSecurityTrustResourceUrl(
`${this._configService.values.OAUTH_URL}/account/password`,
);
@ -100,6 +102,7 @@ export class UserProfileScreenComponent implements OnInit {
private _initializeForm(): void {
try {
this.form = this._getForm();
this._profileModel = {
email: this._userService.currentUser.email,
firstName: this._userService.currentUser.firstName,
@ -111,6 +114,7 @@ export class UserProfileScreenComponent implements OnInit {
this.form.get('email').disable();
}
this.form.patchValue(this._profileModel, { emitEvent: false });
this.initialFormValue = this.form.getRawValue();
} catch (e) {
} finally {
this._loadingService.stop();

View File

@ -3,8 +3,9 @@ import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component';
import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard';
const routes = [{ path: '', component: UserProfileScreenComponent }];
const routes = [{ path: '', component: UserProfileScreenComponent, canDeactivate: [PendingChangesGuard] }];
@NgModule({
declarations: [UserProfileScreenComponent],

View File

@ -3,7 +3,6 @@ import { AppStateService } from '@state/app-state.service';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { saveAs } from 'file-saver';
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
import { DictionaryService } from '@shared/services/dictionary.service';
@ -16,7 +15,7 @@ import { Dictionary } from '@red/domain';
templateUrl: './dictionary-overview-screen.component.html',
styleUrls: ['./dictionary-overview-screen.component.scss'],
})
export class DictionaryOverviewScreenComponent extends ComponentHasChanges implements OnInit, OnDestroy {
export class DictionaryOverviewScreenComponent implements OnInit, OnDestroy {
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
@ -37,9 +36,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
private readonly _dialogService: AdminDialogService,
protected readonly _translateService: TranslateService,
private readonly _dictionaryService: DictionaryService,
) {
super(_translateService);
}
) {}
get hasChanges() {
return this._dictionaryManager.editor.hasChanges;

View File

@ -3,7 +3,6 @@ import { PermissionsService } from '@services/permissions.service';
import { Debounce, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core';
import { saveAs } from 'file-saver';
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
import { RulesService } from '../../services/rules.service';
@ -17,7 +16,7 @@ import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorCon
styleUrls: ['./rules-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RulesScreenComponent extends ComponentHasChanges implements OnInit {
export class RulesScreenComponent implements OnInit {
readonly iconButtonTypes = IconButtonTypes;
readonly editorOptions: IStandaloneEditorConstructionOptions = {
theme: 'vs',
@ -43,9 +42,7 @@ export class RulesScreenComponent extends ComponentHasChanges implements OnInit
private readonly _toaster: Toaster,
protected readonly _translateService: TranslateService,
private readonly _loadingService: LoadingService,
) {
super(_translateService);
}
) {}
get hasChanges(): boolean {
return this.currentLines.toString() !== this.initialLines.toString();

@ -1 +1 @@
Subproject commit 2e36f3cac77411c4cef0ec5b51165aa62a9d075c
Subproject commit 867d7b089ee3d10abf42bf6957c02f7f48ffdb7f