paperless-ngx/src-ui/src/app/components/document-detail/document-detail.component.ts

316 lines
12 KiB
TypeScript
Raw Normal View History

2022-02-15 23:43:54 -08:00
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
2020-10-27 01:10:18 +01:00
import { FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { NgbModal, NgbNav } from '@ng-bootstrap/ng-bootstrap';
2020-10-27 01:10:18 +01:00
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata';
2020-10-27 01:10:18 +01:00
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
2020-12-16 16:44:54 +01:00
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe';
2020-10-27 01:10:18 +01:00
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
import { DocumentTypeService } from 'src/app/services/rest/document-type.service';
import { DocumentService } from 'src/app/services/rest/document.service';
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component';
2020-10-27 01:10:18 +01:00
import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component';
import { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component';
2020-12-18 14:31:09 -08:00
import { PDFDocumentProxy } from 'ng2-pdf-viewer';
2021-01-03 21:44:53 +01:00
import { ToastService } from 'src/app/services/toast.service';
2021-01-05 22:11:42 +01:00
import { TextComponent } from '../common/input/text/text.component';
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
2021-01-25 23:00:57 -08:00
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms';
2022-02-15 23:43:54 -08:00
import { Observable, Subject, BehaviorSubject } from 'rxjs';
import { first, takeUntil, switchMap, map, debounceTime, distinctUntilChanged } from 'rxjs/operators';
2021-01-29 16:48:51 +01:00
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions';
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
2020-10-30 22:46:43 +01:00
2020-10-27 01:10:18 +01:00
@Component({
selector: 'app-document-detail',
templateUrl: './document-detail.component.html',
styleUrls: ['./document-detail.component.scss']
2020-10-27 01:10:18 +01:00
})
2022-02-15 23:43:54 -08:00
export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponent {
2020-10-27 01:10:18 +01:00
2021-01-05 22:11:42 +01:00
@ViewChild("inputTitle")
titleInput: TextComponent
expandOriginalMetadata = false
expandArchivedMetadata = false
error: any
networkActive = false
2020-12-08 15:28:09 +01:00
2020-10-27 01:10:18 +01:00
documentId: number
document: PaperlessDocument
metadata: PaperlessDocumentMetadata
2021-01-29 16:48:51 +01:00
suggestions: PaperlessDocumentSuggestions
2020-10-27 01:10:18 +01:00
title: string
titleSubject: Subject<string> = new Subject()
2020-10-27 01:10:18 +01:00
previewUrl: string
downloadUrl: string
downloadOriginalUrl: string
2020-10-27 01:10:18 +01:00
correspondents: PaperlessCorrespondent[]
documentTypes: PaperlessDocumentType[]
documentForm: FormGroup = new FormGroup({
title: new FormControl(''),
content: new FormControl(''),
2020-11-04 13:10:23 +01:00
created: new FormControl(),
correspondent: new FormControl(),
document_type: new FormControl(),
2020-10-27 01:10:18 +01:00
archive_serial_number: new FormControl(),
tags: new FormControl([])
2020-10-27 01:10:18 +01:00
})
2020-12-18 14:47:06 -08:00
previewCurrentPage: number = 1
2020-12-19 01:15:40 +01:00
previewNumPages: number = 1
2020-12-18 14:31:09 -08:00
2021-01-25 23:00:57 -08:00
store: BehaviorSubject<any>
isDirty$: Observable<boolean>
2022-02-15 23:43:54 -08:00
unsubscribeNotifier: Subject<any> = new Subject()
2021-01-25 23:00:57 -08:00
@ViewChild('nav') nav: NgbNav
2020-12-17 07:33:20 -08:00
@ViewChild('pdfPreview') set pdfPreview(element) {
// this gets called when compontent added or removed from DOM
2022-02-16 12:00:19 -08:00
if (element && element.nativeElement.offsetParent !== null && this.nav?.activeId == 4) { // its visible
setTimeout(()=> this.nav?.select(1));
}
}
2020-10-27 01:10:18 +01:00
constructor(
2020-12-18 14:31:09 -08:00
private documentsService: DocumentService,
2020-10-27 01:10:18 +01:00
private route: ActivatedRoute,
private correspondentService: CorrespondentService,
private documentTypeService: DocumentTypeService,
private router: Router,
private modalService: NgbModal,
private openDocumentService: OpenDocumentsService,
2020-12-16 16:44:54 +01:00
private documentListViewService: DocumentListViewService,
2021-01-03 21:44:53 +01:00
private documentTitlePipe: DocumentTitlePipe,
private toastService: ToastService,
private settings: SettingsService) {
this.titleSubject.pipe(
debounceTime(1000),
distinctUntilChanged(),
takeUntil(this.unsubscribeNotifier)
).subscribe(titleValue => {
this.title = titleValue
this.documentForm.patchValue({'title': titleValue})
})
}
titleKeyUp(event) {
this.titleSubject.next(event.target?.value)
}
get useNativePdfViewer(): boolean {
return this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER)
}
2020-10-27 01:10:18 +01:00
getContentType() {
return this.metadata?.has_archive_version ? 'application/pdf' : this.metadata?.original_mime_type
}
2020-10-27 01:10:18 +01:00
ngOnInit(): void {
2022-02-15 23:43:54 -08:00
this.documentForm.valueChanges.pipe(takeUntil(this.unsubscribeNotifier)).subscribe(wow => {
2020-11-04 13:10:23 +01:00
Object.assign(this.document, this.documentForm.value)
})
2022-02-15 23:43:54 -08:00
this.correspondentService.listAll().pipe(first()).subscribe(result => this.correspondents = result.results)
this.documentTypeService.listAll().pipe(first()).subscribe(result => this.documentTypes = result.results)
2020-10-27 01:10:18 +01:00
2022-02-16 02:27:17 -08:00
this.route.paramMap.pipe(switchMap(paramMap => {
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.downloadUrl = this.documentsService.getDownloadUrl(this.documentId)
this.downloadOriginalUrl = this.documentsService.getDownloadUrl(this.documentId, true)
2021-01-29 16:48:51 +01:00
this.suggestions = null
if (this.openDocumentService.getOpenDocument(this.documentId)) {
this.updateComponent(this.openDocumentService.getOpenDocument(this.documentId))
} else {
this.openDocumentService.openDocument(doc)
this.updateComponent(doc)
}
2020-10-27 01:10:18 +01:00
2022-02-16 02:27:17 -08:00
// 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]
2022-02-16 02:27:17 -08:00
})
this.isDirty$ = dirtyCheck(this.documentForm, this.store.asObservable())
return this.isDirty$.pipe(map(dirty => ({doc, dirty})))
2022-02-16 02:27:17 -08:00
}))
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(({doc, dirty}) => {
this.openDocumentService.setDirty(doc.id, dirty)
2022-02-16 02:27:17 -08:00
}, error => {this.router.navigate(['404'])})
2020-10-27 01:10:18 +01:00
}
2022-02-15 23:43:54 -08:00
ngOnDestroy() : void {
this.unsubscribeNotifier.next();
this.unsubscribeNotifier.complete();
}
2020-11-04 13:10:23 +01:00
updateComponent(doc: PaperlessDocument) {
this.document = doc
2022-02-15 23:43:54 -08:00
this.documentsService.getMetadata(doc.id).pipe(first()).subscribe(result => {
this.metadata = result
2021-02-03 15:22:20 +01:00
}, error => {
this.metadata = null
})
2022-02-15 23:43:54 -08:00
this.documentsService.getSuggestions(doc.id).pipe(first()).subscribe(result => {
2021-01-29 16:48:51 +01:00
this.suggestions = result
2021-02-03 15:22:20 +01:00
}, error => {
this.suggestions = null
})
2020-12-16 16:44:54 +01:00
this.title = this.documentTitlePipe.transform(doc.title)
this.documentForm.patchValue(doc)
2020-11-04 13:10:23 +01:00
}
createDocumentType(newName: string) {
2020-10-27 01:10:18 +01:00
var modal = this.modalService.open(DocumentTypeEditDialogComponent, {backdrop: 'static'})
modal.componentInstance.dialogMode = 'create'
if (newName) modal.componentInstance.object = { name: newName }
2022-02-16 02:27:17 -08:00
modal.componentInstance.success.pipe(switchMap(newDocumentType => {
return this.documentTypeService.listAll().pipe(map(documentTypes => ({newDocumentType, documentTypes})))
}))
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(({newDocumentType, documentTypes}) => {
this.documentTypes = documentTypes.results
this.documentForm.get('document_type').setValue(newDocumentType.id)
2020-10-27 01:10:18 +01:00
})
}
createCorrespondent(newName: string) {
2020-10-27 01:10:18 +01:00
var modal = this.modalService.open(CorrespondentEditDialogComponent, {backdrop: 'static'})
modal.componentInstance.dialogMode = 'create'
if (newName) modal.componentInstance.object = { name: newName }
2022-02-16 02:27:17 -08:00
modal.componentInstance.success.pipe(switchMap(newCorrespondent => {
return this.correspondentService.listAll().pipe(map(correspondents => ({newCorrespondent, correspondents})))
}))
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(({newCorrespondent, correspondents}) => {
this.correspondents = correspondents.results
this.documentForm.get('correspondent').setValue(newCorrespondent.id)
2020-10-27 01:10:18 +01:00
})
}
discard() {
2022-02-15 23:43:54 -08:00
this.documentsService.get(this.documentId).pipe(first()).subscribe(doc => {
Object.assign(this.document, doc)
this.title = doc.title
this.documentForm.patchValue(doc)
}, error => {this.router.navigate(['404'])})
2020-10-27 01:10:18 +01:00
}
2020-12-18 14:31:09 -08:00
save() {
this.networkActive = true
this.store.next(this.documentForm.value)
2022-02-15 23:43:54 -08:00
this.documentsService.update(this.document).pipe(first()).subscribe(result => {
2020-10-27 01:10:18 +01:00
this.close()
this.networkActive = false
this.error = null
}, error => {
this.networkActive = false
this.error = error.error
2020-10-27 01:10:18 +01:00
})
}
saveEditNext() {
this.networkActive = true
this.store.next(this.documentForm.value)
2022-02-16 02:27:17 -08:00
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})))
2022-02-16 02:27:17 -08:00
}))
.pipe(first())
2022-02-16 02:27:17 -08:00
.subscribe(({updateResult, nextDocId, closeResult}) => {
this.error = null
this.networkActive = false
if (closeResult && updateResult && nextDocId) {
2022-02-16 02:27:17 -08:00
this.router.navigate(['documents', nextDocId])
this.titleInput?.focus()
2022-02-16 02:27:17 -08:00
}
}, error => {
this.networkActive = false
this.error = error.error
2020-10-27 01:10:18 +01:00
})
}
2022-02-16 01:06:22 -08:00
close() {
this.openDocumentService.closeDocument(this.document).pipe(first()).subscribe(closed => {
if (!closed) return;
if (this.documentListViewService.activeSavedViewId) {
this.router.navigate(['view', this.documentListViewService.activeSavedViewId])
2021-01-26 20:46:28 -08:00
} else {
2022-02-16 01:06:22 -08:00
this.router.navigate(['documents'])
2021-01-26 20:46:28 -08:00
}
})
}
2020-10-27 01:10:18 +01:00
delete() {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
modal.componentInstance.title = $localize`Confirm delete`
2020-12-30 16:29:22 +01:00
modal.componentInstance.messageBold = $localize`Do you really want to delete document "${this.document.title}"?`
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.btnCaption = $localize`Delete document`
2022-02-16 02:27:17 -08:00
modal.componentInstance.confirmClicked.pipe(switchMap(() => {
2021-01-03 21:44:53 +01:00
modal.componentInstance.buttonsEnabled = false
2022-02-16 02:27:17 -08:00
return this.documentsService.delete(this.document)
}))
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
modal.close()
this.close()
}, error => {
this.toastService.showError($localize`Error deleting document: ${JSON.stringify(error)}`)
modal.componentInstance.buttonsEnabled = true
2020-10-27 01:10:18 +01:00
})
}
2020-12-17 21:36:21 +01:00
moreLike() {
this.documentListViewService.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: this.documentId.toString()}])
2020-12-17 21:36:21 +01:00
}
2020-10-27 01:10:18 +01:00
hasNext() {
return this.documentListViewService.hasNext(this.documentId)
}
2020-12-18 14:31:09 -08:00
hasPrevious() {
return this.documentListViewService.hasPrevious(this.documentId)
}
nextDoc() {
this.documentListViewService.getNext(this.document.id).subscribe((nextDocId: number) => {
this.router.navigate(['documents', nextDocId])
})
}
previousDoc () {
this.documentListViewService.getPrevious(this.document.id).subscribe((prevDocId: number) => {
this.router.navigate(['documents', prevDocId])
})
}
2020-12-18 14:31:09 -08:00
pdfPreviewLoaded(pdf: PDFDocumentProxy) {
this.previewNumPages = pdf.numPages
}
2020-10-27 01:10:18 +01:00
}