File data service extends entities service

This commit is contained in:
Adina Țeudan 2022-04-04 15:52:20 +03:00
parent 55679f2e07
commit 2e4fd3faf3
7 changed files with 100 additions and 111 deletions

View File

@ -10,13 +10,12 @@ import {
SuperType,
SuperTypes,
} from '@models/file/super-types';
import { List } from '@iqser/common-ui';
import { IListable, List } from '@iqser/common-ui';
export class AnnotationWrapper implements Record<string, unknown> {
export class AnnotationWrapper implements IListable, Record<string, unknown> {
[x: string]: unknown;
superType: SuperType;
typeValue: string;
recategorizationType: string;
color: string;
@ -40,21 +39,17 @@ export class AnnotationWrapper implements Record<string, unknown> {
rectangle?: boolean;
section?: string;
reference: List;
imported?: boolean;
image?: boolean;
manual?: boolean;
hidden?: boolean;
pending?: boolean;
hintDictionary?: boolean;
textAfter?: string;
textBefore?: string;
isChangeLogEntry?: boolean;
changeLogType?: 'ADDED' | 'REMOVED' | 'CHANGED';
engines?: string[];
hasBeenResized: boolean;
hasBeenRecategorized: boolean;
hasLegalBasisChanged: boolean;
@ -62,6 +57,10 @@ export class AnnotationWrapper implements Record<string, unknown> {
hasBeenForcedRedaction: boolean;
hasBeenRemovedByManualOverride: boolean;
get searchKey(): string {
return this.id;
}
get isChangeLogRemoved() {
return this.changeLogType === 'REMOVED';
}

View File

@ -181,11 +181,11 @@
>.
</ng-container>
<ng-container *ngIf="(fileDataService.numberOfVisibleAnnotations$ | async) === 0">
<ng-container *ngIf="(fileDataService.allLength$ | async) === 0">
{{ 'file-preview.tabs.annotations.no-annotations' | translate }}
</ng-container>
<ng-container *ngIf="(fileDataService.numberOfVisibleAnnotations$ | async) > 0 && displayedPages.length === 0"
<ng-container *ngIf="(fileDataService.allLength$ | async) > 0 && displayedPages.length === 0"
>{{ 'file-preview.tabs.annotations.wrong-filters' | translate }}
<a (click)="filterService.reset()" class="with-underline" translate="file-preview.tabs.annotations.reset"></a>
{{ 'file-preview.tabs.annotations.the-filters' | translate }}

View File

@ -137,7 +137,7 @@ export class FileWorkloadComponent {
const primary$ = this.filterService.getFilterModels$('primaryFilters');
const secondary$ = this.filterService.getFilterModels$('secondaryFilters');
return combineLatest([this.fileDataService.visibleAnnotations$, primary$, secondary$]).pipe(
return combineLatest([this.fileDataService.all$, primary$, secondary$]).pipe(
map(([annotations, primary, secondary]) => this._filterAnnotations(annotations, primary, secondary)),
);
}

View File

@ -193,7 +193,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
this.pdfViewer.disableHotkeys();
await this._configureTextPopup();
this.annotationManager.addEventListener('annotationSelected', async (annotations: Annotation[], action) => {
this.annotationManager.addEventListener('annotationSelected', (annotations: Annotation[], action) => {
let nextAnnotations: Annotation[];
if (action === 'deselected') {
@ -213,12 +213,11 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
}
if (!this.multiSelectService.isEnabled) {
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
this.pdfViewer.deselectAnnotations(
visibleAnnotations.filter(wrapper => !nextAnnotations.find(ann => ann.Id === wrapper.id)),
this._fileDataService.all.filter(wrapper => !nextAnnotations.find(ann => ann.Id === wrapper.id)),
);
}
await this.#configureAnnotationSpecificActions(annotations);
this.#configureAnnotationSpecificActions(annotations);
this._toggleRectangleAnnotationAction(annotations.length === 1 && annotations[0].ReadOnly);
});
@ -350,7 +349,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
});
}
async #configureAnnotationSpecificActions(viewerAnnotations: Annotation[]) {
#configureAnnotationSpecificActions(viewerAnnotations: Annotation[]) {
if (!this.canPerformActions) {
if (this.instance.UI.annotationPopup.getItems().length) {
this.instance.UI.annotationPopup.update([]);
@ -358,10 +357,7 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
return;
}
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
const annotationWrappers: AnnotationWrapper[] = viewerAnnotations
.map(va => visibleAnnotations.find(a => a.id === va.Id))
.filter(va => !!va);
const annotationWrappers: AnnotationWrapper[] = viewerAnnotations.map(va => this._fileDataService.find(va.Id)).filter(va => !!va);
this.instance.UI.annotationPopup.update([]);
if (annotationWrappers.length === 0) {

View File

@ -8,7 +8,7 @@ import { AnnotationDrawService } from './services/annotation-draw.service';
import { AnnotationActionsService } from './services/annotation-actions.service';
import { FilePreviewStateService } from './services/file-preview-state.service';
import { AnnotationReferencesService } from './services/annotation-references.service';
import { FilterService } from '@iqser/common-ui';
import { EntitiesService, FilterService, ListingService, SearchService } from '@iqser/common-ui';
import { AnnotationProcessingService } from '../dossier/services/annotation-processing.service';
import { dossiersServiceProvider } from '@services/entity-services/dossiers.service.provider';
import { PageRotationService } from './services/page-rotation.service';
@ -33,7 +33,10 @@ export const filePreviewScreenProviders = [
PdfViewer,
AnnotationProcessingService,
FileDataService,
{ provide: EntitiesService, useExisting: FileDataService },
dossiersServiceProvider,
ViewerHeaderConfigService,
TooltipsService,
ListingService,
SearchService,
];

View File

@ -8,6 +8,7 @@ import {
Debounce,
ErrorService,
FilterService,
ListingService,
LoadingService,
NestedFilter,
OnAttach,
@ -88,6 +89,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _errorService: ErrorService,
readonly state: FilePreviewStateService,
private readonly _filterService: FilterService,
readonly listingService: ListingService<AnnotationWrapper>,
readonly permissionsService: PermissionsService,
readonly multiSelectService: MultiSelectService,
private readonly _activatedRoute: ActivatedRoute,
@ -189,7 +191,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
await this._stampPDF();
await this.rebuildFilters();
this.#rebuildFilters();
}
ngOnDetach(): void {
@ -234,38 +236,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
}
async rebuildFilters() {
const startTime = new Date().getTime();
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
const annotationFilters = this._annotationProcessingService.getAnnotationFilter(visibleAnnotations);
const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters;
this._filterService.addFilterGroup({
slug: 'primaryFilters',
filterTemplate: this._filterTemplate,
filters: processFilters(primaryFilters, annotationFilters),
});
const secondaryFilters = this._filterService.getGroup('secondaryFilters')?.filters;
this._filterService.addFilterGroup({
slug: 'secondaryFilters',
filterTemplate: this._filterTemplate,
filters: processFilters(
secondaryFilters,
AnnotationProcessingService.secondaryAnnotationFilters(this._fileDataService.viewedPages),
),
});
this._logger.info(`[FILTERS] Rebuild time: ${new Date().getTime() - startTime} ms`);
}
async handleAnnotationSelected(annotationIds: string[]) {
handleAnnotationSelected(annotationIds: string[]) {
if (annotationIds.length > 0) {
this._workloadComponent.pagesPanelActive = false;
}
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
this.selectedAnnotations = annotationIds
.map(id => visibleAnnotations.find(annotation => annotation.id === id))
.filter(ann => ann !== undefined);
this.selectedAnnotations = annotationIds.map(id => this._fileDataService.find(id)).filter(ann => ann !== undefined);
if (this.selectedAnnotations.length > 1) {
this.multiSelectService.activate();
}
@ -466,6 +441,29 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
return this._cleanupAndRedrawAnnotations(annotationsToDraw);
}
#rebuildFilters() {
const startTime = new Date().getTime();
const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this._fileDataService.all);
const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters;
this._filterService.addFilterGroup({
slug: 'primaryFilters',
filterTemplate: this._filterTemplate,
filters: processFilters(primaryFilters, annotationFilters),
});
const secondaryFilters = this._filterService.getGroup('secondaryFilters')?.filters;
this._filterService.addFilterGroup({
slug: 'secondaryFilters',
filterTemplate: this._filterTemplate,
filters: processFilters(
secondaryFilters,
AnnotationProcessingService.secondaryAnnotationFilters(this._fileDataService.viewedPages),
),
});
this._logger.info(`[FILTERS] Rebuild time: ${new Date().getTime() - startTime} ms`);
}
async #updateQueryParamsPage(page: number): Promise<void> {
// Add current page in URL query params
const extras: NavigationExtras = {
@ -503,7 +501,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
async #deactivateMultiSelect() {
this.multiSelectService.deactivate();
this.pdf.deselectAllAnnotations();
await this.handleAnnotationSelected([]);
this.handleAnnotationSelected([]);
}
private _setExcludedPageStyles() {
@ -626,13 +624,12 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private async _cleanupAndRedrawAnnotations(newAnnotations: readonly AnnotationWrapper[]) {
const currentFilters = this._filterService.getGroup('primaryFilters')?.filters || [];
await this.rebuildFilters();
this.#rebuildFilters();
const startTime = new Date().getTime();
if (currentFilters) {
const visibleAnnotations = await this._fileDataService.visibleAnnotations;
this._handleDeltaAnnotationFilters(currentFilters, visibleAnnotations);
this._handleDeltaAnnotationFilters(currentFilters, this._fileDataService.all);
}
await this._annotationDrawService.drawAnnotations(newAnnotations);

View File

@ -13,7 +13,7 @@ import {
import { AnnotationWrapper } from '../../../models/file/annotation.wrapper';
import { BehaviorSubject, firstValueFrom, iif, Observable, Subject } from 'rxjs';
import { RedactionLogEntry } from '../../../models/file/redaction-log.entry';
import { Injectable } from '@angular/core';
import { Injectable, Injector } 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';
@ -21,12 +21,11 @@ import { DictionariesMapService } from '@services/entity-services/dictionaries-m
import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { PermissionsService } from '@services/permissions.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { shareDistinctLast, shareLast, Toaster } from '@iqser/common-ui';
import { EntitiesService, shareLast, Toaster } from '@iqser/common-ui';
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 dayjs from 'dayjs';
import { NGXLogger } from 'ngx-logger';
import Annotation = Core.Annotations.Annotation;
@ -34,16 +33,13 @@ import Annotation = Core.Annotations.Annotation;
const DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
@Injectable()
export class FileDataService {
export class FileDataService extends EntitiesService<AnnotationWrapper> {
viewedPages: IViewedPage[] = [];
missingTypes = new Set<string>();
readonly hasChangeLog$ = new BehaviorSubject<boolean>(false);
readonly annotations$: Observable<Record<string, AnnotationWrapper>>;
readonly visibleAnnotations$: Observable<AnnotationWrapper[]>;
readonly numberOfVisibleAnnotations$: Observable<number>;
readonly hiddenAnnotations = new Set<string>();
readonly #redactionLog$ = new Subject<IRedactionLog>();
readonly #textHighlights$ = new BehaviorSubject<AnnotationWrapper[]>([]);
@ -57,25 +53,23 @@ export class FileDataService {
private readonly _redactionLogService: RedactionLogService,
private readonly _textHighlightsService: TextHighlightService,
private readonly _toaster: Toaster,
private readonly _router: Router,
private readonly _logger: NGXLogger,
protected readonly _injector: Injector,
) {
super(_injector, AnnotationWrapper);
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(Object.values(annotations), viewMode))),
this._viewModeService.viewMode$
.pipe(
switchMap(viewMode =>
iif(
() => viewMode === ViewModes.TEXT_HIGHLIGHTS,
this.#textHighlights$,
this.annotations$.pipe(map(annotations => this.#getVisibleAnnotations(Object.values(annotations), viewMode))),
),
),
),
shareDistinctLast(),
);
this.numberOfVisibleAnnotations$ = this.visibleAnnotations$.pipe(map(annotations => annotations.length));
}
get visibleAnnotations() {
return firstValueFrom(this.visibleAnnotations$);
tap(annotations => this.setEntities(annotations)),
)
.subscribe();
}
get annotations() {
@ -86,7 +80,7 @@ export class FileDataService {
return this.#redactionLog$.pipe(
withLatestFrom(this._state.file$),
map(([redactionLog, file]) => this.#buildAnnotations(redactionLog, file)),
tap(() => this.checkMissingTypes()),
tap(() => this.#checkMissingTypes()),
map(annotations =>
this._userPreferenceService.areDevFeaturesEnabled ? annotations : annotations.filter(a => !a.isFalsePositive),
),
@ -102,20 +96,10 @@ export class FileDataService {
this._logger.info('[ANNOTATIONS] Load annotations');
await this.loadViewedPages(file);
await this.#loadViewedPages(file);
await this.loadRedactionLog();
}
checkMissingTypes() {
if (this.missingTypes.size > 0) {
this._toaster.error(_('error.missing-types'), {
disableTimeOut: true,
params: { missingTypes: Array.from(this.missingTypes).join(', ') },
});
this.missingTypes.clear();
}
}
async loadTextHighlights() {
const redactionPerColor = this._textHighlightsService.getTextHighlights(this._state.dossierId, this._state.fileId);
const textHighlights = this.#buildTextHighlights(await firstValueFrom(redactionPerColor));
@ -124,34 +108,11 @@ export class FileDataService {
return textHighlights;
}
async loadViewedPages(file: File) {
if (!this._permissionsService.canMarkPagesAsViewed(file)) {
this.viewedPages = [];
return;
}
this.viewedPages = await firstValueFrom(this._viewedPagesService.getViewedPages(file.dossierId, file.fileId));
}
loadRedactionLog() {
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) {
return annotations.filter(annotation => {
if (viewMode === 'STANDARD') {
return !annotation.isChangeLogRemoved;
}
if (viewMode === 'DELTA') {
return annotation.isChangeLogEntry;
}
return annotation.previewAnnotation;
});
}
updateHiddenAnnotations(viewerAnnotations: Annotation[], hidden: boolean) {
const annotationId = viewerAnnotations[0].Id;
if (hidden) {
@ -165,6 +126,39 @@ export class FileDataService {
return this.hiddenAnnotations.has(annotationId);
}
#checkMissingTypes() {
if (this.missingTypes.size > 0) {
this._toaster.error(_('error.missing-types'), {
disableTimeOut: true,
params: { missingTypes: Array.from(this.missingTypes).join(', ') },
});
this.missingTypes.clear();
}
}
async #loadViewedPages(file: File) {
if (!this._permissionsService.canMarkPagesAsViewed(file)) {
this.viewedPages = [];
return;
}
this.viewedPages = await firstValueFrom(this._viewedPagesService.getViewedPages(file.dossierId, file.fileId));
}
#getVisibleAnnotations(annotations: AnnotationWrapper[], viewMode: ViewMode) {
return annotations.filter(annotation => {
if (viewMode === 'STANDARD') {
return !annotation.isChangeLogRemoved;
}
if (viewMode === 'DELTA') {
return annotation.isChangeLogEntry;
}
return annotation.previewAnnotation;
});
}
#buildTextHighlights(redactionPerColor: Record<string, ImportedRedaction[]>): AnnotationWrapper[] {
if (!redactionPerColor) {
return [];