add secondary filters
This commit is contained in:
parent
885269c7d1
commit
9bbdf522bb
@ -11,7 +11,8 @@
|
||||
[chevron]="true"
|
||||
[filterTemplate]="annotationFilterTemplate"
|
||||
[actionsTemplate]="annotationFilterActionTemplate"
|
||||
[filters]="annotationFilters"
|
||||
[primaryFilters]="primaryFilters"
|
||||
[secondaryFilters]="secondaryFilters"
|
||||
></redaction-filter>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -6,7 +6,6 @@ import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
|
||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||
import { debounce } from '../../../../utils/debounce';
|
||||
import { FileDataModel } from '../../../../models/file/file-data.model';
|
||||
import { PermissionsService } from '../../../../services/permissions.service';
|
||||
|
||||
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
||||
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
||||
@ -29,7 +28,8 @@ export class FileWorkloadComponent {
|
||||
@Input() activeViewerPage: number;
|
||||
@Input() shouldDeselectAnnotationsOnPageChange: boolean;
|
||||
@Input() dialogRef: MatDialogRef<any>;
|
||||
@Input() annotationFilters: FilterModel[];
|
||||
@Input() primaryFilters: FilterModel[];
|
||||
@Input() secondaryFilters: FilterModel[];
|
||||
@Input() fileData: FileDataModel;
|
||||
@Input() hideSkipped: boolean;
|
||||
@Input() annotationActionsTemplate: TemplateRef<any>;
|
||||
@ -45,7 +45,6 @@ export class FileWorkloadComponent {
|
||||
public quickScrollLastEnabled = false;
|
||||
public displayedPages: number[] = [];
|
||||
public pagesPanelActive = true;
|
||||
public filterOnlyWithComments = false;
|
||||
|
||||
@ViewChild('annotationsElement') private _annotationsElement: ElementRef;
|
||||
@ViewChild('quickNavigation') private _quickNavigationElement: ElementRef;
|
||||
@ -106,8 +105,8 @@ export class FileWorkloadComponent {
|
||||
}
|
||||
|
||||
@debounce(0)
|
||||
public filtersChanged(filters: FilterModel[]) {
|
||||
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this._annotations, filters);
|
||||
public filtersChanged(filters: { primary: FilterModel[]; secondary?: FilterModel[] }) {
|
||||
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this._annotations, filters.primary, filters.secondary);
|
||||
this.displayedPages = Object.keys(this.displayedAnnotations).map((key) => Number(key));
|
||||
this.computeQuickNavButtonsState();
|
||||
this._changeDetectorRef.markForCheck();
|
||||
|
||||
@ -36,4 +36,6 @@
|
||||
<redaction-annotation-icon *ngIf="filter.key === 'updated'" type="square" label="U" [color]="dictionaryColor"></redaction-annotation-icon>
|
||||
<redaction-annotation-icon *ngIf="filter.key === 'image'" type="square" label="I" [color]="dictionaryColor"></redaction-annotation-icon>
|
||||
|
||||
<mat-icon *ngIf="filter.key.startsWith('red:')" [svgIcon]="filter.key"></mat-icon>
|
||||
|
||||
{{ filter.label | translate }}
|
||||
|
||||
@ -6,4 +6,11 @@
|
||||
redaction-annotation-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 8px;
|
||||
opacity: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,7 +233,8 @@
|
||||
[activeViewerPage]="activeViewerPage"
|
||||
[(shouldDeselectAnnotationsOnPageChange)]="shouldDeselectAnnotationsOnPageChange"
|
||||
[dialogRef]="dialogRef"
|
||||
[annotationFilters]="annotationFilters"
|
||||
[primaryFilters]="primaryFilters"
|
||||
[secondaryFilters]="secondaryFilters"
|
||||
[fileData]="fileData"
|
||||
[hideSkipped]="hideSkipped"
|
||||
[annotationActionsTemplate]="annotationActionsTemplate"
|
||||
|
||||
@ -51,7 +51,8 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
annotationData: AnnotationData;
|
||||
selectedAnnotations: AnnotationWrapper[];
|
||||
viewReady = false;
|
||||
annotationFilters: FilterModel[];
|
||||
primaryFilters: FilterModel[];
|
||||
secondaryFilters: FilterModel[];
|
||||
loadingMessage: string;
|
||||
canPerformAnnotationActions: boolean;
|
||||
filesAutoUpdateTimer: Subscription;
|
||||
@ -217,8 +218,12 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
this.userPreferenceService.areDevFeaturesEnabled
|
||||
);
|
||||
const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this.annotations);
|
||||
this.annotationFilters = processFilters(this.annotationFilters, annotationFilters);
|
||||
this._workloadComponent.filtersChanged(this.annotationFilters);
|
||||
this.primaryFilters = processFilters(this.primaryFilters, annotationFilters);
|
||||
this.secondaryFilters = processFilters(this.secondaryFilters, AnnotationProcessingService.secondaryAnnotationFilters);
|
||||
this._workloadComponent.filtersChanged({
|
||||
primary: this.primaryFilters,
|
||||
secondary: this.secondaryFilters
|
||||
});
|
||||
console.log('[REDACTION] Process time: ' + (new Date().getTime() - processStartTime) + 'ms');
|
||||
console.log(
|
||||
'[REDACTION] Annotation Redraw and filter rebuild time: ' +
|
||||
@ -522,12 +527,12 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
}
|
||||
|
||||
private _handleDeltaAnnotationFilters(currentPageAnnotations: AnnotationWrapper[], newPageAnnotations: AnnotationWrapper[]) {
|
||||
const hasAnyFilterSet = this.annotationFilters.find((f) => f.checked || f.indeterminate);
|
||||
const hasAnyFilterSet = this.primaryFilters.find((f) => f.checked || f.indeterminate);
|
||||
if (hasAnyFilterSet) {
|
||||
const oldPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations);
|
||||
const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newPageAnnotations);
|
||||
handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, this.annotationFilters);
|
||||
this._workloadComponent.filtersChanged(this.annotationFilters);
|
||||
handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, this.primaryFilters);
|
||||
this._workloadComponent.filtersChanged({ primary: this.primaryFilters });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,14 +6,14 @@
|
||||
#statusFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.status'"
|
||||
[filters]="statusFilters"
|
||||
[primaryFilters]="statusFilters"
|
||||
[icon]="'red:status'"
|
||||
></redaction-filter>
|
||||
<redaction-filter
|
||||
#peopleFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.people'"
|
||||
[filters]="peopleFilters"
|
||||
[primaryFilters]="peopleFilters"
|
||||
[icon]="'red:user'"
|
||||
></redaction-filter>
|
||||
<redaction-filter
|
||||
@ -21,7 +21,7 @@
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.needs-work'"
|
||||
[filterTemplate]="needsWorkTemplate"
|
||||
[filters]="needsWorkFilters"
|
||||
[primaryFilters]="needsWorkFilters"
|
||||
[icon]="'red:needs-work'"
|
||||
></redaction-filter>
|
||||
<redaction-filter
|
||||
@ -29,7 +29,7 @@
|
||||
#ruleSetFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.rulesets'"
|
||||
[filters]="ruleSetFilters"
|
||||
[primaryFilters]="ruleSetFilters"
|
||||
[icon]="'red:template'"
|
||||
></redaction-filter>
|
||||
<redaction-search-input [form]="searchForm" [placeholder]="'project-listing.search'"></redaction-search-input>
|
||||
|
||||
@ -6,14 +6,14 @@
|
||||
#statusFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.status'"
|
||||
[filters]="statusFilters"
|
||||
[primaryFilters]="statusFilters"
|
||||
[icon]="'red:status'"
|
||||
></redaction-filter>
|
||||
<redaction-filter
|
||||
#peopleFilter
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.assigned-people'"
|
||||
[filters]="peopleFilters"
|
||||
[primaryFilters]="peopleFilters"
|
||||
[icon]="'red:user'"
|
||||
></redaction-filter>
|
||||
<redaction-filter
|
||||
@ -21,7 +21,7 @@
|
||||
(filtersChanged)="filtersChanged()"
|
||||
[filterLabel]="'filters.needs-work'"
|
||||
[filterTemplate]="needsWorkTemplate"
|
||||
[filters]="needsWorkFilters"
|
||||
[primaryFilters]="needsWorkFilters"
|
||||
[icon]="'red:needs-work'"
|
||||
></redaction-filter>
|
||||
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AppStateService } from '../../../state/app-state.service';
|
||||
import { AnnotationWrapper } from '../../../models/file/annotation.wrapper';
|
||||
import { FilterModel, FilterTypes } from '../../shared/components/filter/model/filter.model';
|
||||
import { FilterModel } from '../../shared/components/filter/model/filter.model';
|
||||
import { handleCheckedValue } from '../../shared/components/filter/utils/filter-utils';
|
||||
import { SuperTypeSorter } from '../../../utils/sorters/super-type-sorter';
|
||||
|
||||
@Injectable()
|
||||
export class AnnotationProcessingService {
|
||||
constructor(private readonly _appStateService: AppStateService) {}
|
||||
|
||||
getAnnotationFilter(annotations: AnnotationWrapper[]): FilterModel[] {
|
||||
const filterMap = new Map<string, FilterModel>();
|
||||
let filters: FilterModel[] = [];
|
||||
const filters: FilterModel[] = [];
|
||||
|
||||
annotations?.forEach((a) => {
|
||||
const topLevelFilter = a.superType !== 'hint' && a.superType !== 'redaction' && a.superType !== 'recommendation';
|
||||
@ -30,7 +27,6 @@ export class AnnotationProcessingService {
|
||||
}
|
||||
const childFilter = {
|
||||
key: a.dictionary,
|
||||
type: FilterTypes.primary,
|
||||
checked: false,
|
||||
filters: [],
|
||||
matches: 1
|
||||
@ -52,16 +48,12 @@ export class AnnotationProcessingService {
|
||||
}
|
||||
}
|
||||
|
||||
filters = filters.sort((a, b) => SuperTypeSorter[a.key] - SuperTypeSorter[b.key]);
|
||||
filters.push(...AnnotationProcessingService._secondaryFilters);
|
||||
|
||||
return filters;
|
||||
return filters.sort((a, b) => SuperTypeSorter[a.key] - SuperTypeSorter[b.key]);
|
||||
}
|
||||
|
||||
private _createParentFilter(key: string, filterMap: Map<string, FilterModel>, filters: FilterModel[], type?: FilterTypes) {
|
||||
private _createParentFilter(key: string, filterMap: Map<string, FilterModel>, filters: FilterModel[]) {
|
||||
const filter: FilterModel = {
|
||||
key: key,
|
||||
type: type || FilterTypes.primary,
|
||||
topLevelFilter: true,
|
||||
matches: 1,
|
||||
label: 'annotation-type.' + key,
|
||||
@ -72,48 +64,27 @@ export class AnnotationProcessingService {
|
||||
return filter;
|
||||
}
|
||||
|
||||
filterAndGroupAnnotations(annotations: AnnotationWrapper[], filters: FilterModel[]): { [key: number]: { annotations: AnnotationWrapper[] } } {
|
||||
filterAndGroupAnnotations(
|
||||
annotations: AnnotationWrapper[],
|
||||
primaryFilters: FilterModel[],
|
||||
secondaryFilters?: FilterModel[]
|
||||
): { [key: number]: { annotations: AnnotationWrapper[] } } {
|
||||
const obj = {};
|
||||
const hasActiveFilters = this._hasActiveFilters(filters);
|
||||
|
||||
const flatFilters: FilterModel[] = [];
|
||||
filters.forEach((filter) => {
|
||||
flatFilters.push(filter);
|
||||
flatFilters.push(...filter?.filters);
|
||||
});
|
||||
|
||||
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);
|
||||
const primaryFlatFilters = this._getFlatFilters(primaryFilters, (f) => f.checked);
|
||||
const secondaryFlatFilters = this._getFlatFilters(secondaryFilters, (f) => f.checked && !!f.checker);
|
||||
|
||||
for (const annotation of annotations) {
|
||||
const pageNumber = annotation.pageNumber;
|
||||
const type = annotation.superType;
|
||||
|
||||
if (hasActiveFilters) {
|
||||
let found = false;
|
||||
for (const filter of primaryFilters) {
|
||||
if (
|
||||
(filter.key === annotation.dictionary &&
|
||||
(annotation.superType === 'hint' || annotation.superType === 'redaction' || annotation.superType === 'recommendation')) ||
|
||||
filter.key === annotation.superType
|
||||
) {
|
||||
let secondaryFiltersNotMatched = false;
|
||||
for (const secondaryFilter of secondaryFilters) {
|
||||
if (!secondaryFilter.action(annotation)) {
|
||||
secondaryFiltersNotMatched = true;
|
||||
}
|
||||
}
|
||||
|
||||
found = !secondaryFiltersNotMatched;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
continue;
|
||||
}
|
||||
if (!this._matchesOne(primaryFlatFilters, (f) => this._checkByFilterKey(f, annotation))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this._matchesAll(secondaryFlatFilters, (f) => f.checker(annotation))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pageNumber = annotation.pageNumber;
|
||||
|
||||
if (!obj[pageNumber]) {
|
||||
obj[pageNumber] = {
|
||||
annotations: [],
|
||||
@ -125,7 +96,7 @@ export class AnnotationProcessingService {
|
||||
}
|
||||
|
||||
obj[pageNumber].annotations.push(annotation);
|
||||
obj[pageNumber][type]++;
|
||||
obj[pageNumber][annotation.superType]++;
|
||||
}
|
||||
|
||||
Object.keys(obj).map((page) => {
|
||||
@ -135,6 +106,44 @@ export class AnnotationProcessingService {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private _getFlatFilters(filters: FilterModel[], filterBy?: (f: FilterModel) => boolean) {
|
||||
const flatFilters: FilterModel[] = [];
|
||||
|
||||
filters.forEach((filter) => {
|
||||
flatFilters.push(filter);
|
||||
flatFilters.push(...filter.filters);
|
||||
});
|
||||
|
||||
return !!filterBy ? flatFilters.filter((f) => filterBy(f)) : flatFilters;
|
||||
}
|
||||
|
||||
private _matchesOne = (filters: FilterModel[], condition: (filter: FilterModel) => boolean): boolean => {
|
||||
if (filters.length === 0) return true;
|
||||
|
||||
for (const filter of filters) {
|
||||
if (condition(filter)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
private _matchesAll = (filters: FilterModel[], condition: (filter: FilterModel) => boolean): boolean => {
|
||||
if (filters.length === 0) return true;
|
||||
|
||||
for (const filter of filters) {
|
||||
if (!condition(filter)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
private _checkByFilterKey = (filter: FilterModel, annotation: AnnotationWrapper) => {
|
||||
const superType = annotation.superType;
|
||||
const isNotTopLevelFilter = superType === 'hint' || superType === 'redaction' || superType === 'recommendation';
|
||||
|
||||
return filter.key === superType || (filter.key === annotation.dictionary && isNotTopLevelFilter);
|
||||
};
|
||||
|
||||
private _sortAnnotations(annotations: AnnotationWrapper[]): AnnotationWrapper[] {
|
||||
return annotations.sort((ann1, ann2) => {
|
||||
if (ann1.pageNumber === ann2.pageNumber) {
|
||||
@ -148,22 +157,15 @@ export class AnnotationProcessingService {
|
||||
});
|
||||
}
|
||||
|
||||
private _hasActiveFilters(filters: FilterModel[]): boolean {
|
||||
return filters.reduce((acc, next) => acc || next.checked || next.indeterminate, false);
|
||||
}
|
||||
|
||||
private static get _secondaryFilters(): FilterModel[] {
|
||||
static get secondaryAnnotationFilters(): FilterModel[] {
|
||||
return [
|
||||
{
|
||||
key: 'with-comments',
|
||||
key: 'red:comment',
|
||||
label: 'filter-menu.with-comments',
|
||||
checked: false,
|
||||
topLevelFilter: true,
|
||||
type: FilterTypes.secondary,
|
||||
filters: [],
|
||||
action: (obj) => {
|
||||
return obj?.comments?.length > 0;
|
||||
}
|
||||
checker: (annotation: AnnotationWrapper) => annotation?.comments?.length > 0
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<redaction-chevron-button *ngIf="chevron" [text]="filterLabel" [matMenuTriggerFor]="filterMenu" [showDot]="hasActiveFilters"></redaction-chevron-button>
|
||||
|
||||
<mat-menu #filterMenu="matMenu" xPosition="before" (closed)="applyFilters()">
|
||||
<div (mouseleave)="filterMouseLeave()" (mouseenter)="filterMouseEnter()">
|
||||
<div (mouseleave)="filterMouseLeave()" (mouseenter)="filterMouseEnter()" [class.pb-24]="secondaryFilters?.length === 0">
|
||||
<div class="filter-menu-header">
|
||||
<div class="all-caps-label" translate="filter-menu.filter-types"></div>
|
||||
<div class="actions">
|
||||
@ -22,16 +22,10 @@
|
||||
*ngTemplateOutlet="defaultFilterTemplate; context: { filter: filter, atLeastOneIsExpandable: atLeastOneFilterIsExpandable }"
|
||||
></ng-template>
|
||||
</div>
|
||||
<div class="filter-options" *ngIf="secondaryFilters?.length">
|
||||
<div class="filter-options" *ngIf="secondaryFilters?.length > 0">
|
||||
<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>-->
|
||||
<div *ngFor="let filter of secondaryFilters">
|
||||
<ng-template
|
||||
*ngTemplateOutlet="defaultFilterTemplate; context: { filter: filter, atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable }"
|
||||
@ -42,21 +36,21 @@
|
||||
</mat-menu>
|
||||
|
||||
<ng-template #defaultFilterLabelTemplate let-filter="filter">
|
||||
{{ filter?.label }}
|
||||
{{ _(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>
|
||||
<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"
|
||||
[checked]="_(filter).checked"
|
||||
[indeterminate]="_(filter).indeterminate"
|
||||
(click)="filterCheckboxClicked($event, filter)"
|
||||
class="filter-menu-checkbox"
|
||||
>
|
||||
@ -64,8 +58,8 @@
|
||||
</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()">
|
||||
<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>
|
||||
|
||||
@ -24,12 +24,6 @@
|
||||
.filter-options {
|
||||
background-color: $grey-2;
|
||||
padding-bottom: 8px;
|
||||
|
||||
mat-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .mat-menu-panel .mat-menu-content:not(:empty) {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef } from '@angular/core';
|
||||
import { AppStateService } from '../../../../state/app-state.service';
|
||||
import { FilterModel, FilterTypes } from './model/filter.model';
|
||||
import { FilterModel } from './model/filter.model';
|
||||
import { handleCheckedValue } from './utils/filter-utils';
|
||||
import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
|
||||
|
||||
@ -19,10 +18,11 @@ import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
|
||||
]
|
||||
})
|
||||
export class FilterComponent implements OnChanges {
|
||||
@Output() filtersChanged = new EventEmitter<FilterModel[]>();
|
||||
@Output() filtersChanged = new EventEmitter<{ primary: FilterModel[]; secondary?: FilterModel[] }>();
|
||||
@Input() filterTemplate: TemplateRef<any>;
|
||||
@Input() actionsTemplate: TemplateRef<any>;
|
||||
@Input() filters: FilterModel[] = [];
|
||||
@Input() primaryFilters: FilterModel[] = [];
|
||||
@Input() secondaryFilters: FilterModel[] = [];
|
||||
@Input() filterLabel = 'filter-menu.label';
|
||||
@Input() icon: string;
|
||||
@Input() chevron = false;
|
||||
@ -33,15 +33,16 @@ export class FilterComponent implements OnChanges {
|
||||
atLeastOneFilterIsExpandable = false;
|
||||
atLeastOneSecondaryFilterIsExpandable = false;
|
||||
|
||||
constructor(public readonly appStateService: AppStateService, private readonly _changeDetectorRef: ChangeDetectorRef) {}
|
||||
constructor(private readonly _changeDetectorRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
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);
|
||||
this.primaryFilters?.forEach((f) => {
|
||||
this.atLeastOneFilterIsExpandable = this.atLeastOneFilterIsExpandable || this.isExpandable(f);
|
||||
});
|
||||
this.secondaryFilters?.forEach((f) => {
|
||||
this.atLeastOneSecondaryFilterIsExpandable = this.atLeastOneSecondaryFilterIsExpandable || this.isExpandable(f);
|
||||
});
|
||||
}
|
||||
|
||||
@ -71,7 +72,7 @@ export class FilterComponent implements OnChanges {
|
||||
}
|
||||
|
||||
get hasActiveFilters(): boolean {
|
||||
for (const filter of this.filters ? this.filters : []) {
|
||||
for (const filter of this.primaryFilters ? this.primaryFilters : []) {
|
||||
if (filter.checked || filter.indeterminate) {
|
||||
return true;
|
||||
}
|
||||
@ -79,16 +80,8 @@ export class FilterComponent implements OnChanges {
|
||||
return false;
|
||||
}
|
||||
|
||||
get primaryFilters() {
|
||||
return this.filters?.filter((f) => f.type === FilterTypes.primary || f.type === undefined);
|
||||
}
|
||||
|
||||
get secondaryFilters() {
|
||||
return this.filters?.filter((f) => f.type === FilterTypes.secondary);
|
||||
}
|
||||
|
||||
applyFilters() {
|
||||
this.filtersChanged.emit(this.filters);
|
||||
this.filtersChanged.emit({ primary: this.primaryFilters, secondary: this.secondaryFilters });
|
||||
}
|
||||
|
||||
toggleFilterExpanded($event: MouseEvent, filter: FilterModel) {
|
||||
@ -97,7 +90,7 @@ export class FilterComponent implements OnChanges {
|
||||
}
|
||||
|
||||
private _setAllFilters(value: boolean) {
|
||||
this.filters?.forEach((f) => {
|
||||
this.primaryFilters?.forEach((f) => {
|
||||
f.checked = value;
|
||||
f.indeterminate = false;
|
||||
f.filters?.forEach((ff) => {
|
||||
@ -123,4 +116,8 @@ export class FilterComponent implements OnChanges {
|
||||
isExpandable(filter: FilterModel) {
|
||||
return filter.filters && filter.filters.length > 0;
|
||||
}
|
||||
|
||||
_(obj): FilterModel {
|
||||
return obj as FilterModel;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
export enum FilterTypes {
|
||||
primary = 'primary',
|
||||
secondary = 'secondary'
|
||||
}
|
||||
|
||||
export interface FilterModel {
|
||||
key: string;
|
||||
type: FilterTypes;
|
||||
label?: string;
|
||||
checked?: boolean;
|
||||
indeterminate?: boolean;
|
||||
@ -13,5 +7,5 @@ export interface FilterModel {
|
||||
topLevelFilter?: boolean;
|
||||
matches?: number;
|
||||
filters?: FilterModel[];
|
||||
action?: (obj?) => boolean;
|
||||
checker?: (obj?) => boolean;
|
||||
}
|
||||
|
||||
@ -1,13 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>status</title>
|
||||
<g id="Styleguide" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Styleguide-Actions" transform="translate(-607.000000, -818.000000)" fill="#283241" fill-rule="nonzero">
|
||||
<g id="Group-6" transform="translate(598.000000, 809.000000)">
|
||||
<g id="status" transform="translate(9.000000, 9.000000)">
|
||||
<path d="M8,0 C4,0 0.8,3.2 0.8,7.2 C0.8,8.88 1.36,10.4 2.4,11.68 L2.4,16 L7.04,14.32 C7.44,14.4 7.68,14.4 8,14.4 C12,14.4 15.2,11.2 15.2,7.2 C15.2,3.2 12,0 8,0 Z M8,12.8 C7.76,12.8 7.44,12.8 7.12,12.72 L6.88,12.72 L4,13.76 L4,11.12 L3.76,10.88 C2.88,9.84 2.4,8.56 2.4,7.2 C2.4,4.08 4.88,1.6 8,1.6 C11.12,1.6 13.6,4.08 13.6,7.2 C13.6,10.32 11.12,12.8 8,12.8 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="comments" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path
|
||||
d="M50,0 C25,0 5,20 5,45 C5,55.5 8.5,65 15,73 L15,100 L44,89.5 C46.5,90 48,90 50,90 C75,90 95,70 95,45 C95,20 75,0 50,0 Z M50,80 C48.5,80 46.5,80 44.5,79.5 L43,79.5 L25,86 L25,69.5 L23.5,68 C18,61.5 15,53.5 15,45 C15,25.5 30.5,10 50,10 C69.5,10 85,25.5 85,45 C85,64.5 69.5,80 50,80 Z"
|
||||
id="Shape" fill="currentColor" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 628 B |
@ -229,6 +229,10 @@ body {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.pb-24 {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.pb-32 {
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user