diff --git a/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts index c2a7a2dde..d8d8967f8 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts @@ -3,11 +3,9 @@ import { ChangeDetectorRef, Component, ElementRef, - EventEmitter, HostListener, Input, OnDestroy, - Output, TemplateRef, ViewChild, } from '@angular/core'; @@ -60,7 +58,6 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy @Input() dialogRef: MatDialogRef; @Input() file!: File; @Input() annotationActionsTemplate: TemplateRef; - @Output() readonly selectPage = new EventEmitter(); displayedPages: number[] = []; pagesPanelActive = true; readonly displayedAnnotations$: Observable>; @@ -275,16 +272,16 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy } scrollQuickNavFirst(): void { - this.selectPage.emit(1); + this.pdf.navigateTo(1); } scrollQuickNavLast(): Promise { - return firstValueFrom(this.state.file$).then(file => this.selectPage.emit(file.numberOfPages)); + return firstValueFrom(this.state.file$).then(file => this.pdf.navigateTo(file.numberOfPages)); } pageSelectedByClick($event: number): void { this.pagesPanelActive = true; - this.selectPage.emit($event); + this.pdf.navigateTo($event); } preventKeyDefault($event: KeyboardEvent): void { @@ -294,11 +291,11 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy } jumpToPreviousWithAnnotations(): void { - this.selectPage.emit(this._prevPageWithAnnotations()); + this.pdf.navigateTo(this._prevPageWithAnnotations()); } jumpToNextWithAnnotations(): void { - this.selectPage.emit(this._nextPageWithAnnotations()); + this.pdf.navigateTo(this._nextPageWithAnnotations()); } navigateAnnotations($event: KeyboardEvent) { @@ -419,26 +416,26 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy if (pageIdx !== -1) { // If active page has annotations if (pageIdx !== this.displayedPages.length - 1) { - this.selectPage.emit(this.displayedPages[pageIdx + 1]); + this.pdf.navigateTo(this.displayedPages[pageIdx + 1]); } } else { // If active page doesn't have annotations const nextPage = this._nextPageWithAnnotations(); if (nextPage) { - this.selectPage.emit(nextPage); + this.pdf.navigateTo(nextPage); } } } else { if (pageIdx !== -1) { // If active page has annotations if (pageIdx !== 0) { - this.selectPage.emit(this.displayedPages[pageIdx - 1]); + this.pdf.navigateTo(this.displayedPages[pageIdx - 1]); } } else { // If active page doesn't have annotations const prevPage = this._prevPageWithAnnotations(); if (prevPage) { - this.selectPage.emit(prevPage); + this.pdf.navigateTo(prevPage); } } } diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.html b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.html index 10cac377b..86030ee0f 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.html +++ b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.html @@ -75,7 +75,6 @@ this._fileDataService.loadAnnotations(file))); - private _lastPage: string; @ViewChild('annotationFilterTemplate', { read: TemplateRef, static: false, @@ -205,9 +204,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this.#rebuildFilters(); } - async ngOnDetach() { - await this._documentViewer.lock(); - this._documentViewer.close(); + ngOnDetach() { + this._viewerHeaderService.resetCompareButtons(); super.ngOnDetach(); this._changeDetectorRef.markForCheck(); } @@ -222,7 +220,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni await this.ngOnInit(); await this._fileDataService.loadRedactionLog(); this._viewerHeaderService.updateElements(); - this._lastPage = previousRoute.queryParams.page; + await this.#updateQueryParamsPage(Number(previousRoute.queryParams.page ?? '1')); this._changeDetectorRef.markForCheck(); } @@ -242,12 +240,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const reanalyzeFiles = reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true }); await firstValueFrom(reanalyzeFiles); } - this.pdfProxyService.loadViewer(); - } - selectPage(pageNumber: number) { - this.pdf.navigateTo(pageNumber); - this._lastPage = pageNumber.toString(); + this.pdfProxyService.loadViewer(); } openManualAnnotationDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) { @@ -320,7 +314,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni @Debounce(100) async viewerReady() { // Go to initial page from query params - const pageNumber: string = this._lastPage || this._activatedRoute.snapshot.queryParams.page; + const pageNumber: string = this._activatedRoute.snapshot.queryParams.page; if (pageNumber) { const file = this.state.file; let page = parseInt(pageNumber, 10); @@ -332,7 +326,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni page = file.numberOfPages; } - this.selectPage(page); + this.pdf.navigateTo(page); } this._loadingService.stop(); @@ -358,20 +352,22 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni loadAnnotations() { const documentLoaded$ = this._documentViewer.loaded$.pipe( - tap(() => { + tap(loaded => { + if (!loaded) { + return; + } this._pageRotationService.clearRotations(); this._viewerHeaderService.disable(ROTATION_ACTION_BUTTONS); + return this.viewerReady(); }), - 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( + filter(([, loaded]) => loaded), tap(() => (start = new Date().getTime())), map(([annotations]) => annotations), startWith([] as AnnotationWrapper[]), @@ -548,7 +544,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this.openManualAnnotationDialog($event); }); - this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page => this.viewerPageChanged(page)); + this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page => + this._ngZone.run(() => this.viewerPageChanged(page)), + ); this.addActiveScreenSubscription = this.pdfProxyService.annotationSelected$.subscribe(); } diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts b/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts index 0098d12b8..796757b48 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts @@ -38,6 +38,7 @@ import { ManualRedactionService } from './services/manual-redaction.service'; import { AnnotationWrapperComponent } from './components/annotation-wrapper/annotation-wrapper.component'; import { AnnotationReferenceComponent } from './components/annotation-reference/annotation-reference.component'; import { ImportRedactionsDialogComponent } from './dialogs/import-redactions-dialog/import-redactions-dialog'; +import { DocumentUnloadedGuard } from './services/document-unloaded.guard'; const routes: Routes = [ { @@ -45,7 +46,7 @@ const routes: Routes = [ component: FilePreviewScreenComponent, pathMatch: 'full', data: { reuse: true }, - canDeactivate: [PendingChangesGuard], + canDeactivate: [PendingChangesGuard, DocumentUnloadedGuard], }, ]; @@ -95,6 +96,6 @@ const components = [ OverlayModule, ColorPickerModule, ], - providers: [FilePreviewDialogService, ManualRedactionService], + providers: [FilePreviewDialogService, ManualRedactionService, DocumentUnloadedGuard], }) export class FilePreviewModule {} diff --git a/apps/red-ui/src/app/modules/file-preview/services/document-unloaded.guard.ts b/apps/red-ui/src/app/modules/file-preview/services/document-unloaded.guard.ts new file mode 100644 index 000000000..d53abf898 --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/services/document-unloaded.guard.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { CanDeactivate } from '@angular/router'; +import { filter, map, Observable } from 'rxjs'; +import { REDDocumentViewer } from '../../pdf-viewer/services/document-viewer.service'; + +@Injectable() +export class DocumentUnloadedGuard implements CanDeactivate { + constructor(private readonly _documentViewer: REDDocumentViewer) {} + + canDeactivate(): Observable { + this._documentViewer.close(); + + return this._documentViewer.loaded$.pipe( + filter(loaded => !loaded), + map(() => true), + ); + } +} diff --git a/apps/red-ui/src/app/modules/pdf-viewer/components/compare-file-input/compare-file-input.component.ts b/apps/red-ui/src/app/modules/pdf-viewer/components/compare-file-input/compare-file-input.component.ts index a16162992..ad92e70c7 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/components/compare-file-input/compare-file-input.component.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/components/compare-file-input/compare-file-input.component.ts @@ -3,7 +3,6 @@ import { HeaderElements } from '../../../file-preview/utils/constants'; import { ConfirmationDialogInput, ConfirmOptions, LoadingService } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { FilesMapService } from '@services/files/files-map.service'; -import { ActivatedRoute } from '@angular/router'; import { SharedDialogService } from '@shared/services/dialog.service'; import { PdfViewer } from '../../services/pdf-viewer.service'; import { ViewerHeaderService } from '../../services/viewer-header.service'; @@ -28,7 +27,6 @@ export class CompareFileInputComponent { private readonly _logger: NGXLogger, private readonly _loadingService: LoadingService, private readonly _documentViewer: REDDocumentViewer, - private readonly _activatedRoute: ActivatedRoute, private readonly _filesMapService: FilesMapService, private readonly _dialogService: SharedDialogService, private readonly _viewerHeaderService: ViewerHeaderService, @@ -44,9 +42,7 @@ export class CompareFileInputComponent { return this._filesMapService.get(dossierId, fileId)?.filename ?? 'document.pdf'; } - async upload(files: FileList) { - await this._documentViewer.lock(); - + upload(files: FileList) { const fileToCompare = files[0]; this.input.nativeElement.value = null; @@ -61,11 +57,14 @@ export class CompareFileInputComponent { fileReader.readAsArrayBuffer(fileToCompare); } - async #createDocumentsAndCompare(blob: ArrayBuffer, fileName: string) { + async #createDocumentsAndCompare(buffer: ArrayBuffer, fileName: string) { + const currentBlob = await this._documentViewer.blob(); + this._documentViewer.close(); + const pdfNet = this._pdf.PDFNet; - const compareDocument = await pdfNet.PDFDoc.createFromBuffer(blob); - const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this._pdf.blob.arrayBuffer()); + const compareDocument = await pdfNet.PDFDoc.createFromBuffer(buffer); + const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await currentBlob.arrayBuffer()); const currentDocumentPageCount = await currentDocument.getPageCount(); const compareDocumentPageCount = await compareDocument.getPageCount(); diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-draw.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-draw.service.ts index 608d99dee..a27bb65b0 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-draw.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-draw.service.ts @@ -33,13 +33,11 @@ export class AnnotationDrawService { ) {} async draw(annotations: List, dossierTemplateId: string, hideSkipped: boolean) { - await this._documentViewer.lock(); try { await this._draw(annotations, dossierTemplateId, hideSkipped); } catch (e) { console.log(e); } - await this._documentViewer.unlock(); } getAndConvertColor(superType: string, dossierTemplateId: string, dictionary?: string) { diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/document-viewer.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/document-viewer.service.ts index 76608b08b..c2e2e03e2 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/document-viewer.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/document-viewer.service.ts @@ -19,9 +19,7 @@ export class REDDocumentViewer { pageComplete$: Observable; keyUp$: Observable; textSelected$: Observable; - selectedText = ''; - #document: DocumentViewer; constructor( @@ -120,15 +118,9 @@ export class REDDocumentViewer { return true; } - async unlock() { - const document = await this.PDFDoc; - if (!document) { - return false; - } - - await document.unlock(); - this._logger.info('[PDF] Unlocked'); - return true; + async blob() { + const data = await this.document.getFileData(); + return new Blob([new Uint8Array(data)], { type: 'application/pdf' }); } setRectangleToolStyles(color: Color) { diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/pdf-viewer.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/pdf-viewer.service.ts index 4b915a7ae..4927ea785 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/pdf-viewer.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/pdf-viewer.service.ts @@ -37,7 +37,6 @@ export class PdfViewer { totalPages$: Observable; #instance: WebViewerInstance; - #currentBlob: Blob; readonly #compareMode$ = new BehaviorSubject(false); readonly #searchButton: IHeaderElement = { type: 'actionButton', @@ -62,10 +61,6 @@ export class PdfViewer { return this.#instance; } - get blob() { - return this.#currentBlob; - } - get PDFNet(): typeof Core.PDFNet { return this.#instance.Core.PDFNet; } @@ -157,10 +152,12 @@ export class PdfViewer { } openCompareMode() { + this._logger.info('[PDF] Open compare mode'); this.#compareMode$.next(true); } closeCompareMode() { + this._logger.info('[PDF] Close compare mode'); this.#compareMode$.next(false); } @@ -173,7 +170,6 @@ export class PdfViewer { const document = await this.PDFNet.PDFDoc.createFromBuffer(await blob.arrayBuffer()); await document.flattenAnnotations(false); - this.#currentBlob = blob; this.fileId = file.fileId; this.dossierId = file.dossierId; diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts index 817562e3e..5bed4881a 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/viewer-header.service.ts @@ -7,8 +7,8 @@ import { TooltipsService } from './tooltips.service'; import { PageRotationService } from './page-rotation.service'; import { PdfViewer } from './pdf-viewer.service'; import { ROTATION_ACTION_BUTTONS } from '../utils/constants'; -import { ActivatedRoute } from '@angular/router'; import { FilesMapService } from '@services/files/files-map.service'; +import { REDDocumentViewer } from './document-viewer.service'; const divider: IHeaderElement = { type: 'divider', @@ -34,6 +34,7 @@ export class ViewerHeaderService { private readonly _injector: Injector, private readonly _translateService: TranslateService, private readonly _pdf: PdfViewer, + private readonly _documentViewer: REDDocumentViewer, private readonly _rotationService: PageRotationService, private readonly _tooltipsService: TooltipsService, ) {} @@ -186,6 +187,11 @@ export class ViewerHeaderService { }); } + resetCompareButtons() { + this.disable([HeaderElements.CLOSE_COMPARE_BUTTON]); + this.enable([HeaderElements.COMPARE_BUTTON]); + } + #toggleRotationActionButtons() { if (this._rotationService.hasRotations) { this.enable(ROTATION_ACTION_BUTTONS); @@ -196,16 +202,13 @@ export class ViewerHeaderService { private _closeCompareMode() { this._pdf.closeCompareMode(); - const activatedRoute = this._injector.get(ActivatedRoute); - const dossierId = activatedRoute.snapshot.paramMap.get('dossierId'); - const fileId = activatedRoute.snapshot.paramMap.get('fileId'); + const { dossierId, fileId } = this._pdf; const file = this._injector.get(FilesMapService).get(dossierId, fileId); const filename = file.filename ?? 'document.pdf'; this._pdf.instance.UI.loadDocument(this.#docBeforeCompare, { filename }); - this.disable([HeaderElements.CLOSE_COMPARE_BUTTON]); - this.enable([HeaderElements.COMPARE_BUTTON]); + this.resetCompareButtons(); this._pdf.navigateTo(1); } @@ -217,10 +220,10 @@ export class ViewerHeaderService { dataElement: HeaderElements.COMPARE_BUTTON, img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'), title: 'Compare', - onClick: () => { + onClick: async () => { const element = compareFileInput.nativeElement as HTMLElement; element.click(); - this.#docBeforeCompare = this._pdf.blob; + this.#docBeforeCompare = await this._documentViewer.blob(); }, }; }