RED-3988: move compare input to pdf viewer

This commit is contained in:
Dan Percic 2022-05-21 09:31:51 +03:00
parent a989708d08
commit c5d82a097f
8 changed files with 134 additions and 112 deletions

View File

@ -1,5 +1,3 @@
<input #compareFileInput (change)="uploadFile($event.target['files'])" accept="application/pdf" class="file-upload-input" type="file" />
<div *ngIf="pdf.loaded$ | async" class="pagination noselect">
<div (click)="pdf.navigatePreviousPage()">
<mat-icon class="chevron-icon" svgIcon="red:nav-prev"></mat-icon>

View File

@ -1,16 +1,4 @@
import {
Component,
ElementRef,
EventEmitter,
Inject,
Input,
NgZone,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild,
} from '@angular/core';
import { Component, EventEmitter, Inject, Input, NgZone, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { Dossier, IHeaderElement, IManualRedactionEntry } from '@red/domain';
import { Core, WebViewerInstance } from '@pdftron/webviewer';
import { TranslateService } from '@ngx-translate/core';
@ -20,21 +8,17 @@ import {
ManualRedactionEntryWrapper,
} from '@models/file/manual-redaction-entry.wrapper';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { environment } from '@environments/environment';
import { AnnotationDrawService } from '../../services/annotation-draw.service';
import { AnnotationActionsService } from '../../services/annotation-actions.service';
import { UserPreferenceService } from '@services/user-preference.service';
import { BASE_HREF_FN, BaseHrefFn } from '../../../../tokens';
import { AutoUnsubscribe, ConfirmationDialogInput, ErrorService, LoadingService, log } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { AutoUnsubscribe, ErrorService, log } from '@iqser/common-ui';
import { toPosition } from '../../utils/pdf-calculation.utils';
import { MultiSelectService } from '../../services/multi-select.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { PageRotationService } from '../../../shared/components/pdf-viewer/page-rotation.service';
import { HeaderElements, TextPopups } from '../../utils/constants';
import { FilePreviewDialogService } from '../../services/file-preview-dialog.service';
import { loadCompareDocumentWrapper } from '../../utils/compare-mode.utils';
import { from } from 'rxjs';
import { FileDataService } from '../../services/file-data.service';
import { ViewerHeaderService } from '../../../shared/components/pdf-viewer/viewer-header.service';
@ -57,7 +41,6 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
@Output() readonly manualAnnotationRequested = new EventEmitter<ManualRedactionEntryWrapper>();
@Output() readonly pageChanged = this.pdf.pageChanged$.pipe(tap(() => this._handleCustomActions()));
@Output() readonly keyUp = this.pdf.keyUp$;
@ViewChild('compareFileInput', { static: true }) compareFileInput: ElementRef;
instance: WebViewerInstance;
private _selectedText = '';
readonly #visibilityOffIcon = this._convertPath('/assets/icons/general/visibility-off.svg');
@ -71,12 +54,10 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
private readonly _translateService: TranslateService,
private readonly _manualRedactionService: ManualRedactionService,
private readonly _dialogService: FilePreviewDialogService,
private readonly _ngZone: NgZone,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _annotationDrawService: AnnotationDrawService,
private readonly _annotationActionsService: AnnotationActionsService,
private readonly _loadingService: LoadingService,
private readonly _pageRotationService: PageRotationService,
private readonly _fileDataService: FileDataService,
private readonly _viewerHeaderService: ViewerHeaderService,
@ -117,74 +98,6 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
}
}
uploadFile(files: FileList) {
const fileToCompare = files[0];
this.compareFileInput.nativeElement.value = null;
if (!fileToCompare) {
console.error('No file to compare!');
return;
}
const fileReader = new FileReader();
fileReader.onload = async () => {
const pdfNet = this.instance.Core.PDFNet;
await pdfNet.initialize(environment.licenseKey ? window.atob(environment.licenseKey) : null);
const compareDocument = await pdfNet.PDFDoc.createFromBuffer(fileReader.result as ArrayBuffer);
const blob = await this._state.blob;
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await blob.arrayBuffer());
const loadCompareDocument = async () => {
this._loadingService.start();
const mergedDocument = await pdfNet.PDFDoc.create();
const file = this._state.file;
await loadCompareDocumentWrapper(
currentDocument,
compareDocument,
mergedDocument,
this.instance,
file,
() => {
this.pdf.openCompareMode();
},
() => {
this.pdf.navigateTo(1);
},
this.instance.Core.PDFNet,
);
this._viewerHeaderService.disable([HeaderElements.COMPARE_BUTTON]);
this._viewerHeaderService.enable([HeaderElements.CLOSE_COMPARE_BUTTON]);
this._loadingService.stop();
};
const currentDocumentPageCount = await currentDocument.getPageCount();
const compareDocumentPageCount = await compareDocument.getPageCount();
if (currentDocumentPageCount !== compareDocumentPageCount) {
this._dialogService.openDialog(
'confirm',
null,
new ConfirmationDialogInput({
title: _('confirmation-dialog.compare-file.title'),
question: _('confirmation-dialog.compare-file.question'),
translateParams: {
fileName: fileToCompare.name,
currentDocumentPageCount,
compareDocumentPageCount,
},
}),
loadCompareDocument,
);
} else {
await loadCompareDocument();
}
};
fileReader.readAsArrayBuffer(fileToCompare);
}
#processSelectedAnnotations(annotations: Annotation[], action) {
let nextAnnotations: Annotation[];
@ -199,9 +112,8 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
nextAnnotations = this._annotationManager.selected;
}
// this.annotationSelected.emit(nextAnnotations.map(ann => ann.Id));
if (action === 'deselected') {
this._toggleRectangleAnnotationAction(true);
this.pdf.disable(TextPopups.ADD_RECTANGLE);
return nextAnnotations.map(ann => ann.Id);
}
@ -241,8 +153,8 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
});
}
private _toggleRectangleAnnotationAction(readonly = false) {
if (!readonly) {
private _toggleRectangleAnnotationAction(disable = false) {
if (!disable) {
this.pdf.enable(TextPopups.ADD_RECTANGLE);
} else {
this.pdf.disable(TextPopups.ADD_RECTANGLE);
@ -250,7 +162,7 @@ export class PdfPaginatorComponent extends AutoUnsubscribe implements OnInit, On
}
private _configureElements() {
this._viewerHeaderService.initialize(this.compareFileInput);
// this._viewerHeaderService.initialize(this.compareFileInput);
const dossierTemplateId = this.dossier.dossierTemplateId;
const color = this._annotationDrawService.getAndConvertColor(dossierTemplateId, 'manual');

View File

@ -201,7 +201,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this._changeDetectorRef.markForCheck();
}
async ngOnAttach(previousRoute: ActivatedRouteSnapshot): Promise<void> {
async ngOnAttach(previousRoute: ActivatedRouteSnapshot) {
if (!this.state.file.canBeOpened) {
return this._navigateToDossier();
}
@ -355,9 +355,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
filter(s => s),
tap(() => this.viewerReady()),
);
const currentPageAnnotations$ = combineLatest([this.pdf.currentPage$, this._fileDataService.annotations$]).pipe(
map(([page, annotations]) => annotations.filter(annotation => annotation.pageNumber === page)),
);
let start;
return combineLatest([currentPageAnnotations$, documentLoaded$]).pipe(
tap(() => (start = new Date().getTime())),
@ -386,7 +388,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
drawChangedAnnotations(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
let annotationsToDraw: readonly AnnotationWrapper[];
const annotations = this._annotationManager.annotations;
const ann = annotations.map(a => oldAnnotations.find(oldAnnotation => oldAnnotation.id === a.Id));
const ann = annotations.map(a => oldAnnotations.some(oldAnnotation => oldAnnotation.id === a.Id));
const hasAnnotations = ann.filter(a => !!a).length > 0;
if (hasAnnotations) {
@ -540,7 +542,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
_('error.deleted-entity.file.action'),
'iqser:expand',
null,
this._navigateToDossier.bind(this),
() => this._navigateToDossier(),
);
this._errorService.set(error);
}
@ -600,6 +602,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
private _navigateToDossier() {
this._router.navigate([this._dossiersService.find(this.dossierId)?.routerLink]);
return this._router.navigate([this._dossiersService.find(this.dossierId)?.routerLink]);
}
}

View File

@ -15,18 +15,18 @@ export class MultiSelectService {
readonly inactive$: Observable<boolean>;
readonly #active$ = new BehaviorSubject(false);
readonly #enabled$ = new BehaviorSubject(true);
#enabled = true;
constructor(viewModeService: ViewModeService, state: FilePreviewStateService) {
[this.active$, this.inactive$] = boolFactory(this.#active$.asObservable());
this.enabled$ = combineLatest([viewModeService.viewMode$, state.isWritable$]).pipe(
map(([viewMode, isWritable]) => isWritable && ENABLED_MULTISELECT.includes(viewMode)),
tap(enabled => this.#enabled$.next(enabled)),
tap(enabled => (this.#enabled = enabled)),
);
}
get isEnabled() {
return this.#enabled$.value;
return this.#enabled;
}
get isActive() {

View File

@ -1,12 +1,35 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { PdfViewer } from './pdf-viewer.service';
import { REDAnnotationManager } from '@shared/components/pdf-viewer/annotation-manager.service';
import { ViewerHeaderService } from '@shared/components/pdf-viewer/viewer-header.service';
import { environment } from '@environments/environment';
import { loadCompareDocumentWrapper } from '../../../file-preview/utils/compare-mode.utils';
import { HeaderElements } from '../../../file-preview/utils/constants';
import { ConfirmationDialogInput, LoadingService } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ActivatedRoute } from '@angular/router';
import { FilesMapService } from '@services/files/files-map.service';
import { SharedDialogService } from '@shared/services/dialog.service';
@Component({
selector: 'redaction-pdf-viewer',
template: ' <div #viewer></div>',
template: `
<div #viewer></div>
<input
#compareFileInput
(change)="uploadFile($event.target['files'])"
accept="application/pdf"
class="file-upload-input"
type="file"
/>
`,
styles: [
`
.file-upload-input {
visibility: hidden;
}
div {
width: calc(100% - 350px);
height: calc(100% - 111px);
@ -18,9 +41,19 @@ import { REDAnnotationManager } from '@shared/components/pdf-viewer/annotation-m
],
})
export class PdfViewerComponent {
@ViewChild('compareFileInput', { static: true }) private readonly _compareFileInput: ElementRef;
#viewer: ElementRef;
constructor(private readonly _pdf: PdfViewer, private readonly _annotationManager: REDAnnotationManager) {}
constructor(
private readonly _filesMapService: FilesMapService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _dialogService: SharedDialogService,
private readonly _loadingService: LoadingService,
private readonly _pdf: PdfViewer,
private readonly _annotationManager: REDAnnotationManager,
private readonly _viewerHeaderService: ViewerHeaderService,
) {}
@ViewChild('viewer', { static: true })
set viewer(value: ElementRef) {
@ -30,6 +63,75 @@ export class PdfViewerComponent {
this.#viewer = value;
const pdfInit = this._pdf.init(value.nativeElement as HTMLElement);
pdfInit.then(instance => this._annotationManager.init(instance.Core.annotationManager));
pdfInit.then(instance => {
this._annotationManager.init(instance.Core.annotationManager);
console.log(this._compareFileInput);
this._viewerHeaderService.initialize(this._compareFileInput);
});
}
uploadFile(files: FileList) {
const fileToCompare = files[0];
this._compareFileInput.nativeElement.value = null;
if (!fileToCompare) {
console.error('No file to compare!');
return;
}
const fileReader = new FileReader();
fileReader.onload = async () => {
const pdfNet = this._pdf.PDFNet;
await pdfNet.initialize(environment.licenseKey ? window.atob(environment.licenseKey) : null);
const compareDocument = await pdfNet.PDFDoc.createFromBuffer(fileReader.result as ArrayBuffer);
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this._pdf.blob.arrayBuffer());
const loadCompareDocument = async () => {
this._loadingService.start();
const mergedDocument = await pdfNet.PDFDoc.create();
const dossierId = this._activatedRoute.snapshot.paramMap.get('dossierId');
const fileId = this._activatedRoute.snapshot.paramMap.get('fileId');
const file = this._filesMapService.get(dossierId, fileId);
await loadCompareDocumentWrapper(
currentDocument,
compareDocument,
mergedDocument,
this._pdf.instance,
file,
() => this._pdf.openCompareMode(),
() => this._pdf.navigateTo(1),
this._pdf.PDFNet,
);
this._viewerHeaderService.disable([HeaderElements.COMPARE_BUTTON]);
this._viewerHeaderService.enable([HeaderElements.CLOSE_COMPARE_BUTTON]);
this._loadingService.stop();
};
const currentDocumentPageCount = await currentDocument.getPageCount();
const compareDocumentPageCount = await compareDocument.getPageCount();
if (currentDocumentPageCount !== compareDocumentPageCount) {
this._dialogService.openDialog(
'confirm',
null,
new ConfirmationDialogInput({
title: _('confirmation-dialog.compare-file.title'),
question: _('confirmation-dialog.compare-file.question'),
translateParams: {
fileName: fileToCompare.name,
currentDocumentPageCount,
compareDocumentPageCount,
},
}),
loadCompareDocument,
);
} else {
await loadCompareDocument();
}
};
fileReader.readAsArrayBuffer(fileToCompare);
}
}

View File

@ -44,6 +44,7 @@ export class PdfViewer {
#instance: WebViewerInstance;
readonly #compareMode$ = new BehaviorSubject(false);
#currentBlob: Blob;
constructor(
private readonly _logger: NGXLogger,
@ -65,6 +66,10 @@ export class PdfViewer {
return this.documentViewer.getDocument();
}
get blob() {
return this.#currentBlob;
}
get PDFNet(): typeof Core.PDFNet {
return this.#instance.Core.PDFNet;
}
@ -247,6 +252,7 @@ export class PdfViewer {
await pdfNet.initialize(environment.licenseKey ? window.atob(environment.licenseKey) : null);
const document = await pdfNet.PDFDoc.createFromBuffer(await blob.arrayBuffer());
await document.flattenAnnotations(false);
this.#currentBlob = blob;
this.#instance.UI.loadDocument(document, { filename: file?.filename + '.pdf' ?? 'document.pdf', onError });
}

View File

@ -200,11 +200,9 @@ export class ViewerHeaderService {
dataElement: HeaderElements.COMPARE_BUTTON,
img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'),
title: 'Compare',
onClick: async () => {
onClick: () => {
compareFileInput.nativeElement.click();
const data = await this._pdf.documentViewer.getDocument().getFileData();
const arr = new Uint8Array(data);
this.#docBeforeCompare = new Blob([arr], { type: 'application/pdf' });
this.#docBeforeCompare = this._pdf.blob;
},
};
}

View File

@ -1,13 +1,17 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { AddDossierDialogComponent } from '../dialogs/add-dossier-dialog/add-dossier-dialog.component';
import { DialogConfig, DialogService } from '@iqser/common-ui';
import { ConfirmationDialogComponent, DialogConfig, DialogService } from '@iqser/common-ui';
type DialogType = 'addDossier';
type DialogType = 'addDossier' | 'confirm';
@Injectable()
export class SharedDialogService extends DialogService<DialogType> {
protected readonly _config: DialogConfig<DialogType> = {
confirm: {
component: ConfirmationDialogComponent,
dialogConfig: { disableClose: false },
},
addDossier: {
component: AddDossierDialogComponent,
dialogConfig: { width: '900px', autoFocus: true },