Repair filter dialog to accept rich expressions
Signed-off-by: trivernis <trivernis@protonmail.com>pull/4/head
parent
2f5e6d8bcb
commit
c2dbf6846c
@ -0,0 +1,3 @@
|
|||||||
|
<div (click)="this.onClick()" [class.selected]="this.selected" class="selectable">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
@ -0,0 +1,7 @@
|
|||||||
|
.selectable.selected {
|
||||||
|
background-color: #5c5c5c;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SelectableComponent } from './selectable.component';
|
||||||
|
|
||||||
|
describe('SelectableComponent', () => {
|
||||||
|
let component: SelectableComponent;
|
||||||
|
let fixture: ComponentFixture<SelectableComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ SelectableComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SelectableComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,25 @@
|
|||||||
|
import {Component, EventEmitter, Output} from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-selectable",
|
||||||
|
templateUrl: "./selectable.component.html",
|
||||||
|
styleUrls: ["./selectable.component.scss"]
|
||||||
|
})
|
||||||
|
export class SelectableComponent {
|
||||||
|
public selected = false;
|
||||||
|
|
||||||
|
@Output() appSelect = new EventEmitter<this>();
|
||||||
|
@Output() appUnselect = new EventEmitter<this>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public onClick(): void {
|
||||||
|
this.selected = !this.selected;
|
||||||
|
if (this.selected) {
|
||||||
|
this.appSelect.emit(this);
|
||||||
|
} else {
|
||||||
|
this.appUnselect.emit(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,177 +1,143 @@
|
|||||||
import {Component, HostListener, Inject, ViewChildren} from "@angular/core";
|
import {Component, Inject, OnChanges, SimpleChanges} from "@angular/core";
|
||||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||||
import {SortDialogComponent} from "../sort-dialog/sort-dialog.component";
|
import {SortDialogComponent} from "../sort-dialog/sort-dialog.component";
|
||||||
import {
|
|
||||||
GenericFilter,
|
|
||||||
OrFilterExpression,
|
|
||||||
SingleFilterExpression
|
|
||||||
} from "../../../../../models/GenericFilter";
|
|
||||||
import {TagQuery} from "../../../../../models/TagQuery";
|
|
||||||
import {Tag} from "../../../../../../api/models/Tag";
|
import {Tag} from "../../../../../../api/models/Tag";
|
||||||
import {
|
import {SearchFilters} from "../../../../../../api/models/SearchFilters";
|
||||||
TagFilterListItemComponent
|
import {FilterExpression, FilterQuery} from "../../../../../../api/api-types/files";
|
||||||
} from "./tag-filter-list-item/tag-filter-list-item.component";
|
import {enumerate, removeByValue} from "../../../../../utils/list-utils";
|
||||||
import {Selectable} from "../../../../../models/Selectable";
|
|
||||||
|
type IndexableSelection<T> = {
|
||||||
|
[key: number]: T
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-filter-dialog",
|
selector: "app-filter-dialog",
|
||||||
templateUrl: "./filter-dialog.component.html",
|
templateUrl: "./filter-dialog.component.html",
|
||||||
styleUrls: ["./filter-dialog.component.scss"]
|
styleUrls: ["./filter-dialog.component.scss"]
|
||||||
})
|
})
|
||||||
export class FilterDialogComponent {
|
export class FilterDialogComponent implements OnChanges {
|
||||||
|
|
||||||
public filters: Selectable<GenericFilter>[];
|
|
||||||
public availableTags: Tag[] = [];
|
public availableTags: Tag[] = [];
|
||||||
public mode: "AND" | "OR" = "AND";
|
public filters = new SearchFilters([]);
|
||||||
|
public renderedFilterEntries: [number, FilterExpression][] = [];
|
||||||
@ViewChildren(
|
private selectedIndices: IndexableSelection<number[]> = {};
|
||||||
TagFilterListItemComponent) filterListItems!: TagFilterListItemComponent[];
|
|
||||||
|
|
||||||
private selectedQueries: TagQuery[] = [];
|
|
||||||
|
|
||||||
constructor(public dialogRef: MatDialogRef<SortDialogComponent>, @Inject(
|
constructor(public dialogRef: MatDialogRef<SortDialogComponent>, @Inject(
|
||||||
MAT_DIALOG_DATA) data: any) {
|
MAT_DIALOG_DATA) data: any) {
|
||||||
this.filters = data.filterEntries.map(
|
|
||||||
(f: GenericFilter) => new Selectable<GenericFilter>(f,
|
|
||||||
false)) ?? [];
|
|
||||||
this.availableTags = data.availableTags ?? [];
|
this.availableTags = data.availableTags ?? [];
|
||||||
|
this.filters = data.filters;
|
||||||
|
this.buildRenderedEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static checkFiltersEqual(l: GenericFilter, r: GenericFilter): boolean {
|
public ngOnChanges(changes: SimpleChanges): void {
|
||||||
const lTags = l.queryList().map(q => q.getNormalizedTag()).sort();
|
if (changes["filters"]) {
|
||||||
const rTags = r.queryList().map(q => q.getNormalizedTag()).sort();
|
this.buildRenderedEntries();
|
||||||
let match = false;
|
|
||||||
|
|
||||||
if (lTags.length == rTags.length) {
|
|
||||||
match = true;
|
|
||||||
|
|
||||||
for (const tag of lTags) {
|
|
||||||
match = rTags.includes(tag);
|
|
||||||
if (!match) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return match;
|
|
||||||
}
|
|
||||||
|
|
||||||
public cancelFilter(): void {
|
public cancelFilter(): void {
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public confirmFilter(): void {
|
public confirmFilter(): void {
|
||||||
this.dialogRef.close(this.filters.map(f => f.data));
|
this.dialogRef.close(this.filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeFilter(event: TagFilterListItemComponent): void {
|
public entrySelect(index: number, subindex: number = -1): void {
|
||||||
const filter = event.expression;
|
this.selectedIndices[index] = this.selectedIndices[index] ?? [];
|
||||||
const index = this.filters.findIndex(f => f === filter);
|
this.selectedIndices[index].push(subindex);
|
||||||
if (index >= 0) {
|
|
||||||
this.filters.splice(index, 1);
|
|
||||||
}
|
}
|
||||||
this.unselectAll();
|
|
||||||
|
public entryUnselect(index: number, subindex: number = -1): void {
|
||||||
|
this.selectedIndices[index] = this.selectedIndices[index] ?? [];
|
||||||
|
removeByValue(this.selectedIndices[index], subindex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addFilter(tag: string) {
|
public addFilter(expression: FilterExpression): void {
|
||||||
const query = TagQuery.fromString(tag);
|
this.filters.addFilterExpression(expression);
|
||||||
|
this.buildRenderedEntries();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.mode === "AND" || this.filters.length === 0) {
|
public removeSelectedFilters(): void {
|
||||||
this.filters.push(
|
const orderedIndices = Object.keys(this.selectedIndices).map(k => Number(k)).sort().reverse();
|
||||||
new Selectable<GenericFilter>(
|
|
||||||
new SingleFilterExpression(query),
|
|
||||||
false));
|
|
||||||
tag = tag.replace(/^-/g, "");
|
|
||||||
|
|
||||||
if (this.filters.filter(t => t.data.partiallyEq(tag)).length > 1) {
|
for (const indexStr of orderedIndices) {
|
||||||
const index = this.filters.findIndex(
|
const index = indexStr;
|
||||||
t => t.data.partiallyEq(tag));
|
const subIndices: number[] = this.selectedIndices[index];
|
||||||
this.filters.splice(index, 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let queryList = this.filters.pop()?.data.queryList() ?? [];
|
|
||||||
|
|
||||||
queryList.push(query);
|
if (subIndices.length === 1 && subIndices[0] === -1) {
|
||||||
const filterExpression = new OrFilterExpression(queryList);
|
this.filters.removeFilterAtIndex(index);
|
||||||
filterExpression.removeDuplicates();
|
} else if (subIndices.length > 0) {
|
||||||
this.filters.push(
|
for (const subIndex of subIndices.sort().reverse()) { // need to remove from the top down to avoid index shifting
|
||||||
new Selectable<GenericFilter>(filterExpression,
|
this.filters.removeSubfilterAtIndex(index, subIndex);
|
||||||
false));
|
|
||||||
}
|
}
|
||||||
this.unselectAll();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public addToSelection(query: TagQuery): void {
|
this.selectedIndices = {};
|
||||||
this.selectedQueries.push(query);
|
this.buildRenderedEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeFromSelection(query: TagQuery): void {
|
public createAndFromSelection(deleteOriginal: boolean): void {
|
||||||
const index = this.selectedQueries.indexOf(query);
|
const expressions: FilterExpression[] = [];
|
||||||
if (index > 0) {
|
|
||||||
this.selectedQueries.splice(index, 1);
|
for (const indexStr in this.selectedIndices) {
|
||||||
|
const index = Number(indexStr);
|
||||||
|
const subindices = this.selectedIndices[index];
|
||||||
|
|
||||||
|
if (subindices.length === 1 && subindices[0] === -1) {
|
||||||
|
expressions.push(this.filters.getFilters()[index]);
|
||||||
|
} else {
|
||||||
|
for (const subIndex of subindices) {
|
||||||
|
const query = this.filters.getSubfilterAtIndex(index, subIndex);
|
||||||
|
if (query) {
|
||||||
|
expressions.push({ Query: query });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unselectAll() {
|
|
||||||
this.filters.forEach(filter => filter.selected = false);
|
|
||||||
this.selectedQueries = [];
|
|
||||||
this.filterListItems.forEach(i => i.selectedIndices = []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public convertSelectionToAndExpression(): void {
|
|
||||||
for (const query of this.selectedQueries) {
|
|
||||||
this.filters.push(
|
|
||||||
new Selectable<GenericFilter>(
|
|
||||||
new SingleFilterExpression(query),
|
|
||||||
false));
|
|
||||||
}
|
}
|
||||||
this.removeFilterDuplicates();
|
if (deleteOriginal) {
|
||||||
this.unselectAll();
|
this.removeSelectedFilters();
|
||||||
|
} else {
|
||||||
|
this.selectedIndices = {};
|
||||||
}
|
}
|
||||||
|
expressions.forEach(e => this.filters.addFilterExpression(e));
|
||||||
public convertSelectionToOrExpression(): void {
|
this.buildRenderedEntries();
|
||||||
const queries = this.selectedQueries;
|
|
||||||
const expression = new OrFilterExpression(queries);
|
|
||||||
this.filters.push(new Selectable<GenericFilter>(expression, false));
|
|
||||||
this.removeFilterDuplicates();
|
|
||||||
this.unselectAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public invertSelection(): void {
|
public createOrFromSelection(deleteOriginal: boolean): void {
|
||||||
this.selectedQueries.forEach(query => query.negate = !query.negate);
|
const queries: FilterQuery[] = [];
|
||||||
}
|
|
||||||
|
|
||||||
private removeFilterDuplicates() {
|
for (const indexStr in this.selectedIndices) {
|
||||||
const filters = this.filters;
|
const index = Number(indexStr);
|
||||||
let newFilters: Selectable<GenericFilter>[] = [];
|
const subindices = this.selectedIndices[index];
|
||||||
|
|
||||||
for (const filterItem of filters) {
|
if (subindices.length === 1 && subindices[0] === -1) {
|
||||||
if (filterItem.data.filter_type == "OrExpression") {
|
const filterEntry = this.filters.getFilters()[index];
|
||||||
(filterItem.data as OrFilterExpression).removeDuplicates();
|
if ("Query" in filterEntry) {
|
||||||
|
queries.push(filterEntry.Query);
|
||||||
}
|
}
|
||||||
if (newFilters.findIndex(
|
} else {
|
||||||
f => FilterDialogComponent.checkFiltersEqual(f.data,
|
for (const subIndex of subindices) {
|
||||||
filterItem.data)) < 0) {
|
const query = this.filters.getSubfilterAtIndex(index, subIndex);
|
||||||
if (filterItem.data.filter_type == "OrExpression" && filterItem.data.queryList().length === 1) {
|
if (query) {
|
||||||
filterItem.data = new SingleFilterExpression(
|
queries.push(query);
|
||||||
filterItem.data.queryList()[0]);
|
|
||||||
}
|
}
|
||||||
newFilters.push(filterItem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.filters = newFilters;
|
|
||||||
}
|
}
|
||||||
|
if (deleteOriginal) {
|
||||||
@HostListener("window:keydown", ["$event"])
|
this.removeSelectedFilters();
|
||||||
private async handleKeydownEvent(event: KeyboardEvent) {
|
} else {
|
||||||
if (event.key === "Shift") {
|
this.selectedIndices = {};
|
||||||
this.mode = "OR";
|
|
||||||
}
|
}
|
||||||
|
if (queries.length > 1) {
|
||||||
|
this.filters.addFilterExpression({ OrExpression: queries });
|
||||||
|
} else if (queries.length === 1) {
|
||||||
|
this.filters.addFilterExpression({ Query: queries[0] });
|
||||||
}
|
}
|
||||||
|
this.buildRenderedEntries();
|
||||||
@HostListener("window:keyup", ["$event"])
|
|
||||||
private async handleKeyupEvent(event: KeyboardEvent) {
|
|
||||||
if (event.key === "Shift") {
|
|
||||||
this.mode = "AND";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildRenderedEntries() {
|
||||||
|
this.renderedFilterEntries = enumerate(this.filters.getFilters());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
<span *ngIf="this.orExpression" class="or-expression">
|
||||||
|
<mat-list>
|
||||||
|
<mat-list-item *ngFor="let entry of this.orExpression"
|
||||||
|
[class.selected]="componentSelectable.selected"
|
||||||
|
class="or-filter-list-item">
|
||||||
|
<app-selectable #componentSelectable
|
||||||
|
(appSelect)="this.entrySelect.emit(entry)"
|
||||||
|
(appUnselect)="this.entryUnselect.emit(entry)">
|
||||||
|
<app-property-query-item *ngIf="this.queryIs(entry[1], 'Property')"
|
||||||
|
[propertyQuery]="this.propertyQuery(entry[1]).Property"></app-property-query-item>
|
||||||
|
<app-tag-query-item *ngIf="this.queryIs(entry[1], 'Tag')"
|
||||||
|
[tagQuery]="this.tagQuery(entry[1]).Tag"></app-tag-query-item>
|
||||||
|
</app-selectable>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="this.query" [class.selected]="singleSelectable.selected" class="query">
|
||||||
|
<app-selectable #singleSelectable
|
||||||
|
(appSelect)="this.appSelect.emit(this.query)"
|
||||||
|
(appUnselect)="this.appUnselect.emit(this.query)">
|
||||||
|
<app-property-query-item *ngIf="this.queryIs(this.query, 'Property')"
|
||||||
|
[propertyQuery]="this.propertyQuery(this.query).Property"></app-property-query-item>
|
||||||
|
<app-tag-query-item *ngIf="this.queryIs(this.query, 'Tag')"
|
||||||
|
[tagQuery]="this.tagQuery(this.query).Tag"></app-tag-query-item>
|
||||||
|
</app-selectable>
|
||||||
|
</span>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { FilterExpressionListItemComponent } from './filter-expression-list-item.component';
|
||||||
|
|
||||||
|
describe('FilterExpressionListItemComponent', () => {
|
||||||
|
let component: FilterExpressionListItemComponent;
|
||||||
|
let fixture: ComponentFixture<FilterExpressionListItemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ FilterExpressionListItemComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(FilterExpressionListItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,57 @@
|
|||||||
|
import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from "@angular/core";
|
||||||
|
import {
|
||||||
|
FilterExpression,
|
||||||
|
FilterQuery,
|
||||||
|
FilterQueryProperty,
|
||||||
|
FilterQueryTag
|
||||||
|
} from "../../../../../../../api/api-types/files";
|
||||||
|
import {enumerate} from "../../../../../../utils/list-utils";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-filter-expression-list-item",
|
||||||
|
templateUrl: "./filter-expression-list-item.component.html",
|
||||||
|
styleUrls: ["./filter-expression-list-item.component.scss"]
|
||||||
|
})
|
||||||
|
export class FilterExpressionListItemComponent implements OnChanges {
|
||||||
|
|
||||||
|
|
||||||
|
@Input() filter!: FilterExpression;
|
||||||
|
@Output() entrySelect = new EventEmitter<[number, FilterQuery]>();
|
||||||
|
@Output() entryUnselect = new EventEmitter<[number, FilterQuery]>();
|
||||||
|
|
||||||
|
@Output() appSelect = new EventEmitter<FilterQuery>();
|
||||||
|
@Output() appUnselect = new EventEmitter<FilterQuery>();
|
||||||
|
|
||||||
|
public orExpression: undefined | [number, FilterQuery][] = undefined;
|
||||||
|
public query: undefined | FilterQuery = undefined;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.parseFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes["filter"]) {
|
||||||
|
this.parseFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public queryIs(query: FilterQuery, key: "Property" | "Tag"): boolean {
|
||||||
|
return key in query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public propertyQuery(query: FilterQuery): FilterQueryProperty {
|
||||||
|
return query as FilterQueryProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public tagQuery(query: FilterQuery): FilterQueryTag {
|
||||||
|
return query as FilterQueryTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseFilter() {
|
||||||
|
if (this.filter && "OrExpression" in this.filter) {
|
||||||
|
this.orExpression = enumerate(this.filter.OrExpression);
|
||||||
|
} else if (this.filter) {
|
||||||
|
this.query = this.filter.Query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
<div (click)="onSelect()" *ngIf="expression.data.filter_type === 'Query'">
|
|
||||||
{{expression.data.getDisplayName()}}
|
|
||||||
<button (click)="this.removeClicked.emit(this)" class="remove-button" mat-button>
|
|
||||||
<ng-icon name="mat-remove"></ng-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="expression.data.filter_type === 'OrExpression'">
|
|
||||||
<mat-list>
|
|
||||||
<mat-list-item (mousedown)="$event.button === 0 && this.selectInnerIndex(entry[0])"
|
|
||||||
*ngFor="let entry of enumerate(this.expression.data.queryList())"
|
|
||||||
[class.selected]="this.selectedIndices.includes(entry[0])"
|
|
||||||
class="or-filter-list-item">
|
|
||||||
<span *ngIf="entry[0] > 0" class="or-span">OR</span>
|
|
||||||
{{entry[1].getNormalizedTag()}}
|
|
||||||
<button (mousedown)="$event.button === 0 && this.removeOrExpression(entry[0])"
|
|
||||||
class="remove-button-inner-list"
|
|
||||||
mat-button>
|
|
||||||
<ng-icon name="mat-remove"></ng-icon>
|
|
||||||
</button>
|
|
||||||
</mat-list-item>
|
|
||||||
</mat-list>
|
|
||||||
</div>
|
|
@ -1,25 +0,0 @@
|
|||||||
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
|
||||||
|
|
||||||
import {TagFilterListItemComponent} from "./tag-filter-list-item.component";
|
|
||||||
|
|
||||||
describe("TagFilterListItemComponent", () => {
|
|
||||||
let component: TagFilterListItemComponent;
|
|
||||||
let fixture: ComponentFixture<TagFilterListItemComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [TagFilterListItemComponent]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(TagFilterListItemComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should create", () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,77 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnChanges,
|
|
||||||
Output,
|
|
||||||
SimpleChanges
|
|
||||||
} from "@angular/core";
|
|
||||||
import {
|
|
||||||
GenericFilter,
|
|
||||||
OrFilterExpression,
|
|
||||||
SingleFilterExpression
|
|
||||||
} from "../../../../../../models/GenericFilter";
|
|
||||||
import {TagQuery} from "../../../../../../models/TagQuery";
|
|
||||||
import {Selectable} from "../../../../../../models/Selectable";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "app-tag-filter-list-item",
|
|
||||||
templateUrl: "./tag-filter-list-item.component.html",
|
|
||||||
styleUrls: ["./tag-filter-list-item.component.scss"]
|
|
||||||
})
|
|
||||||
export class TagFilterListItemComponent implements OnChanges {
|
|
||||||
|
|
||||||
@Input() expression!: Selectable<GenericFilter>;
|
|
||||||
@Output() removeClicked = new EventEmitter<TagFilterListItemComponent>();
|
|
||||||
@Output() querySelect = new EventEmitter<TagQuery>();
|
|
||||||
@Output() queryUnselect = new EventEmitter<TagQuery>();
|
|
||||||
|
|
||||||
public selectedIndices: number[] = [];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnChanges(changes: SimpleChanges): void {
|
|
||||||
if (changes["expression"]) {
|
|
||||||
this.selectedIndices = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enumerate<T>(items: T[]): [number, T][] {
|
|
||||||
return items.map((value, index) => [index, value]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeOrExpression(index: number) {
|
|
||||||
const expression = this.expression.data as OrFilterExpression;
|
|
||||||
expression.removeQueryEntry(index);
|
|
||||||
|
|
||||||
if (expression.filter.length == 0) {
|
|
||||||
this.removeClicked.emit(this);
|
|
||||||
} else if (expression.filter.length == 1) {
|
|
||||||
this.expression.data = new SingleFilterExpression(
|
|
||||||
expression.filter[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public selectInnerIndex(index: number): void {
|
|
||||||
const expression = this.expression.data as OrFilterExpression;
|
|
||||||
|
|
||||||
if (this.selectedIndices.includes(index)) {
|
|
||||||
const elementIndex = this.selectedIndices.indexOf(index);
|
|
||||||
this.selectedIndices.splice(elementIndex, 1);
|
|
||||||
this.queryUnselect.emit(expression.filter[index]);
|
|
||||||
} else {
|
|
||||||
this.selectedIndices.push(index);
|
|
||||||
this.querySelect.emit(expression.filter[index]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public onSelect(): void {
|
|
||||||
this.expression.selected = !this.expression.selected;
|
|
||||||
if (this.expression.selected) {
|
|
||||||
this.querySelect.emit(this.expression.data.filter as TagQuery);
|
|
||||||
} else {
|
|
||||||
this.queryUnselect.emit(this.expression.data.filter as TagQuery);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,15 @@
|
|||||||
|
export function enumerate<T>(list: T[]): [number, T][] {
|
||||||
|
const enumeratedEntries = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
enumeratedEntries.push([i, list[i]] as [number, T]);
|
||||||
|
}
|
||||||
|
return enumeratedEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeByValue<T>(list: T[], entry: T) {
|
||||||
|
const index = list.indexOf(entry);
|
||||||
|
if (index >= 0) {
|
||||||
|
list.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue