mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-09 09:58:20 -05:00
Merge pull request #13 from shamoon/feature/unsaved-changes
Warnings for unsaved changes, 'intelligent' buttons when editing
This commit is contained in:
commit
8ef913b117
15
src-ui/package-lock.json
generated
15
src-ui/package-lock.json
generated
@ -19,6 +19,7 @@
|
|||||||
"@angular/router": "~11.2.14",
|
"@angular/router": "~11.2.14",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^9.1.2",
|
"@ng-bootstrap/ng-bootstrap": "^9.1.2",
|
||||||
"@ng-select/ng-select": "^7.0.0",
|
"@ng-select/ng-select": "^7.0.0",
|
||||||
|
"@ngneat/dirty-check-forms": "^1.1.0",
|
||||||
"bootstrap": "^4.5.0",
|
"bootstrap": "^4.5.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"ng2-pdf-viewer": "^6.3.2",
|
"ng2-pdf-viewer": "^6.3.2",
|
||||||
@ -2427,6 +2428,14 @@
|
|||||||
"@angular/forms": ">=11.0.0 <12.0.0"
|
"@angular/forms": ">=11.0.0 <12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ngneat/dirty-check-forms": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ngneat/dirty-check-forms/-/dirty-check-forms-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Ak6SUMUV2oFlaylhUnar1yT4ahmq3Y2mHrd9uQHesE0iUZWfQTrIN07kMtwyT2JXR/x4RqdAmvp/+IJ+QlUPGg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"tslib": "^1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ngtools/webpack": {
|
"node_modules/@ngtools/webpack": {
|
||||||
"version": "11.2.14",
|
"version": "11.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-11.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-11.2.14.tgz",
|
||||||
@ -18869,6 +18878,12 @@
|
|||||||
"tslib": "^2.0.0"
|
"tslib": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@ngneat/dirty-check-forms": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ngneat/dirty-check-forms/-/dirty-check-forms-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Ak6SUMUV2oFlaylhUnar1yT4ahmq3Y2mHrd9uQHesE0iUZWfQTrIN07kMtwyT2JXR/x4RqdAmvp/+IJ+QlUPGg==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@ngtools/webpack": {
|
"@ngtools/webpack": {
|
||||||
"version": "11.2.14",
|
"version": "11.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-11.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-11.2.14.tgz",
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"@angular/router": "~11.2.14",
|
"@angular/router": "~11.2.14",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^9.1.2",
|
"@ng-bootstrap/ng-bootstrap": "^9.1.2",
|
||||||
"@ng-select/ng-select": "^7.0.0",
|
"@ng-select/ng-select": "^7.0.0",
|
||||||
|
"@ngneat/dirty-check-forms": "^1.1.0",
|
||||||
"bootstrap": "^4.5.0",
|
"bootstrap": "^4.5.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"ng2-pdf-viewer": "^6.3.2",
|
"ng2-pdf-viewer": "^6.3.2",
|
||||||
|
@ -11,6 +11,7 @@ import { SettingsComponent } from './components/manage/settings/settings.compone
|
|||||||
import { TagListComponent } from './components/manage/tag-list/tag-list.component';
|
import { TagListComponent } from './components/manage/tag-list/tag-list.component';
|
||||||
import { NotFoundComponent } from './components/not-found/not-found.component';
|
import { NotFoundComponent } from './components/not-found/not-found.component';
|
||||||
import {DocumentAsnComponent} from "./components/document-asn/document-asn.component";
|
import {DocumentAsnComponent} from "./components/document-asn/document-asn.component";
|
||||||
|
import { DirtyFormGuard } from './guards/dirty-form.guard';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: '', redirectTo: 'dashboard', pathMatch: 'full'},
|
{path: '', redirectTo: 'dashboard', pathMatch: 'full'},
|
||||||
@ -19,13 +20,12 @@ const routes: Routes = [
|
|||||||
{path: 'documents', component: DocumentListComponent },
|
{path: 'documents', component: DocumentListComponent },
|
||||||
{path: 'view/:id', component: DocumentListComponent },
|
{path: 'view/:id', component: DocumentListComponent },
|
||||||
{path: 'documents/:id', component: DocumentDetailComponent },
|
{path: 'documents/:id', component: DocumentDetailComponent },
|
||||||
{path: 'asn/:id', component: DocumentAsnComponent },
|
{path: 'asn/:id', component: DocumentAsnComponent },
|
||||||
|
|
||||||
{path: 'tags', component: TagListComponent },
|
{path: 'tags', component: TagListComponent },
|
||||||
{path: 'documenttypes', component: DocumentTypeListComponent },
|
{path: 'documenttypes', component: DocumentTypeListComponent },
|
||||||
{path: 'correspondents', component: CorrespondentListComponent },
|
{path: 'correspondents', component: CorrespondentListComponent },
|
||||||
{path: 'logs', component: LogsComponent },
|
{path: 'logs', component: LogsComponent },
|
||||||
{path: 'settings', component: SettingsComponent },
|
{path: 'settings', component: SettingsComponent, canDeactivate: [DirtyFormGuard] },
|
||||||
]},
|
]},
|
||||||
|
|
||||||
{path: '404', component: NotFoundComponent},
|
{path: '404', component: NotFoundComponent},
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router, Params } from '@angular/router';
|
import { ActivatedRoute, Router, Params } from '@angular/router';
|
||||||
import { from, Observable, Subscription, BehaviorSubject } from 'rxjs';
|
import { from, Observable, Subscription, BehaviorSubject } from 'rxjs';
|
||||||
import { debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators';
|
import { debounceTime, distinctUntilChanged, map, switchMap, first } from 'rxjs/operators';
|
||||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
|
import { PaperlessDocument } from 'src/app/data/paperless-document';
|
||||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
|
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
|
||||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
|
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
|
||||||
@ -18,7 +18,7 @@ import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type';
|
|||||||
templateUrl: './app-frame.component.html',
|
templateUrl: './app-frame.component.html',
|
||||||
styleUrls: ['./app-frame.component.scss']
|
styleUrls: ['./app-frame.component.scss']
|
||||||
})
|
})
|
||||||
export class AppFrameComponent implements OnInit {
|
export class AppFrameComponent {
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public router: Router,
|
public router: Router,
|
||||||
@ -28,9 +28,7 @@ export class AppFrameComponent implements OnInit {
|
|||||||
public savedViewService: SavedViewService,
|
public savedViewService: SavedViewService,
|
||||||
private list: DocumentListViewService,
|
private list: DocumentListViewService,
|
||||||
private meta: Meta
|
private meta: Meta
|
||||||
) {
|
) { }
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
versionString = `${environment.appTitle} ${environment.version}`
|
versionString = `${environment.appTitle} ${environment.version}`
|
||||||
|
|
||||||
@ -81,32 +79,36 @@ export class AppFrameComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
closeDocument(d: PaperlessDocument) {
|
closeDocument(d: PaperlessDocument) {
|
||||||
this.closeMenu()
|
this.openDocumentsService.closeDocument(d).pipe(first()).subscribe(confirmed => {
|
||||||
this.openDocumentsService.closeDocument(d)
|
if (confirmed) {
|
||||||
|
this.closeMenu()
|
||||||
let route = this.activatedRoute.snapshot
|
let route = this.activatedRoute.snapshot
|
||||||
while (route.firstChild) {
|
while (route.firstChild) {
|
||||||
route = route.firstChild
|
route = route.firstChild
|
||||||
}
|
}
|
||||||
if (route.component == DocumentDetailComponent && route.params['id'] == d.id) {
|
if (route.component == DocumentDetailComponent && route.params['id'] == d.id) {
|
||||||
this.router.navigate([""])
|
this.router.navigate([""])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
closeAll() {
|
closeAll() {
|
||||||
this.closeMenu()
|
// user may need to confirm losing unsaved changes
|
||||||
this.openDocumentsService.closeAll()
|
this.openDocumentsService.closeAll().pipe(first()).subscribe(confirmed => {
|
||||||
|
if (confirmed) {
|
||||||
|
this.closeMenu()
|
||||||
|
|
||||||
let route = this.activatedRoute.snapshot
|
// TODO: is there a better way to do this?
|
||||||
while (route.firstChild) {
|
let route = this.activatedRoute
|
||||||
route = route.firstChild
|
while (route.firstChild) {
|
||||||
}
|
route = route.firstChild
|
||||||
if (route.component == DocumentDetailComponent) {
|
}
|
||||||
this.router.navigate([""])
|
if (route.component === DocumentDetailComponent) {
|
||||||
}
|
this.router.navigate([""])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ngOnInit() {
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get displayName() {
|
get displayName() {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
||||||
<button type="button" class="close" aria-label="Close" (click)="cancelClicked()">
|
<button type="button" class="close" aria-label="Close" (click)="cancel()">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -9,8 +9,8 @@
|
|||||||
<p *ngIf="message">{{message}}</p>
|
<p *ngIf="message">{{message}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline-dark" (click)="cancelClicked()" [disabled]="!buttonsEnabled" i18n>Cancel</button>
|
<button type="button" class="btn btn-outline-dark" (click)="cancel()" [disabled]="!buttonsEnabled" i18n>Cancel</button>
|
||||||
<button type="button" class="btn" [class]="btnClass" (click)="confirmClicked.emit()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
|
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
|
||||||
{{btnCaption}}
|
{{btnCaption}}
|
||||||
<span *ngIf="!confirmButtonEnabled"> ({{seconds}})</span>
|
<span *ngIf="!confirmButtonEnabled"> ({{seconds}})</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-confirm-dialog',
|
selector: 'app-confirm-dialog',
|
||||||
templateUrl: './confirm-dialog.component.html',
|
templateUrl: './confirm-dialog.component.html',
|
||||||
styleUrls: ['./confirm-dialog.component.scss']
|
styleUrls: ['./confirm-dialog.component.scss']
|
||||||
})
|
})
|
||||||
export class ConfirmDialogComponent implements OnInit {
|
export class ConfirmDialogComponent {
|
||||||
|
|
||||||
constructor(public activeModal: NgbActiveModal) { }
|
constructor(public activeModal: NgbActiveModal) { }
|
||||||
|
|
||||||
@ -34,6 +35,8 @@ export class ConfirmDialogComponent implements OnInit {
|
|||||||
confirmButtonEnabled = true
|
confirmButtonEnabled = true
|
||||||
seconds = 0
|
seconds = 0
|
||||||
|
|
||||||
|
confirmSubject: Subject<boolean>
|
||||||
|
|
||||||
delayConfirm(seconds: number) {
|
delayConfirm(seconds: number) {
|
||||||
this.confirmButtonEnabled = false
|
this.confirmButtonEnabled = false
|
||||||
this.seconds = seconds
|
this.seconds = seconds
|
||||||
@ -46,10 +49,15 @@ export class ConfirmDialogComponent implements OnInit {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
cancel() {
|
||||||
}
|
this.confirmSubject?.next(false)
|
||||||
|
this.confirmSubject?.complete()
|
||||||
cancelClicked() {
|
|
||||||
this.activeModal.close()
|
this.activeModal.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
confirm() {
|
||||||
|
this.confirmClicked.emit()
|
||||||
|
this.confirmSubject?.next(true)
|
||||||
|
this.confirmSubject?.complete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
<a ngbNavLink i18n>Details</a>
|
<a ngbNavLink i18n>Details</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
|
|
||||||
<app-input-text #inputTitle i18n-title title="Title" formControlName="title" [error]="error?.title"></app-input-text>
|
<app-input-text #inputTitle i18n-title title="Title" formControlName="title" (keyup)="titleKeyUp($event)" [error]="error?.title"></app-input-text>
|
||||||
<app-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" formControlName='archive_serial_number'></app-input-number>
|
<app-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" formControlName='archive_serial_number'></app-input-number>
|
||||||
<app-input-date i18n-title title="Date created" formControlName="created" [error]="error?.created"></app-input-date>
|
<app-input-date i18n-title title="Date created" formControlName="created" [error]="error?.created"></app-input-date>
|
||||||
<app-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true"
|
<app-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true"
|
||||||
@ -127,18 +127,26 @@
|
|||||||
<li [ngbNavItem]="4" class="d-md-none">
|
<li [ngbNavItem]="4" class="d-md-none">
|
||||||
<a ngbNavLink>Preview</a>
|
<a ngbNavLink>Preview</a>
|
||||||
<ng-template ngbNavContent *ngIf="pdfPreview.offsetParent == undefined">
|
<ng-template ngbNavContent *ngIf="pdfPreview.offsetParent == undefined">
|
||||||
<div class="pdf-viewer-container" *ngIf="getContentType() == 'application/pdf'">
|
<ng-container *ngIf="getContentType() == 'application/pdf'">
|
||||||
<pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true"></pdf-viewer>
|
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
|
||||||
</div>
|
<pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [render-text-mode]="2"></pdf-viewer>
|
||||||
|
</div>
|
||||||
|
<ng-template #nativePdfViewer>
|
||||||
|
<object [data]="previewUrl | safe" class="preview-sticky" width="100%"></object>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="getContentType() == 'text/plain'">
|
||||||
|
<object [data]="previewUrl | safe" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
|
||||||
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
||||||
|
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive">Discard</button>
|
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive || !(isDirty$ | async)">Discard</button>
|
||||||
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive">Save & next</button>
|
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || !(isDirty$ | async)">Save & next</button>
|
||||||
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive || !(isDirty$ | async)">Save</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -154,6 +162,5 @@
|
|||||||
<ng-container *ngIf="getContentType() == 'text/plain'">
|
<ng-container *ngIf="getContentType() == 'text/plain'">
|
||||||
<object [data]="previewUrl | safe" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
|
<object [data]="previewUrl | safe" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
|
||||||
import { FormControl, FormGroup } from '@angular/forms';
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { NgbModal, NgbNav } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal, NgbNav } from '@ng-bootstrap/ng-bootstrap';
|
||||||
@ -19,6 +19,9 @@ import { PDFDocumentProxy } from 'ng2-pdf-viewer';
|
|||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
import { TextComponent } from '../common/input/text/text.component';
|
import { TextComponent } from '../common/input/text/text.component';
|
||||||
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
|
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
|
||||||
|
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms';
|
||||||
|
import { Observable, Subject, BehaviorSubject } from 'rxjs';
|
||||||
|
import { first, takeUntil, switchMap, map, debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||||
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions';
|
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions';
|
||||||
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
|
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
|
||||||
|
|
||||||
@ -27,7 +30,7 @@ import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
|
|||||||
templateUrl: './document-detail.component.html',
|
templateUrl: './document-detail.component.html',
|
||||||
styleUrls: ['./document-detail.component.scss']
|
styleUrls: ['./document-detail.component.scss']
|
||||||
})
|
})
|
||||||
export class DocumentDetailComponent implements OnInit {
|
export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponent {
|
||||||
|
|
||||||
@ViewChild("inputTitle")
|
@ViewChild("inputTitle")
|
||||||
titleInput: TextComponent
|
titleInput: TextComponent
|
||||||
@ -45,6 +48,7 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
suggestions: PaperlessDocumentSuggestions
|
suggestions: PaperlessDocumentSuggestions
|
||||||
|
|
||||||
title: string
|
title: string
|
||||||
|
titleSubject: Subject<string> = new Subject()
|
||||||
previewUrl: string
|
previewUrl: string
|
||||||
downloadUrl: string
|
downloadUrl: string
|
||||||
downloadOriginalUrl: string
|
downloadOriginalUrl: string
|
||||||
@ -65,11 +69,14 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
previewCurrentPage: number = 1
|
previewCurrentPage: number = 1
|
||||||
previewNumPages: number = 1
|
previewNumPages: number = 1
|
||||||
|
|
||||||
|
store: BehaviorSubject<any>
|
||||||
|
isDirty$: Observable<boolean>
|
||||||
|
unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
@ViewChild('nav') nav: NgbNav
|
@ViewChild('nav') nav: NgbNav
|
||||||
@ViewChild('pdfPreview') set pdfPreview(element) {
|
@ViewChild('pdfPreview') set pdfPreview(element) {
|
||||||
// this gets called when compontent added or removed from DOM
|
// this gets called when compontent added or removed from DOM
|
||||||
if (element && element.nativeElement.offsetParent !== null) { // its visible
|
if (element && element.nativeElement.offsetParent !== null && this.nav?.activeId == 4) { // its visible
|
||||||
|
|
||||||
setTimeout(()=> this.nav?.select(1));
|
setTimeout(()=> this.nav?.select(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,7 +92,19 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
private documentListViewService: DocumentListViewService,
|
private documentListViewService: DocumentListViewService,
|
||||||
private documentTitlePipe: DocumentTitlePipe,
|
private documentTitlePipe: DocumentTitlePipe,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private settings: SettingsService) { }
|
private settings: SettingsService) {
|
||||||
|
this.titleSubject.pipe(
|
||||||
|
debounceTime(200),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
takeUntil(this.unsubscribeNotifier)
|
||||||
|
).subscribe(titleValue => {
|
||||||
|
this.documentForm.patchValue({'title': titleValue})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
titleKeyUp(event) {
|
||||||
|
this.titleSubject.next(event.target?.value)
|
||||||
|
}
|
||||||
|
|
||||||
get useNativePdfViewer(): boolean {
|
get useNativePdfViewer(): boolean {
|
||||||
return this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER)
|
return this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER)
|
||||||
@ -96,15 +115,18 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.documentForm.valueChanges.subscribe(wow => {
|
this.documentForm.valueChanges.pipe(takeUntil(this.unsubscribeNotifier)).subscribe(wow => {
|
||||||
Object.assign(this.document, this.documentForm.value)
|
Object.assign(this.document, this.documentForm.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.correspondentService.listAll().subscribe(result => this.correspondents = result.results)
|
this.correspondentService.listAll().pipe(first()).subscribe(result => this.correspondents = result.results)
|
||||||
this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results)
|
this.documentTypeService.listAll().pipe(first()).subscribe(result => this.documentTypes = result.results)
|
||||||
|
|
||||||
this.route.paramMap.subscribe(paramMap => {
|
this.route.paramMap.pipe(switchMap(paramMap => {
|
||||||
this.documentId = +paramMap.get('id')
|
const documentId = +paramMap.get('id')
|
||||||
|
return this.documentsService.get(documentId)
|
||||||
|
})).pipe(switchMap((doc) => {
|
||||||
|
this.documentId = doc.id
|
||||||
this.previewUrl = this.documentsService.getPreviewUrl(this.documentId)
|
this.previewUrl = this.documentsService.getPreviewUrl(this.documentId)
|
||||||
this.downloadUrl = this.documentsService.getDownloadUrl(this.documentId)
|
this.downloadUrl = this.documentsService.getDownloadUrl(this.documentId)
|
||||||
this.downloadOriginalUrl = this.documentsService.getDownloadUrl(this.documentId, true)
|
this.downloadOriginalUrl = this.documentsService.getDownloadUrl(this.documentId, true)
|
||||||
@ -112,23 +134,44 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
if (this.openDocumentService.getOpenDocument(this.documentId)) {
|
if (this.openDocumentService.getOpenDocument(this.documentId)) {
|
||||||
this.updateComponent(this.openDocumentService.getOpenDocument(this.documentId))
|
this.updateComponent(this.openDocumentService.getOpenDocument(this.documentId))
|
||||||
} else {
|
} else {
|
||||||
this.documentsService.get(this.documentId).subscribe(doc => {
|
this.openDocumentService.openDocument(doc)
|
||||||
this.openDocumentService.openDocument(doc)
|
this.updateComponent(doc)
|
||||||
this.updateComponent(doc)
|
|
||||||
}, error => {this.router.navigate(['404'])})
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
|
// Initialize dirtyCheck
|
||||||
|
this.store = new BehaviorSubject({
|
||||||
|
title: doc.title,
|
||||||
|
content: doc.content,
|
||||||
|
created: doc.created,
|
||||||
|
correspondent: doc.correspondent,
|
||||||
|
document_type: doc.document_type,
|
||||||
|
archive_serial_number: doc.archive_serial_number,
|
||||||
|
tags: doc.tags
|
||||||
|
})
|
||||||
|
|
||||||
|
this.isDirty$ = dirtyCheck(this.documentForm, this.store.asObservable())
|
||||||
|
|
||||||
|
return this.isDirty$.pipe(map(dirty => ({doc, dirty})))
|
||||||
|
}))
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe(({doc, dirty}) => {
|
||||||
|
this.openDocumentService.setDirty(doc.id, dirty)
|
||||||
|
}, error => {this.router.navigate(['404'])})
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() : void {
|
||||||
|
this.unsubscribeNotifier.next();
|
||||||
|
this.unsubscribeNotifier.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateComponent(doc: PaperlessDocument) {
|
updateComponent(doc: PaperlessDocument) {
|
||||||
this.document = doc
|
this.document = doc
|
||||||
this.documentsService.getMetadata(doc.id).subscribe(result => {
|
this.documentsService.getMetadata(doc.id).pipe(first()).subscribe(result => {
|
||||||
this.metadata = result
|
this.metadata = result
|
||||||
}, error => {
|
}, error => {
|
||||||
this.metadata = null
|
this.metadata = null
|
||||||
})
|
})
|
||||||
this.documentsService.getSuggestions(doc.id).subscribe(result => {
|
this.documentsService.getSuggestions(doc.id).pipe(first()).subscribe(result => {
|
||||||
this.suggestions = result
|
this.suggestions = result
|
||||||
}, error => {
|
}, error => {
|
||||||
this.suggestions = null
|
this.suggestions = null
|
||||||
@ -141,11 +184,13 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
var modal = this.modalService.open(DocumentTypeEditDialogComponent, {backdrop: 'static'})
|
var modal = this.modalService.open(DocumentTypeEditDialogComponent, {backdrop: 'static'})
|
||||||
modal.componentInstance.dialogMode = 'create'
|
modal.componentInstance.dialogMode = 'create'
|
||||||
if (newName) modal.componentInstance.object = { name: newName }
|
if (newName) modal.componentInstance.object = { name: newName }
|
||||||
modal.componentInstance.success.subscribe(newDocumentType => {
|
modal.componentInstance.success.pipe(switchMap(newDocumentType => {
|
||||||
this.documentTypeService.listAll().subscribe(documentTypes => {
|
return this.documentTypeService.listAll().pipe(map(documentTypes => ({newDocumentType, documentTypes})))
|
||||||
this.documentTypes = documentTypes.results
|
}))
|
||||||
this.documentForm.get('document_type').setValue(newDocumentType.id)
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
})
|
.subscribe(({newDocumentType, documentTypes}) => {
|
||||||
|
this.documentTypes = documentTypes.results
|
||||||
|
this.documentForm.get('document_type').setValue(newDocumentType.id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,16 +198,18 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
var modal = this.modalService.open(CorrespondentEditDialogComponent, {backdrop: 'static'})
|
var modal = this.modalService.open(CorrespondentEditDialogComponent, {backdrop: 'static'})
|
||||||
modal.componentInstance.dialogMode = 'create'
|
modal.componentInstance.dialogMode = 'create'
|
||||||
if (newName) modal.componentInstance.object = { name: newName }
|
if (newName) modal.componentInstance.object = { name: newName }
|
||||||
modal.componentInstance.success.subscribe(newCorrespondent => {
|
modal.componentInstance.success.pipe(switchMap(newCorrespondent => {
|
||||||
this.correspondentService.listAll().subscribe(correspondents => {
|
return this.correspondentService.listAll().pipe(map(correspondents => ({newCorrespondent, correspondents})))
|
||||||
this.correspondents = correspondents.results
|
}))
|
||||||
this.documentForm.get('correspondent').setValue(newCorrespondent.id)
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
})
|
.subscribe(({newCorrespondent, correspondents}) => {
|
||||||
|
this.correspondents = correspondents.results
|
||||||
|
this.documentForm.get('correspondent').setValue(newCorrespondent.id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
discard() {
|
discard() {
|
||||||
this.documentsService.get(this.documentId).subscribe(doc => {
|
this.documentsService.get(this.documentId).pipe(first()).subscribe(doc => {
|
||||||
Object.assign(this.document, doc)
|
Object.assign(this.document, doc)
|
||||||
this.title = doc.title
|
this.title = doc.title
|
||||||
this.documentForm.patchValue(doc)
|
this.documentForm.patchValue(doc)
|
||||||
@ -171,7 +218,8 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
|
|
||||||
save() {
|
save() {
|
||||||
this.networkActive = true
|
this.networkActive = true
|
||||||
this.documentsService.update(this.document).subscribe(result => {
|
this.store.next(this.documentForm.value)
|
||||||
|
this.documentsService.update(this.document).pipe(first()).subscribe(result => {
|
||||||
this.close()
|
this.close()
|
||||||
this.networkActive = false
|
this.networkActive = false
|
||||||
this.error = null
|
this.error = null
|
||||||
@ -183,18 +231,20 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
|
|
||||||
saveEditNext() {
|
saveEditNext() {
|
||||||
this.networkActive = true
|
this.networkActive = true
|
||||||
this.documentsService.update(this.document).subscribe(result => {
|
this.store.next(this.documentForm.value)
|
||||||
|
this.documentsService.update(this.document).pipe(switchMap(updateResult => {
|
||||||
|
return this.documentListViewService.getNext(this.documentId).pipe(map(nextDocId => ({nextDocId, updateResult})))
|
||||||
|
})).pipe(switchMap(({nextDocId, updateResult}) => {
|
||||||
|
if (nextDocId && updateResult) return this.openDocumentService.closeDocument(this.document).pipe(map(closeResult => ({updateResult, nextDocId, closeResult})))
|
||||||
|
}))
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe(({updateResult, nextDocId, closeResult}) => {
|
||||||
this.error = null
|
this.error = null
|
||||||
this.documentListViewService.getNext(this.document.id).subscribe(nextDocId => {
|
this.networkActive = false
|
||||||
this.networkActive = false
|
if (closeResult && updateResult && nextDocId) {
|
||||||
if (nextDocId) {
|
this.router.navigate(['documents', nextDocId])
|
||||||
this.openDocumentService.closeDocument(this.document)
|
this.titleInput?.focus()
|
||||||
this.router.navigate(['documents', nextDocId])
|
}
|
||||||
this.titleInput.focus()
|
|
||||||
}
|
|
||||||
}, error => {
|
|
||||||
this.networkActive = false
|
|
||||||
})
|
|
||||||
}, error => {
|
}, error => {
|
||||||
this.networkActive = false
|
this.networkActive = false
|
||||||
this.error = error.error
|
this.error = error.error
|
||||||
@ -202,12 +252,14 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.openDocumentService.closeDocument(this.document)
|
this.openDocumentService.closeDocument(this.document).pipe(first()).subscribe(closed => {
|
||||||
if (this.documentListViewService.activeSavedViewId) {
|
if (!closed) return;
|
||||||
this.router.navigate(['view', this.documentListViewService.activeSavedViewId])
|
if (this.documentListViewService.activeSavedViewId) {
|
||||||
} else {
|
this.router.navigate(['view', this.documentListViewService.activeSavedViewId])
|
||||||
this.router.navigate(['documents'])
|
} else {
|
||||||
}
|
this.router.navigate(['documents'])
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
@ -217,17 +269,18 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
modal.componentInstance.message = $localize`The files for this document will be deleted permanently. This operation cannot be undone.`
|
modal.componentInstance.message = $localize`The files for this document will be deleted permanently. This operation cannot be undone.`
|
||||||
modal.componentInstance.btnClass = "btn-danger"
|
modal.componentInstance.btnClass = "btn-danger"
|
||||||
modal.componentInstance.btnCaption = $localize`Delete document`
|
modal.componentInstance.btnCaption = $localize`Delete document`
|
||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.pipe(switchMap(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled = false
|
||||||
this.documentsService.delete(this.document).subscribe(() => {
|
return this.documentsService.delete(this.document)
|
||||||
modal.close()
|
}))
|
||||||
this.close()
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
}, error => {
|
.subscribe(() => {
|
||||||
this.toastService.showError($localize`Error deleting document: ${JSON.stringify(error)}`)
|
modal.close()
|
||||||
modal.componentInstance.buttonsEnabled = true
|
this.close()
|
||||||
})
|
}, error => {
|
||||||
|
this.toastService.showError($localize`Error deleting document: ${JSON.stringify(error)}`)
|
||||||
|
modal.componentInstance.buttonsEnabled = true
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
moreLike() {
|
moreLike() {
|
||||||
|
@ -170,5 +170,5 @@
|
|||||||
|
|
||||||
<div [ngbNavOutlet]="nav" class="border-left border-right border-bottom p-3 mb-3 shadow-sm"></div>
|
<div [ngbNavOutlet]="nav" class="border-left border-right border-bottom p-3 mb-3 shadow-sm"></div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary" i18n>Save</button>
|
<button type="submit" class="btn btn-primary" [disabled]="!(isDirty$ | async)" i18n>Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,40 +1,45 @@
|
|||||||
import { Component, Inject, LOCALE_ID, OnInit, Renderer2 } from '@angular/core';
|
import { Component, Inject, LOCALE_ID, OnInit, OnDestroy, Renderer2 } from '@angular/core';
|
||||||
import { FormControl, FormGroup } from '@angular/forms';
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
|
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
|
||||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
|
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
|
||||||
import { LanguageOption, SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
|
import { LanguageOption, SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms';
|
||||||
|
import { Observable, Subscription, BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings',
|
selector: 'app-settings',
|
||||||
templateUrl: './settings.component.html',
|
templateUrl: './settings.component.html',
|
||||||
styleUrls: ['./settings.component.scss']
|
styleUrls: ['./settings.component.scss']
|
||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit {
|
export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
||||||
|
|
||||||
savedViewGroup = new FormGroup({})
|
savedViewGroup = new FormGroup({})
|
||||||
|
|
||||||
settingsForm = new FormGroup({
|
settingsForm = new FormGroup({
|
||||||
'bulkEditConfirmationDialogs': new FormControl(this.settings.get(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS)),
|
'bulkEditConfirmationDialogs': new FormControl(null),
|
||||||
'bulkEditApplyOnClose': new FormControl(this.settings.get(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE)),
|
'bulkEditApplyOnClose': new FormControl(null),
|
||||||
'documentListItemPerPage': new FormControl(this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)),
|
'documentListItemPerPage': new FormControl(null),
|
||||||
'darkModeUseSystem': new FormControl(this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM)),
|
'darkModeUseSystem': new FormControl(null),
|
||||||
'darkModeEnabled': new FormControl(this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED)),
|
'darkModeEnabled': new FormControl(null),
|
||||||
'darkModeInvertThumbs': new FormControl(this.settings.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)),
|
'useNativePdfViewer': new FormControl(null),
|
||||||
'useNativePdfViewer': new FormControl(this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER)),
|
|
||||||
'savedViews': this.savedViewGroup,
|
'savedViews': this.savedViewGroup,
|
||||||
'displayLanguage': new FormControl(this.settings.getLanguage()),
|
'displayLanguage': new FormControl(null),
|
||||||
'dateLocale': new FormControl(this.settings.get(SETTINGS_KEYS.DATE_LOCALE)),
|
'dateLocale': new FormControl(null),
|
||||||
'dateFormat': new FormControl(this.settings.get(SETTINGS_KEYS.DATE_FORMAT)),
|
'dateFormat': new FormControl(null),
|
||||||
'notificationsConsumerNewDocument': new FormControl(this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT)),
|
'notificationsConsumerNewDocument': new FormControl(null),
|
||||||
'notificationsConsumerSuccess': new FormControl(this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)),
|
'notificationsConsumerSuccess': new FormControl(null),
|
||||||
'notificationsConsumerFailed': new FormControl(this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED)),
|
'notificationsConsumerFailed': new FormControl(null),
|
||||||
'notificationsConsumerSuppressOnDashboard': new FormControl(this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD)),
|
'notificationsConsumerSuppressOnDashboard': new FormControl(null),
|
||||||
})
|
})
|
||||||
|
|
||||||
savedViews: PaperlessSavedView[]
|
savedViews: PaperlessSavedView[]
|
||||||
|
|
||||||
|
store: BehaviorSubject<any>
|
||||||
|
storeSub: Subscription
|
||||||
|
isDirty$: Observable<boolean>
|
||||||
|
|
||||||
get computedDateLocale(): string {
|
get computedDateLocale(): string {
|
||||||
return this.settingsForm.value.dateLocale || this.settingsForm.value.displayLanguage || this.currentLocale
|
return this.settingsForm.value.dateLocale || this.settingsForm.value.displayLanguage || this.currentLocale
|
||||||
}
|
}
|
||||||
@ -50,17 +55,53 @@ export class SettingsComponent implements OnInit {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.savedViewService.listAll().subscribe(r => {
|
this.savedViewService.listAll().subscribe(r => {
|
||||||
this.savedViews = r.results
|
this.savedViews = r.results
|
||||||
|
let storeData = {
|
||||||
|
'bulkEditConfirmationDialogs': this.settings.get(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS),
|
||||||
|
'bulkEditApplyOnClose': this.settings.get(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE),
|
||||||
|
'documentListItemPerPage': this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE),
|
||||||
|
'darkModeUseSystem': this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM),
|
||||||
|
'darkModeEnabled': this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED),
|
||||||
|
'useNativePdfViewer': this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER),
|
||||||
|
'savedViews': {},
|
||||||
|
'displayLanguage': this.settings.getLanguage(),
|
||||||
|
'dateLocale': this.settings.get(SETTINGS_KEYS.DATE_LOCALE),
|
||||||
|
'dateFormat': this.settings.get(SETTINGS_KEYS.DATE_FORMAT),
|
||||||
|
'notificationsConsumerNewDocument': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT),
|
||||||
|
'notificationsConsumerSuccess': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS),
|
||||||
|
'notificationsConsumerFailed': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED),
|
||||||
|
'notificationsConsumerSuppressOnDashboard': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD),
|
||||||
|
}
|
||||||
|
|
||||||
for (let view of this.savedViews) {
|
for (let view of this.savedViews) {
|
||||||
|
storeData.savedViews[view.id.toString()] = {
|
||||||
|
"id": view.id,
|
||||||
|
"name": view.name,
|
||||||
|
"show_on_dashboard": view.show_on_dashboard,
|
||||||
|
"show_in_sidebar": view.show_in_sidebar
|
||||||
|
}
|
||||||
this.savedViewGroup.addControl(view.id.toString(), new FormGroup({
|
this.savedViewGroup.addControl(view.id.toString(), new FormGroup({
|
||||||
"id": new FormControl(view.id),
|
"id": new FormControl(null),
|
||||||
"name": new FormControl(view.name),
|
"name": new FormControl(null),
|
||||||
"show_on_dashboard": new FormControl(view.show_on_dashboard),
|
"show_on_dashboard": new FormControl(null),
|
||||||
"show_in_sidebar": new FormControl(view.show_in_sidebar)
|
"show_in_sidebar": new FormControl(null)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.store = new BehaviorSubject(storeData)
|
||||||
|
|
||||||
|
this.storeSub = this.store.asObservable().subscribe(state => {
|
||||||
|
this.settingsForm.patchValue(state, { emitEvent: false })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Initialize dirtyCheck
|
||||||
|
this.isDirty$ = dirtyCheck(this.settingsForm, this.store.asObservable())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.storeSub && this.storeSub.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
deleteSavedView(savedView: PaperlessSavedView) {
|
deleteSavedView(savedView: PaperlessSavedView) {
|
||||||
this.savedViewService.delete(savedView).subscribe(() => {
|
this.savedViewService.delete(savedView).subscribe(() => {
|
||||||
this.savedViewGroup.removeControl(savedView.id.toString())
|
this.savedViewGroup.removeControl(savedView.id.toString())
|
||||||
@ -84,6 +125,7 @@ export class SettingsComponent implements OnInit {
|
|||||||
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED, this.settingsForm.value.notificationsConsumerFailed)
|
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED, this.settingsForm.value.notificationsConsumerFailed)
|
||||||
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, this.settingsForm.value.notificationsConsumerSuppressOnDashboard)
|
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, this.settingsForm.value.notificationsConsumerSuppressOnDashboard)
|
||||||
this.settings.setLanguage(this.settingsForm.value.displayLanguage)
|
this.settings.setLanguage(this.settingsForm.value.displayLanguage)
|
||||||
|
this.store.next(this.settingsForm.value)
|
||||||
this.documentListViewService.updatePageSize()
|
this.documentListViewService.updatePageSize()
|
||||||
this.settings.updateDarkModeSettings()
|
this.settings.updateDarkModeSettings()
|
||||||
this.toastService.showInfo($localize`Settings saved successfully.`)
|
this.toastService.showInfo($localize`Settings saved successfully.`)
|
||||||
|
29
src-ui/src/app/guards/dirty-form.guard.ts
Normal file
29
src-ui/src/app/guards/dirty-form.guard.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { DirtyCheckGuard } from '@ngneat/dirty-check-forms';
|
||||||
|
import { Observable, Subject } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class DirtyFormGuard extends DirtyCheckGuard {
|
||||||
|
constructor(private modalService: NgbModal) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmChanges(): Observable<boolean> {
|
||||||
|
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
|
||||||
|
modal.componentInstance.title = $localize`Unsaved Changes`
|
||||||
|
modal.componentInstance.messageBold = $localize`You have unsaved changes.`
|
||||||
|
modal.componentInstance.message = $localize`Are you sure you want to leave?`
|
||||||
|
modal.componentInstance.btnClass = "btn-warning"
|
||||||
|
modal.componentInstance.btnCaption = $localize`Leave page`
|
||||||
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
|
modal.componentInstance.buttonsEnabled = false
|
||||||
|
modal.close()
|
||||||
|
})
|
||||||
|
const subject = new Subject<boolean>()
|
||||||
|
modal.componentInstance.confirmSubject = subject
|
||||||
|
return subject.asObservable()
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,10 @@ import { Injectable } from '@angular/core';
|
|||||||
import { PaperlessDocument } from '../data/paperless-document';
|
import { PaperlessDocument } from '../data/paperless-document';
|
||||||
import { OPEN_DOCUMENT_SERVICE } from '../data/storage-keys';
|
import { OPEN_DOCUMENT_SERVICE } from '../data/storage-keys';
|
||||||
import { DocumentService } from './rest/document.service';
|
import { DocumentService } from './rest/document.service';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component';
|
||||||
|
import { Observable, Subject, of } from 'rxjs';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -10,7 +14,7 @@ export class OpenDocumentsService {
|
|||||||
|
|
||||||
private MAX_OPEN_DOCUMENTS = 5
|
private MAX_OPEN_DOCUMENTS = 5
|
||||||
|
|
||||||
constructor(private documentService: DocumentService) {
|
constructor(private documentService: DocumentService, private modalService: NgbModal) {
|
||||||
if (sessionStorage.getItem(OPEN_DOCUMENT_SERVICE.DOCUMENTS)) {
|
if (sessionStorage.getItem(OPEN_DOCUMENT_SERVICE.DOCUMENTS)) {
|
||||||
try {
|
try {
|
||||||
this.openDocuments = JSON.parse(sessionStorage.getItem(OPEN_DOCUMENT_SERVICE.DOCUMENTS))
|
this.openDocuments = JSON.parse(sessionStorage.getItem(OPEN_DOCUMENT_SERVICE.DOCUMENTS))
|
||||||
@ -22,6 +26,7 @@ export class OpenDocumentsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private openDocuments: PaperlessDocument[] = []
|
private openDocuments: PaperlessDocument[] = []
|
||||||
|
private dirtyDocuments: Set<number> = new Set<number>()
|
||||||
|
|
||||||
refreshDocument(id: number) {
|
refreshDocument(id: number) {
|
||||||
let index = this.openDocuments.findIndex(doc => doc.id == id)
|
let index = this.openDocuments.findIndex(doc => doc.id == id)
|
||||||
@ -53,17 +58,62 @@ export class OpenDocumentsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closeDocument(doc: PaperlessDocument) {
|
setDirty(documentId: number, dirty: boolean) {
|
||||||
|
if (dirty) this.dirtyDocuments.add(documentId)
|
||||||
|
else this.dirtyDocuments.delete(documentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDocument(doc: PaperlessDocument): Observable<boolean> {
|
||||||
let index = this.openDocuments.findIndex(d => d.id == doc.id)
|
let index = this.openDocuments.findIndex(d => d.id == doc.id)
|
||||||
if (index > -1) {
|
if (index == -1) return of(true);
|
||||||
|
if (!this.dirtyDocuments.has(doc.id)) {
|
||||||
this.openDocuments.splice(index, 1)
|
this.openDocuments.splice(index, 1)
|
||||||
this.save()
|
this.save()
|
||||||
|
return of(true)
|
||||||
|
} else {
|
||||||
|
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
|
||||||
|
modal.componentInstance.title = $localize`Unsaved Changes`
|
||||||
|
modal.componentInstance.messageBold = $localize`You have unsaved changes.`
|
||||||
|
modal.componentInstance.message = $localize`Are you sure you want to close this document?`
|
||||||
|
modal.componentInstance.btnClass = "btn-warning"
|
||||||
|
modal.componentInstance.btnCaption = $localize`Close document`
|
||||||
|
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
||||||
|
modal.componentInstance.buttonsEnabled = false
|
||||||
|
modal.close()
|
||||||
|
this.openDocuments.splice(index, 1)
|
||||||
|
this.dirtyDocuments.delete(doc.id)
|
||||||
|
this.save()
|
||||||
|
})
|
||||||
|
const subject = new Subject<boolean>()
|
||||||
|
modal.componentInstance.confirmSubject = subject
|
||||||
|
return subject.asObservable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closeAll() {
|
closeAll(): Observable<boolean> {
|
||||||
this.openDocuments.splice(0, this.openDocuments.length)
|
if (this.dirtyDocuments.size) {
|
||||||
this.save()
|
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
|
||||||
|
modal.componentInstance.title = $localize`Unsaved Changes`
|
||||||
|
modal.componentInstance.messageBold = $localize`You have unsaved changes.`
|
||||||
|
modal.componentInstance.message = $localize`Are you sure you want to close all documents?`
|
||||||
|
modal.componentInstance.btnClass = "btn-warning"
|
||||||
|
modal.componentInstance.btnCaption = $localize`Close documents`
|
||||||
|
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
||||||
|
modal.componentInstance.buttonsEnabled = false
|
||||||
|
modal.close()
|
||||||
|
this.openDocuments.splice(0, this.openDocuments.length)
|
||||||
|
this.dirtyDocuments.clear()
|
||||||
|
this.save()
|
||||||
|
})
|
||||||
|
const subject = new Subject<boolean>()
|
||||||
|
modal.componentInstance.confirmSubject = subject
|
||||||
|
return subject.asObservable()
|
||||||
|
} else {
|
||||||
|
this.openDocuments.splice(0, this.openDocuments.length)
|
||||||
|
this.dirtyDocuments.clear()
|
||||||
|
this.save()
|
||||||
|
return of(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user