diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index add4c23ae..92a402ec1 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -98,6 +98,7 @@ import { HtmlDebugScreenComponent } from './screens/html-debug-screen/html-debug import { ReportDownloadBtnComponent } from './components/buttons/report-download-btn/report-download-btn.component'; import { ProjectListingActionsComponent } from './screens/project-listing-screen/project-listing-actions/project-listing-actions.component'; import { HasScrollbarDirective } from './utils/has-scrollbar.directive'; +import { PendingChangesGuard } from './utils/can-deactivate.guard'; export function HttpLoaderFactory(httpClient: HttpClient) { return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json'); @@ -179,6 +180,7 @@ const routes = [ path: 'dictionaries/:type', component: DictionaryOverviewScreenComponent, canActivate: [CompositeRouteGuard], + canDeactivate: [PendingChangesGuard], data: { routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] } @@ -195,6 +197,7 @@ const routes = [ path: 'rules', component: RulesScreenComponent, canActivate: [CompositeRouteGuard], + canDeactivate: [PendingChangesGuard], data: { routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] } diff --git a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts index 5fb3163f9..42aec8f96 100644 --- a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts @@ -10,6 +10,7 @@ import { NotificationService, NotificationType } from '../../../notification/not import { TranslateService } from '@ngx-translate/core'; import { Observable } from 'rxjs'; import { saveAs } from 'file-saver'; +import { ComponentHasChanges } from '../../../utils/can-deactivate.guard'; declare var ace; @@ -18,7 +19,7 @@ declare var ace; templateUrl: './dictionary-overview-screen.component.html', styleUrls: ['./dictionary-overview-screen.component.scss'] }) -export class DictionaryOverviewScreenComponent { +export class DictionaryOverviewScreenComponent extends ComponentHasChanges { static readonly MIN_WORD_LENGTH: number = 2; public compareActive = false; @@ -49,13 +50,14 @@ export class DictionaryOverviewScreenComponent { constructor( public readonly permissionsService: PermissionsService, private readonly _notificationService: NotificationService, - private readonly _translateService: TranslateService, + protected readonly _translateService: TranslateService, private readonly _dictionaryControllerService: DictionaryControllerService, private readonly _dialogService: DialogService, private readonly _router: Router, private readonly _activatedRoute: ActivatedRoute, private readonly _appStateService: AppStateService ) { + super(_translateService); this._activatedRoute.params.subscribe((params) => { this.dictionary = this._appStateService.dictionaryData[params.type]; if (!this.dictionary) { diff --git a/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts index bb35a74f0..e1fef741a 100644 --- a/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts @@ -5,6 +5,7 @@ import { RulesControllerService } from '@redaction/red-ui-http'; import { NotificationService, NotificationType } from '../../../notification/notification.service'; import { TranslateService } from '@ngx-translate/core'; import { saveAs } from 'file-saver'; +import { ComponentHasChanges } from '../../../utils/can-deactivate.guard'; declare var ace; @@ -13,7 +14,7 @@ declare var ace; templateUrl: './rules-screen.component.html', styleUrls: ['./rules-screen.component.scss'] }) -export class RulesScreenComponent { +export class RulesScreenComponent extends ComponentHasChanges { public aceOptions = { showPrintMargin: false }; public rules: string; public processing = true; @@ -33,8 +34,9 @@ export class RulesScreenComponent { public readonly permissionsService: PermissionsService, private readonly _rulesControllerService: RulesControllerService, private readonly _notificationService: NotificationService, - private readonly _translateService: TranslateService + protected readonly _translateService: TranslateService ) { + super(_translateService); this._initialize(); } diff --git a/apps/red-ui/src/app/utils/can-deactivate.guard.ts b/apps/red-ui/src/app/utils/can-deactivate.guard.ts new file mode 100644 index 000000000..001e23f87 --- /dev/null +++ b/apps/red-ui/src/app/utils/can-deactivate.guard.ts @@ -0,0 +1,38 @@ +import { CanDeactivate } from '@angular/router'; +import { Directive, HostListener, Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; + +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'); + } + } +} + +@Injectable({ providedIn: 'root' }) +export class PendingChangesGuard implements CanDeactivate { + constructor(private readonly translateService: TranslateService) {} + + 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')); + } +} diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 4c5e73292..4bef713fd 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -650,5 +650,6 @@ "mark-unread": "Mark as unread" }, "rule-editor": "Rule Editor", - "watermark": "Watermark" + "watermark": "Watermark", + "pending-changes-guard": "WARNING: You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes." }