diff --git a/apps/red-ui/src/app/models/file/file-data.model.ts b/apps/red-ui/src/app/models/file/file-data.model.ts deleted file mode 100644 index acceaa6ce..000000000 --- a/apps/red-ui/src/app/models/file/file-data.model.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { - ChangeType, - Dictionary, - File, - IRedactionLog, - IRedactionLogEntry, - IViewedPage, - LogEntryStatus, - ManualRedactionType, - TextHighlightResponse, - ViewMode, -} from '@red/domain'; -import { AnnotationWrapper } from './annotation.wrapper'; -import * as moment from 'moment'; -import { BehaviorSubject } from 'rxjs'; -import { RedactionLogEntry } from './redaction-log.entry'; - -export class FileDataModel { - static readonly DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes; - allAnnotations: AnnotationWrapper[] = []; - readonly hasChangeLog$ = new BehaviorSubject(false); - missingTypes = new Set(); - textHighlightAnnotations: AnnotationWrapper[] = []; - - constructor( - private readonly _file: File, - private _redactionLog: IRedactionLog, - public viewedPages?: IViewedPage[], - private _dictionaryData?: Dictionary[], - private _areDevFeaturesEnabled?: boolean, - ) { - this._buildAllAnnotations(); - } - - get redactionLog(): IRedactionLog { - return this._redactionLog; - } - - set redactionLog(redactionLog: IRedactionLog) { - this._redactionLog = redactionLog; - this._buildAllAnnotations(); - } - - set textHighlights(textHighlightResponse: TextHighlightResponse) { - const highlights = []; - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - for (const color of Object.keys(textHighlightResponse.redactionPerColor)) { - for (const entry of textHighlightResponse.redactionPerColor[color]) { - const annotation = AnnotationWrapper.fromHighlight(color, entry); - highlights.push(annotation); - } - } - this.textHighlightAnnotations = highlights; - } - - getVisibleAnnotations(viewMode: ViewMode) { - if (viewMode === 'TEXT_HIGHLIGHTS') { - return this.textHighlightAnnotations; - } - - return this.allAnnotations.filter(annotation => { - if (viewMode === 'STANDARD') { - return !annotation.isChangeLogRemoved; - } else if (viewMode === 'DELTA') { - return annotation.isChangeLogEntry; - } else { - return annotation.previewAnnotation; - } - }); - } - - private _buildAllAnnotations() { - const entries: RedactionLogEntry[] = this._convertData(); - - const previousAnnotations = [...this.allAnnotations]; - this.allAnnotations = entries - .map(entry => AnnotationWrapper.fromData(entry)) - .filter(ann => ann.manual || !this._file.excludedPages.includes(ann.pageNumber)); - - if (!this._areDevFeaturesEnabled) { - this.allAnnotations = this.allAnnotations.filter(annotation => !annotation.isFalsePositive); - } - - this._setHiddenPropertyToNewAnnotations(this.allAnnotations, previousAnnotations); - } - - private _setHiddenPropertyToNewAnnotations(newAnnotations: AnnotationWrapper[], oldAnnotations: AnnotationWrapper[]) { - newAnnotations.forEach(newAnnotation => { - const oldAnnotation = oldAnnotations.find(a => a.annotationId === newAnnotation.annotationId); - if (oldAnnotation) { - newAnnotation.hidden = oldAnnotation.hidden; - } - }); - } - - private _convertData(): RedactionLogEntry[] { - let result: RedactionLogEntry[] = []; - - const reasonAnnotationIds: { [key: string]: RedactionLogEntry[] } = {}; - this.redactionLog.redactionLogEntry?.forEach(redactionLogEntry => { - // copy the redactionLog Entry - - const changeLogValues = this.#getChangeLogValues(redactionLogEntry); - const dictionaryData = this._dictionaryData.find(dict => dict.type === redactionLogEntry.type); - if (!dictionaryData) { - this.missingTypes.add(redactionLogEntry.type); - return; - } - - const redactionLogEntryWrapper: RedactionLogEntry = new RedactionLogEntry( - redactionLogEntry, - changeLogValues.changeLogType, - changeLogValues.isChangeLogEntry, - changeLogValues.hidden, - this.redactionLog.legalBasis, - !!dictionaryData?.hint, - ); - - if ( - redactionLogEntry.manualChanges?.find( - mc => - mc.manualRedactionType === ManualRedactionType.ADD_TO_DICTIONARY && - (mc.annotationStatus === LogEntryStatus.APPROVED || mc.annotationStatus === LogEntryStatus.REQUESTED), - ) - ) { - // for dictionary entries -> I.E accepted recommendations or false positives, - // check reason - if (!reasonAnnotationIds[redactionLogEntry.reason]) { - reasonAnnotationIds[redactionLogEntry.reason] = [redactionLogEntryWrapper]; - } else { - reasonAnnotationIds[redactionLogEntry.reason].push(redactionLogEntryWrapper); - } - } - - result.push(redactionLogEntryWrapper); - }); - - const reasonKeys = Object.keys(reasonAnnotationIds); - result = result.filter(r => { - const matched = reasonKeys.indexOf(r.id) >= 0; - if (matched) { - reasonAnnotationIds[r.id].forEach(value => { - value.reason = null; - }); - } - return !matched; - }); - - result = result.filter(r => !r.hidden); - - return result; - } - - #getChangeLogValues(redactionLogEntry: IRedactionLogEntry): { - hidden: boolean; - changeLogType: ChangeType; - isChangeLogEntry: boolean; - } { - if (this._file.numberOfAnalyses > 1) { - const viableChanges = redactionLogEntry.changes.filter(c => c.analysisNumber > 1); - viableChanges.sort((a, b) => moment(a.dateTime).valueOf() - moment(b.dateTime).valueOf()); - - const lastChange = viableChanges.length >= 1 ? viableChanges[viableChanges.length - 1] : undefined; - const page = redactionLogEntry.positions?.[0].page; - - const viewedPage = this.viewedPages.filter(p => p.page === page).pop(); - - // page has been seen -> let's see if it's a change - if (viewedPage) { - const viewTime = moment(viewedPage.viewedTime).valueOf() - FileDataModel.DELTA_VIEW_TIME; - // these are all unseen changes - const relevantChanges = viableChanges.filter(change => moment(change.dateTime).valueOf() > viewTime); - // at least one unseen change - if (relevantChanges.length > 0) { - // at least 1 relevant change - viewedPage.showAsUnseen = moment(viewedPage.viewedTime).valueOf() < moment(lastChange.dateTime).valueOf(); - this.hasChangeLog$.next(true); - return { - changeLogType: relevantChanges[relevantChanges.length - 1].type, - isChangeLogEntry: true, - hidden: false, - }; - } else { - // no relevant changes - hide removed anyway - return { - changeLogType: null, - isChangeLogEntry: false, - hidden: lastChange && lastChange.type === 'REMOVED', - }; - } - } else { - // Page doesn't have a view-time - return { - changeLogType: null, - isChangeLogEntry: false, - hidden: lastChange && lastChange.type === 'REMOVED', - }; - } - } else { - return { - changeLogType: null, - isChangeLogEntry: false, - hidden: false, - }; - } - // console.log(wrapper.changeLogType, wrapper.hidden, wrapper.isChangeLogEntry, wrapper.value, lastChange); - } -} diff --git a/apps/red-ui/src/app/modules/dossier/services/pdf-viewer-data.service.ts b/apps/red-ui/src/app/modules/dossier/services/pdf-viewer-data.service.ts deleted file mode 100644 index c2144329f..000000000 --- a/apps/red-ui/src/app/modules/dossier/services/pdf-viewer-data.service.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Injectable } from '@angular/core'; -import { forkJoin, Observable, of } from 'rxjs'; -import { catchError, map, tap } from 'rxjs/operators'; -import { FileDataModel } from '@models/file/file-data.model'; -import { PermissionsService } from '@services/permissions.service'; -import { Dictionary, File, IRedactionLog, IViewedPage, TextHighlightResponse } from '@red/domain'; -import { RedactionLogService } from './redaction-log.service'; -import { ViewedPagesService } from '@services/entity-services/viewed-pages.service'; -import { UserPreferenceService } from '@services/user-preference.service'; -import { FilePreviewStateService } from '../../file-preview/services/file-preview-state.service'; -import { Toaster } from '@iqser/common-ui'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; -import { TextHighlightService } from './text-highlight.service'; - -@Injectable() -export class PdfViewerDataService { - constructor( - private readonly _permissionsService: PermissionsService, - private readonly _redactionLogService: RedactionLogService, - private readonly _textHighlightService: TextHighlightService, - private readonly _viewedPagesService: ViewedPagesService, - private readonly _userPreferenceService: UserPreferenceService, - private readonly _stateService: FilePreviewStateService, - private readonly _toaster: Toaster, - private readonly _dictionariesMapService: DictionariesMapService, - ) {} - - loadRedactionLogFor(dossierId: string, fileId: string) { - return this._redactionLogService.getRedactionLog(dossierId, fileId).pipe( - tap(redactionLog => redactionLog.redactionLogEntry.sort((a, b) => a.positions[0].page - b.positions[0].page)), - catchError(() => of({})), - ); - } - - loadTextHighlightsFor(dossierId: string, fileId: string): Observable { - return this._textHighlightService.getTextHighlights(dossierId, fileId).pipe(catchError(() => of({}))); - } - - loadDataFor(newFile: File): Observable { - const redactionLog$ = this.loadRedactionLogFor(newFile.dossierId, newFile.fileId); - const viewedPages$ = this.getViewedPagesFor(newFile); - - return forkJoin([redactionLog$, viewedPages$]).pipe( - map((data: [redactionLog: IRedactionLog, viewedPages: IViewedPage[]]) => { - const dictionaries: Dictionary[] = this._dictionariesMapService.get(this._stateService.dossierTemplateId); - const fileDataModel = new FileDataModel(newFile, ...data, dictionaries, this._userPreferenceService.areDevFeaturesEnabled); - if (fileDataModel.missingTypes.size > 0) { - this._toaster.error(_('error.missing-types'), { - disableTimeOut: true, - params: { missingTypes: Array.from(fileDataModel.missingTypes).join(', ') }, - }); - } - return fileDataModel; - }), - ); - } - - getViewedPagesFor(file: File) { - if (this._permissionsService.canMarkPagesAsViewed(file)) { - return this._viewedPagesService.getViewedPages(file.dossierId, file.fileId).pipe(catchError(() => of([]))); - } - return of([]); - } -} diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview-providers.ts b/apps/red-ui/src/app/modules/file-preview/file-preview-providers.ts index 2459dde14..98c44dc3e 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview-providers.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview-providers.ts @@ -7,7 +7,6 @@ import { SkippedService } from './services/skipped.service'; import { AnnotationDrawService } from './services/annotation-draw.service'; import { AnnotationActionsService } from './services/annotation-actions.service'; import { FilePreviewStateService } from './services/file-preview-state.service'; -import { PdfViewerDataService } from '../dossier/services/pdf-viewer-data.service'; import { AnnotationReferencesService } from './services/annotation-references.service'; import { FilterService } from '@iqser/common-ui'; import { AnnotationProcessingService } from '../dossier/services/annotation-processing.service'; @@ -27,7 +26,6 @@ export const filePreviewScreenProviders = [ AnnotationDrawService, AnnotationActionsService, FilePreviewStateService, - PdfViewerDataService, AnnotationReferencesService, PageRotationService, PdfViewer, 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 e467f8aeb..6fa9e83cd 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 @@ -25,7 +25,6 @@ import { File, ViewMode } from '@red/domain'; import { PermissionsService } from '@services/permissions.service'; import { combineLatest, firstValueFrom, Observable, of, timer } from 'rxjs'; import { UserPreferenceService } from '@services/user-preference.service'; -import { PdfViewerDataService } from '../dossier/services/pdf-viewer-data.service'; import { clearStamps, download, handleFilterDelta, stampPDFPage } from '../../utils'; import { FileWorkloadComponent } from './components/file-workload/file-workload.component'; import { TranslateService } from '@ngx-translate/core'; @@ -93,7 +92,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni private readonly _router: Router, private readonly _annotationProcessingService: AnnotationProcessingService, private readonly _annotationDrawService: AnnotationDrawService, - private readonly _pdfViewerDataService: PdfViewerDataService, private readonly _filesService: FilesService, private readonly _ngZone: NgZone, private readonly _fileManagementService: FileManagementService, @@ -196,9 +194,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } case 'TEXT_HIGHLIGHTS': { this._loadingService.start(); - const textHighlights = await firstValueFrom(this._pdfViewerDataService.loadTextHighlightsFor(this.dossierId, this.fileId)); this._pdf.hideAnnotations(annotations); - this._fileDataService.textHighlights = textHighlights; + await this._fileDataService.loadTextHighlights(); await this._annotationDrawService.drawAnnotations(this._fileDataService.textHighlightAnnotations); this._loadingService.stop(); } @@ -613,9 +610,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni } const currentPageAnnotations = this.visibleAnnotations.filter(a => a.pageNumber === page); - await this._fileDataService.setRedactionLog( - await firstValueFrom(this._pdfViewerDataService.loadRedactionLogFor(this.dossierId, this.fileId)), - ); + await this._fileDataService.loadRedactionLog(); this._deleteAnnotations(currentPageAnnotations); await this._cleanupAndRedrawAnnotations(annotation => annotation.pageNumber === page); 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 e678980f1..eb9574377 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 @@ -23,6 +23,7 @@ import { PermissionsService } from '../../../services/permissions.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { Toaster } from '../../../../../../../libs/common-ui/src'; import { RedactionLogService } from '../../dossier/services/redaction-log.service'; +import { TextHighlightService } from '../../dossier/services/text-highlight.service'; @Injectable() export class FileDataService { @@ -43,6 +44,7 @@ export class FileDataService { private readonly _dictionariesMapService: DictionariesMapService, private readonly _permissionsService: PermissionsService, private readonly _redactionLogService: RedactionLogService, + private readonly _textHighlightsService: TextHighlightService, private readonly _toaster: Toaster, ) {} @@ -70,12 +72,13 @@ export class FileDataService { this.shouldUpdateAnnotations = true; this.#redactionLogHash = newRedactionLogHash; + this.missingTypes.clear(); await this.#buildAllAnnotations(); } async load(file: File) { this.viewedPages = await firstValueFrom(this.getViewedPagesFor(file)); - await this.setRedactionLog(await firstValueFrom(this.loadRedactionLog())); + await this.loadRedactionLog(); if (this.missingTypes.size > 0) { this._toaster.error(_('error.missing-types'), { @@ -85,6 +88,12 @@ export class FileDataService { } } + async loadTextHighlights() { + const { dossierId, fileId } = this._state; + const highlights = this._textHighlightsService.getTextHighlights(dossierId, fileId).pipe(catchError(() => of({}))); + this.textHighlights = await firstValueFrom(highlights); + } + getViewedPagesFor(file: File) { if (this._permissionsService.canMarkPagesAsViewed(file)) { return this._viewedPagesService.getViewedPages(file.dossierId, file.fileId); @@ -92,11 +101,13 @@ export class FileDataService { return of([] as IViewedPage[]); } - loadRedactionLog() { - return this._redactionLogService.getRedactionLog(this._state.dossierId, this._state.fileId).pipe( + async 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({})), ); + + return this.setRedactionLog(await firstValueFrom(redactionLog$)); } getVisibleAnnotations(viewMode: ViewMode) {