From 885269c7d1bbfa32b34ef96f6156ea002fb749d7 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 23 Apr 2021 17:22:35 +0300 Subject: [PATCH] secondary filters wip --- .../file-workload.component.html | 1 - .../file-workload/file-workload.component.ts | 4 +- .../file-preview-screen.component.ts | 4 +- .../services/annotation-processing.service.ts | 72 +++++++++++------ .../components/filter/filter.component.html | 79 +++++++++++-------- .../components/filter/filter.component.ts | 45 +++++------ .../components/filter/model/filter.model.ts | 7 ++ 7 files changed, 126 insertions(+), 86 deletions(-) diff --git a/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.html b/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.html index b89a4ecdc..f4e7949f3 100644 --- a/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.html +++ b/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.html @@ -12,7 +12,6 @@ [filterTemplate]="annotationFilterTemplate" [actionsTemplate]="annotationFilterActionTemplate" [filters]="annotationFilters" - [enableFilterOptions]="true" > diff --git a/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.ts index ba2859840..fa54fbbc3 100644 --- a/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.ts +++ b/apps/red-ui/src/app/modules/projects/components/file-workload/file-workload.component.ts @@ -106,8 +106,8 @@ export class FileWorkloadComponent { } @debounce(0) - public filtersChanged($event: { filters: FilterModel[]; extraFilterBy?: (annotation: AnnotationWrapper) => boolean }) { - this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this._annotations, $event.filters, $event.extraFilterBy); + public filtersChanged(filters: FilterModel[]) { + this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this._annotations, filters); this.displayedPages = Object.keys(this.displayedAnnotations).map((key) => Number(key)); this.computeQuickNavButtonsState(); this._changeDetectorRef.markForCheck(); diff --git a/apps/red-ui/src/app/modules/projects/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/projects/screens/file-preview-screen/file-preview-screen.component.ts index f5cc77839..e09b48f8c 100644 --- a/apps/red-ui/src/app/modules/projects/screens/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/projects/screens/file-preview-screen/file-preview-screen.component.ts @@ -218,7 +218,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, ); const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this.annotations); this.annotationFilters = processFilters(this.annotationFilters, annotationFilters); - this._workloadComponent.filtersChanged({ filters: this.annotationFilters }); + this._workloadComponent.filtersChanged(this.annotationFilters); console.log('[REDACTION] Process time: ' + (new Date().getTime() - processStartTime) + 'ms'); console.log( '[REDACTION] Annotation Redraw and filter rebuild time: ' + @@ -527,7 +527,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, const oldPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations); const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newPageAnnotations); handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, this.annotationFilters); - this._workloadComponent.filtersChanged({ filters: this.annotationFilters }); + this._workloadComponent.filtersChanged(this.annotationFilters); } } diff --git a/apps/red-ui/src/app/modules/projects/services/annotation-processing.service.ts b/apps/red-ui/src/app/modules/projects/services/annotation-processing.service.ts index 5fe3b3592..0f7691e0b 100644 --- a/apps/red-ui/src/app/modules/projects/services/annotation-processing.service.ts +++ b/apps/red-ui/src/app/modules/projects/services/annotation-processing.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { AppStateService } from '../../../state/app-state.service'; import { AnnotationWrapper } from '../../../models/file/annotation.wrapper'; -import { FilterModel } from '../../shared/components/filter/model/filter.model'; +import { FilterModel, FilterTypes } from '../../shared/components/filter/model/filter.model'; import { handleCheckedValue } from '../../shared/components/filter/utils/filter-utils'; import { SuperTypeSorter } from '../../../utils/sorters/super-type-sorter'; @@ -11,7 +11,7 @@ export class AnnotationProcessingService { getAnnotationFilter(annotations: AnnotationWrapper[]): FilterModel[] { const filterMap = new Map(); - const filters: FilterModel[] = []; + let filters: FilterModel[] = []; annotations?.forEach((a) => { const topLevelFilter = a.superType !== 'hint' && a.superType !== 'redaction' && a.superType !== 'recommendation'; @@ -28,7 +28,13 @@ export class AnnotationProcessingService { if (!parentFilter) { parentFilter = this._createParentFilter(a.superType, filterMap, filters); } - const childFilter = { key: a.dictionary, checked: false, filters: [], matches: 1 }; + const childFilter = { + key: a.dictionary, + type: FilterTypes.primary, + checked: false, + filters: [], + matches: 1 + }; filterMap.set(key, childFilter); parentFilter.filters.push(childFilter); } @@ -46,12 +52,16 @@ export class AnnotationProcessingService { } } - return filters.sort((a, b) => SuperTypeSorter[a.key] - SuperTypeSorter[b.key]); + filters = filters.sort((a, b) => SuperTypeSorter[a.key] - SuperTypeSorter[b.key]); + filters.push(...AnnotationProcessingService._secondaryFilters); + + return filters; } - private _createParentFilter(key: string, filterMap: Map, filters: FilterModel[]) { + private _createParentFilter(key: string, filterMap: Map, filters: FilterModel[], type?: FilterTypes) { const filter: FilterModel = { key: key, + type: type || FilterTypes.primary, topLevelFilter: true, matches: 1, label: 'annotation-type.' + key, @@ -62,38 +72,40 @@ export class AnnotationProcessingService { return filter; } - filterAndGroupAnnotations( - annotations: AnnotationWrapper[], - filters: FilterModel[], - extraFilterBy?: (annotation: AnnotationWrapper) => boolean - ): { [key: number]: { annotations: AnnotationWrapper[] } } { + filterAndGroupAnnotations(annotations: AnnotationWrapper[], filters: FilterModel[]): { [key: number]: { annotations: AnnotationWrapper[] } } { const obj = {}; - const hasActiveFilters = this._hasActiveFilters(filters); - const flatFilters = []; + const flatFilters: FilterModel[] = []; filters.forEach((filter) => { flatFilters.push(filter); - flatFilters.push(...filter.filters); + flatFilters.push(...filter?.filters); }); - for (const annotation of annotations) { - if (typeof extraFilterBy === 'function' && !extraFilterBy(annotation)) { - continue; - } + const primaryFilters = flatFilters.filter((f) => f.type === FilterTypes.primary && f.checked); + const secondaryFilters = flatFilters.filter((f) => f.type === FilterTypes.secondary && f.checked && f.action); + console.log(secondaryFilters); + + for (const annotation of annotations) { const pageNumber = annotation.pageNumber; const type = annotation.superType; if (hasActiveFilters) { let found = false; - for (const filter of flatFilters) { + for (const filter of primaryFilters) { if ( - filter.checked && - ((filter.key === annotation.dictionary && + (filter.key === annotation.dictionary && (annotation.superType === 'hint' || annotation.superType === 'redaction' || annotation.superType === 'recommendation')) || - filter.key === annotation.superType) + filter.key === annotation.superType ) { - found = true; + let secondaryFiltersNotMatched = false; + for (const secondaryFilter of secondaryFilters) { + if (!secondaryFilter.action(annotation)) { + secondaryFiltersNotMatched = true; + } + } + + found = !secondaryFiltersNotMatched; break; } } @@ -139,4 +151,20 @@ export class AnnotationProcessingService { private _hasActiveFilters(filters: FilterModel[]): boolean { return filters.reduce((acc, next) => acc || next.checked || next.indeterminate, false); } + + private static get _secondaryFilters(): FilterModel[] { + return [ + { + key: 'with-comments', + label: 'filter-menu.with-comments', + checked: false, + topLevelFilter: true, + type: FilterTypes.secondary, + filters: [], + action: (obj) => { + return obj?.comments?.length > 0; + } + } + ]; + } } diff --git a/apps/red-ui/src/app/modules/shared/components/filter/filter.component.html b/apps/red-ui/src/app/modules/shared/components/filter/filter.component.html index 471b7196a..9bce292ef 100644 --- a/apps/red-ui/src/app/modules/shared/components/filter/filter.component.html +++ b/apps/red-ui/src/app/modules/shared/components/filter/filter.component.html @@ -17,48 +17,59 @@
-
-
-
- - -
-
-   -
- - - - -
-
-
- - - - -
-
+
+
-
+
-
- - - {{ 'filter-menu.with-comments' | translate }} - + + + + + + +
+
- + {{ filter?.label }} + + +
+
+ + +
+
+   +
+ + + + +
+
+
+ + + + +
+
+
diff --git a/apps/red-ui/src/app/modules/shared/components/filter/filter.component.ts b/apps/red-ui/src/app/modules/shared/components/filter/filter.component.ts index 03007cda0..5f5153f5a 100644 --- a/apps/red-ui/src/app/modules/shared/components/filter/filter.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/filter/filter.component.ts @@ -1,10 +1,8 @@ -import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef } from '@angular/core'; import { AppStateService } from '../../../../state/app-state.service'; -import { FilterModel } from './model/filter.model'; +import { FilterModel, FilterTypes } from './model/filter.model'; import { handleCheckedValue } from './utils/filter-utils'; -import { MatMenuTrigger } from '@angular/material/menu'; import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox'; -import { AnnotationWrapper } from '../../../../models/file/annotation.wrapper'; @Component({ selector: 'redaction-filter', @@ -21,31 +19,30 @@ import { AnnotationWrapper } from '../../../../models/file/annotation.wrapper'; ] }) export class FilterComponent implements OnChanges { - @Output() filtersChanged = new EventEmitter<{ filters: FilterModel[]; extraFilterBy?: (annotation: AnnotationWrapper) => boolean }>(); + @Output() filtersChanged = new EventEmitter(); @Input() filterTemplate: TemplateRef; @Input() actionsTemplate: TemplateRef; @Input() filters: FilterModel[] = []; @Input() filterLabel = 'filter-menu.label'; - @Input() enableFilterOptions = false; @Input() icon: string; @Input() chevron = false; - @ViewChild(MatMenuTrigger) trigger: MatMenuTrigger; mouseOver = true; mouseOverTimeout: number; - atLeastOnFilterIsExpandable = false; - filterOnlyWithComments = false; + atLeastOneFilterIsExpandable = false; + atLeastOneSecondaryFilterIsExpandable = false; constructor(public readonly appStateService: AppStateService, private readonly _changeDetectorRef: ChangeDetectorRef) {} ngOnChanges(changes: SimpleChanges): void { - this.atLeastOnFilterIsExpandable = false; - if (this.filters) { - this.filters.forEach((f) => { - this.atLeastOnFilterIsExpandable = this.atLeastOnFilterIsExpandable || this.isExpandable(f); - }); - } + this.atLeastOneFilterIsExpandable = false; + this.atLeastOneSecondaryFilterIsExpandable = false; + this.filters?.forEach((f) => { + if (f.type === FilterTypes.primary) this.atLeastOneFilterIsExpandable = this.atLeastOneFilterIsExpandable || this.isExpandable(f); + if (f.type === FilterTypes.secondary) + this.atLeastOneSecondaryFilterIsExpandable = this.atLeastOneSecondaryFilterIsExpandable || this.isExpandable(f); + }); } filterCheckboxClicked($event: any, filter: FilterModel, parent?: FilterModel) { @@ -65,12 +62,6 @@ export class FilterComponent implements OnChanges { this.applyFilters(); } - filterOptionsCheckboxClicked($event: Event) { - $event.stopPropagation(); - this.filterOnlyWithComments = !this.filterOnlyWithComments; - this.applyFilters(); - } - activateAllFilters() { this._setAllFilters(true); } @@ -88,12 +79,16 @@ export class FilterComponent implements OnChanges { return false; } - applyFilters() { - this.filtersChanged.emit({ filters: this.filters, extraFilterBy: this._extraFilterBy }); + get primaryFilters() { + return this.filters?.filter((f) => f.type === FilterTypes.primary || f.type === undefined); } - private get _extraFilterBy(): (a: AnnotationWrapper) => boolean { - return this.enableFilterOptions && this.filterOnlyWithComments ? (a) => a.comments.length !== 0 : (a) => true; + get secondaryFilters() { + return this.filters?.filter((f) => f.type === FilterTypes.secondary); + } + + applyFilters() { + this.filtersChanged.emit(this.filters); } toggleFilterExpanded($event: MouseEvent, filter: FilterModel) { diff --git a/apps/red-ui/src/app/modules/shared/components/filter/model/filter.model.ts b/apps/red-ui/src/app/modules/shared/components/filter/model/filter.model.ts index 13396e7b1..08012cbf3 100644 --- a/apps/red-ui/src/app/modules/shared/components/filter/model/filter.model.ts +++ b/apps/red-ui/src/app/modules/shared/components/filter/model/filter.model.ts @@ -1,5 +1,11 @@ +export enum FilterTypes { + primary = 'primary', + secondary = 'secondary' +} + export interface FilterModel { key: string; + type: FilterTypes; label?: string; checked?: boolean; indeterminate?: boolean; @@ -7,4 +13,5 @@ export interface FilterModel { topLevelFilter?: boolean; matches?: number; filters?: FilterModel[]; + action?: (obj?) => boolean; }