Add filter option "only with comments" to annotation filters
This commit is contained in:
parent
f47795cbec
commit
a4308c654d
@ -12,6 +12,7 @@
|
||||
[filterTemplate]="annotationFilterTemplate"
|
||||
[actionsTemplate]="annotationFilterActionTemplate"
|
||||
[filters]="annotationFilters"
|
||||
[enableFilterOptions]="true"
|
||||
></redaction-filter>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -45,6 +45,7 @@ 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;
|
||||
@ -109,8 +110,8 @@ export class FileWorkloadComponent {
|
||||
}
|
||||
|
||||
@debounce(0)
|
||||
public filtersChanged(filters: FilterModel[]) {
|
||||
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this._annotations, filters);
|
||||
public filtersChanged($event: { filters: FilterModel[]; extraFilterBy?: (annotation: AnnotationWrapper) => boolean }) {
|
||||
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this._annotations, $event.filters, $event.extraFilterBy);
|
||||
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(this.annotationFilters);
|
||||
this._workloadComponent.filtersChanged({ filters: 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(this.annotationFilters);
|
||||
this._workloadComponent.filtersChanged({ filters: this.annotationFilters });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -62,7 +62,11 @@ export class AnnotationProcessingService {
|
||||
return filter;
|
||||
}
|
||||
|
||||
filterAndGroupAnnotations(annotations: AnnotationWrapper[], filters: FilterModel[]): { [key: number]: { annotations: AnnotationWrapper[] } } {
|
||||
filterAndGroupAnnotations(
|
||||
annotations: AnnotationWrapper[],
|
||||
filters: FilterModel[],
|
||||
extraFilterBy?: (annotation: AnnotationWrapper) => boolean
|
||||
): { [key: number]: { annotations: AnnotationWrapper[] } } {
|
||||
const obj = {};
|
||||
|
||||
const hasActiveFilters = this._hasActiveFilters(filters);
|
||||
@ -73,6 +77,10 @@ export class AnnotationProcessingService {
|
||||
flatFilters.push(...filter.filters);
|
||||
});
|
||||
for (const annotation of annotations) {
|
||||
if (typeof extraFilterBy === 'function' && !extraFilterBy(annotation)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pageNumber = annotation.pageNumber;
|
||||
const type = annotation.superType;
|
||||
|
||||
@ -103,6 +111,7 @@ export class AnnotationProcessingService {
|
||||
skipped: 0
|
||||
};
|
||||
}
|
||||
|
||||
obj[pageNumber].annotations.push(annotation);
|
||||
obj[pageNumber][type]++;
|
||||
}
|
||||
|
||||
@ -1,58 +1,63 @@
|
||||
<div class="filter-root">
|
||||
<redaction-icon-button
|
||||
*ngIf="!chevron"
|
||||
[text]="filterLabel"
|
||||
[icon]="icon"
|
||||
[matMenuTriggerFor]="filterMenu"
|
||||
[showDot]="hasActiveFilters"
|
||||
></redaction-icon-button>
|
||||
<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 class="filter-menu-header">
|
||||
<div class="all-caps-label" translate="filter-menu.filter-types"></div>
|
||||
<div class="actions">
|
||||
<div class="all-caps-label primary pointer" translate="filter-menu.all" (click)="activateAllFilters(); $event.stopPropagation()"></div>
|
||||
<div class="all-caps-label primary pointer" translate="filter-menu.none" (click)="deactivateAllFilters(); $event.stopPropagation()"></div>
|
||||
</div>
|
||||
<redaction-icon-button
|
||||
*ngIf="!chevron"
|
||||
[text]="filterLabel"
|
||||
[icon]="icon"
|
||||
[matMenuTriggerFor]="filterMenu"
|
||||
[showDot]="hasActiveFilters"
|
||||
></redaction-icon-button>
|
||||
|
||||
<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 class="filter-menu-header">
|
||||
<div class="all-caps-label" translate="filter-menu.filter-types"></div>
|
||||
<div class="actions">
|
||||
<div class="all-caps-label primary pointer" translate="filter-menu.all" (click)="activateAllFilters(); $event.stopPropagation()"></div>
|
||||
<div class="all-caps-label primary pointer" translate="filter-menu.none" (click)="deactivateAllFilters(); $event.stopPropagation()"></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); $event.stopPropagation()"
|
||||
class="filter-menu-checkbox"
|
||||
color="primary"
|
||||
>
|
||||
<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 *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 *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); $event.stopPropagation()"
|
||||
color="primary"
|
||||
>
|
||||
<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 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>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div class="filter-options" *ngIf="enableFilterOptions">
|
||||
<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>
|
||||
</div>
|
||||
</mat-menu>
|
||||
|
||||
<ng-template #defaultFilterTemplate let-filter="filter">
|
||||
{{ filter?.label }}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
@import '../../../../../assets/styles/red-variables';
|
||||
|
||||
.filter-menu-options,
|
||||
.filter-menu-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -15,6 +16,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
.filter-menu-options {
|
||||
margin-top: 8px;
|
||||
padding: 16px 16px 3px;
|
||||
}
|
||||
|
||||
.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) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
::ng-deep .filter-menu-checkbox {
|
||||
width: 100%;
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { FilterModel } 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',
|
||||
@ -20,11 +21,12 @@ import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
|
||||
]
|
||||
})
|
||||
export class FilterComponent implements OnChanges {
|
||||
@Output() filtersChanged = new EventEmitter<FilterModel[]>();
|
||||
@Output() filtersChanged = new EventEmitter<{ filters: FilterModel[]; extraFilterBy?: (annotation: AnnotationWrapper) => boolean }>();
|
||||
@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;
|
||||
|
||||
@ -34,6 +36,7 @@ export class FilterComponent implements OnChanges {
|
||||
mouseOverTimeout: number;
|
||||
|
||||
atLeastOnFilterIsExpandable = false;
|
||||
filterOnlyWithComments = false;
|
||||
|
||||
constructor(public readonly appStateService: AppStateService, private readonly _changeDetectorRef: ChangeDetectorRef) {}
|
||||
|
||||
@ -47,6 +50,8 @@ export class FilterComponent implements OnChanges {
|
||||
}
|
||||
|
||||
filterCheckboxClicked($event: any, filter: FilterModel, parent?: FilterModel) {
|
||||
$event.stopPropagation();
|
||||
|
||||
filter.checked = !filter.checked;
|
||||
if (parent) {
|
||||
handleCheckedValue(parent);
|
||||
@ -61,7 +66,13 @@ export class FilterComponent implements OnChanges {
|
||||
this.applyFilters();
|
||||
}
|
||||
|
||||
public activateAllFilters() {
|
||||
filterOptionsCheckboxClicked($event: Event) {
|
||||
$event.stopPropagation();
|
||||
this.filterOnlyWithComments = !this.filterOnlyWithComments;
|
||||
this.applyFilters();
|
||||
}
|
||||
|
||||
activateAllFilters() {
|
||||
this._setAllFilters(true);
|
||||
}
|
||||
|
||||
@ -78,8 +89,12 @@ export class FilterComponent implements OnChanges {
|
||||
return false;
|
||||
}
|
||||
|
||||
public applyFilters() {
|
||||
this.filtersChanged.emit(this.filters);
|
||||
applyFilters() {
|
||||
this.filtersChanged.emit({ filters: this.filters, extraFilterBy: this._extraFilterBy });
|
||||
}
|
||||
|
||||
private get _extraFilterBy(): (a: AnnotationWrapper) => boolean {
|
||||
return this.enableFilterOptions && this.filterOnlyWithComments ? (a) => a.comments.length !== 0 : (a) => true;
|
||||
}
|
||||
|
||||
toggleFilterExpanded($event: MouseEvent, filter: FilterModel) {
|
||||
|
||||
@ -514,7 +514,9 @@
|
||||
"label": "Filter",
|
||||
"all": "All",
|
||||
"none": "None",
|
||||
"filter-types": "Filter types"
|
||||
"filter-types": "Filter types",
|
||||
"filter-options": "Filter options",
|
||||
"with-comments": "Show only annotations with comments"
|
||||
},
|
||||
"sorting": {
|
||||
"recent": "Recent",
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<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>
|
||||
<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>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 647 B After Width: | Height: | Size: 1.0 KiB |
Loading…
x
Reference in New Issue
Block a user