From c8e575239581e15bf4ae753f52a3a2135598ae13 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Mon, 18 Nov 2024 18:20:57 +0200 Subject: [PATCH] WIP on master RED-9996 - pre-filter available annotation filter types in new DocuMine component view --- .../documine-export.component.ts | 2 +- ...le-structured-component-value.component.ts | 4 +- ...ctured-component-management.component.html | 4 +- ...ructured-component-management.component.ts | 28 +++------- .../file-preview-screen.component.ts | 56 ++++++++++++++++++- .../services/file-preview-state.service.ts | 16 ++---- .../file-download-btn.component.ts | 2 +- .../component-log.service.ts | 16 +++--- .../lib/component-log/component-log-entry.ts | 9 ++- .../src/lib/watermarks/watermark.model.ts | 1 - 10 files changed, 87 insertions(+), 51 deletions(-) rename apps/red-ui/src/app/services/{files => entity-services}/component-log.service.ts (95%) diff --git a/apps/red-ui/src/app/modules/file-preview/components/documine-export/documine-export.component.ts b/apps/red-ui/src/app/modules/file-preview/components/documine-export/documine-export.component.ts index 74c75e514..071f8df4a 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/documine-export/documine-export.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/documine-export/documine-export.component.ts @@ -1,10 +1,10 @@ import { Component, input, Input } from '@angular/core'; import { firstValueFrom } from 'rxjs'; import { Dossier, File } from '@red/domain'; -import { ComponentLogService } from '@services/files/component-log.service'; import { MatTooltip } from '@angular/material/tooltip'; import { TranslateModule } from '@ngx-translate/core'; import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu'; +import { ComponentLogService } from '@services/entity-services/component-log.service'; @Component({ selector: 'redaction-documine-export', diff --git a/apps/red-ui/src/app/modules/file-preview/components/editable-structured-component-value/editable-structured-component-value.component.ts b/apps/red-ui/src/app/modules/file-preview/components/editable-structured-component-value/editable-structured-component-value.component.ts index 128482377..b3f91e1ad 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/editable-structured-component-value/editable-structured-component-value.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/editable-structured-component-value/editable-structured-component-value.component.ts @@ -88,7 +88,7 @@ export class EditableStructuredComponentValueComponent implements OnInit { } this.deselectLast.emit(); this.selected = true; - this._state.componentReferenceIds = this.#getUniqueReferencesIds(this.currentEntry().componentValues); + this._state.componentReferenceIds.set(this.#getUniqueReferencesIds(this.currentEntry().componentValues)); } } @@ -104,7 +104,7 @@ export class EditableStructuredComponentValueComponent implements OnInit { $event?.stopImmediatePropagation(); this.selected = false; this.editing = false; - this._state.componentReferenceIds = null; + this._state.componentReferenceIds.set(null); } cancel($event?: MouseEvent) { diff --git a/apps/red-ui/src/app/modules/file-preview/components/structured-component-management/structured-component-management.component.html b/apps/red-ui/src/app/modules/file-preview/components/structured-component-management/structured-component-management.component.html index faa6c3677..575c6a236 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/structured-component-management/structured-component-management.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/structured-component-management/structured-component-management.component.html @@ -3,7 +3,7 @@ -
+
{{ 'component-management.table-header.component' | translate }}
@@ -12,7 +12,7 @@
-
+
(undefined); - protected readonly componentLogData$ = toObservable(this.componentLogData); protected readonly iconButtonTypes = IconButtonTypes; - protected displayedComponents$: Observable; @Input() file: File; @Input() dictionaries: Dictionary[]; @ViewChildren('editableComponent') editableComponents: List; constructor( - private readonly _componentLogService: ComponentLogService, private readonly _loadingService: LoadingService, private readonly _componentLogFilterService: ComponentLogFilterService, private readonly _filterService: FilterService, private readonly _state: FilePreviewStateService, + protected readonly componentLogService: ComponentLogService, ) { effect(async () => { this._state.file(); @@ -48,7 +43,6 @@ export class StructuredComponentManagementComponent implements OnInit { async ngOnInit(): Promise { await this.#loadData(); - this.displayedComponents$ = this.#displayedComponents$(); } deselectLast() { @@ -61,7 +55,7 @@ export class StructuredComponentManagementComponent implements OnInit { async revertOverride(originalKey: string) { this._loadingService.start(); await firstValueFrom( - this._componentLogService.revertOverride(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, [originalKey]), + this.componentLogService.revertOverride(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, [originalKey]), ); await this.#loadData(); } @@ -69,29 +63,21 @@ export class StructuredComponentManagementComponent implements OnInit { async overrideValue(componentLogEntry: IComponentLogEntry) { this._loadingService.start(); await firstValueFrom( - this._componentLogService.override(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, componentLogEntry), + this.componentLogService.override(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, componentLogEntry), ); await this.#loadData(); } - #displayedComponents$() { - const componentLogFilters$ = this._filterService.getFilterModels$('componentLogFilters'); - return combineLatest([this.componentLogData$, componentLogFilters$]).pipe( - map(([components, filters]) => this._componentLogFilterService.filterComponents(components, filters)), - ); - } - async #loadData(): Promise { - const componentLogData = await firstValueFrom( - this._componentLogService.getComponentLogData( + await firstValueFrom( + this.componentLogService.loadComponentLogData( this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, this.dictionaries, ), ); - this.#computeFilters(componentLogData); - this.componentLogData.set(componentLogData); + this.#computeFilters(this.componentLogService.all); this._loadingService.stop(); } diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts index a6f5aefff..ab7ed93b1 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts @@ -17,7 +17,15 @@ import { LoadingService, Toaster, } from '@iqser/common-ui'; -import { copyLocalStorageFiltersValues, FilterService, NestedFilter, processFilters } from '@iqser/common-ui/lib/filtering'; +import { + copyLocalStorageFiltersValues, + Filter, + FilterService, + IFilter, + INestedFilter, + NestedFilter, + processFilters, +} from '@iqser/common-ui/lib/filtering'; import { AutoUnsubscribe, Bind, bool, List, OnAttach, OnDetach } from '@iqser/common-ui/lib/utils'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { ManualRedactionEntryTypes, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; @@ -73,6 +81,7 @@ import { StructuredComponentManagementComponent } from './components/structured- import { DocumentInfoService } from './services/document-info.service'; import { RectangleAnnotationDialog } from './dialogs/rectangle-annotation-dialog/rectangle-annotation-dialog.component'; import { ANNOTATION_ACTION_ICONS, ANNOTATION_ACTIONS } from './utils/constants'; +import { ComponentLogService } from '@services/entity-services/component-log.service'; @Component({ templateUrl: './file-preview-screen.component.html', @@ -147,6 +156,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _multiSelectService: MultiSelectService, private readonly _documentInfoService: DocumentInfoService, + private readonly _componentLogService: ComponentLogService, ) { super(); effect(() => { @@ -197,6 +207,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni this._documentInfoService.shown(); this.#updateViewerPosition(); }); + + effect(() => { + this.state.componentReferenceIds(); + this.#rebuildFilters(); + }); } get changed() { @@ -538,8 +553,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni #rebuildFilters() { const startTime = new Date().getTime(); - const annotationFilters = this._annotationProcessingService.getAnnotationFilter(); + let annotationFilters = this._annotationProcessingService.getAnnotationFilter(); const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters; + + if (this.isDocumine) { + annotationFilters = this.#filterAnnotationFilters(annotationFilters); + } + this._filterService.addFilterGroup({ slug: 'primaryFilters', filterTemplate: this._filterTemplate, @@ -832,6 +852,38 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni }; } + #filterAnnotationFilters(annotationFilters: INestedFilter[]) { + const components = this._componentLogService.all; + const filteredComponentIds = untracked(this.state.componentReferenceIds); + + if (filteredComponentIds && annotationFilters) { + const filteredComponentIdsSet = new Set(filteredComponentIds); + + const references = new Set(); + for (const component of components) { + for (const componentValue of component.componentValues) { + for (const reference of componentValue.entityReferences) { + if (filteredComponentIdsSet.has(reference.id)) { + references.add(reference.type); + } + } + } + } + + return annotationFilters + .map(filter => { + const filteredChildren = filter.children.filter(c => references.has(c.label.replace(/ /g, '_').toLowerCase())); + if (filteredChildren.length) { + return { ...filter, children: filteredChildren }; + } + return null; + }) + .filter(f => f !== null); + } + + return annotationFilters; + } + #updateViewerPosition() { if (this.isDocumine) { if (this._documentInfoService.shown()) { diff --git a/apps/red-ui/src/app/modules/file-preview/services/file-preview-state.service.ts b/apps/red-ui/src/app/modules/file-preview/services/file-preview-state.service.ts index ff821733f..aac95d66c 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/file-preview-state.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/file-preview-state.service.ts @@ -1,6 +1,6 @@ import { HttpEvent, HttpEventType, HttpProgressEvent, HttpResponse } from '@angular/common/http'; import { computed, effect, inject, Injectable, signal, Signal, WritableSignal } from '@angular/core'; -import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; +import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop'; import { LoadingService, wipeCache } from '@iqser/common-ui'; import { getParam } from '@iqser/common-ui/lib/utils'; import { TranslateService } from '@ngx-translate/core'; @@ -14,7 +14,7 @@ import { FilesMapService } from '@services/files/files-map.service'; import { FilesService } from '@services/files/files.service'; import { PermissionsService } from '@services/permissions.service'; import { NGXLogger } from 'ngx-logger'; -import { BehaviorSubject, firstValueFrom, from, merge, Observable, of, pairwise, Subject, switchMap } from 'rxjs'; +import { firstValueFrom, from, merge, Observable, of, pairwise, Subject, switchMap } from 'rxjs'; import { filter, map, startWith, tap, withLatestFrom } from 'rxjs/operators'; import { ViewModeService } from './view-mode.service'; @@ -42,7 +42,7 @@ export class FilePreviewStateService { readonly dossierDictionary: Signal; readonly blob$: Observable; readonly componentReferenceIds$: Observable; - readonly #componentReferenceIds$ = new BehaviorSubject(null); + readonly componentReferenceIds = signal([]); readonly dossierId = getParam(DOSSIER_ID); readonly dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); readonly fileId = getParam(FILE_ID); @@ -64,7 +64,7 @@ export class FilePreviewStateService { this.dossier = toSignal(dossiersServiceResolver().getEntityChanged$(this.dossierId)); this.file$ = inject(FilesMapService).watch$(this.dossierId, this.fileId); this.file = toSignal(this.file$); - this.componentReferenceIds$ = this.#componentReferenceIds$.asObservable(); + this.componentReferenceIds$ = toObservable(this.componentReferenceIds); this.excludedPages = signal(this.file().excludedPages); this.isWritable = computed(() => { const isWritable = this._permissionsService.canPerformAnnotationActions(this.file(), this.dossier()); @@ -94,10 +94,6 @@ export class FilePreviewStateService { ); } - set componentReferenceIds(ids: string[]) { - this.#componentReferenceIds$.next(ids); - } - get dictionaries(): Dictionary[] { const dictionaries = this._dictionariesMapService.get(this.dossierTemplateId); if (this.dossierDictionary()) { @@ -134,10 +130,6 @@ export class FilePreviewStateService { ); } - get componentReferenceIds() { - return this.#componentReferenceIds$.getValue(); - } - reloadBlob(): void { this.#reloadBlob$.next(true); } diff --git a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts index 1107af8b2..d6265e542 100644 --- a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts @@ -9,7 +9,7 @@ import { APP_BASE_HREF } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu'; import { firstValueFrom } from 'rxjs'; -import { ComponentLogService } from '@services/files/component-log.service'; +import { ComponentLogService } from '@services/entity-services/component-log.service'; @Component({ selector: 'redaction-file-download-btn', diff --git a/apps/red-ui/src/app/services/files/component-log.service.ts b/apps/red-ui/src/app/services/entity-services/component-log.service.ts similarity index 95% rename from apps/red-ui/src/app/services/files/component-log.service.ts rename to apps/red-ui/src/app/services/entity-services/component-log.service.ts index 49235895e..e08c91607 100644 --- a/apps/red-ui/src/app/services/files/component-log.service.ts +++ b/apps/red-ui/src/app/services/entity-services/component-log.service.ts @@ -1,15 +1,14 @@ import { Injectable } from '@angular/core'; -import { GenericService } from '@iqser/common-ui'; -import { catchError, map, tap } from 'rxjs/operators'; -import { Observable, of } from 'rxjs'; -import { HttpHeaders } from '@angular/common/http'; -import { saveAs } from 'file-saver'; +import { EntitiesService } from '@iqser/common-ui'; import { ComponentDetails, ComponentLogEntry, Dictionary, IComponentLogData, IComponentLogEntry, IFile } from '@red/domain'; +import { Observable, of } from 'rxjs'; +import { catchError, map, tap } from 'rxjs/operators'; import { mapEach } from '@common-ui/utils'; -import { FilePreviewStateService } from '../../modules/file-preview/services/file-preview-state.service'; +import { saveAs } from 'file-saver'; +import { HttpHeaders } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) -export class ComponentLogService extends GenericService { +export class ComponentLogService extends EntitiesService { protected readonly _defaultModelPath = ''; #componentLogRequest( @@ -35,7 +34,7 @@ export class ComponentLogService extends GenericService { ); } - getComponentLogData( + loadComponentLogData( dossierTemplateId: string, dossierId: string, fileId: string, @@ -47,6 +46,7 @@ export class ComponentLogService extends GenericService { map(componentDetails => this.#mapComponentDetails(componentDetails)), mapEach(log => new ComponentLogEntry(log)), map(log => this.#updateDisplayValue(log, dictionaries)), + tap(log => this.setEntities(log)), ); } diff --git a/libs/red-domain/src/lib/component-log/component-log-entry.ts b/libs/red-domain/src/lib/component-log/component-log-entry.ts index 1a53a3bb5..a6ca39a2a 100644 --- a/libs/red-domain/src/lib/component-log/component-log-entry.ts +++ b/libs/red-domain/src/lib/component-log/component-log-entry.ts @@ -1,4 +1,5 @@ import { ComponentValue, IComponentValue } from './component-value'; +import { IListable } from '@iqser/common-ui'; export type ComponentDetails = Record>; @@ -9,16 +10,22 @@ export interface IComponentLogEntry { overridden?: boolean; } -export class ComponentLogEntry implements IComponentLogEntry { +export class ComponentLogEntry implements IComponentLogEntry, IListable { + readonly id: string; readonly name: string; readonly originalKey: string; readonly componentValues: ComponentValue[]; readonly overridden: boolean; constructor(entry: IComponentLogEntry) { + this.id = entry.name; this.name = entry.name; this.originalKey = entry.name; this.componentValues = entry.componentValues; this.overridden = !!entry.overridden; } + + get searchKey(): string { + return this.originalKey; + } } diff --git a/libs/red-domain/src/lib/watermarks/watermark.model.ts b/libs/red-domain/src/lib/watermarks/watermark.model.ts index adb9a11e1..bff94f7e9 100644 --- a/libs/red-domain/src/lib/watermarks/watermark.model.ts +++ b/libs/red-domain/src/lib/watermarks/watermark.model.ts @@ -2,7 +2,6 @@ import { IWatermark, WATERMARK_HORIZONTAL_ALIGNMENTS, WATERMARK_VERTICAL_ALIGNMENTS, - WatermarkAlignment, WatermarkHorizontalAlignment, WatermarkOrientation, WatermarkVerticalAlignment,