use reactive annotations
This commit is contained in:
parent
ee6f8dab02
commit
c8a2e0b580
@ -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()
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)))),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -5,5 +5,5 @@ export interface TextHighlightResponse {
|
||||
dossierId?: string;
|
||||
fileId?: string;
|
||||
operation?: TextHighlightOperation;
|
||||
redactionPerColor?: { [key: string]: ImportedRedaction[] };
|
||||
redactionPerColor?: Record<string, ImportedRedaction[]>;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user