File data service extends entities service
This commit is contained in:
parent
55679f2e07
commit
2e4fd3faf3
@ -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';
|
||||
}
|
||||
|
||||
@ -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 }}
|
||||
|
||||
@ -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)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
];
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 [];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user