From be8316f60929b18ed53abb574e038f2d95727a50 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 18 Mar 2022 11:50:51 +0200 Subject: [PATCH] draw only changed annotations --- .../dossier/services/redaction-log.service.ts | 8 +- .../annotation-references-list.component.html | 2 +- .../annotation-references-list.component.ts | 14 +- .../file-preview-screen.component.html | 2 +- .../file-preview-screen.component.ts | 182 ++++++++---------- .../services/annotation-references.service.ts | 18 +- .../services/file-data.service.ts | 71 ++++--- 7 files changed, 147 insertions(+), 150 deletions(-) diff --git a/apps/red-ui/src/app/modules/dossier/services/redaction-log.service.ts b/apps/red-ui/src/app/modules/dossier/services/redaction-log.service.ts index 4ecda514f..0eba00b09 100644 --- a/apps/red-ui/src/app/modules/dossier/services/redaction-log.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/redaction-log.service.ts @@ -1,6 +1,8 @@ import { Injectable, Injector } from '@angular/core'; import { GenericService, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { IRedactionLog, ISectionGrid } from '@red/domain'; +import { catchError, tap } from 'rxjs/operators'; +import { of } from 'rxjs'; @Injectable({ providedIn: 'root', @@ -17,7 +19,11 @@ export class RedactionLogService extends GenericService { queryParams.push({ key: 'withManualRedactions', value: withManualRedactions }); } - return this._getOne([dossierId, fileId], 'redactionLog', queryParams); + const redactionLog$ = this._getOne([dossierId, fileId], 'redactionLog', queryParams); + return redactionLog$.pipe( + tap(redactionLog => redactionLog.redactionLogEntry.sort((a, b) => a.positions[0].page - b.positions[0].page)), + catchError(() => of({} as IRedactionLog)), + ); } @Validate() diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-references-list/annotation-references-list.component.html b/apps/red-ui/src/app/modules/file-preview/components/annotation-references-list/annotation-references-list.component.html index e8bca50cc..5d7a09c1c 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-references-list/annotation-references-list.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-references-list/annotation-references-list.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-references-list/annotation-references-list.component.ts b/apps/red-ui/src/app/modules/file-preview/components/annotation-references-list/annotation-references-list.component.ts index 08786b685..e5e63c4b6 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-references-list/annotation-references-list.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-references-list/annotation-references-list.component.ts @@ -1,10 +1,6 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationReferencesService } from '../../services/annotation-references.service'; -import { Observable, switchMap } from 'rxjs'; -import { filter } from 'rxjs/operators'; -import { FileDataService } from '../../services/file-data.service'; -import { filterEach } from '../../../../../../../../libs/common-ui/src'; @Component({ selector: 'redaction-annotation-references-list', @@ -15,16 +11,8 @@ import { filterEach } from '../../../../../../../../libs/common-ui/src'; export class AnnotationReferencesListComponent { @Input() selectedAnnotations: AnnotationWrapper[]; @Output() readonly referenceClicked = new EventEmitter(); - references$ = this._annotationReferences; - constructor(readonly annotationReferencesService: AnnotationReferencesService, private readonly _fileDataService: FileDataService) {} - - private get _annotationReferences(): Observable { - return this.annotationReferencesService.annotation$.pipe( - filter(annotation => !!annotation), - switchMap(({ reference }) => this._fileDataService.annotations$.pipe(filterEach(a => reference.includes(a.annotationId)))), - ); - } + constructor(readonly annotationReferencesService: AnnotationReferencesService) {} isSelected(annotationId: string): boolean { return this.selectedAnnotations.some(a => a.annotationId === annotationId); 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 69c5cdda7..6086e7a64 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 @@ -90,7 +90,7 @@ (selectAnnotations)="selectAnnotations($event)" (selectPage)="selectPage($event)" *ngIf="!file.excluded" - [activeViewerPage]="activeViewerPage" + [activeViewerPage]="pdf.currentPage" [annotationActionsTemplate]="annotationActionsTemplate" [dialogRef]="dialogRef" [file]="file" diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts index 151933941..a13995a7a 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts @@ -64,11 +64,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni fullScreen = false; selectedAnnotations: AnnotationWrapper[] = []; displayPdfViewer = false; - activeViewerPage: number = null; readonly canPerformAnnotationActions$: Observable; readonly fileId = this.stateService.fileId; readonly dossierId = this.stateService.dossierId; - readonly file$ = this.stateService.file$.pipe(tap(file => this._loadFileData(file))); + readonly file$ = this.stateService.file$.pipe(tap(() => this._fileDataService.loadAnnotations())); ready = false; private _lastPage: string; @@ -80,38 +79,37 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni private readonly _filterTemplate: TemplateRef; constructor( - readonly permissionsService: PermissionsService, - readonly userPreferenceService: UserPreferenceService, - readonly stateService: FilePreviewStateService, - private readonly _watermarkService: WatermarkService, - private readonly _changeDetectorRef: ChangeDetectorRef, - private readonly _activatedRoute: ActivatedRoute, - private readonly _dialogService: FilePreviewDialogService, + readonly pdf: PdfViewer, private readonly _router: Router, - private readonly _annotationProcessingService: AnnotationProcessingService, - private readonly _annotationDrawService: AnnotationDrawService, - private readonly _filesService: FilesService, private readonly _ngZone: NgZone, - private readonly _fileManagementService: FileManagementService, - private readonly _loadingService: LoadingService, + private readonly _filesService: FilesService, + private readonly _errorService: ErrorService, + readonly stateService: FilePreviewStateService, private readonly _filterService: FilterService, - private readonly _translateService: TranslateService, + readonly permissionsService: PermissionsService, + readonly multiSelectService: MultiSelectService, + private readonly _activatedRoute: ActivatedRoute, + private readonly _loadingService: LoadingService, + private readonly _skippedService: SkippedService, + readonly documentInfoService: DocumentInfoService, private readonly _filesMapService: FilesMapService, private readonly _dossiersService: DossiersService, - private readonly _reanalysisService: ReanalysisService, - private readonly _errorService: ErrorService, - private readonly _pageRotationService: PageRotationService, - private readonly _skippedService: SkippedService, private readonly _fileDataService: FileDataService, - private readonly _pdf: PdfViewer, - private readonly _manualAnnotationService: ManualAnnotationService, - readonly excludedPagesService: ExcludedPagesService, private readonly _viewModeService: ViewModeService, - readonly multiSelectService: MultiSelectService, - readonly documentInfoService: DocumentInfoService, + readonly excludedPagesService: ExcludedPagesService, + private readonly _watermarkService: WatermarkService, + private readonly _translateService: TranslateService, + readonly userPreferenceService: UserPreferenceService, + private readonly _changeDetectorRef: ChangeDetectorRef, + private readonly _reanalysisService: ReanalysisService, + private readonly _dialogService: FilePreviewDialogService, + private readonly _pageRotationService: PageRotationService, + private readonly _annotationDrawService: AnnotationDrawService, + private readonly _fileManagementService: FileManagementService, + private readonly _manualAnnotationService: ManualAnnotationService, + private readonly _annotationProcessingService: AnnotationProcessingService, ) { super(); - this.bla(); this.canPerformAnnotationActions$ = this._canPerformAnnotationActions$; document.documentElement.addEventListener('fullscreenchange', () => { @@ -142,13 +140,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } async updateViewMode(): Promise { - if (!this._pdf.ready) { + if (!this.pdf.ready) { return; } - this._pdf.deleteAnnotations(this._fileDataService.textHighlights.map(a => a.id)); + this.pdf.deleteAnnotations(this._fileDataService.textHighlights.map(a => a.id)); - const annotations = this._pdf.getAnnotations(a => a.getCustomData('redact-manager')); + const annotations = this.pdf.getAnnotations(a => a.getCustomData('redact-manager')); const redactions = annotations.filter(a => a.getCustomData('redaction')); switch (this._viewModeService.viewMode) { @@ -162,8 +160,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni .filter(a => !ocrAnnotationIds.includes(a.Id)); const nonStandardEntries = annotations.filter(a => a.getCustomData('changeLogRemoved') === 'true'); this._setAnnotationsOpacity(standardEntries, true); - this._pdf.showAnnotations(standardEntries); - this._pdf.hideAnnotations(nonStandardEntries); + this.pdf.showAnnotations(standardEntries); + this.pdf.hideAnnotations(nonStandardEntries); break; } case 'DELTA': { @@ -171,21 +169,21 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const nonChangeLogEntries = annotations.filter(a => a.getCustomData('changeLog') === 'false'); this._setAnnotationsColor(redactions, 'annotationColor'); this._setAnnotationsOpacity(changeLogEntries, true); - this._pdf.showAnnotations(changeLogEntries); - this._pdf.hideAnnotations(nonChangeLogEntries); + this.pdf.showAnnotations(changeLogEntries); + this.pdf.hideAnnotations(nonChangeLogEntries); break; } case 'REDACTED': { const nonRedactionEntries = annotations.filter(a => a.getCustomData('redaction') === 'false'); this._setAnnotationsOpacity(redactions); this._setAnnotationsColor(redactions, 'redactionColor'); - this._pdf.showAnnotations(redactions); - this._pdf.hideAnnotations(nonRedactionEntries); + this.pdf.showAnnotations(redactions); + this.pdf.hideAnnotations(nonRedactionEntries); break; } case 'TEXT_HIGHLIGHTS': { this._loadingService.start(); - this._pdf.hideAnnotations(annotations); + this.pdf.hideAnnotations(annotations); const highlights = await this._fileDataService.loadTextHighlights(); await this._annotationDrawService.drawAnnotations(highlights); this._loadingService.stop(); @@ -234,7 +232,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni async rebuildFilters(deletePreviousAnnotations = false) { const startTime = new Date().getTime(); if (deletePreviousAnnotations) { - this._pdf.deleteAnnotations(); + this.pdf.deleteAnnotations(); console.log(`[REDACTION] Delete previous annotations time: ${new Date().getTime() - startTime} ms`); } @@ -277,14 +275,14 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni selectAnnotations(annotations?: AnnotationWrapper[]) { if (annotations) { const annotationsToSelect = this.multiSelectService.isActive ? [...this.selectedAnnotations, ...annotations] : annotations; - this._pdf.selectAnnotations(annotationsToSelect, this.multiSelectService.isActive); + this.pdf.selectAnnotations(annotationsToSelect, this.multiSelectService.isActive); } else { - this._pdf.deselectAllAnnotations(); + this.pdf.deselectAllAnnotations(); } } selectPage(pageNumber: number) { - this._pdf.navigateToPage(pageNumber); + this.pdf.navigateToPage(pageNumber); this._workloadComponent?.scrollAnnotationsToPage(pageNumber, 'always'); this._lastPage = pageNumber.toString(); } @@ -298,7 +296,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni async ({ manualRedactionEntry }: ManualRedactionEntryWrapper) => { const addAnnotation$ = this._manualAnnotationService.addAnnotation(manualRedactionEntry, this.dossierId, this.fileId); await firstValueFrom(addAnnotation$.pipe(catchError(() => of(undefined)))); - await this._fileDataService.load(await this.stateService.file); + await this._fileDataService.loadAnnotations(); }, ); }); @@ -363,17 +361,16 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni }; await this._router.navigate([], extras); - this.activeViewerPage = this._pdf.currentPage; this._changeDetectorRef.markForCheck(); } viewerReady() { this.ready = true; - this._pdf.ready = true; + this.pdf.ready = true; this._setExcludedPageStyles(); - this._pdf.documentViewer.addEventListener('pageComplete', () => { + this.pdf.documentViewer.addEventListener('pageComplete', () => { this._setExcludedPageStyles(); }); @@ -382,7 +379,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni if (pageNumber) { setTimeout(() => { this.selectPage(parseInt(pageNumber, 10)); - this.activeViewerPage = this._pdf.currentPage; this._scrollViews(); this._changeDetectorRef.markForCheck(); this._loadingService.stop(); @@ -421,41 +417,37 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni download(await firstValueFrom(originalFile), file.filename); } - bla() { - const documentLoaded$ = this._pdf.documentLoaded$.pipe(tap(() => this.viewerReady())); + loadAnnotations() { + const documentLoaded$ = this.pdf.documentLoaded$.pipe(tap(() => this.viewerReady())); let start; - combineLatest([documentLoaded$, this._fileDataService.annotations$]) - .pipe( - withLatestFrom(this.stateService.file$), - filter(([, file]) => !file.isProcessing), - tap(() => (start = new Date().getTime())), - map(([[, annotations]]) => annotations), - startWith([] as AnnotationWrapper[]), - pairwise(), - tap(annotations => this.deleteAnnotations(...annotations)), - switchMap(annotations => this.drawChangedAnnotations(...annotations)), - tap(() => console.log(`%c [ANNOTATIONS] Processing time: ${new Date().getTime() - start}`, 'color: aqua')), - tap(() => this.updateViewMode()), - ) - .subscribe(); + return combineLatest([documentLoaded$, this._fileDataService.annotations$]).pipe( + withLatestFrom(this.stateService.file$), + filter(([, file]) => !file.isProcessing), + tap(() => (start = new Date().getTime())), + map(([[, annotations]]) => annotations), + startWith({} as Record), + pairwise(), + tap(annotations => this.deleteAnnotations(...annotations)), + switchMap(annotations => this.drawChangedAnnotations(...annotations)), + tap(() => console.log(`%c [ANNOTATIONS] Processing time: ${new Date().getTime() - start}`, 'color: aqua')), + tap(() => this.updateViewMode()), + ); } - deleteAnnotations(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) { - const annotationsToDelete = oldAnnotations.filter( - oldAnnotation => !newAnnotations.some(newAnnotation => newAnnotation.id === oldAnnotation.id), - ); + deleteAnnotations(oldAnnotations: Record, newAnnotations: Record) { + const annotationsToDelete = Object.values(oldAnnotations).filter(oldAnnotation => !newAnnotations[oldAnnotation.id]); if (annotationsToDelete.length === 0) { return; } console.log('%c [ANNOTATIONS] To delete: ', 'color: aqua', annotationsToDelete); - this._pdf.deleteAnnotations(annotationsToDelete.map(annotation => annotation.id)); + this.pdf.deleteAnnotations(annotationsToDelete.map(annotation => annotation.id)); } - drawChangedAnnotations(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) { - const annotationsToDraw = newAnnotations.filter(newAnnotation => { - const oldAnnotation = oldAnnotations.find(annotation => annotation.id === newAnnotation.id); + drawChangedAnnotations(oldAnnotations: Record, newAnnotations: Record) { + const annotationsToDraw = Object.values(newAnnotations).filter(newAnnotation => { + const oldAnnotation = oldAnnotations[newAnnotation.id]; if (!oldAnnotation) { return true; } @@ -480,23 +472,23 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni console.log('%c [ANNOTATIONS] To draw: ', 'color: aqua', annotationsToDraw); const annotationsToDrawIds = annotationsToDraw.map(a => a.annotationId); - this._pdf.deleteAnnotations(annotationsToDrawIds); - return this._cleanupAndRedrawAnnotations(annotation => annotationsToDrawIds.includes(annotation.annotationId)); + this.pdf.deleteAnnotations(annotationsToDrawIds); + return this._cleanupAndRedrawAnnotations(annotationsToDraw); } async #deactivateMultiSelect() { this.multiSelectService.deactivate(); - this._pdf.deselectAllAnnotations(); + this.pdf.deselectAllAnnotations(); await this.handleAnnotationSelected([]); } private _setExcludedPageStyles() { const file = this._filesMapService.get(this.dossierId, this.fileId); setTimeout(() => { - const iframeDoc = this._pdf.UI.iframeWindow.document; - const pageContainer = iframeDoc.getElementById(`pageWidgetContainer${this.activeViewerPage}`); + const iframeDoc = this.pdf.UI.iframeWindow.document; + const pageContainer = iframeDoc.getElementById(`pageWidgetContainer${this.pdf.currentPage}`); if (pageContainer) { - if (file.excludedPages.includes(this.activeViewerPage)) { + if (file.excludedPages.includes(this.pdf.currentPage)) { pageContainer.classList.add('excluded-page'); } else { pageContainer.classList.remove('excluded-page'); @@ -506,16 +498,16 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } private async _stampPDF() { - const pdfDoc = await this._pdf.documentViewer.getDocument().getPDFDoc(); + const pdfDoc = await this.pdf.documentViewer.getDocument().getPDFDoc(); const file = await this.stateService.file; const allPages = [...Array(file.numberOfPages).keys()].map(page => page + 1); - if (!pdfDoc || !this._pdf.ready) { + if (!pdfDoc || !this.pdf.ready) { return; } try { - await clearStamps(pdfDoc, this._pdf.PDFNet, allPages); + await clearStamps(pdfDoc, this.pdf.PDFNet, allPages); } catch (e) { console.log('Error clearing stamps: ', e); return; @@ -529,8 +521,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } else { await this._stampExcludedPages(pdfDoc, file.excludedPages); } - this._pdf.documentViewer.refreshAll(); - this._pdf.documentViewer.updateView([this.activeViewerPage], this.activeViewerPage); + + this.pdf.documentViewer.refreshAll(); + this.pdf.documentViewer.updateView([this.pdf.currentPage], this.pdf.currentPage); this._changeDetectorRef.markForCheck(); } @@ -538,7 +531,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const watermark = await firstValueFrom(this._watermarkService.getWatermark(dossierTemplateId)); await stampPDFPage( document, - this._pdf.PDFNet, + this.pdf.PDFNet, watermark.text, watermark.fontSize, watermark.fontType, @@ -553,7 +546,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni if (excludedPages && excludedPages.length > 0) { await stampPDFPage( document, - this._pdf.PDFNet, + this.pdf.PDFNet, this._translateService.instant('file-preview.excluded-from-redaction') as string, 17, 'courier', @@ -566,6 +559,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } private _subscribeToFileUpdates(): void { + this.addActiveScreenSubscription = this.loadAnnotations().subscribe(); + this.addActiveScreenSubscription = timer(0, 5000) .pipe( switchMap(() => this.stateService.file$), @@ -600,27 +595,14 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni ); } - private async _loadFileData(file: File): Promise { - if (!file || file.isError) { - const dossier = await this.stateService.dossier; - return this._router.navigate([dossier.routerLink]); - } - - if (file.isUnprocessed) { - return; - } - - await this._fileDataService.load(file); - } - @Debounce(0) private _scrollViews() { this._workloadComponent?.scrollQuickNavigation(); this._workloadComponent?.scrollAnnotations(); } - private async _cleanupAndRedrawAnnotations(newAnnotationsFilter?: (annotation: AnnotationWrapper) => boolean) { - if (!this._pdf.ready) { + private async _cleanupAndRedrawAnnotations(newAnnotations: AnnotationWrapper[]) { + if (!this.pdf.ready) { return; } @@ -628,8 +610,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni await this.rebuildFilters(); const startTime = new Date().getTime(); - const annotations = await this._fileDataService.annotations; - const newAnnotations = newAnnotationsFilter ? annotations.filter(newAnnotationsFilter) : annotations; if (currentFilters) { const visibleAnnotations = await this._fileDataService.visibleAnnotations; @@ -670,11 +650,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } private _handleIgnoreAnnotationsDrawing(hideSkipped: boolean): void { - const ignored = this._pdf.getAnnotations(a => a.getCustomData('skipped')); + const ignored = this.pdf.getAnnotations(a => a.getCustomData('skipped')); if (hideSkipped) { - this._pdf.hideAnnotations(ignored); + this.pdf.hideAnnotations(ignored); } else { - this._pdf.showAnnotations(ignored); + this.pdf.showAnnotations(ignored); } } diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-references.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-references.service.ts index 5cdca8162..79a603d67 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-references.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-references.service.ts @@ -1,15 +1,27 @@ import { Injectable } from '@angular/core'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { shareDistinctLast } from '@iqser/common-ui'; +import { BehaviorSubject, Observable, switchMap } from 'rxjs'; +import { filterEach, shareDistinctLast } from '@iqser/common-ui'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; +import { filter, map } from 'rxjs/operators'; +import { FileDataService } from './file-data.service'; @Injectable() export class AnnotationReferencesService { readonly annotation$: Observable; + readonly references$: Observable; private readonly _annotation$ = new BehaviorSubject(undefined); - constructor() { + constructor(private readonly _fileDataService: FileDataService) { this.annotation$ = this._annotation$.asObservable().pipe(shareDistinctLast()); + this.references$ = this.#references$; + } + + get #references$(): Observable { + const annotations$ = this._fileDataService.annotations$.pipe(map(dict => Object.values(dict))); + return this.annotation$.pipe( + filter(annotation => !!annotation), + switchMap(({ reference }) => annotations$.pipe(filterEach(a => reference.includes(a.annotationId)))), + ); } show(annotation: AnnotationWrapper) { diff --git a/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts b/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts index 13518b326..a53669d21 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts @@ -12,21 +12,22 @@ import { } from '@red/domain'; import { AnnotationWrapper } from '../../../models/file/annotation.wrapper'; import * as moment from 'moment'; -import { BehaviorSubject, firstValueFrom, iif, Observable, of } from 'rxjs'; +import { BehaviorSubject, firstValueFrom, iif, Observable, Subject } from 'rxjs'; import { RedactionLogEntry } from '../../../models/file/redaction-log.entry'; import { Injectable } from '@angular/core'; import { FilePreviewStateService } from './file-preview-state.service'; import { ViewedPagesService } from '../../../services/entity-services/viewed-pages.service'; import { UserPreferenceService } from '../../../services/user-preference.service'; import { DictionariesMapService } from '../../../services/entity-services/dictionaries-map.service'; -import { catchError, map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; +import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; import { PermissionsService } from '../../../services/permissions.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { shareLast, Toaster } from '../../../../../../../libs/common-ui/src'; +import { log, shareDistinctLast, shareLast, Toaster } from '../../../../../../../libs/common-ui/src'; import { RedactionLogService } from '../../dossier/services/redaction-log.service'; import { TextHighlightService } from '../../dossier/services/text-highlight.service'; import { ViewModeService } from './view-mode.service'; import { Core } from '@pdftron/webviewer'; +import { Router } from '@angular/router'; import Annotation = Core.Annotations.Annotation; const DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes; @@ -34,14 +35,14 @@ const DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes; @Injectable() export class FileDataService { viewedPages: IViewedPage[] = []; - readonly hasChangeLog$ = new BehaviorSubject(false); missingTypes = new Set(); - shouldUpdateAnnotations = false; - readonly annotations$: Observable; + + readonly hasChangeLog$ = new BehaviorSubject(false); + readonly annotations$: Observable>; readonly visibleAnnotations$: Observable; readonly hiddenAnnotations = new Set(); - readonly #redactionLog$ = new BehaviorSubject({}); + readonly #redactionLog$ = new Subject(); readonly #textHighlights$ = new BehaviorSubject([]); constructor( @@ -54,6 +55,7 @@ export class FileDataService { private readonly _redactionLogService: RedactionLogService, private readonly _textHighlightsService: TextHighlightService, private readonly _toaster: Toaster, + private readonly _router: Router, ) { this.annotations$ = this.#annotations$; this.visibleAnnotations$ = this._viewModeService.viewMode$.pipe( @@ -61,10 +63,11 @@ export class FileDataService { iif( () => viewMode === ViewModes.TEXT_HIGHLIGHTS, this.#textHighlights$, - this.annotations$.pipe(map(annotations => this.getVisibleAnnotations(annotations, viewMode))), + this.annotations$.pipe(map(annotations => this.getVisibleAnnotations(Object.values(annotations), viewMode))), ), ), - shareLast(), + log('Visible annotations: '), + shareDistinctLast(), ); } @@ -73,7 +76,7 @@ export class FileDataService { } get annotations() { - return firstValueFrom(this.annotations$); + return firstValueFrom(this.annotations$.pipe(map(dict => Object.values(dict)))); } get textHighlights() { @@ -88,12 +91,24 @@ export class FileDataService { map(annotations => this._userPreferenceService.areDevFeaturesEnabled ? annotations : annotations.filter(a => !a.isFalsePositive), ), + map(annotations => Object.assign({} as Record, ...annotations.map(a => ({ [a.id]: a })))), shareLast(), ); } - async load(file: File) { - this.viewedPages = await firstValueFrom(this.getViewedPagesFor(file)); + async loadAnnotations() { + const file = await this._state.file; + + if (!file || file.isError) { + const dossier = await this._state.dossier; + return this._router.navigate([dossier.routerLink]); + } + + if (file.isUnprocessed) { + return; + } + + await this.loadViewedPages(file); await this.loadRedactionLog(); } @@ -108,29 +123,25 @@ export class FileDataService { } async loadTextHighlights() { - const { dossierId, fileId } = this._state; - const redactionPerColor = await firstValueFrom(this._textHighlightsService.getTextHighlights(dossierId, fileId)); - const textHighlights = this.#buildTextHighlights(redactionPerColor); + const redactionPerColor = this._textHighlightsService.getTextHighlights(this._state.dossierId, this._state.fileId); + const textHighlights = this.#buildTextHighlights(await firstValueFrom(redactionPerColor)); this.#textHighlights$.next(textHighlights); return textHighlights; } - getViewedPagesFor(file: File) { - if (this._permissionsService.canMarkPagesAsViewed(file)) { - return this._viewedPagesService.getViewedPages(file.dossierId, file.fileId); + async loadViewedPages(file: File) { + if (!this._permissionsService.canMarkPagesAsViewed(file)) { + this.viewedPages = []; + return; } - return of([] as IViewedPage[]); + + this.viewedPages = await firstValueFrom(this._viewedPagesService.getViewedPages(file.dossierId, file.fileId)); } loadRedactionLog() { - const redactionLog$ = this._redactionLogService.getRedactionLog(this._state.dossierId, this._state.fileId).pipe( - tap(redactionLog => redactionLog.redactionLogEntry.sort((a, b) => a.positions[0].page - b.positions[0].page)), - catchError(() => of({})), - tap(redactionLog => this.#redactionLog$.next(redactionLog)), - ); - - return firstValueFrom(redactionLog$); + const redactionLog$ = this._redactionLogService.getRedactionLog(this._state.dossierId, this._state.fileId); + return firstValueFrom(redactionLog$.pipe(tap(redactionLog => this.#redactionLog$.next(redactionLog)))); } getVisibleAnnotations(annotations: AnnotationWrapper[], viewMode: ViewMode) { @@ -179,12 +190,12 @@ export class FileDataService { #convertData(redactionLog: IRedactionLog, file: File): RedactionLogEntry[] { let result: RedactionLogEntry[] = []; const reasonAnnotationIds: { [key: string]: RedactionLogEntry[] } = {}; - const _dictionaryData = this._dictionariesMapService.get(this._state.dossierTemplateId); + const dictionaries = this._dictionariesMapService.get(this._state.dossierTemplateId); redactionLog.redactionLogEntry?.forEach(redactionLogEntry => { const changeLogValues = this.#getChangeLogValues(redactionLogEntry, file); - const dictionaryData = _dictionaryData.find(dict => dict.type === redactionLogEntry.type); - if (!dictionaryData) { + const dictionary = dictionaries.find(dict => dict.type === redactionLogEntry.type); + if (!dictionary) { this.missingTypes.add(redactionLogEntry.type); return; } @@ -195,7 +206,7 @@ export class FileDataService { changeLogValues.isChangeLogEntry, changeLogValues.hidden, redactionLog.legalBasis, - !!dictionaryData?.hint, + !!dictionary?.hint, ); if (