Feature-parity migration to ngx-extended-pdf-viewer

This commit is contained in:
shamoon 2023-11-24 12:20:54 -08:00
parent 5297626816
commit 81bbc8405a
8 changed files with 107 additions and 104 deletions

View File

@ -65,18 +65,15 @@
"src/assets", "src/assets",
"src/manifest.webmanifest", "src/manifest.webmanifest",
{ {
"glob": "pdf.worker.min.js", "glob": "**/*",
"input": "node_modules/pdfjs-dist/build/", "input": "node_modules/ngx-extended-pdf-viewer/assets/",
"output": "/assets/js/" "output": "/assets/"
} }
], ],
"styles": [ "styles": [
"src/styles.scss" "src/styles.scss"
], ],
"scripts": [], "scripts": [],
"allowedCommonJsDependencies": [
"ng2-pdf-viewer"
],
"vendorChunk": true, "vendorChunk": true,
"extractLicenses": false, "extractLicenses": false,
"buildOptimizer": false, "buildOptimizer": false,

View File

@ -25,9 +25,9 @@
"bootstrap": "^5.3.2", "bootstrap": "^5.3.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"mime-names": "^1.0.0", "mime-names": "^1.0.0",
"ng2-pdf-viewer": "^10.0.0",
"ngx-color": "^9.0.0", "ngx-color": "^9.0.0",
"ngx-cookie-service": "^16.0.1", "ngx-cookie-service": "^16.0.1",
"ngx-extended-pdf-viewer": "^18.1.9",
"ngx-file-drop": "^16.0.0", "ngx-file-drop": "^16.0.0",
"ngx-ui-tour-ng-bootstrap": "^13.0.6", "ngx-ui-tour-ng-bootstrap": "^13.0.6",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
@ -8863,12 +8863,6 @@
"url": "https://github.com/fb55/domhandler?sponsor=1" "url": "https://github.com/fb55/domhandler?sponsor=1"
} }
}, },
"node_modules/dommatrix": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz",
"integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==",
"deprecated": "dommatrix is no longer maintained. Please use @thednp/dommatrix."
},
"node_modules/domutils": { "node_modules/domutils": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
@ -13527,6 +13521,11 @@
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"dev": true "dev": true
}, },
"node_modules/lodash.deburr": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz",
"integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ=="
},
"node_modules/lodash.memoize": { "node_modules/lodash.memoize": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -14209,18 +14208,6 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true "dev": true
}, },
"node_modules/ng2-pdf-viewer": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/ng2-pdf-viewer/-/ng2-pdf-viewer-10.0.0.tgz",
"integrity": "sha512-zEefcAsTpDoxFceQYs3ycPMaUAkt5UX4OcTstVQoNqRK6w+vOY+V8z8aFCuBwnt+7iN1EHaIpquOf4S9mWc04g==",
"dependencies": {
"pdfjs-dist": "~2.16.105",
"tslib": "^2.3.0"
},
"peerDependencies": {
"pdfjs-dist": "~2.16.105"
}
},
"node_modules/ngx-color": { "node_modules/ngx-color": {
"version": "9.0.0", "version": "9.0.0",
"resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-9.0.0.tgz", "resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-9.0.0.tgz",
@ -14247,6 +14234,19 @@
"@angular/core": "^16.0.0" "@angular/core": "^16.0.0"
} }
}, },
"node_modules/ngx-extended-pdf-viewer": {
"version": "18.1.9",
"resolved": "https://registry.npmjs.org/ngx-extended-pdf-viewer/-/ngx-extended-pdf-viewer-18.1.9.tgz",
"integrity": "sha512-puISS6h1JoHObo0BZK68EhlWlI215AWP5RJ5D86yuWiBgVYeNUa8JrEVnaJtQ/bI6WbfvleaBe8NBwKnM0Bqsw==",
"dependencies": {
"lodash.deburr": "^4.1.0",
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": ">=12.0.0 <18.0.0",
"@angular/core": ">=12.0.0 <18.0.0"
}
},
"node_modules/ngx-file-drop": { "node_modules/ngx-file-drop": {
"version": "16.0.0", "version": "16.0.0",
"resolved": "https://registry.npmjs.org/ngx-file-drop/-/ngx-file-drop-16.0.0.tgz", "resolved": "https://registry.npmjs.org/ngx-file-drop/-/ngx-file-drop-16.0.0.tgz",
@ -15456,23 +15456,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/pdfjs-dist": {
"version": "2.16.105",
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz",
"integrity": "sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==",
"dependencies": {
"dommatrix": "^1.0.3",
"web-streams-polyfill": "^3.2.1"
},
"peerDependencies": {
"worker-loader": "^3.0.8"
},
"peerDependenciesMeta": {
"worker-loader": {
"optional": true
}
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -18118,14 +18101,6 @@
"defaults": "^1.0.3" "defaults": "^1.0.3"
} }
}, },
"node_modules/web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
"engines": {
"node": ">= 8"
}
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",

View File

@ -27,9 +27,9 @@
"bootstrap": "^5.3.2", "bootstrap": "^5.3.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"mime-names": "^1.0.0", "mime-names": "^1.0.0",
"ng2-pdf-viewer": "^10.0.0",
"ngx-color": "^9.0.0", "ngx-color": "^9.0.0",
"ngx-cookie-service": "^16.0.1", "ngx-cookie-service": "^16.0.1",
"ngx-extended-pdf-viewer": "^18.1.9",
"ngx-file-drop": "^16.0.0", "ngx-file-drop": "^16.0.0",
"ngx-ui-tour-ng-bootstrap": "^13.0.6", "ngx-ui-tour-ng-bootstrap": "^13.0.6",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",

View File

@ -51,7 +51,7 @@ import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-v
import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component' import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component'
import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component' import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component'
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component' import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component'
import { PdfViewerModule } from 'ng2-pdf-viewer' import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer'
import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component' import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component'
import { YesNoPipe } from './pipes/yes-no.pipe' import { YesNoPipe } from './pipes/yes-no.pipe'
import { FileSizePipe } from './pipes/file-size.pipe' import { FileSizePipe } from './pipes/file-size.pipe'
@ -265,11 +265,11 @@ function initializeApp(settings: SettingsService) {
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
NgxFileDropModule, NgxFileDropModule,
PdfViewerModule,
NgSelectModule, NgSelectModule,
ColorSliderModule, ColorSliderModule,
TourNgBootstrapModule, TourNgBootstrapModule,
DragDropModule, DragDropModule,
NgxExtendedPdfViewerModule,
], ],
providers: [ providers: [
{ {

View File

@ -16,7 +16,7 @@
<p class="mb-0">{{toast.content}}</p> <p class="mb-0">{{toast.content}}</p>
<details *ngIf="toast.error"> <details *ngIf="toast.error">
<div class="mt-2"> <div class="mt-2">
<dl class="row mb-0" *ngIf="isDetailedError(toast.error)"> <dl class="row mb-0" *ngIf="isDetailedError(toast.error); else simpleError">
<dt class="col-sm-3 fw-normal text-end">URL</dt> <dt class="col-sm-3 fw-normal text-end">URL</dt>
<dd class="col-sm-9">{{ toast.error.url }}</dd> <dd class="col-sm-9">{{ toast.error.url }}</dd>
<dt class="col-sm-3 fw-normal text-end" i18n>Status</dt> <dt class="col-sm-3 fw-normal text-end" i18n>Status</dt>
@ -24,6 +24,12 @@
<dt class="col-sm-3 fw-normal text-end" i18n>Error</dt> <dt class="col-sm-3 fw-normal text-end" i18n>Error</dt>
<dd class="col-sm-9">{{ getErrorText(toast.error) }}</dd> <dd class="col-sm-9">{{ getErrorText(toast.error) }}</dd>
</dl> </dl>
<ng-template #simpleError>
<dl class="row mb-0">
<dt class="col-sm-3 fw-normal text-end" i18n>Error</dt>
<dd class="col-sm-9">{{ getErrorText(toast.error) }}</dd>
</dl>
</ng-template>
<div class="row"> <div class="row">
<div class="col offset-sm-3"> <div class="col offset-sm-3">
<button class="btn btn-sm btn-outline-dark" (click)="copyError(toast.error)"> <button class="btn btn-sm btn-outline-dark" (click)="copyError(toast.error)">

View File

@ -189,24 +189,7 @@
<li [ngbNavItem]="DocumentDetailNavIDs.Preview" class="d-md-none"> <li [ngbNavItem]="DocumentDetailNavIDs.Preview" class="d-md-none">
<a ngbNavLink i18n>Preview</a> <a ngbNavLink i18n>Preview</a>
<ng-template ngbNavContent *ngIf="!pdfPreview.offsetParent"> <ng-template ngbNavContent *ngIf="!pdfPreview.offsetParent">
<div class="position-relative"> <ng-container *ngTemplateOutlet="previewContent"></ng-container>
<ng-container *ngIf="getContentType() === 'application/pdf'">
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
<pdf-viewer [src]="{ url: previewUrl, password: password }" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (error)="onError($event)" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
</div>
<ng-template #nativePdfViewer>
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
</ng-template>
</ng-container>
<ng-container *ngIf="getContentType() === 'text/plain'">
<object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-light overflow-auto" width="100%"></object>
</ng-container>
<div *ngIf="requiresPassword" class="password-prompt">
<form>
<input autocomplete="" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
</form>
</div>
</div>
</ng-template> </ng-template>
</li> </li>
@ -233,9 +216,30 @@
</div> </div>
<div class="col-md-6 col-xl-8 mb-3 d-none d-md-block position-relative" #pdfPreview> <div class="col-md-6 col-xl-8 mb-3 d-none d-md-block position-relative" #pdfPreview>
<ng-container *ngTemplateOutlet="previewContent"></ng-container>
</div>
</div>
<ng-template #previewContent>
<ng-container *ngIf="getContentType() === 'application/pdf'"> <ng-container *ngIf="getContentType() === 'application/pdf'">
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer"> <div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
<pdf-viewer [src]="{ url: previewUrl, password: password }" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (error)="onError($event)" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer> <ngx-extended-pdf-viewer
[src]="previewUrl"
[useBrowserLocale]="true"
[showPagingButtons]="false"
[showRotateButton]="false"
[showHandToolButton]="false"
[showOpenFileButton]="false"
[showDownloadButton]="false"
[showDrawEditor]="false"
[showTextEditor]="false"
[showSecondaryToolbarButton]="false"
[showStampEditor]="false"
(pagesLoaded)="onPagesLoaded($event)"
(pdfLoadingFailed)="onPdfLoadingFailed($event)"
[(page)]="previewCurrentPage">
</ngx-extended-pdf-viewer>
</div> </div>
<ng-template #nativePdfViewer> <ng-template #nativePdfViewer>
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object> <object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
@ -244,11 +248,9 @@
<ng-container *ngIf="renderAsPlainText"> <ng-container *ngIf="renderAsPlainText">
<div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div> <div [innerText]="previewText" class="preview-sticky bg-light p-3 overflow-auto" width="100%"></div>
</ng-container> </ng-container>
<div *ngIf="requiresPassword" class="password-prompt"> <div *ngIf="showPasswordField" class="password-prompt">
<form> <form>
<input autocomplete="" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" /> <input autocomplete="" autofocus="true" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
</form> </form>
</div> </div>
</div> </ng-template>
</div>

View File

@ -653,14 +653,14 @@ describe('DocumentDetailComponent', () => {
it('should support password-protected PDFs with a password field', () => { it('should support password-protected PDFs with a password field', () => {
initNormally() initNormally()
component.onError({ name: 'PasswordException' }) // normally dispatched by pdf viewer component.onError({ name: 'PasswordException' }) // normally dispatched by pdf viewer
expect(component.requiresPassword).toBeTruthy() expect(component.showPasswordField).toBeTruthy()
fixture.detectChanges() fixture.detectChanges()
expect( expect(
fixture.debugElement.query(By.css('input[type=password]')) fixture.debugElement.query(By.css('input[type=password]'))
).not.toBeUndefined() ).not.toBeUndefined()
component.password = 'foo' component.password = 'foo'
component.pdfPreviewLoaded({ numPages: 1000 } as any) component.pdfPreviewLoaded({ numPages: 1000 } as any)
expect(component.requiresPassword).toBeFalsy() expect(component.showPasswordField).toBeFalsy()
}) })
it('should support Enter key in password field', () => { it('should support Enter key in password field', () => {

View File

@ -1,4 +1,10 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core' import {
Component,
OnInit,
OnDestroy,
ViewChild,
ChangeDetectorRef,
} from '@angular/core'
import { FormArray, FormControl, FormGroup } from '@angular/forms' import { FormArray, FormControl, FormGroup } from '@angular/forms'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { import {
@ -21,7 +27,6 @@ import { DocumentService } from 'src/app/services/rest/document.service'
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
import { CorrespondentEditDialogComponent } from '../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' import { CorrespondentEditDialogComponent } from '../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { DocumentTypeEditDialogComponent } from '../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component' import { DocumentTypeEditDialogComponent } from '../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
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 } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
@ -69,6 +74,9 @@ import {
} from 'src/app/data/paperless-custom-field' } from 'src/app/data/paperless-custom-field'
import { PaperlessCustomFieldInstance } from 'src/app/data/paperless-custom-field-instance' import { PaperlessCustomFieldInstance } from 'src/app/data/paperless-custom-field-instance'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { InvalidPDFException } from 'ngx-extended-pdf-viewer/lib/events/invalid-pdf-exception'
import { PagesLoadedEvent } from 'ngx-extended-pdf-viewer/lib/events/pages-loaded-event'
import { pdfDefaultOptions } from 'ngx-extended-pdf-viewer'
enum DocumentDetailNavIDs { enum DocumentDetailNavIDs {
Details = 1, Details = 1,
@ -136,8 +144,7 @@ export class DocumentDetailComponent
unsubscribeNotifier: Subject<any> = new Subject() unsubscribeNotifier: Subject<any> = new Subject()
docChangeNotifier: Subject<any> = new Subject() docChangeNotifier: Subject<any> = new Subject()
requiresPassword: boolean = false showPasswordField: boolean = false
password: string
ogDate: Date ogDate: Date
@ -160,6 +167,8 @@ export class DocumentDetailComponent
DocumentDetailNavIDs = DocumentDetailNavIDs DocumentDetailNavIDs = DocumentDetailNavIDs
activeNavID: number activeNavID: number
setPasswordCallback: (password: string) => void
constructor( constructor(
private documentsService: DocumentService, private documentsService: DocumentService,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -176,9 +185,23 @@ export class DocumentDetailComponent
private permissionsService: PermissionsService, private permissionsService: PermissionsService,
private userService: UserService, private userService: UserService,
private customFieldsService: CustomFieldsService, private customFieldsService: CustomFieldsService,
private http: HttpClient private http: HttpClient,
private ref: ChangeDetectorRef
) { ) {
super() super()
pdfDefaultOptions.passwordPrompt = {
// ngx-extended-pdf-viewer PasswordPrompt
open: () => {
this.showPasswordField = true
ref.detectChanges() // manually trigger change detection
},
setUpdateCallback: (
updateCallback: (password: string) => void,
reason: 1 | 2
) => {
this.setPasswordCallback = updateCallback
},
}
} }
titleKeyUp(event) { titleKeyUp(event) {
@ -397,7 +420,6 @@ export class DocumentDetailComponent
updateComponent(doc: PaperlessDocument) { updateComponent(doc: PaperlessDocument) {
this.document = doc this.document = doc
this.requiresPassword = false
// this.customFields = doc.custom_fields.concat([]) // this.customFields = doc.custom_fields.concat([])
this.updateFormForCustomFields() this.updateFormForCustomFields()
this.documentsService this.documentsService
@ -727,20 +749,21 @@ export class DocumentDetailComponent
}) })
} }
pdfPreviewLoaded(pdf: PDFDocumentProxy) { onPagesLoaded(event: PagesLoadedEvent) {
this.previewNumPages = pdf.numPages this.previewNumPages = event.pagesCount
if (this.password) this.requiresPassword = false
} }
onError(event) { onPdfLoadingFailed(event: InvalidPDFException) {
if (event.name == 'PasswordException') { this.toastService.showError($localize`Error loading PDF`, {
this.requiresPassword = true error: event.message,
} })
this.ref.detectChanges() // manually trigger change detection
} }
onPasswordKeyUp(event: KeyboardEvent) { onPasswordKeyUp(event: KeyboardEvent) {
if ('Enter' == event.key) { if ('Enter' == event.key) {
this.password = (event.target as HTMLInputElement).value this.showPasswordField = false
this.setPasswordCallback((event.target as HTMLInputElement).value)
} }
} }