, @Inject(
+ MAT_DIALOG_DATA) data: any) {
+ this.filters = data.filterEntries;
+ this.validTags = data.validTags;
+
+ this.suggestionTags = this.formControl.valueChanges.pipe(startWith(null),
+ map(
+ (tag: string | null) => tag ? this.filterSuggestionTag(
+ tag) : this.validTags.slice(0, 20)));
+ }
+
+ public cancelFilter(): void {
+ this.dialogRef.close();
+ }
+
+ public confirmFilter(): void {
+ this.dialogRef.close(this.filters);
+ }
+
+ private filterSuggestionTag(tag: string) {
+ const negated = tag.startsWith("-");
+ const normalizedTag = tag.replace(/^-/, "");
+
+ return this.validTags.filter(
+ t => t.includes(normalizedTag) && this.filters.findIndex(
+ f => f.eq(t)) < 0)
+ .map(t => negated ? "-" + t : t)
+ .slice(0, 20);
+ }
+
+ public addFilterByAutocomplete(event: MatAutocompleteSelectedEvent): void {
+ this.addFilter(event.option.value);
+ this.formControl.setValue(null);
+ this.tagInput.nativeElement.value = '';
+ }
+
+ public addFilterByInput(): void {
+ this.addFilter(this.formControl.value);
+ this.formControl.setValue(null);
+ this.tagInput.nativeElement.value = '';
+ }
+
+ public addFilter(tag: string) {
+ const query = TagQuery.fromString(tag);
+
+ if (this.mode === "AND") {
+ this.filters.push(new SingleFilterExpression(query));
+ tag = tag.replace(/^-/g, '');
+
+ if (this.filters.filter(t => t.partiallyEq(tag)).length > 1) {
+ const index = this.filters.findIndex(t => t.partiallyEq(tag));
+ this.filters.splice(index, 1);
+ }
+ } else {
+ let queryList = this.filters.pop()?.queryList() ?? [];
+
+ queryList.push(query);
+ this.filters.push(new OrFilterExpression(queryList));
+ }
+ }
+
+ @HostListener("window:keydown", ["$event"])
+ private async handleKeydownEvent(event: KeyboardEvent) {
+ if (event.key === "Shift") {
+ this.mode = "OR";
+ }
+ }
+
+ @HostListener("window:keyup", ["$event"])
+ private async handleKeyupEvent(event: KeyboardEvent) {
+ if (event.key === "Shift") {
+ this.mode = "AND";
+ }
+ }
+}
diff --git a/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.html b/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.html
new file mode 100644
index 0000000..2682840
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.html
@@ -0,0 +1,16 @@
+
+ {{expression.getDisplayName()}}
+
+
+
+
+
+ {{query.getNormalizedTag()}}
+
+
+
+
diff --git a/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.scss b/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.scss
new file mode 100644
index 0000000..4e0e0a9
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.scss
@@ -0,0 +1,16 @@
+.remove-button {
+ position: absolute;
+ top: calc(0.5em - 15px);
+ right: 0;
+}
+
+mat-list {
+ height: 100%;
+ width: 100%;
+}
+
+mat-list-item.or-filter-list-item {
+ padding: 0.5em 0;
+ height: 100%;
+ width: 100%;
+}
diff --git a/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.spec.ts b/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.spec.ts
new file mode 100644
index 0000000..c6a054a
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TagFilterListItemComponent } from './tag-filter-list-item.component';
+
+describe('TagFilterListItemComponent', () => {
+ let component: TagFilterListItemComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ TagFilterListItemComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TagFilterListItemComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.ts b/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.ts
new file mode 100644
index 0000000..7997d47
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component.ts
@@ -0,0 +1,18 @@
+import {Component, Input, OnInit} from '@angular/core';
+import {FilterExpression} from "../../../../models/FilterExpression";
+
+@Component({
+ selector: 'app-tag-filter-list-item',
+ templateUrl: './tag-filter-list-item.component.html',
+ styleUrls: ['./tag-filter-list-item.component.scss']
+})
+export class TagFilterListItemComponent {
+
+ @Input() expression!: FilterExpression;
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/mediarepo-ui/src/app/models/FilterExpression.ts b/mediarepo-ui/src/app/models/FilterExpression.ts
index 33ac090..391877d 100644
--- a/mediarepo-ui/src/app/models/FilterExpression.ts
+++ b/mediarepo-ui/src/app/models/FilterExpression.ts
@@ -9,6 +9,10 @@ export interface FilterExpression {
partiallyEq(value: any): boolean;
getDisplayName(): string;
+
+ clone(): FilterExpression;
+
+ queryList(): TagQuery[];
}
export class OrFilterExpression implements FilterExpression{
@@ -30,6 +34,15 @@ export class OrFilterExpression implements FilterExpression{
public getDisplayName(): string {
return this.filter.map(t => t.getNormalizedTag()).join(" OR ");
}
+
+ public clone(): OrFilterExpression {
+ let tags = this.filter.map((t: TagQuery) => new TagQuery(t.tag, t.negate));
+ return new OrFilterExpression(tags)
+ }
+
+ public queryList(): TagQuery[] {
+ return this.filter;
+ }
}
export class SingleFilterExpression implements FilterExpression {
@@ -51,4 +64,12 @@ export class SingleFilterExpression implements FilterExpression {
public getDisplayName(): string {
return this.filter.getNormalizedTag();
}
+
+ public clone(): FilterExpression {
+ return new SingleFilterExpression(new TagQuery(this.filter.tag, this.filter.negate))
+ }
+
+ public queryList(): TagQuery[] {
+ return [this.filter]
+ }
}
diff --git a/mediarepo-ui/src/app/models/TagQuery.ts b/mediarepo-ui/src/app/models/TagQuery.ts
index f10e907..493f5c7 100644
--- a/mediarepo-ui/src/app/models/TagQuery.ts
+++ b/mediarepo-ui/src/app/models/TagQuery.ts
@@ -1,7 +1,17 @@
+import {SingleFilterExpression} from "./FilterExpression";
+
export class TagQuery {
constructor(public tag: string, public negate: boolean) {
}
+ public static fromString(tag: string): TagQuery {
+ if (tag.startsWith("-")) {
+ return new TagQuery(tag.replace(/^-/g, ''), true);
+ } else {
+ return new TagQuery(tag, false);
+ }
+ }
+
public getNormalizedTag(): string {
return this.negate ? "-" + this.tag : this.tag;
}