import { PDFDocumentProxy, PdfViewerModule } from 'ng2-pdf-viewer'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { DeviceDetectorService } from 'ngx-device-detector'
-import { BehaviorSubject, Observable, Subject } from 'rxjs'
+import { BehaviorSubject, Observable, of, Subject } from 'rxjs'
import {
+ catchError,
debounceTime,
distinctUntilChanged,
filter,
}
}
+ private mapDocToForm(doc: Document): any {
+ return {
+ ...doc,
+ permissions_form: { owner: doc.owner, set_permissions: doc.permissions },
+ }
+ }
+
+ private mapFormToDoc(value: any): any {
+ const docValues = { ...value }
+ docValues['owner'] = value['permissions_form']?.owner
+ docValues['set_permissions'] = value['permissions_form']?.set_permissions
+ delete docValues['permissions_form']
+ return docValues
+ }
+
+ private prepareForm(doc: Document): void {
+ this.documentForm.reset(this.mapDocToForm(doc), { emitEvent: false })
+ if (!this.userCanEditDoc(doc)) {
+ this.documentForm.disable({ emitEvent: false })
+ } else {
+ this.documentForm.enable({ emitEvent: false })
+ }
+ if (doc.__changedFields) {
+ doc.__changedFields.forEach((field) => {
+ if (field === 'owner' || field === 'set_permissions') {
+ this.documentForm.get('permissions_form')?.markAsDirty()
+ } else {
+ this.documentForm.get(field)?.markAsDirty()
+ }
+ })
+ }
+ }
+
+ private setupDirtyTracking(
+ currentDocument: Document,
+ originalDocument: Document
+ ): void {
+ this.store = new BehaviorSubject({
+ title: originalDocument.title,
+ content: originalDocument.content,
+ created: originalDocument.created,
+ correspondent: originalDocument.correspondent,
+ document_type: originalDocument.document_type,
+ storage_path: originalDocument.storage_path,
+ archive_serial_number: originalDocument.archive_serial_number,
+ tags: [...originalDocument.tags],
+ permissions_form: {
+ owner: originalDocument.owner,
+ set_permissions: originalDocument.permissions,
+ },
+ custom_fields: [...originalDocument.custom_fields],
+ })
+ this.isDirty$ = dirtyCheck(this.documentForm, this.store.asObservable())
+ this.isDirty$
+ .pipe(
+ takeUntil(this.unsubscribeNotifier),
+ takeUntil(this.docChangeNotifier)
+ )
+ .subscribe((dirty) =>
+ this.openDocumentService.setDirty(
+ currentDocument,
+ dirty,
+ this.getChangedFields()
+ )
+ )
+ }
+
+ private loadDocument(documentId: number): void {
+ this.previewUrl = this.documentsService.getPreviewUrl(documentId)
+ this.http
+ .get(this.previewUrl, { responseType: 'text' })
+ .pipe(
+ first(),
+ takeUntil(this.unsubscribeNotifier),
+ takeUntil(this.docChangeNotifier)
+ )
+ .subscribe({
+ next: (res) => (this.previewText = res.toString()),
+ error: (err) =>
+ (this.previewText = $localize`An error occurred loading content: ${
+ err.message ?? err.toString()
+ }`),
+ })
+ this.thumbUrl = this.documentsService.getThumbUrl(documentId)
+ this.documentsService
+ .get(documentId)
+ .pipe(
+ catchError(() => {
+ // 404 is handled in the subscribe below
+ return of(null)
+ }),
+ first(),
+ takeUntil(this.unsubscribeNotifier),
+ takeUntil(this.docChangeNotifier)
+ )
+ .subscribe({
+ next: (doc) => {
+ if (!doc) {
+ this.router.navigate(['404'], { replaceUrl: true })
+ return
+ }
+ this.documentId = doc.id
+ this.suggestions = null
+ const openDocument = this.openDocumentService.getOpenDocument(
+ this.documentId
+ )
+ const useDoc = openDocument || doc
+ if (openDocument) {
+ if (
+ new Date(doc.modified) > new Date(openDocument.modified) &&
+ !this.modalService.hasOpenModals()
+ ) {
+ const modal = this.modalService.open(ConfirmDialogComponent)
+ modal.componentInstance.title = $localize`Document changes detected`
+ modal.componentInstance.messageBold = $localize`The version of this document in your browser session appears older than the existing version.`
+ modal.componentInstance.message = $localize`Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.`
+ modal.componentInstance.cancelBtnClass = 'visually-hidden'
+ modal.componentInstance.btnCaption = $localize`Ok`
+ modal.componentInstance.confirmClicked.subscribe(() =>
+ modal.close()
+ )
+ }
+ } else {
+ this.openDocumentService
+ .openDocument(doc)
+ .pipe(
+ first(),
+ takeUntil(this.unsubscribeNotifier),
+ takeUntil(this.docChangeNotifier)
+ )
+ .subscribe()
+ }
+ this.updateComponent(useDoc)
+ this.titleSubject
+ .pipe(
+ debounceTime(1000),
+ distinctUntilChanged(),
+ takeUntil(this.docChangeNotifier),
+ takeUntil(this.unsubscribeNotifier)
+ )
+ .subscribe((titleValue) => {
+ if (titleValue !== this.titleInput.value) return
+ this.title = titleValue
+ this.documentForm.patchValue({ title: titleValue })
+ })
+ this.setupDirtyTracking(useDoc, doc)
+ },
+ })
+ }
+
ngOnInit(): void {
this.setZoom(this.settings.get(SETTINGS_KEYS.PDF_VIEWER_ZOOM_SETTING))
this.documentForm.valueChanges
.pipe(takeUntil(this.unsubscribeNotifier))
- .subscribe(() => {
+ .subscribe((values) => {
this.error = null
- const docValues = Object.assign({}, this.documentForm.value)
- docValues['owner'] =
- this.documentForm.get('permissions_form').value['owner']
- docValues['set_permissions'] =
- this.documentForm.get('permissions_form').value['set_permissions']
- delete docValues['permissions_form']
- Object.assign(this.document, docValues)
+ Object.assign(this.document, this.mapFormToDoc(values))
})
if (
this.route.paramMap
.pipe(
- filter((paramMap) => {
- // only init when changing docs & section is set
- return (
+ filter(
+ (paramMap) =>
+paramMap.get('id') !== this.documentId &&
paramMap.get('section')?.length > 0
- )
- }),
- takeUntil(this.unsubscribeNotifier),
- switchMap((paramMap) => {
- const documentId = +paramMap.get('id')
- this.docChangeNotifier.next(documentId)
- // Dont wait to get the preview
- this.previewUrl = this.documentsService.getPreviewUrl(documentId)
- this.http.get(this.previewUrl, { responseType: 'text' }).subscribe({
- next: (res) => {
- this.previewText = res.toString()
- },
- error: (err) => {
- this.previewText = $localize`An error occurred loading content: ${
- err.message ?? err.toString()
- }`
- },
- })
- this.thumbUrl = this.documentsService.getThumbUrl(documentId)
- return this.documentsService.get(documentId)
- })
+ ),
+ takeUntil(this.unsubscribeNotifier)
)
- .pipe(
- switchMap((doc) => {
- this.documentId = doc.id
- this.suggestions = null
- const openDocument = this.openDocumentService.getOpenDocument(
- this.documentId
- )
-
- if (openDocument) {
- if (
- new Date(doc.modified) > new Date(openDocument.modified) &&
- !this.modalService.hasOpenModals()
- ) {
- let modal = this.modalService.open(ConfirmDialogComponent)
- modal.componentInstance.title = $localize`Document changes detected`
- modal.componentInstance.messageBold = $localize`The version of this document in your browser session appears older than the existing version.`
- modal.componentInstance.message = $localize`Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.`
- modal.componentInstance.cancelBtnClass = 'visually-hidden'
- modal.componentInstance.btnCaption = $localize`Ok`
- modal.componentInstance.confirmClicked.subscribe(() =>
- modal.close()
- )
- }
-
- // Prevent mutating stale form values into the next document: only sync if it still matches the active document.
- if (
- this.documentForm.dirty &&
- (this.document?.id === openDocument.id || !this.document)
- ) {
- Object.assign(openDocument, this.documentForm.value)
- openDocument['owner'] =
- this.documentForm.get('permissions_form').value['owner']
- openDocument['permissions'] =
- this.documentForm.get('permissions_form').value[
- 'set_permissions'
- ]
- delete openDocument['permissions_form']
- }
- if (openDocument.__changedFields) {
- openDocument.__changedFields.forEach((field) => {
- if (field === 'owner' || field === 'set_permissions') {
- this.documentForm.get('permissions_form').markAsDirty()
- } else {
- this.documentForm.get(field)?.markAsDirty()
- }
- })
- }
- this.updateComponent(openDocument)
- } else {
- this.openDocumentService.openDocument(doc)
- this.updateComponent(doc)
- }
-
- this.titleSubject
- .pipe(
- debounceTime(1000),
- distinctUntilChanged(),
- takeUntil(this.docChangeNotifier),
- takeUntil(this.unsubscribeNotifier)
- )
- .subscribe({
- next: (titleValue) => {
- // In the rare case when the field changed just after debounced event was fired.
- // We dont want to overwrite what's actually in the text field, so just return
- if (titleValue !== this.titleInput.value) return
-
- this.title = titleValue
- this.documentForm.patchValue({ title: titleValue })
- },
- complete: () => {
- // doc changed so we manually check dirty in case title was changed
- if (
- this.store.getValue().title !==
- this.documentForm.get('title').value
- ) {
- this.openDocumentService.setDirty(
- doc,
- true,
- this.getChangedFields()
- )
- }
- },
- })
-
- // Initialize dirtyCheck
- this.store = new BehaviorSubject({
- title: doc.title,
- content: doc.content,
- created: doc.created,
- correspondent: doc.correspondent,
- document_type: doc.document_type,
- storage_path: doc.storage_path,
- archive_serial_number: doc.archive_serial_number,
- tags: [...doc.tags],
- permissions_form: {
- owner: doc.owner,
- set_permissions: doc.permissions,
- },
- custom_fields: [...doc.custom_fields],
- })
-
- this.isDirty$ = dirtyCheck(
- this.documentForm,
- this.store.asObservable()
- )
+ .subscribe((paramMap) => {
+ const documentId = +paramMap.get('id')
+ this.docChangeNotifier.next(documentId)
+ this.loadDocument(documentId)
+ })
- return this.isDirty$.pipe(
- takeUntil(this.unsubscribeNotifier),
- map((dirty) => ({ doc, dirty }))
+ this.route.paramMap
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe((paramMap) => {
+ const section = paramMap.get('section')
+ if (section) {
+ const navIDKey: string = Object.keys(DocumentDetailNavIDs).find(
+ (navID) => navID.toLowerCase() == section
)
- })
- )
- .subscribe({
- next: ({ doc, dirty }) => {
- this.openDocumentService.setDirty(doc, dirty, this.getChangedFields())
- },
- error: (error) => {
- this.router.navigate(['404'], {
+ if (navIDKey) {
+ this.activeNavID = DocumentDetailNavIDs[navIDKey]
+ }
+ } else if (paramMap.get('id')) {
+ this.router.navigate(['documents', +paramMap.get('id'), 'details'], {
replaceUrl: true,
})
- },
- })
-
- this.route.paramMap.subscribe((paramMap) => {
- const section = paramMap.get('section')
- if (section) {
- const navIDKey: string = Object.keys(DocumentDetailNavIDs).find(
- (navID) => navID.toLowerCase() == section
- )
- if (navIDKey) {
- this.activeNavID = DocumentDetailNavIDs[navIDKey]
}
- } else if (paramMap.get('id')) {
- this.router.navigate(['documents', +paramMap.get('id'), 'details'], {
- replaceUrl: true,
- })
- }
- })
+ })
this.hotKeyService
.addShortcut({
})
}
this.title = this.documentTitlePipe.transform(doc.title)
- const docFormValues = Object.assign({}, doc)
- docFormValues['permissions_form'] = {
- owner: doc.owner,
- set_permissions: doc.permissions,
- }
-
- this.documentForm.patchValue(docFormValues, { emitEvent: false })
- if (!this.userCanEdit) this.documentForm.disable()
- setTimeout(() => {
- // check again after a tick in case form was dirty
- if (!this.userCanEdit) this.documentForm.disable()
- else this.documentForm.enable()
- }, 10)
+ this.prepareForm(doc)
}
get customFieldFormFields(): FormArray {
discard() {
this.documentsService
.get(this.documentId)
- .pipe(first())
+ .pipe(
+ first(),
+ takeUntil(this.unsubscribeNotifier),
+ takeUntil(this.docChangeNotifier)
+ )
.subscribe({
next: (doc) => {
Object.assign(this.document, doc)
.patch(this.getChangedFields())
.pipe(
switchMap((updateResult) => {
- return this.documentListViewService
- .getNext(this.documentId)
- .pipe(map((nextDocId) => ({ nextDocId, updateResult })))
+ return this.documentListViewService.getNext(this.documentId).pipe(
+ map((nextDocId) => ({ nextDocId, updateResult })),
+ takeUntil(this.unsubscribeNotifier)
+ )
})
)
.pipe(
return this.openDocumentService
.closeDocument(this.document)
.pipe(
- map((closeResult) => ({ updateResult, nextDocId, closeResult }))
+ map(
+ (closeResult) => ({ updateResult, nextDocId, closeResult }),
+ takeUntil(this.unsubscribeNotifier)
+ )
)
}
})
) {
doc.owner = this.store.value.permissions_form.owner
}
+ return !this.document || this.userCanEditDoc(doc)
+ }
+
+ private userCanEditDoc(doc: Document): boolean {
return (
- !this.document ||
- (this.permissionsService.currentUserCan(
+ this.permissionsService.currentUserCan(
PermissionAction.Change,
PermissionType.Document
) &&
- this.permissionsService.currentUserHasObjectPermissions(
- PermissionAction.Change,
- doc
- ))
+ this.permissionsService.currentUserHasObjectPermissions(
+ PermissionAction.Change,
+ doc
+ )
)
}
}
private tryRenderTiff() {
- this.http.get(this.previewUrl, { responseType: 'arraybuffer' }).subscribe({
- next: (res) => {
- /* istanbul ignore next */
- try {
- // See UTIF.js > _imgLoaded
- const tiffIfds: any[] = UTIF.decode(res)
- var vsns = tiffIfds,
- ma = 0,
- page = vsns[0]
- if (tiffIfds[0].subIFD) vsns = vsns.concat(tiffIfds[0].subIFD)
- for (var i = 0; i < vsns.length; i++) {
- var img = vsns[i]
- if (img['t258'] == null || img['t258'].length < 3) continue
- var ar = img['t256'] * img['t257']
- if (ar > ma) {
- ma = ar
- page = img
+ this.http
+ .get(this.previewUrl, { responseType: 'arraybuffer' })
+ .pipe(
+ first(),
+ takeUntil(this.unsubscribeNotifier),
+ takeUntil(this.docChangeNotifier)
+ )
+ .subscribe({
+ next: (res) => {
+ /* istanbul ignore next */
+ try {
+ // See UTIF.js > _imgLoaded
+ const tiffIfds: any[] = UTIF.decode(res)
+ var vsns = tiffIfds,
+ ma = 0,
+ page = vsns[0]
+ if (tiffIfds[0].subIFD) vsns = vsns.concat(tiffIfds[0].subIFD)
+ for (var i = 0; i < vsns.length; i++) {
+ var img = vsns[i]
+ if (img['t258'] == null || img['t258'].length < 3) continue
+ var ar = img['t256'] * img['t257']
+ if (ar > ma) {
+ ma = ar
+ page = img
+ }
}
+ UTIF.decodeImage(res, page, tiffIfds)
+ const rgba = UTIF.toRGBA8(page)
+ const { width: w, height: h } = page
+ var cnv = document.createElement('canvas')
+ cnv.width = w
+ cnv.height = h
+ var ctx = cnv.getContext('2d'),
+ imgd = ctx.createImageData(w, h)
+ for (var i = 0; i < rgba.length; i++) imgd.data[i] = rgba[i]
+ ctx.putImageData(imgd, 0, 0)
+ this.tiffURL = cnv.toDataURL()
+ } catch (err) {
+ this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}`
}
- UTIF.decodeImage(res, page, tiffIfds)
- const rgba = UTIF.toRGBA8(page)
- const { width: w, height: h } = page
- var cnv = document.createElement('canvas')
- cnv.width = w
- cnv.height = h
- var ctx = cnv.getContext('2d'),
- imgd = ctx.createImageData(w, h)
- for (var i = 0; i < rgba.length; i++) imgd.data[i] = rgba[i]
- ctx.putImageData(imgd, 0, 0)
- this.tiffURL = cnv.toDataURL()
- } catch (err) {
+ },
+ error: (err) => {
this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}`
- }
- },
- error: (err) => {
- this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}`
- },
- })
+ },
+ })
}
}