From 0175eab031a17877275305d532c50f5dd46e0fc5 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 6 Aug 2022 19:30:39 -0700 Subject: [PATCH] Fix browser unsaved changes with custom guard --- src-ui/messages.xlf | 51 +++++++++++-------- src-ui/src/app/app-routing.module.ts | 2 + src-ui/src/app/app.module.ts | 2 + .../app-frame/app-frame.component.ts | 12 +++-- .../document-detail.component.ts | 11 ++-- src-ui/src/app/guards/dirty-doc.guard.ts | 20 ++++++++ .../app/services/open-documents.service.ts | 11 ++-- 7 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 src-ui/src/app/guards/dirty-doc.guard.ts diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 08e08608d..c79413a4b 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -179,21 +179,21 @@ <source>Decrement hours</source> <context-group purpose="location"> <context context-type="sourcefile">node_modules/src/timepicker/timepicker.ts</context> - <context context-type="linenumber">240,243</context> + <context context-type="linenumber">239,240</context> </context-group> </trans-unit> <trans-unit id="ngb.timepicker.increment-minutes" datatype="html"> <source>Increment minutes</source> <context-group purpose="location"> <context context-type="sourcefile">node_modules/src/timepicker/timepicker.ts</context> - <context context-type="linenumber">268</context> + <context context-type="linenumber">264,268</context> </context-group> </trans-unit> <trans-unit id="ngb.timepicker.decrement-minutes" datatype="html"> <source>Decrement minutes</source> <context-group purpose="location"> <context context-type="sourcefile">node_modules/src/timepicker/timepicker.ts</context> - <context context-type="linenumber">288,289</context> + <context context-type="linenumber">287,289</context> </context-group> </trans-unit> <trans-unit id="ngb.timepicker.SS" datatype="html"> @@ -1648,7 +1648,7 @@ <source>Confirm delete</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> - <context context-type="linenumber">439</context> + <context context-type="linenumber">442</context> </context-group> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> @@ -1659,35 +1659,35 @@ <source>Do you really want to delete document "<x id="PH" equiv-text="this.document.title"/>"?</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> - <context context-type="linenumber">440</context> + <context context-type="linenumber">443</context> </context-group> </trans-unit> <trans-unit id="6691075929777935948" datatype="html"> <source>The files for this document will be deleted permanently. This operation cannot be undone.</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> - <context context-type="linenumber">441</context> + <context context-type="linenumber">444</context> </context-group> </trans-unit> <trans-unit id="719892092227206532" datatype="html"> <source>Delete document</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> - <context context-type="linenumber">443</context> + <context context-type="linenumber">446</context> </context-group> </trans-unit> <trans-unit id="1844801255494293730" datatype="html"> <source>Error deleting document: <x id="PH" equiv-text="JSON.stringify(error)"/></source> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> - <context context-type="linenumber">459</context> + <context context-type="linenumber">462</context> </context-group> </trans-unit> <trans-unit id="7362691899087997122" datatype="html"> <source>Redo OCR confirm</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> - <context context-type="linenumber">479</context> + <context context-type="linenumber">482</context> </context-group> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> @@ -1698,14 +1698,14 @@ <source>This operation will permanently redo OCR for this document.</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> - <context context-type="linenumber">480</context> + <context context-type="linenumber">483</context> </context-group> </trans-unit> <trans-unit id="5641451190833696892" datatype="html"> <source>This operation cannot be undone.</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> - <context context-type="linenumber">481</context> + <context context-type="linenumber">484</context> </context-group> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> @@ -1720,7 +1720,7 @@ <source>Proceed</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> - <context context-type="linenumber">483</context> + <context context-type="linenumber">486</context> </context-group> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> @@ -1731,7 +1731,7 @@ <source>Redo OCR operation will begin in the background.</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> - <context context-type="linenumber">491</context> + <context context-type="linenumber">494</context> </context-group> </trans-unit> <trans-unit id="8008978164775353960" datatype="html"> @@ -1740,7 +1740,7 @@ )"/></source> <context-group purpose="location"> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> - <context context-type="linenumber">502,504</context> + <context context-type="linenumber">505,507</context> </context-group> </trans-unit> <trans-unit id="6857598786757174736" datatype="html"> @@ -3200,6 +3200,13 @@ <context context-type="linenumber">39</context> </context-group> </trans-unit> + <trans-unit id="5948496158474272829" datatype="html"> + <source>Warning: You have unsaved changes to your document(s).</source> + <context-group purpose="location"> + <context context-type="sourcefile">src/app/guards/dirty-doc.guard.ts</context> + <context context-type="linenumber">18</context> + </context-group> + </trans-unit> <trans-unit id="159901853873315050" datatype="html"> <source>Unsaved Changes</source> <context-group purpose="location"> @@ -3208,11 +3215,11 @@ </context-group> <context-group purpose="location"> <context context-type="sourcefile">src/app/services/open-documents.service.ts</context> - <context context-type="linenumber">111</context> + <context context-type="linenumber">116</context> </context-group> <context-group purpose="location"> <context context-type="sourcefile">src/app/services/open-documents.service.ts</context> - <context context-type="linenumber">138</context> + <context context-type="linenumber">143</context> </context-group> </trans-unit> <trans-unit id="2573823578527613511" datatype="html"> @@ -3223,7 +3230,7 @@ </context-group> <context-group purpose="location"> <context context-type="sourcefile">src/app/services/open-documents.service.ts</context> - <context context-type="linenumber">139</context> + <context context-type="linenumber">144</context> </context-group> </trans-unit> <trans-unit id="3305084982600522070" datatype="html"> @@ -3360,35 +3367,35 @@ <source>You have unsaved changes to the document</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/services/open-documents.service.ts</context> - <context context-type="linenumber">113</context> + <context context-type="linenumber">118</context> </context-group> </trans-unit> <trans-unit id="2089045849587358256" datatype="html"> <source>Are you sure you want to close this document?</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/services/open-documents.service.ts</context> - <context context-type="linenumber">117</context> + <context context-type="linenumber">122</context> </context-group> </trans-unit> <trans-unit id="2885986061416655600" datatype="html"> <source>Close document</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/services/open-documents.service.ts</context> - <context context-type="linenumber">119</context> + <context context-type="linenumber">124</context> </context-group> </trans-unit> <trans-unit id="6755718693176327396" datatype="html"> <source>Are you sure you want to close all documents?</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/services/open-documents.service.ts</context> - <context context-type="linenumber">140</context> + <context context-type="linenumber">145</context> </context-group> </trans-unit> <trans-unit id="4215561719980781894" datatype="html"> <source>Close documents</source> <context-group purpose="location"> <context context-type="sourcefile">src/app/services/open-documents.service.ts</context> - <context context-type="linenumber">142</context> + <context context-type="linenumber">147</context> </context-group> </trans-unit> <trans-unit id="3553216189604488439" datatype="html"> diff --git a/src-ui/src/app/app-routing.module.ts b/src-ui/src/app/app-routing.module.ts index 65db5f97e..c62357c5d 100644 --- a/src-ui/src/app/app-routing.module.ts +++ b/src-ui/src/app/app-routing.module.ts @@ -14,12 +14,14 @@ import { DocumentAsnComponent } from './components/document-asn/document-asn.com import { DirtyFormGuard } from './guards/dirty-form.guard' import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component' import { TasksComponent } from './components/manage/tasks/tasks.component' +import { DirtyDocGuard } from './guards/dirty-doc.guard' const routes: Routes = [ { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, { path: '', component: AppFrameComponent, + canDeactivate: [DirtyDocGuard], children: [ { path: 'dashboard', component: DashboardComponent }, { path: 'documents', component: DocumentListComponent }, diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index f4c49d95d..edbd261f6 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -67,6 +67,7 @@ import { ApiVersionInterceptor } from './interceptors/api-version.interceptor' import { ColorSliderModule } from 'ngx-color/slider' import { ColorComponent } from './components/common/input/color/color.component' import { DocumentAsnComponent } from './components/document-asn/document-asn.component' +import { DirtyDocGuard } from './guards/dirty-doc.guard' import localeBe from '@angular/common/locales/be' import localeCs from '@angular/common/locales/cs' @@ -209,6 +210,7 @@ function initializeApp(settings: SettingsService) { DocumentTitlePipe, { provide: NgbDateAdapter, useClass: ISODateAdapter }, { provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter }, + DirtyDocGuard, ], bootstrap: [AppComponent], }) diff --git a/src-ui/src/app/components/app-frame/app-frame.component.ts b/src-ui/src/app/components/app-frame/app-frame.component.ts index 441a75330..6e758d262 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.ts +++ b/src-ui/src/app/components/app-frame/app-frame.component.ts @@ -1,6 +1,6 @@ -import { Component } from '@angular/core' +import { Component, HostListener } from '@angular/core' import { FormControl } from '@angular/forms' -import { ActivatedRoute, Router, Params } from '@angular/router' +import { ActivatedRoute, Router } from '@angular/router' import { from, Observable } from 'rxjs' import { debounceTime, @@ -23,13 +23,14 @@ import { } from 'src/app/services/rest/remote-version.service' import { SettingsService } from 'src/app/services/settings.service' import { TasksService } from 'src/app/services/tasks.service' +import { ComponentCanDeactivate } from 'src/app/guards/dirty-doc.guard' @Component({ selector: 'app-app-frame', templateUrl: './app-frame.component.html', styleUrls: ['./app-frame.component.scss'], }) -export class AppFrameComponent { +export class AppFrameComponent implements ComponentCanDeactivate { constructor( public router: Router, private activatedRoute: ActivatedRoute, @@ -64,6 +65,11 @@ export class AppFrameComponent { return this.openDocumentsService.getOpenDocuments() } + @HostListener('window:beforeunload') + canDeactivate(): Observable<boolean> | boolean { + return !this.openDocumentsService.hasDirty() + } + searchAutoComplete = (text$: Observable<string>) => text$.pipe( debounceTime(200), diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index f86ee1ea8..203a56f04 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -206,7 +206,7 @@ export class DocumentDetailComponent this.store.getValue().title !== this.documentForm.get('title').value ) { - this.openDocumentService.setDirty(doc.id, true) + this.openDocumentService.setDirty(doc, true) } }, }) @@ -228,12 +228,15 @@ export class DocumentDetailComponent this.store.asObservable() ) - return this.isDirty$.pipe(map((dirty) => ({ doc, dirty }))) + return this.isDirty$.pipe( + takeUntil(this.unsubscribeNotifier), + map((dirty) => ({ doc, dirty })) + ) }) ) .subscribe({ next: ({ doc, dirty }) => { - this.openDocumentService.setDirty(doc.id, dirty) + this.openDocumentService.setDirty(doc, dirty) }, error: (error) => { this.router.navigate(['404']) @@ -349,7 +352,7 @@ export class DocumentDetailComponent Object.assign(this.document, doc) this.title = doc.title this.documentForm.patchValue(doc) - this.openDocumentService.setDirty(doc.id, false) + this.openDocumentService.setDirty(doc, false) }, error: () => { this.router.navigate(['404']) diff --git a/src-ui/src/app/guards/dirty-doc.guard.ts b/src-ui/src/app/guards/dirty-doc.guard.ts new file mode 100644 index 000000000..10362db45 --- /dev/null +++ b/src-ui/src/app/guards/dirty-doc.guard.ts @@ -0,0 +1,20 @@ +import { CanDeactivate } from '@angular/router' +import { Injectable } from '@angular/core' +import { Observable } from 'rxjs' + +export interface ComponentCanDeactivate { + canDeactivate: () => boolean | Observable<boolean> +} + +@Injectable() +export class DirtyDocGuard implements CanDeactivate<ComponentCanDeactivate> { + canDeactivate( + component: ComponentCanDeactivate + ): boolean | Observable<boolean> { + return component.canDeactivate() + ? true + : confirm( + $localize`Warning: You have unsaved changes to your document(s).` + ) + } +} diff --git a/src-ui/src/app/services/open-documents.service.ts b/src-ui/src/app/services/open-documents.service.ts index d7746d261..8533166c3 100644 --- a/src-ui/src/app/services/open-documents.service.ts +++ b/src-ui/src/app/services/open-documents.service.ts @@ -92,9 +92,14 @@ export class OpenDocumentsService { } } - setDirty(documentId: number, dirty: boolean) { - if (dirty) this.dirtyDocuments.add(documentId) - else this.dirtyDocuments.delete(documentId) + setDirty(doc: PaperlessDocument, dirty: boolean) { + if (!this.openDocuments.find((d) => d.id == doc.id)) return + if (dirty) this.dirtyDocuments.add(doc.id) + else this.dirtyDocuments.delete(doc.id) + } + + hasDirty(): boolean { + return this.dirtyDocuments.size > 0 } closeDocument(doc: PaperlessDocument): Observable<boolean> {