]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Implement warning on close unsaved view 1868/head
authorMichael Shamoon <4887959+shamoon@users.noreply.github.com>
Sun, 30 Oct 2022 06:14:33 +0000 (23:14 -0700)
committerMichael Shamoon <4887959+shamoon@users.noreply.github.com>
Sun, 30 Oct 2022 06:14:33 +0000 (23:14 -0700)
src-ui/src/app/app-routing.module.ts
src-ui/src/app/app.module.ts
src-ui/src/app/components/common/confirm-dialog/confirm-dialog.component.html
src-ui/src/app/components/common/confirm-dialog/confirm-dialog.component.ts
src-ui/src/app/components/document-list/document-list.component.html
src-ui/src/app/guards/dirty-saved-view.guard.ts [new file with mode: 0644]

index c62357c5d0f7cd17494b1b6b2ac2dd700d1b7624..4dad24c514c8c03cada5d2d5a071dabd39cd08b2 100644 (file)
@@ -15,6 +15,7 @@ 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'
+import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
 
 const routes: Routes = [
   { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
@@ -24,8 +25,16 @@ const routes: Routes = [
     canDeactivate: [DirtyDocGuard],
     children: [
       { path: 'dashboard', component: DashboardComponent },
-      { path: 'documents', component: DocumentListComponent },
-      { path: 'view/:id', component: DocumentListComponent },
+      {
+        path: 'documents',
+        component: DocumentListComponent,
+        canDeactivate: [DirtySavedViewGuard],
+      },
+      {
+        path: 'view/:id',
+        component: DocumentListComponent,
+        canDeactivate: [DirtySavedViewGuard],
+      },
       { path: 'documents/:id', component: DocumentDetailComponent },
       { path: 'asn/:id', component: DocumentAsnComponent },
       { path: 'tags', component: TagListComponent },
index 02fd8ea66a5c8d0296afc299b12cbeb358fc8b0a..29c85341b064528a936f3eb79d9112c57ca4c5c7 100644 (file)
@@ -69,6 +69,7 @@ import { ColorComponent } from './components/common/input/color/color.component'
 import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
 import { DocumentCommentsComponent } from './components/document-comments/document-comments.component'
 import { DirtyDocGuard } from './guards/dirty-doc.guard'
+import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
 import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
 import { StoragePathEditDialogComponent } from './components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
 import { SettingsService } from './services/settings.service'
@@ -215,6 +216,7 @@ function initializeApp(settings: SettingsService) {
     { provide: NgbDateAdapter, useClass: ISODateAdapter },
     { provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter },
     DirtyDocGuard,
+    DirtySavedViewGuard,
   ],
   bootstrap: [AppComponent],
 })
index 92e27e3705443add62faef4a783c64b87922782c..290581a4773ad0bdd2d56f437a1a94dd4f39c3fe 100644 (file)
@@ -16,4 +16,7 @@
         <ngb-progressbar *ngIf="!confirmButtonEnabled" style="height: 1px;" type="dark" [max]="secondsTotal" [value]="seconds"></ngb-progressbar>
         <span class="visually-hidden">{{ seconds | number: '1.0-0' }} seconds</span>
       </button>
+      <button *ngIf="alternativeBtnCaption" type="button" class="btn" [class]="alternativeBtnClass" (click)="alternative()" [disabled]="!alternativeButtonEnabled || !buttonsEnabled">
+        {{alternativeBtnCaption}}
+      </button>
     </div>
index ddf0bfd7ca861ffb0db24f373e27b6d31dc82b59..59d84bbe88f227b08bcc91d11577cece663f136e 100644 (file)
@@ -13,6 +13,9 @@ export class ConfirmDialogComponent {
   @Output()
   public confirmClicked = new EventEmitter()
 
+  @Output()
+  public alternativeClicked = new EventEmitter()
+
   @Input()
   title = $localize`Confirmation`
 
@@ -28,14 +31,22 @@ export class ConfirmDialogComponent {
   @Input()
   btnCaption = $localize`Confirm`
 
+  @Input()
+  alternativeBtnClass = 'btn-secondary'
+
+  @Input()
+  alternativeBtnCaption
+
   @Input()
   buttonsEnabled = true
 
   confirmButtonEnabled = true
+  alternativeButtonEnabled = true
   seconds = 0
   secondsTotal = 0
 
   confirmSubject: Subject<boolean>
+  alternativeSubject: Subject<boolean>
 
   delayConfirm(seconds: number) {
     const refreshInterval = 0.15 // s
@@ -68,4 +79,10 @@ export class ConfirmDialogComponent {
     this.confirmSubject?.next(true)
     this.confirmSubject?.complete()
   }
+
+  alternative() {
+    this.alternativeClicked.emit()
+    this.alternativeSubject?.next(true)
+    this.alternativeSubject?.complete()
+  }
 }
index 5cec94919a857198067ba9f68b75cad36e460b96..9357813f603393fac717eb7cc3d8392cf8fcec97 100644 (file)
   </div>
 
   <div class="btn-group ms-2 flex-fill" ngbDropdown role="group">
-    <button class="btn btn-sm btn-outline-primary dropdown-toggle flex-fill" tourAnchor="tour.documents-views" ngbDropdownToggle i18n>Views</button>
+    <button class="btn btn-sm btn-outline-primary dropdown-toggle flex-fill" tourAnchor="tour.documents-views" ngbDropdownToggle>
+      <ng-container i18n>Views</ng-container>
+      <div *ngIf="savedViewIsModified" class="position-absolute top-0 start-100 p-2 translate-middle badge bg-secondary border border-light rounded-circle">
+        <span class="visually-hidden">selected</span>
+      </div>
+    </button>
     <div class="dropdown-menu shadow dropdown-menu-right" ngbDropdownMenu>
       <ng-container *ngIf="!list.activeSavedViewId">
         <button ngbDropdownItem *ngFor="let view of savedViewService.allViews" (click)="loadViewConfig(view.id)">{{view.name}}</button>
diff --git a/src-ui/src/app/guards/dirty-saved-view.guard.ts b/src-ui/src/app/guards/dirty-saved-view.guard.ts
new file mode 100644 (file)
index 0000000..0044a2e
--- /dev/null
@@ -0,0 +1,51 @@
+import { CanDeactivate } from '@angular/router'
+import { Injectable } from '@angular/core'
+import { first, Observable, Subject } from 'rxjs'
+import { DocumentListComponent } from '../components/document-list/document-list.component'
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { ConfirmDialogComponent } from '../components/common/confirm-dialog/confirm-dialog.component'
+
+@Injectable()
+export class DirtySavedViewGuard
+  implements CanDeactivate<DocumentListComponent>
+{
+  constructor(private modalService: NgbModal) {}
+
+  canDeactivate(
+    component: DocumentListComponent
+  ): boolean | Observable<boolean> {
+    return component.savedViewIsModified ? this.warn(component) : true
+  }
+
+  warn(component: DocumentListComponent) {
+    let modal = this.modalService.open(ConfirmDialogComponent, {
+      backdrop: 'static',
+    })
+    modal.componentInstance.title = $localize`Unsaved Changes`
+    modal.componentInstance.messageBold =
+      $localize`You have unsaved changes to the saved view` +
+      ' "' +
+      component.getTitle()
+    ;('".')
+    modal.componentInstance.message = $localize`Are you sure you want to close this saved view?`
+    modal.componentInstance.btnClass = 'btn-secondary'
+    modal.componentInstance.btnCaption = $localize`Close`
+    modal.componentInstance.alternativeBtnClass = 'btn-primary'
+    modal.componentInstance.alternativeBtnCaption = $localize`Save and close`
+    modal.componentInstance.alternativeClicked.pipe(first()).subscribe(() => {
+      modal.componentInstance.buttonsEnabled = false
+      component.saveViewConfig()
+      modal.close()
+    })
+    modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
+      modal.componentInstance.buttonsEnabled = false
+      modal.close()
+    })
+
+    const subject = new Subject<boolean>()
+    modal.componentInstance.confirmSubject = subject
+    modal.componentInstance.alternativeSubject = subject
+
+    return subject
+  }
+}