use reactive annotations

This commit is contained in:
Dan Percic 2022-03-16 16:05:32 +02:00
parent ee6f8dab02
commit c8a2e0b580
12 changed files with 159 additions and 121 deletions

View File

@ -1,8 +1,9 @@
import { Injectable, Injector } from '@angular/core';
import { GenericService, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
import { TextHighlightOperation, TextHighlightRequest, TextHighlightResponse } from '@red/domain';
import { ImportedRedaction, TextHighlightOperation, TextHighlightRequest, TextHighlightResponse } from '@red/domain';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { tap } from 'rxjs/operators';
import { catchError, map, tap } from 'rxjs/operators';
import { of } from 'rxjs';
@Injectable({
providedIn: 'root',
@ -20,7 +21,10 @@ export class TextHighlightService extends GenericService<TextHighlightResponse>
operation: TextHighlightOperation.INFO,
};
return this._post(request);
return this._post(request).pipe(
map(response => response.redactionPerColor),
catchError(() => of({} as Record<string, ImportedRedaction[]>)),
);
}
@Validate()

View File

@ -9,6 +9,7 @@ import { MultiSelectService } from '../../services/multi-select.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { HelpModeService, ScrollableParentView, ScrollableParentViews } from '@iqser/common-ui';
import { PdfViewer } from '../../services/pdf-viewer.service';
import { FileDataService } from '../../services/file-data.service';
export const AnnotationButtonTypes = {
dark: 'dark',
@ -39,6 +40,7 @@ export class AnnotationActionsComponent implements OnChanges {
private readonly _pdf: PdfViewer,
private readonly _state: FilePreviewStateService,
private readonly _permissionsService: PermissionsService,
private readonly _fileDataService: FileDataService,
) {}
private _annotations: AnnotationWrapper[];
@ -101,14 +103,14 @@ export class AnnotationActionsComponent implements OnChanges {
$event.stopPropagation();
this._pdf.annotationManager.hideAnnotations(this.viewerAnnotations);
this._pdf.annotationManager.deselectAllAnnotations();
this.annotationActionsService.updateHiddenAnnotation(this.annotations, this.viewerAnnotations, true);
this._fileDataService.updateHiddenAnnotations(this.viewerAnnotations, true);
}
showAnnotation($event: MouseEvent) {
$event.stopPropagation();
this._pdf.annotationManager.showAnnotations(this.viewerAnnotations);
this._pdf.annotationManager.deselectAllAnnotations();
this.annotationActionsService.updateHiddenAnnotation(this.annotations, this.viewerAnnotations, false);
this._fileDataService.updateHiddenAnnotations(this.viewerAnnotations, false);
}
resize($event: MouseEvent) {

View File

@ -1,9 +1,10 @@
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 } from 'rxjs';
import { filter, map } from 'rxjs/operators';
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',
@ -21,7 +22,7 @@ export class AnnotationReferencesListComponent {
private get _annotationReferences(): Observable<AnnotationWrapper[]> {
return this.annotationReferencesService.annotation$.pipe(
filter(annotation => !!annotation),
map(({ reference }) => this._fileDataService.allAnnotations.filter(a => reference.includes(a.annotationId))),
switchMap(({ reference }) => this._fileDataService.annotations$.pipe(filterEach(a => reference.includes(a.annotationId)))),
);
}

View File

@ -24,7 +24,7 @@ import {
shareDistinctLast,
shareLast,
} from '@iqser/common-ui';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { combineLatest, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { File } from '@red/domain';
import { ExcludedPagesService } from '../../services/excluded-pages.service';
@ -66,7 +66,6 @@ export class FileWorkloadComponent {
readonly showExcludedPages$: Observable<boolean>;
readonly title$: Observable<string>;
readonly isHighlights$: Observable<boolean>;
private _annotations$ = new BehaviorSubject<AnnotationWrapper[]>([]);
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
@ -89,11 +88,6 @@ export class FileWorkloadComponent {
this.title$ = this._title$;
}
@Input()
set annotations(value: AnnotationWrapper[]) {
this._annotations$.next(value);
}
get activeAnnotations(): AnnotationWrapper[] | undefined {
return this.displayedAnnotations.get(this.activeViewerPage);
}
@ -142,7 +136,7 @@ export class FileWorkloadComponent {
const primary$ = this.filterService.getFilterModels$('primaryFilters');
const secondary$ = this.filterService.getFilterModels$('secondaryFilters');
return combineLatest([this._annotations$.asObservable(), primary$, secondary$]).pipe(
return combineLatest([this.fileDataService.visibleAnnotations$, primary$, secondary$]).pipe(
map(([annotations, primary, secondary]) => this._filterAnnotations(annotations, primary, secondary)),
);
}

View File

@ -41,6 +41,7 @@ import { PageRotationService } from '../../services/page-rotation.service';
import { ALLOWED_KEYBOARD_SHORTCUTS, HeaderElements, TextPopups } from '../../shared/constants';
import { FilePreviewDialogService } from '../../services/file-preview-dialog.service';
import { loadCompareDocumentWrapper } from '../../../dossier/utils/compare-mode.utils';
import { FileDataService } from '../../services/file-data.service';
import Tools = Core.Tools;
import TextTool = Tools.TextTool;
import Annotation = Core.Annotations.Annotation;
@ -60,7 +61,6 @@ function getDivider(hiddenOn?: readonly ('desktop' | 'mobile' | 'tablet')[]) {
export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnChanges {
@Input() dossier: Dossier;
@Input() canPerformActions = false;
@Input() annotations: AnnotationWrapper[];
@Output() readonly fileReady = new EventEmitter();
@Output() readonly annotationSelected = new EventEmitter<string[]>();
@Output() readonly manualAnnotationRequested = new EventEmitter<ManualRedactionEntryWrapper>();
@ -89,6 +89,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
private readonly _loadingService: LoadingService,
private readonly _fileManagementService: FileManagementService,
private readonly _pageRotationService: PageRotationService,
private readonly _fileDataService: FileDataService,
readonly stateService: FilePreviewStateService,
readonly viewModeService: ViewModeService,
readonly multiSelectService: MultiSelectService,
@ -226,18 +227,19 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
this.pdf.disableHotkeys();
await this._configureTextPopup();
this.annotationManager.addEventListener('annotationSelected', (annotations: Annotation[], action) => {
this.annotationManager.addEventListener('annotationSelected', async (annotations: Annotation[], action) => {
const nextAnnotations = this.multiSelectService.isEnabled ? this.annotationManager.getSelectedAnnotations() : annotations;
this.annotationSelected.emit(nextAnnotations.map(ann => ann.Id));
if (action === 'deselected') {
this._toggleRectangleAnnotationAction(true);
} else {
if (!this.multiSelectService.isEnabled) {
this.pdf.deselectAnnotations(this.annotations.filter(wrapper => !nextAnnotations.find(ann => ann.Id === wrapper.id)));
}
this._configureAnnotationSpecificActions(annotations);
this._toggleRectangleAnnotationAction(annotations.length === 1 && annotations[0].ReadOnly);
return this._toggleRectangleAnnotationAction(true);
}
if (!this.multiSelectService.isEnabled) {
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
this.pdf.deselectAnnotations(visibleAnnotations.filter(wrapper => !nextAnnotations.find(ann => ann.Id === wrapper.id)));
}
this.#configureAnnotationSpecificActions(annotations);
this._toggleRectangleAnnotationAction(annotations.length === 1 && annotations[0].ReadOnly);
});
this.annotationManager.addEventListener('annotationChanged', (annotations: Annotation[]) => {
@ -480,12 +482,13 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
});
}
private _configureAnnotationSpecificActions(viewerAnnotations: Annotation[]) {
async #configureAnnotationSpecificActions(viewerAnnotations: Annotation[]) {
if (!this.canPerformActions) {
return;
}
const annotationWrappers = viewerAnnotations.map(va => this.annotations.find(a => a.id === va.Id)).filter(va => !!va);
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
const annotationWrappers = viewerAnnotations.map(va => visibleAnnotations.find(a => a.id === va.Id)).filter(va => !!va);
this.instance.UI.annotationPopup.update([]);
if (annotationWrappers.length === 0) {
@ -513,7 +516,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
this.annotationManager.showAnnotations(viewerAnnotations);
}
this.annotationManager.deselectAllAnnotations();
this._annotationActionsService.updateHiddenAnnotation(this.annotations, viewerAnnotations, allAreVisible);
this._fileDataService.updateHiddenAnnotations(viewerAnnotations, allAreVisible);
});
},
},

View File

@ -69,7 +69,6 @@
(pageChanged)="viewerPageChanged($event)"
(viewerReady)="viewerReady()"
*ngIf="displayPdfViewer"
[annotations]="visibleAnnotations"
[canPerformActions]="canPerformAnnotationActions$ | async"
[class.hidden]="!ready"
[dossier]="dossier"
@ -95,7 +94,6 @@
*ngIf="!file.excluded"
[activeViewerPage]="activeViewerPage"
[annotationActionsTemplate]="annotationActionsTemplate"
[annotations]="visibleAnnotations"
[dialogRef]="dialogRef"
[file]="file"
[selectedAnnotations]="selectedAnnotations"

View File

@ -125,14 +125,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
return this._pageRotationService.hasRotations();
}
get visibleAnnotations(): AnnotationWrapper[] {
return this._fileDataService.getVisibleAnnotations(this._viewModeService.viewMode);
}
get allAnnotations(): AnnotationWrapper[] {
return this._fileDataService.allAnnotations;
}
private get _canPerformAnnotationActions$() {
const viewMode$ = this._viewModeService.viewMode$.pipe(tap(() => this.#deactivateMultiSelect()));
@ -154,15 +146,17 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
return;
}
this._pdf.deleteAnnotations(this._fileDataService.textHighlightAnnotations.map(a => a.id));
this._pdf.deleteAnnotations(this._fileDataService.textHighlights.map(a => a.id));
const ocrAnnotationIds = this.allAnnotations.filter(a => a.isOCR).map(a => a.id);
const annotations = this._pdf.getAnnotations(a => a.getCustomData('redact-manager'));
const redactions = annotations.filter(a => a.getCustomData('redaction'));
switch (this._viewModeService.viewMode) {
case 'STANDARD': {
this._setAnnotationsColor(redactions, 'annotationColor');
const wrappers = await this._fileDataService.annotations;
const ocrAnnotationIds = wrappers.filter(a => a.isOCR).map(a => a.id);
const standardEntries = annotations
.filter(a => a.getCustomData('changeLogRemoved') === 'false')
.filter(a => !ocrAnnotationIds.includes(a.Id));
@ -192,14 +186,14 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
case 'TEXT_HIGHLIGHTS': {
this._loadingService.start();
this._pdf.hideAnnotations(annotations);
await this._fileDataService.loadTextHighlights();
await this._annotationDrawService.drawAnnotations(this._fileDataService.textHighlightAnnotations);
const highlights = await this._fileDataService.loadTextHighlights();
await this._annotationDrawService.drawAnnotations(highlights);
this._loadingService.stop();
}
}
await this._stampPDF();
this.rebuildFilters();
await this.rebuildFilters();
}
ngOnDetach(): void {
@ -237,7 +231,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.displayPdfViewer = true;
}
rebuildFilters(deletePreviousAnnotations = false): void {
async rebuildFilters(deletePreviousAnnotations = false) {
const startTime = new Date().getTime();
if (deletePreviousAnnotations) {
this._pdf.deleteAnnotations();
@ -246,7 +240,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
const processStartTime = new Date().getTime();
const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this.visibleAnnotations);
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
const annotationFilters = this._annotationProcessingService.getAnnotationFilter(visibleAnnotations);
const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters;
this._filterService.addFilterGroup({
slug: 'primaryFilters',
@ -266,9 +261,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
console.log(`[REDACTION] Filter rebuild time: ${new Date().getTime() - startTime}`);
}
handleAnnotationSelected(annotationIds: string[]) {
async handleAnnotationSelected(annotationIds: string[]) {
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
this.selectedAnnotations = annotationIds
.map(id => this.visibleAnnotations.find(annotationWrapper => annotationWrapper.id === id))
.map(id => visibleAnnotations.find(annotation => annotation.id === id))
.filter(ann => ann !== undefined);
if (this.selectedAnnotations.length > 1) {
this.multiSelectService.activate();
@ -447,10 +443,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
download(await firstValueFrom(originalFile), file.filename);
}
#deactivateMultiSelect(): void {
async #deactivateMultiSelect() {
this.multiSelectService.deactivate();
this._pdf.deselectAllAnnotations();
this.handleAnnotationSelected([]);
await this.handleAnnotationSelected([]);
}
private _setExcludedPageStyles() {
@ -498,7 +494,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
private async _stampPreview(document: PDFNet.PDFDoc, dossierTemplateId: string) {
const watermark = await this._watermarkService.getWatermark(dossierTemplateId).toPromise();
const watermark = await firstValueFrom(this._watermarkService.getWatermark(dossierTemplateId));
await stampPDFPage(
document,
this._pdf.PDFNet,
@ -605,7 +601,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
return;
}
const currentPageAnnotations = this.visibleAnnotations.filter(a => a.pageNumber === page);
const currentPageAnnotations = (await this._fileDataService.visibleAnnotations).filter(a => a.pageNumber === page);
await this._fileDataService.loadRedactionLog();
this._pdf.deleteAnnotations(currentPageAnnotations.map(a => a.id));
@ -618,14 +614,15 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
const currentFilters = this._filterService.getGroup('primaryFilters')?.filters || [];
this.rebuildFilters();
await this.rebuildFilters();
const startTime = new Date().getTime();
const annotations = this.allAnnotations;
const annotations = await this._fileDataService.annotations;
const newAnnotations = newAnnotationsFilter ? annotations.filter(newAnnotationsFilter) : annotations;
if (currentFilters) {
this._handleDeltaAnnotationFilters(currentFilters, this.visibleAnnotations);
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
this._handleDeltaAnnotationFilters(currentFilters, visibleAnnotations);
}
await this._annotationDrawService.drawAnnotations(newAnnotations);

View File

@ -24,7 +24,6 @@ import { MatDialog } from '@angular/material/dialog';
import { FilePreviewStateService } from './file-preview-state.service';
import { PdfViewer } from './pdf-viewer.service';
import { FilePreviewDialogService } from './file-preview-dialog.service';
import Annotation = Core.Annotations.Annotation;
import Quad = Core.Math.Quad;
@Injectable()
@ -414,12 +413,6 @@ export class AnnotationActionsService {
return availableActions;
}
updateHiddenAnnotation(annotations: AnnotationWrapper[], viewerAnnotations: Annotation[], hidden: boolean) {
const annotationId = viewerAnnotations[0].Id;
const annotationToBeUpdated = annotations.find((a: AnnotationWrapper) => a.annotationId === annotationId);
annotationToBeUpdated.hidden = hidden;
}
resize($event: MouseEvent, annotationWrapper: AnnotationWrapper) {
$event?.stopPropagation();

View File

@ -14,6 +14,7 @@ import { DossiersService } from '@services/dossiers/dossiers.service';
import { PdfViewer } from './pdf-viewer.service';
import { FilePreviewStateService } from './file-preview-state.service';
import { ViewModeService } from './view-mode.service';
import { FileDataService } from './file-data.service';
import Annotation = Core.Annotations.Annotation;
@Injectable()
@ -31,6 +32,7 @@ export class AnnotationDrawService {
private readonly _pdf: PdfViewer,
private readonly _state: FilePreviewStateService,
private readonly _viewModeService: ViewModeService,
private readonly _fileDataService: FileDataService,
) {}
drawAnnotations(annotationWrappers: AnnotationWrapper[]) {
@ -200,7 +202,7 @@ export class AnnotationDrawService {
annotationWrapper.isChangeLogRemoved ||
(this._skippedService.hideSkipped && annotationWrapper.isSkipped) ||
annotationWrapper.isOCR ||
annotationWrapper.hidden;
this._fileDataService.isHidden(annotationWrapper.annotationId);
annotation.setCustomData('redact-manager', 'true');
annotation.setCustomData('redaction', String(annotationWrapper.previewAnnotation));
annotation.setCustomData('skipped', String(annotationWrapper.isSkipped));

View File

@ -1,66 +1,100 @@
import {
ChangeType,
File,
ImportedRedaction,
IRedactionLog,
IRedactionLogEntry,
IViewedPage,
LogEntryStatus,
ManualRedactionType,
TextHighlightResponse,
ViewMode,
ViewModes,
} from '@red/domain';
import { AnnotationWrapper } from '../../../models/file/annotation.wrapper';
import * as moment from 'moment';
import { BehaviorSubject, firstValueFrom, of } from 'rxjs';
import { BehaviorSubject, firstValueFrom, iif, Observable, of } 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, tap } from 'rxjs/operators';
import { catchError, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { PermissionsService } from '../../../services/permissions.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { Toaster } from '../../../../../../../libs/common-ui/src';
import { log, 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 Annotation = Core.Annotations.Annotation;
const DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
@Injectable()
export class FileDataService {
static readonly DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
viewedPages: IViewedPage[] = [];
allAnnotations: AnnotationWrapper[] = [];
readonly hasChangeLog$ = new BehaviorSubject<boolean>(false);
missingTypes = new Set<string>();
textHighlightAnnotations: AnnotationWrapper[] = [];
shouldUpdateAnnotations = false;
readonly annotations$: Observable<AnnotationWrapper[]>;
readonly visibleAnnotations$: Observable<AnnotationWrapper[]>;
readonly hiddenAnnotations = new Set<string>();
#redactionLog: IRedactionLog;
#redactionLogHash = '';
readonly #redactionLog$ = new BehaviorSubject<IRedactionLog>({});
readonly #textHighlights$ = new BehaviorSubject<AnnotationWrapper[]>([]);
constructor(
private readonly _state: FilePreviewStateService,
private readonly _viewedPagesService: ViewedPagesService,
private readonly _viewModeService: ViewModeService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _dictionariesMapService: DictionariesMapService,
private readonly _permissionsService: PermissionsService,
private readonly _redactionLogService: RedactionLogService,
private readonly _textHighlightsService: TextHighlightService,
private readonly _toaster: Toaster,
) {}
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;
) {
this.annotations$ = this.#annotations$;
this.visibleAnnotations$ = this._viewModeService.viewMode$.pipe(
switchMap(viewMode =>
iif(
() => viewMode === ViewModes.TEXT_HIGHLIGHTS,
this.#textHighlights$,
this.annotations$.pipe(map(annotations => this.getVisibleAnnotations(annotations, viewMode))),
),
),
log('visible annotations: '),
shareLast(),
);
}
async setRedactionLog(redactionLog: IRedactionLog) {
get visibleAnnotations() {
return firstValueFrom(this.visibleAnnotations$);
}
get annotations() {
return firstValueFrom(this.annotations$);
}
get textHighlights() {
return this.#textHighlights$.value;
}
get #annotations$() {
return this.#redactionLog$.pipe(
withLatestFrom(this._state.file$),
map(([redactionLog, file]) => this.#buildAnnotations(redactionLog, file)),
map(annotations =>
this._userPreferenceService.areDevFeaturesEnabled ? annotations : annotations.filter(a => !a.isFalsePositive),
),
shareLast(),
);
}
setRedactionLog(redactionLog: IRedactionLog) {
this.#redactionLog = redactionLog;
const hash = require('object-hash');
const newRedactionLogHash = hash(redactionLog.redactionLogEntry ?? []);
@ -73,7 +107,6 @@ export class FileDataService {
this.shouldUpdateAnnotations = true;
this.#redactionLogHash = newRedactionLogHash;
this.missingTypes.clear();
await this.#buildAllAnnotations();
}
async load(file: File) {
@ -90,8 +123,11 @@ 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);
const redactionPerColor = await firstValueFrom(this._textHighlightsService.getTextHighlights(dossierId, fileId));
const textHighlights = this.#buildTextHighlights(redactionPerColor);
this.#textHighlights$.next(textHighlights);
return textHighlights;
}
getViewedPagesFor(file: File) {
@ -105,60 +141,61 @@ export class FileDataService {
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 this.setRedactionLog(await firstValueFrom(redactionLog$));
this.setRedactionLog(await firstValueFrom(redactionLog$));
}
getVisibleAnnotations(viewMode: ViewMode) {
if (viewMode === 'TEXT_HIGHLIGHTS') {
return this.textHighlightAnnotations;
}
return this.allAnnotations.filter(annotation => {
getVisibleAnnotations(annotations: AnnotationWrapper[], viewMode: ViewMode) {
return annotations.filter(annotation => {
if (viewMode === 'STANDARD') {
return !annotation.isChangeLogRemoved;
} else if (viewMode === 'DELTA') {
return annotation.isChangeLogEntry;
} else {
return annotation.previewAnnotation;
}
if (viewMode === 'DELTA') {
return annotation.isChangeLogEntry;
}
return annotation.previewAnnotation;
});
}
async #buildAllAnnotations() {
const file = await this._state.file;
const entries: RedactionLogEntry[] = this.#convertData(file);
updateHiddenAnnotations(viewerAnnotations: Annotation[], hidden: boolean) {
const annotationId = viewerAnnotations[0].Id;
if (hidden) {
this.hiddenAnnotations.add(annotationId);
} else {
this.hiddenAnnotations.delete(annotationId);
}
}
const previousAnnotations = [...this.allAnnotations];
this.allAnnotations = entries
.map(entry => AnnotationWrapper.fromData(entry))
.filter(ann => ann.manual || !file.excludedPages.includes(ann.pageNumber));
isHidden(annotationId: string) {
return this.hiddenAnnotations.has(annotationId);
}
if (!this._userPreferenceService.areDevFeaturesEnabled) {
this.allAnnotations = this.allAnnotations.filter(annotation => !annotation.isFalsePositive);
#buildTextHighlights(redactionPerColor: Record<string, ImportedRedaction[]>): AnnotationWrapper[] {
if (!redactionPerColor) {
return [];
}
this._setHiddenPropertyToNewAnnotations(this.allAnnotations, previousAnnotations);
const highlights = Object.entries(redactionPerColor);
return highlights.flatMap(([color, entries]) => entries.map(entry => AnnotationWrapper.fromHighlight(color, entry)));
}
private _setHiddenPropertyToNewAnnotations(newAnnotations: AnnotationWrapper[], oldAnnotations: AnnotationWrapper[]) {
newAnnotations.forEach(newAnnotation => {
const oldAnnotation = oldAnnotations.find(a => a.annotationId === newAnnotation.annotationId);
if (oldAnnotation) {
newAnnotation.hidden = oldAnnotation.hidden;
}
});
#buildAnnotations(redactionLog: IRedactionLog, file: File) {
const entries: RedactionLogEntry[] = this.#convertData(redactionLog, file);
const annotations = entries.map(entry => AnnotationWrapper.fromData(entry));
return annotations.filter(ann => ann.manual || !file.excludedPages.includes(ann.pageNumber));
}
#convertData(file: File): RedactionLogEntry[] {
#convertData(redactionLog: IRedactionLog, file: File): RedactionLogEntry[] {
let result: RedactionLogEntry[] = [];
const reasonAnnotationIds: { [key: string]: RedactionLogEntry[] } = {};
const _dictionaryData = this._dictionariesMapService.get(this._state.dossierTemplateId);
this.#redactionLog.redactionLogEntry?.forEach(redactionLogEntry => {
// copy the redactionLog Entry
redactionLog.redactionLogEntry?.forEach(redactionLogEntry => {
const changeLogValues = this.#getChangeLogValues(redactionLogEntry, file);
const dictionaryData = _dictionaryData.find(dict => dict.type === redactionLogEntry.type);
if (!dictionaryData) {
@ -171,7 +208,7 @@ export class FileDataService {
changeLogValues.changeLogType,
changeLogValues.isChangeLogEntry,
changeLogValues.hidden,
this.#redactionLog.legalBasis,
redactionLog.legalBasis,
!!dictionaryData?.hint,
);
@ -229,7 +266,7 @@ export class FileDataService {
// page has been seen -> let's see if it's a change
if (viewedPage) {
const viewTime = moment(viewedPage.viewedTime).valueOf() - FileDataService.DELTA_VIEW_TIME;
const viewTime = moment(viewedPage.viewedTime).valueOf() - DELTA_VIEW_TIME;
// these are all unseen changes
const relevantChanges = viableChanges.filter(change => moment(change.dateTime).valueOf() > viewTime);
// at least one unseen change

View File

@ -1 +1,8 @@
export type ViewMode = 'STANDARD' | 'DELTA' | 'REDACTED' | 'TEXT_HIGHLIGHTS';
export const ViewModes = {
STANDARD: 'STANDARD',
DELTA: 'DELTA',
REDACTED: 'REDACTED',
TEXT_HIGHLIGHTS: 'TEXT_HIGHLIGHTS',
} as const;
export type ViewMode = keyof typeof ViewModes;

View File

@ -5,5 +5,5 @@ export interface TextHighlightResponse {
dossierId?: string;
fileId?: string;
operation?: TextHighlightOperation;
redactionPerColor?: { [key: string]: ImportedRedaction[] };
redactionPerColor?: Record<string, ImportedRedaction[]>;
}