diff --git a/apps/red-ui/src/app/guards/can-deactivate.guard.ts b/apps/red-ui/src/app/guards/can-deactivate.guard.ts index b92aabb6c..11282404a 100644 --- a/apps/red-ui/src/app/guards/can-deactivate.guard.ts +++ b/apps/red-ui/src/app/guards/can-deactivate.guard.ts @@ -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; } @Injectable({ providedIn: 'root' }) export class PendingChangesGuard implements CanDeactivate { - constructor(private readonly _translateService: TranslateService) {} + constructor(private _dialogService: ConfirmationDialogService) {} canDeactivate(component: ComponentCanDeactivate): boolean | Observable { - // 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; } } diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html index 94a52a5c3..cf0f4412e 100644 --- a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html +++ b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html @@ -1,4 +1,4 @@ -
+
@@ -36,7 +36,7 @@
-
diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.ts b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.ts index 73657ced5..cf008b8b7 100644 --- a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.ts +++ b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.ts @@ -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 { 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(); } diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/notifications.module.ts b/apps/red-ui/src/app/modules/account/screens/notifications/notifications.module.ts index d7a134836..ef7874558 100644 --- a/apps/red-ui/src/app/modules/account/screens/notifications/notifications.module.ts +++ b/apps/red-ui/src/app/modules/account/screens/notifications/notifications.module.ts @@ -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], diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts index 1f4ec6c23..b186c7000 100644 --- a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts @@ -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(); diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts index 64bf3d6f4..775a99569 100644 --- a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts @@ -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], diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts index 6f32d193a..a538a0b92 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts @@ -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; diff --git a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts index 9e4809505..f97c9242d 100644 --- a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts @@ -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(); diff --git a/libs/common-ui b/libs/common-ui index 2e36f3cac..867d7b089 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit 2e36f3cac77411c4cef0ec5b51165aa62a9d075c +Subproject commit 867d7b089ee3d10abf42bf6957c02f7f48ffdb7f