secondary filters wip
This commit is contained in:
parent
f502cda56b
commit
885269c7d1
@ -12,7 +12,6 @@
|
||||
[filterTemplate]="annotationFilterTemplate"
|
||||
[actionsTemplate]="annotationFilterActionTemplate"
|
||||
[filters]="annotationFilters"
|
||||
[enableFilterOptions]="true"
|
||||
></redaction-filter>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<string, FilterModel>();
|
||||
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<string, FilterModel>, filters: FilterModel[]) {
|
||||
private _createParentFilter(key: string, filterMap: Map<string, FilterModel>, 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;
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,48 +17,59 @@
|
||||
<div class="all-caps-label primary pointer" translate="filter-menu.none" (click)="deactivateAllFilters(); $event.stopPropagation()"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngFor="let filter of filters">
|
||||
<div class="mat-menu-item flex" (click)="toggleFilterExpanded($event, filter)">
|
||||
<div class="arrow-wrapper" *ngIf="isExpandable(filter)">
|
||||
<mat-icon *ngIf="filter.expanded" svgIcon="red:arrow-down" color="accent"></mat-icon>
|
||||
<mat-icon *ngIf="!filter.expanded" color="accent" svgIcon="red:arrow-right"></mat-icon>
|
||||
</div>
|
||||
<div class="arrow-wrapper spacer" *ngIf="atLeastOnFilterIsExpandable && !isExpandable(filter)">
|
||||
|
||||
</div>
|
||||
<mat-checkbox
|
||||
[checked]="filter.checked"
|
||||
[indeterminate]="filter.indeterminate"
|
||||
(click)="filterCheckboxClicked($event, filter)"
|
||||
class="filter-menu-checkbox"
|
||||
>
|
||||
<ng-template *ngTemplateOutlet="filterTemplate ? filterTemplate : defaultFilterTemplate; context: { filter: filter }"></ng-template>
|
||||
</mat-checkbox>
|
||||
<ng-template *ngTemplateOutlet="actionsTemplate ? actionsTemplate : null; context: { filter: filter }"></ng-template>
|
||||
</div>
|
||||
<div *ngIf="filter.filters?.length && filter.expanded">
|
||||
<div *ngFor="let subFilter of filter.filters" class="padding-left mat-menu-item" (click)="$event.stopPropagation()">
|
||||
<mat-checkbox [checked]="subFilter.checked" (click)="filterCheckboxClicked($event, subFilter, filter)">
|
||||
<ng-template *ngTemplateOutlet="filterTemplate ? filterTemplate : defaultFilterTemplate; context: { filter: subFilter }"> </ng-template>
|
||||
</mat-checkbox>
|
||||
<ng-template *ngTemplateOutlet="actionsTemplate ? actionsTemplate : null; context: { filter: subFilter }"></ng-template>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngFor="let filter of primaryFilters">
|
||||
<ng-template
|
||||
*ngTemplateOutlet="defaultFilterTemplate; context: { filter: filter, atLeastOneIsExpandable: atLeastOneFilterIsExpandable }"
|
||||
></ng-template>
|
||||
</div>
|
||||
<div class="filter-options" *ngIf="enableFilterOptions">
|
||||
<div class="filter-options" *ngIf="secondaryFilters?.length">
|
||||
<div class="filter-menu-options">
|
||||
<div class="all-caps-label" translate="filter-menu.filter-options"></div>
|
||||
</div>
|
||||
<div class="mat-menu-item flex">
|
||||
<mat-checkbox class="filter-menu-checkbox" (click)="filterOptionsCheckboxClicked($event)" [checked]="filterOnlyWithComments">
|
||||
<mat-icon svgIcon="red:comment"></mat-icon>
|
||||
{{ 'filter-menu.with-comments' | translate }}
|
||||
</mat-checkbox>
|
||||
<!-- <div class="mat-menu-item flex">-->
|
||||
<!-- <mat-checkbox class="filter-menu-checkbox" (click)="filterOptionsCheckboxClicked($event)" [checked]="filterOnlyWithComments">-->
|
||||
<!-- <mat-icon svgIcon="red:comment"></mat-icon>-->
|
||||
<!-- {{ 'filter-menu.with-comments' | translate }}-->
|
||||
<!-- </mat-checkbox>-->
|
||||
<!-- </div>-->
|
||||
<div *ngFor="let filter of secondaryFilters">
|
||||
<ng-template
|
||||
*ngTemplateOutlet="defaultFilterTemplate; context: { filter: filter, atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable }"
|
||||
></ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-menu>
|
||||
|
||||
<ng-template #defaultFilterTemplate let-filter="filter">
|
||||
<ng-template #defaultFilterLabelTemplate let-filter="filter">
|
||||
{{ filter?.label }}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #defaultFilterTemplate let-filter="filter" let-atLeastOneIsExpandable="atLeastOneIsExpandable">
|
||||
<div class="mat-menu-item flex" (click)="toggleFilterExpanded($event, filter)">
|
||||
<div class="arrow-wrapper" *ngIf="isExpandable(filter)">
|
||||
<mat-icon *ngIf="filter.expanded" svgIcon="red:arrow-down" color="accent"></mat-icon>
|
||||
<mat-icon *ngIf="!filter.expanded" color="accent" svgIcon="red:arrow-right"></mat-icon>
|
||||
</div>
|
||||
<div class="arrow-wrapper spacer" *ngIf="atLeastOneIsExpandable && !isExpandable(filter)">
|
||||
|
||||
</div>
|
||||
<mat-checkbox
|
||||
[checked]="filter.checked"
|
||||
[indeterminate]="filter.indeterminate"
|
||||
(click)="filterCheckboxClicked($event, filter)"
|
||||
class="filter-menu-checkbox"
|
||||
>
|
||||
<ng-template *ngTemplateOutlet="filterTemplate ? filterTemplate : defaultFilterLabelTemplate; context: { filter: filter }"></ng-template>
|
||||
</mat-checkbox>
|
||||
<ng-template *ngTemplateOutlet="actionsTemplate ? actionsTemplate : null; context: { filter: filter }"></ng-template>
|
||||
</div>
|
||||
<div *ngIf="filter.filters?.length && filter.expanded">
|
||||
<div *ngFor="let subFilter of filter.filters" class="padding-left mat-menu-item" (click)="$event.stopPropagation()">
|
||||
<mat-checkbox [checked]="subFilter.checked" (click)="filterCheckboxClicked($event, subFilter, filter)">
|
||||
<ng-template *ngTemplateOutlet="filterTemplate ? filterTemplate : defaultFilterLabelTemplate; context: { filter: subFilter }"></ng-template>
|
||||
</mat-checkbox>
|
||||
<ng-template *ngTemplateOutlet="actionsTemplate ? actionsTemplate : null; context: { filter: subFilter }"></ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@ -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<FilterModel[]>();
|
||||
@Input() filterTemplate: TemplateRef<any>;
|
||||
@Input() actionsTemplate: TemplateRef<any>;
|
||||
@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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user