draw only changed annotations

This commit is contained in:
Dan Percic 2022-03-18 11:50:51 +02:00
parent 7399f44881
commit be8316f609
7 changed files with 147 additions and 150 deletions

View File

@ -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<unknown> {
queryParams.push({ key: 'withManualRedactions', value: withManualRedactions });
}
return this._getOne<IRedactionLog>([dossierId, fileId], 'redactionLog', queryParams);
const redactionLog$ = this._getOne<IRedactionLog>([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()

View File

@ -1,4 +1,4 @@
<div *ngIf="references$ | async as references" class="content-container">
<div *ngIf="annotationReferencesService.references$ | async as references" class="content-container">
<div class="dialog references-dialog">
<div class="references-header flex">
<div class="small-label uppercase">

View File

@ -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<AnnotationWrapper>();
references$ = this._annotationReferences;
constructor(readonly annotationReferencesService: AnnotationReferencesService, private readonly _fileDataService: FileDataService) {}
private get _annotationReferences(): Observable<AnnotationWrapper[]> {
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);

View File

@ -90,7 +90,7 @@
(selectAnnotations)="selectAnnotations($event)"
(selectPage)="selectPage($event)"
*ngIf="!file.excluded"
[activeViewerPage]="activeViewerPage"
[activeViewerPage]="pdf.currentPage"
[annotationActionsTemplate]="annotationActionsTemplate"
[dialogRef]="dialogRef"
[file]="file"

View File

@ -64,11 +64,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
fullScreen = false;
selectedAnnotations: AnnotationWrapper[] = [];
displayPdfViewer = false;
activeViewerPage: number = null;
readonly canPerformAnnotationActions$: Observable<boolean>;
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<unknown>;
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<void> {
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<string, 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()),
);
}
deleteAnnotations(oldAnnotations: AnnotationWrapper[], newAnnotations: AnnotationWrapper[]) {
const annotationsToDelete = oldAnnotations.filter(
oldAnnotation => !newAnnotations.some(newAnnotation => newAnnotation.id === oldAnnotation.id),
);
deleteAnnotations(oldAnnotations: Record<string, AnnotationWrapper>, newAnnotations: Record<string, AnnotationWrapper>) {
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<string, AnnotationWrapper>, newAnnotations: Record<string, AnnotationWrapper>) {
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<void | boolean> {
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);
}
}

View File

@ -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<AnnotationWrapper>;
readonly references$: Observable<AnnotationWrapper[]>;
private readonly _annotation$ = new BehaviorSubject<AnnotationWrapper | undefined>(undefined);
constructor() {
constructor(private readonly _fileDataService: FileDataService) {
this.annotation$ = this._annotation$.asObservable().pipe(shareDistinctLast());
this.references$ = this.#references$;
}
get #references$(): Observable<AnnotationWrapper[]> {
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) {

View File

@ -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<boolean>(false);
missingTypes = new Set<string>();
shouldUpdateAnnotations = false;
readonly annotations$: Observable<AnnotationWrapper[]>;
readonly hasChangeLog$ = new BehaviorSubject<boolean>(false);
readonly annotations$: Observable<Record<string, AnnotationWrapper>>;
readonly visibleAnnotations$: Observable<AnnotationWrapper[]>;
readonly hiddenAnnotations = new Set<string>();
readonly #redactionLog$ = new BehaviorSubject<IRedactionLog>({});
readonly #redactionLog$ = new Subject<IRedactionLog>();
readonly #textHighlights$ = new BehaviorSubject<AnnotationWrapper[]>([]);
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<string, AnnotationWrapper>, ...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 (