From 01bf1bbabcdc7bcda506845d767af95b90b16d59 Mon Sep 17 00:00:00 2001 From: infacc Date: Mon, 19 Jun 2023 12:31:17 +0200 Subject: [PATCH 01/29] add plugin filter editor --- src/app/app.module.ts | 4 + .../plugin-filter-editor.component.html | 52 +++++++ .../plugin-filter-editor.component.sass | 16 ++ .../plugin-filter-editor.component.spec.ts | 23 +++ .../plugin-filter-editor.component.ts | 54 +++++++ .../plugin-filter-node.component.html | 60 +++++++ .../plugin-filter-node.component.sass | 51 ++++++ .../plugin-filter-node.component.spec.ts | 23 +++ .../plugin-filter-node.component.ts | 146 ++++++++++++++++++ .../template-details.component.html | 45 +----- .../template-details.component.ts | 11 +- 11 files changed, 436 insertions(+), 49 deletions(-) create mode 100644 src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html create mode 100644 src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.sass create mode 100644 src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.spec.ts create mode 100644 src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts create mode 100644 src/app/components-small/plugin-filter-node/plugin-filter-node.component.html create mode 100644 src/app/components-small/plugin-filter-node/plugin-filter-node.component.sass create mode 100644 src/app/components-small/plugin-filter-node/plugin-filter-node.component.spec.ts create mode 100644 src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 56c65f4..7fb620a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -82,6 +82,8 @@ import { ChangeUiTemplateComponent } from './dialogs/change-ui-template/change-u import { ReactiveFormsModule } from '@angular/forms'; import { TemplateDetailsComponent } from './components-small/template-details/template-details.component'; import { ExperimentWorkspaceDetailComponent } from './components/experiment-workspace-detail/experiment-workspace-detail.component'; +import { PluginFilterNodeComponent } from './components-small/plugin-filter-node/plugin-filter-node.component'; +import { PluginFilterEditorComponent } from './components-small/plugin-filter-editor/plugin-filter-editor.component'; @NgModule({ declarations: [ @@ -124,6 +126,8 @@ import { ExperimentWorkspaceDetailComponent } from './components/experiment-work TemplateDetailsComponent, ExperimentWorkspaceDetailComponent, ImportExperimentComponent, + PluginFilterNodeComponent, + PluginFilterEditorComponent, ], imports: [ BrowserModule, diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html new file mode 100644 index 0000000..5e8fb51 --- /dev/null +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html @@ -0,0 +1,52 @@ +
+

Filter Editor

+ +
+ Filter String Info + + Enter a filter string as JSON object. Filter strings have the following keys: +
+
name
Represents the name of a plugin.
+
tag
Allows filtering elements by their assigned tags.
+
version
Uses PEP 440 version specifier to filter elements based on specific versions or version ranges.
+
not
Specifies a filter string to exclude certain elements.
+
and
Includes multiple filter strings, with elements passing all conditions included in the filtered results (intersection).
+
or
Includes multiple filter strings, with elements meeting at least one condition included in the filtered results (union).
+
+ +

Examples:

+ + + + + + + + + +
+ + Filter String: + + + +
diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.sass b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.sass new file mode 100644 index 0000000..8dfa516 --- /dev/null +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.sass @@ -0,0 +1,16 @@ +.form-field + width: 100% + +.copy-button + position: absolute + top: 0 + right: 0 + +.filter-editor + display: flex + flex-direction: column + gap: 1rem + padding-bottom: 0px + +.editor-description + margin: 0 \ No newline at end of file diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.spec.ts b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.spec.ts new file mode 100644 index 0000000..74ce911 --- /dev/null +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PluginFilterEditorComponent } from './plugin-filter-editor.component'; + +describe('PluginFilterEditorComponent', () => { + let component: PluginFilterEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PluginFilterEditorComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PluginFilterEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts new file mode 100644 index 0000000..c3879c5 --- /dev/null +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts @@ -0,0 +1,54 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormControl, Validators } from '@angular/forms'; +import { ApiLink } from 'src/app/services/api-data-types'; +import { PluginRegistryBaseService } from 'src/app/services/registry.service'; +import { TemplateTabApiObject } from 'src/app/services/templates.service'; + +@Component({ + selector: 'qhana-plugin-filter-editor', + templateUrl: './plugin-filter-editor.component.html', + styleUrls: ['./plugin-filter-editor.component.sass'] +}) +export class PluginFilterEditorComponent implements OnInit { + @Input() tabLink: ApiLink | null = null; + + filterString: string = "{}"; + filterControl = new FormControl(this.filterString, [Validators.required, Validators.minLength(2)]); // TODO: Add validator for JSON + + filterObject: any = null; + + constructor(private registry: PluginRegistryBaseService) { } + + ngOnInit(): void { + if (this.tabLink == null) { + console.warn("No tab link provided to plugin filter node component"); + return; + } + this.registry.getByApiLink(this.tabLink).then(response => { + this.filterString = response?.data?.filterString ?? this.filterString; + this.filterObject = JSON.parse(this.filterString); + }); + } + + updateFilter(event: [number, any]) { + const [index, value] = event; + this.filterObject = value; + this.filterString = JSON.stringify(this.filterObject, null, 2); + this.filterControl.setValue(this.filterString); + } + + copyFilterString() { + navigator.clipboard.writeText(this.filterString); + } + + updateFilterEditor() { + const filterValue = this.filterControl.value; + if (filterValue == null) { + return; + } + if (this.filterControl.valid) { + this.filterObject = JSON.parse(filterValue); + this.filterString = filterValue; + } + } +} diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html new file mode 100644 index 0000000..d3989e7 --- /dev/null +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html @@ -0,0 +1,60 @@ + + +
+ + +
+ +
+ + AND + OR + +
+ + + +
+
+
+
+ + + + +
+ + Filter Type: + + Name + Tag + Version + + + + Filter String: + + + +
+ Invert +
+
diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.sass b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.sass new file mode 100644 index 0000000..67151f0 --- /dev/null +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.sass @@ -0,0 +1,51 @@ +.form-field + width: 100% + +.filter-node + border: 1px solid #7b7b7b + +.filter-node, .filter-node > mat-card-content + display: flex + flex-direction: column + justify-content: flex-start + gap: 10px + font-size: 12px + +.filter-leaf + padding: 8px 16px + +::ng-deep .mat-card-header-text + margin: 0px + +.config-header + width: 100% + display: flex + flex-direction: row + justify-content: space-between + +.config-header-buttons + display: flex + flex-direction: row + justify-content: flex-end + gap: 10px + +.config-header > mat-button-toggle-group + height: 32px + +.config-header > mat-button-toggle-group > mat-button-toggle + height: 32px + font-size: 12px + display: flex + align-items: center + +.config-filter + display: flex + flex-direction: row + justify-content: space-between + align-items: center + gap: 10px + font-size: 12px + +::ng-deep .config-filter > mat-form-field > .mat-form-field-wrapper + margin: 0px + padding: 0px diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.spec.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.spec.ts new file mode 100644 index 0000000..a2a4b2e --- /dev/null +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PluginFilterNodeComponent } from './plugin-filter-node.component'; + +describe('PluginFilterNodeComponent', () => { + let component: PluginFilterNodeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PluginFilterNodeComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PluginFilterNodeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts new file mode 100644 index 0000000..55ecebe --- /dev/null +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts @@ -0,0 +1,146 @@ +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; + +@Component({ + selector: 'qhana-plugin-filter-node', + templateUrl: './plugin-filter-node.component.html', + styleUrls: ['./plugin-filter-node.component.sass'] +}) +export class PluginFilterNodeComponent implements OnInit { + @Input() filterObject: any; + @Input() index: number = 0; + @Input() depth: number = 0; + @Output() childChange = new EventEmitter<[number, any]>(); + + type: 'and' | 'or' | 'name' | 'tag' | 'version' | null = null; + children: any[] | null = null; + value: string | null = null; + inverted: boolean = false; + + constructor() { } + + ngOnInit(): void { + this.setupFilter(); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.filterObject != null) { + this.setupFilter(); + } + } + + setupFilter() { + const currentValue = this.filterObject; + const filter = currentValue.not ?? this.filterObject; + this.inverted = currentValue.not != null; + if (filter == null) { + console.warn("No filter object provided to plugin filter node component"); + return; + } + const isLeaf = (filter.name != null) || filter.tag || filter.version; + if (isLeaf) { + this.type = filter.version ? 'version' : filter.tag ? 'tag' : 'name'; + this.value = filter.name ?? filter.tag ?? filter.version; + } else { + this.type = filter.and ? 'and' : 'or'; + this.children = filter.and ?? filter.or; + } + this.filterObject = currentValue; + this.childChange.emit([this.index, this.filterObject]); + } + + setFilter(type: 'name' | 'tag' | 'version' | 'and' | 'or') { + const isLeaf = type === 'name' || type === 'tag' || type === 'version'; + this.type = type; + this.children = isLeaf ? null : []; + this.value = isLeaf ? "" : null; + this.updateFilterObject(); + } + + deleteFilter() { + if (this.depth === 0) { + this.type = null; + this.children = null; + this.value = null; + this.inverted = false; + this.filterObject = {}; + } else { + this.filterObject = null; + } + this.childChange.emit([this.index, this.filterObject]); + } + + addFilter(type: 'name' | 'tag' | 'version' | 'and' | 'or' = 'name') { + if (this.children == null) { + console.warn("No children provided to plugin filter node component"); + return; + } + this.children.push({ + [type]: type === 'and' || type === 'or' ? [] : "" + }); + this.updateFilterObject(); + } + + updateFilterObject() { + if (this.type == null) { + console.warn("No type provided to plugin filter node component"); + return; + } + const filter = this.filterObject.not ?? this.filterObject; + filter[this.type] = this.children ?? this.value; + this.childChange.emit([this.index, this.filterObject]); + } + + updateChild(event: [number, any]) { + if (this.children == null) { + console.warn("No children provided to plugin filter node component"); + return; + } + const [index, value] = event; + if (value == null) { + this.children.splice(index, 1); + } else { + this.children[index] = value; + } + this.updateFilterObject(); + } + + changeType(type: 'name' | 'tag' | 'version' | 'and' | 'or') { + if (type == null) { + return; + } + const filter = this.filterObject.not ?? this.filterObject; + const isLeaf = type === 'name' || type === 'tag' || type === 'version'; + if (this.type != null) { + delete filter[this.type] + } + this.type = type; + if (isLeaf) { + this.children = null; + filter[type] = this.value ?? ""; + } else { + this.value = null; + filter[type] = this.children ?? []; + } + this.childChange.emit([this.index, this.filterObject]); + } + + updateFilterString(event: any) { + this.value = event.target.value; + this.updateFilterObject(); + } + + invertFilter() { + this.inverted = !this.inverted; + if (this.inverted) { + this.filterObject = { not: this.filterObject }; + } else { + this.filterObject = this.filterObject.not; + } + this.childChange.emit([this.index, this.filterObject]); + } + + isFilterEmpty(): boolean { + const filter = this.filterObject.not ?? this.filterObject; + return Object.keys(filter).length === 0; + } +} diff --git a/src/app/components-small/template-details/template-details.component.html b/src/app/components-small/template-details/template-details.component.html index 1ea9d40..7d7fc05 100644 --- a/src/app/components-small/template-details/template-details.component.html +++ b/src/app/components-small/template-details/template-details.component.html @@ -20,50 +20,7 @@

Template Tab

Sort Key: - - Filter String: - - -
- Filter String Info - - Enter a filter string as JSON object. Filter strings have the following keys: -
-
name
Represents the name of a plugin.
-
tag
Allows filtering elements by their assigned tags.
-
version
Uses PEP 440 version specifier to filter elements based on specific versions or version ranges.
-
not
Specifies a filter string to exclude certain elements.
-
and
Includes multiple filter strings, with elements passing all conditions included in the filtered results (intersection).
-
or
Includes multiple filter strings, with elements meeting at least one condition included in the filtered results (union).
-
- -

Examples:

- - - - - - - - - -
+
\ No newline at end of file diff --git a/src/app/components-small/template-details/template-details.component.ts b/src/app/components-small/template-details/template-details.component.ts index 7464e8e..e8e1253 100644 --- a/src/app/components-small/template-details/template-details.component.ts +++ b/src/app/components-small/template-details/template-details.component.ts @@ -1,8 +1,9 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { ApiLink, ApiResponse } from 'src/app/services/api-data-types'; import { PluginRegistryBaseService } from 'src/app/services/registry.service'; import { TemplateApiObject, TemplateTabApiObject } from 'src/app/services/templates.service'; +import { PluginFilterEditorComponent } from '../plugin-filter-editor/plugin-filter-editor.component'; export function isInSetValidator(validValues: any[]): Validators { return (control: FormControl): { [key: string]: any } | null => { @@ -28,6 +29,8 @@ export class TemplateDetailsComponent implements OnInit { @Input() templateLink: ApiLink | null = null; @Input() tabLink: ApiLink | null = null; + @ViewChild("filterEditor") filterEditor: PluginFilterEditorComponent | null = null; + locations: Location[] = [ { value: "workspace", description: "Workspace (appears in the plugin sidebar)" }, { value: "experiment-navigation", description: "Experiment Navigation (appears in the top navigation bar)" } @@ -37,7 +40,6 @@ export class TemplateDetailsComponent implements OnInit { name: "", description: "", sortKey: 0, - filterString: "{}", location: this.locations[0].value }; @@ -45,7 +47,6 @@ export class TemplateDetailsComponent implements OnInit { name: [this.initialValues.name, Validators.required], description: this.initialValues.description, sortKey: this.initialValues.sortKey, - filterString: [this.initialValues.filterString, Validators.minLength(2)], // TODO: validate using JSON schema location: [this.initialValues.location, [Validators.required, isInSetValidator(this.locations.map(location => location.value))]] }); @@ -58,7 +59,6 @@ export class TemplateDetailsComponent implements OnInit { name: response?.data?.name ?? this.initialValues.name, description: response?.data?.description ?? this.initialValues.description, sortKey: response?.data?.sortKey ?? this.initialValues.sortKey, - filterString: response?.data?.filterString ?? this.initialValues.filterString, location: response?.data?.location ?? this.initialValues.location }); }); @@ -78,13 +78,14 @@ export class TemplateDetailsComponent implements OnInit { console.warn("TemplateDetailsComponent: neither templateLink nor tabLink is set"); return; } + const filterString = this.filterEditor?.filterString ?? "{}"; const link = response?.links?.find(link => link.rel.some(rel => rel === findString) && link.resourceType == "ui-template-tab") ?? null; if (link != null) { this.registry.submitByApiLink(link, { name: this.templateForm.value.name, description: this.templateForm.value.description, sortKey: this.templateForm.value.sortKey, - filterString: this.templateForm.value.filterString, + filterString: filterString, location: this.templateForm.value.location }); if (this.templateLink != null) { From e55a407c7445cb3afa4810077ec70171825300f1 Mon Sep 17 00:00:00 2001 From: infacc Date: Mon, 3 Jul 2023 12:21:42 +0200 Subject: [PATCH 02/29] show plugin editor when no tab link is provided --- .../plugin-filter-editor/plugin-filter-editor.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts index c3879c5..5ca2aac 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts @@ -15,13 +15,12 @@ export class PluginFilterEditorComponent implements OnInit { filterString: string = "{}"; filterControl = new FormControl(this.filterString, [Validators.required, Validators.minLength(2)]); // TODO: Add validator for JSON - filterObject: any = null; + filterObject: any = {}; constructor(private registry: PluginRegistryBaseService) { } ngOnInit(): void { if (this.tabLink == null) { - console.warn("No tab link provided to plugin filter node component"); return; } this.registry.getByApiLink(this.tabLink).then(response => { From 8b1fe43d4fb1733e6dcb7cf0443394f46f8ed551 Mon Sep 17 00:00:00 2001 From: infacc Date: Tue, 4 Jul 2023 15:52:55 +0200 Subject: [PATCH 03/29] hide mat-card if filter is empty --- .../plugin-filter-node.component.html | 118 +++++++++--------- .../plugin-filter-node.component.sass | 6 + 2 files changed, 66 insertions(+), 58 deletions(-) diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html index d3989e7..f630a11 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html @@ -1,60 +1,62 @@ - - -
- - -
- -
- - AND - OR - -
- - - +
+
+ + +
+ + + +
+ + AND + OR + +
+ + + +
+
+
+ + + + +
+ + Filter Type: + + Name + Tag + Version + + + + Filter String: + + +
- - - - - - -
- - Filter Type: - - Name - Tag - Version - - - - Filter String: - - - -
- Invert -
-
+ Invert + + +
\ No newline at end of file diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.sass b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.sass index 67151f0..5948034 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.sass +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.sass @@ -1,3 +1,9 @@ +.create-buttons + display: flex + flex-direction: row + justify-content: flex-start + gap: 10px + .form-field width: 100% From 5616886cc6ea4f5e1cf1909f4cd55f6f3b6875a6 Mon Sep 17 00:00:00 2001 From: infacc Date: Tue, 11 Jul 2023 10:14:30 +0200 Subject: [PATCH 04/29] refactor: improve filter editor usability --- src/app/app.module.ts | 2 + .../plugin-filter-editor.component.html | 77 ++++++++++--------- .../plugin-filter-editor.component.ts | 2 + .../plugin-filter-node.component.html | 19 +++-- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7fb620a..269c5e8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -20,6 +20,7 @@ import { FormsModule } from '@angular/forms'; import { MatBadgeModule } from '@angular/material/badge'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatChipsModule } from '@angular/material/chips'; @@ -140,6 +141,7 @@ import { PluginFilterEditorComponent } from './components-small/plugin-filter-ed MatCardModule, MatButtonModule, MatButtonToggleModule, + MatSlideToggleModule, MatCommonModule, MatTabsModule, MatRippleModule, diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html index 5e8fb51..8a2653e 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html @@ -1,52 +1,57 @@

Filter Editor

- -
- Filter String Info - - Enter a filter string as JSON object. Filter strings have the following keys: -
-
name
Represents the name of a plugin.
-
tag
Allows filtering elements by their assigned tags.
-
version
Uses PEP 440 version specifier to filter elements based on specific versions or version ranges.
-
not
Specifies a filter string to exclude certain elements.
-
and
Includes multiple filter strings, with elements passing all conditions included in the filtered results (intersection).
-
or
Includes multiple filter strings, with elements meeting at least one condition included in the filtered results (union).
-
- -

Examples:

+ + Editor Mode {{ showEditor ? '(UI)' : '(JSON)' }} + + + - + + +
+ Filter String Info + + Enter a filter string as JSON object. Filter strings have the following keys: +
+
name
Represents the name of a plugin.
+
tag
Allows filtering elements by their assigned tags.
+
version
Uses PEP 440 version specifier to filter elements based on specific versions or version ranges.
+
not
Specifies a filter string to exclude certain elements.
+
and
Includes multiple filter strings, with elements passing all conditions included in the filtered results (intersection).
+
or
Includes multiple filter strings, with elements meeting at least one condition included in the filtered results (union).
+
+ +

Examples:

+ + - - - + + + - - - + + + - -
- - Filter String: - - - + + +
+
diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts index 5ca2aac..73438cb 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts @@ -17,6 +17,8 @@ export class PluginFilterEditorComponent implements OnInit { filterObject: any = {}; + showEditor: boolean = true; + constructor(private registry: PluginRegistryBaseService) { } ngOnInit(): void { diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html index f630a11..88dc7c5 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html @@ -2,7 +2,11 @@
+ + -
@@ -39,6 +47,7 @@ [depth]="depth + 1" (childChange)="updateChild($event)">
+

Not:

Filter Type: @@ -52,11 +61,11 @@ -
- Invert + Invert - Negate this filter
\ No newline at end of file From 76d7744e41e764d0d7f0f46050d219536f1b285a Mon Sep 17 00:00:00 2001 From: infacc Date: Wed, 12 Jul 2023 13:46:34 +0200 Subject: [PATCH 05/29] refactor: avoid ViewChild decorator in filter editor --- .../plugin-filter-editor.component.ts | 4 +++- .../template-details/template-details.component.html | 2 +- .../template-details/template-details.component.ts | 11 +++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts index 73438cb..56891a2 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { FormControl, Validators } from '@angular/forms'; import { ApiLink } from 'src/app/services/api-data-types'; import { PluginRegistryBaseService } from 'src/app/services/registry.service'; @@ -11,6 +11,7 @@ import { TemplateTabApiObject } from 'src/app/services/templates.service'; }) export class PluginFilterEditorComponent implements OnInit { @Input() tabLink: ApiLink | null = null; + @Output() filterEmitter: EventEmitter = new EventEmitter(); filterString: string = "{}"; filterControl = new FormControl(this.filterString, [Validators.required, Validators.minLength(2)]); // TODO: Add validator for JSON @@ -36,6 +37,7 @@ export class PluginFilterEditorComponent implements OnInit { this.filterObject = value; this.filterString = JSON.stringify(this.filterObject, null, 2); this.filterControl.setValue(this.filterString); + this.filterEmitter.emit(this.filterString); } copyFilterString() { diff --git a/src/app/components-small/template-details/template-details.component.html b/src/app/components-small/template-details/template-details.component.html index 7d7fc05..33561a9 100644 --- a/src/app/components-small/template-details/template-details.component.html +++ b/src/app/components-small/template-details/template-details.component.html @@ -20,7 +20,7 @@

Template Tab

Sort Key: - +
\ No newline at end of file diff --git a/src/app/components-small/template-details/template-details.component.ts b/src/app/components-small/template-details/template-details.component.ts index e8e1253..c27deca 100644 --- a/src/app/components-small/template-details/template-details.component.ts +++ b/src/app/components-small/template-details/template-details.component.ts @@ -1,9 +1,8 @@ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { ApiLink, ApiResponse } from 'src/app/services/api-data-types'; import { PluginRegistryBaseService } from 'src/app/services/registry.service'; import { TemplateApiObject, TemplateTabApiObject } from 'src/app/services/templates.service'; -import { PluginFilterEditorComponent } from '../plugin-filter-editor/plugin-filter-editor.component'; export function isInSetValidator(validValues: any[]): Validators { return (control: FormControl): { [key: string]: any } | null => { @@ -29,7 +28,7 @@ export class TemplateDetailsComponent implements OnInit { @Input() templateLink: ApiLink | null = null; @Input() tabLink: ApiLink | null = null; - @ViewChild("filterEditor") filterEditor: PluginFilterEditorComponent | null = null; + filterString: string = "{}"; locations: Location[] = [ { value: "workspace", description: "Workspace (appears in the plugin sidebar)" }, @@ -78,7 +77,7 @@ export class TemplateDetailsComponent implements OnInit { console.warn("TemplateDetailsComponent: neither templateLink nor tabLink is set"); return; } - const filterString = this.filterEditor?.filterString ?? "{}"; + const filterString = this.filterString; const link = response?.links?.find(link => link.rel.some(rel => rel === findString) && link.resourceType == "ui-template-tab") ?? null; if (link != null) { this.registry.submitByApiLink(link, { @@ -94,4 +93,8 @@ export class TemplateDetailsComponent implements OnInit { } } } + + onFilterChange(filterString: string) { + this.filterString = filterString; + } } From 4e2edb34b1e5c1264772b9793d39b220647761f9 Mon Sep 17 00:00:00 2001 From: infacc Date: Wed, 12 Jul 2023 13:53:51 +0200 Subject: [PATCH 06/29] simplify isLeaf check --- .../plugin-filter-node/plugin-filter-node.component.html | 2 +- .../plugin-filter-node/plugin-filter-node.component.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html index 88dc7c5..0d4accd 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html @@ -13,7 +13,7 @@ Filter
- +
diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts index 55ecebe..f4f9ccd 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts @@ -36,7 +36,7 @@ export class PluginFilterNodeComponent implements OnInit { console.warn("No filter object provided to plugin filter node component"); return; } - const isLeaf = (filter.name != null) || filter.tag || filter.version; + const isLeaf = filter.and == null && filter.or == null; if (isLeaf) { this.type = filter.version ? 'version' : filter.tag ? 'tag' : 'name'; this.value = filter.name ?? filter.tag ?? filter.version; @@ -49,7 +49,7 @@ export class PluginFilterNodeComponent implements OnInit { } setFilter(type: 'name' | 'tag' | 'version' | 'and' | 'or') { - const isLeaf = type === 'name' || type === 'tag' || type === 'version'; + const isLeaf = type !== 'and' && type !== 'or'; this.type = type; this.children = isLeaf ? null : []; this.value = isLeaf ? "" : null; @@ -109,7 +109,7 @@ export class PluginFilterNodeComponent implements OnInit { return; } const filter = this.filterObject.not ?? this.filterObject; - const isLeaf = type === 'name' || type === 'tag' || type === 'version'; + const isLeaf = type !== 'and' && type !== 'or'; if (this.type != null) { delete filter[this.type] } From 38879c2e58e0ebe021af04d3429332651e7b6bdc Mon Sep 17 00:00:00 2001 From: infacc <33042539+infacc@users.noreply.github.com> Date: Wed, 12 Jul 2023 14:20:23 +0200 Subject: [PATCH 07/29] rectify warning message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fabian Bühler --- .../plugin-filter-node/plugin-filter-node.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts index 55ecebe..28b6f32 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts @@ -71,7 +71,7 @@ export class PluginFilterNodeComponent implements OnInit { addFilter(type: 'name' | 'tag' | 'version' | 'and' | 'or' = 'name') { if (this.children == null) { - console.warn("No children provided to plugin filter node component"); + console.warn("Cannot add child filter because the plugin filter node component is a leaf node!"); return; } this.children.push({ From e71700b05d786576978849afbb1e55f0355f9216 Mon Sep 17 00:00:00 2001 From: infacc Date: Wed, 12 Jul 2023 14:24:12 +0200 Subject: [PATCH 08/29] refactor: simplify filter setup --- .../plugin-filter-node.component.ts | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts index f4f9ccd..6c95ce7 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts @@ -1,5 +1,7 @@ import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +type FilterType = 'and' | 'or' | 'name' | 'tag' | 'version'; + @Component({ selector: 'qhana-plugin-filter-node', templateUrl: './plugin-filter-node.component.html', @@ -11,7 +13,7 @@ export class PluginFilterNodeComponent implements OnInit { @Input() depth: number = 0; @Output() childChange = new EventEmitter<[number, any]>(); - type: 'and' | 'or' | 'name' | 'tag' | 'version' | null = null; + type: FilterType | null = null; children: any[] | null = null; value: string | null = null; inverted: boolean = false; @@ -36,19 +38,24 @@ export class PluginFilterNodeComponent implements OnInit { console.warn("No filter object provided to plugin filter node component"); return; } + // Should filters have multiple attributes at some point, this must be changed + const type = Object.keys(filter)[0]; + if (type !== 'and' && type !== 'or' && type !== 'name' && type !== 'tag' && type !== 'version') { + console.warn("Invalid filter type provided to plugin filter node component"); + return; + } + this.type = type as FilterType; const isLeaf = filter.and == null && filter.or == null; if (isLeaf) { - this.type = filter.version ? 'version' : filter.tag ? 'tag' : 'name'; - this.value = filter.name ?? filter.tag ?? filter.version; + this.value = filter[this.type]; } else { - this.type = filter.and ? 'and' : 'or'; - this.children = filter.and ?? filter.or; + this.children = filter[this.type]; } this.filterObject = currentValue; this.childChange.emit([this.index, this.filterObject]); } - setFilter(type: 'name' | 'tag' | 'version' | 'and' | 'or') { + setFilter(type: FilterType) { const isLeaf = type !== 'and' && type !== 'or'; this.type = type; this.children = isLeaf ? null : []; @@ -69,7 +76,7 @@ export class PluginFilterNodeComponent implements OnInit { this.childChange.emit([this.index, this.filterObject]); } - addFilter(type: 'name' | 'tag' | 'version' | 'and' | 'or' = 'name') { + addFilter(type: FilterType = 'name') { if (this.children == null) { console.warn("No children provided to plugin filter node component"); return; @@ -104,7 +111,7 @@ export class PluginFilterNodeComponent implements OnInit { this.updateFilterObject(); } - changeType(type: 'name' | 'tag' | 'version' | 'and' | 'or') { + changeType(type: FilterType) { if (type == null) { return; } From fae56c082ee9c2650d08ce854acd61bbb373d4f0 Mon Sep 17 00:00:00 2001 From: infacc Date: Thu, 13 Jul 2023 13:16:26 +0200 Subject: [PATCH 09/29] handle filter deletion by parent --- .../plugin-filter-editor.component.html | 2 +- .../plugin-filter-editor.component.ts | 6 ++++++ .../plugin-filter-node.component.html | 6 +++--- .../plugin-filter-node.component.ts | 17 +++++++---------- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html index 8a2653e..5eb2532 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html @@ -3,7 +3,7 @@

Filter Editor

Editor Mode {{ showEditor ? '(UI)' : '(JSON)' }} - + Filter String: diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts index 56891a2..8e889b3 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts @@ -54,4 +54,10 @@ export class PluginFilterEditorComponent implements OnInit { this.filterString = filterValue; } } + + deleteFilter(index: number) { + this.filterString = "{}"; + this.filterControl.setValue(this.filterString); + this.updateFilterEditor(); + } } diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html index 0d4accd..c202c7a 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html @@ -34,7 +34,7 @@ add Filter -
@@ -44,7 +44,7 @@ + [depth]="depth + 1" (childChange)="updateChild($event)" (delete)="deleteChild($event)">

Not:

@@ -61,7 +61,7 @@

Not:

-
diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts index a41f993..24260d4 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts @@ -12,6 +12,7 @@ export class PluginFilterNodeComponent implements OnInit { @Input() index: number = 0; @Input() depth: number = 0; @Output() childChange = new EventEmitter<[number, any]>(); + @Output() delete = new EventEmitter(); type: FilterType | null = null; children: any[] | null = null; @@ -63,17 +64,13 @@ export class PluginFilterNodeComponent implements OnInit { this.updateFilterObject(); } - deleteFilter() { - if (this.depth === 0) { - this.type = null; - this.children = null; - this.value = null; - this.inverted = false; - this.filterObject = {}; - } else { - this.filterObject = null; + deleteChild(index: number) { + if (this.children == null) { + console.warn("Cannot delete child filter: Filter has no children!"); + return; } - this.childChange.emit([this.index, this.filterObject]); + this.children.splice(index, 1); + this.updateFilterObject(); } addFilter(type: FilterType = 'name') { From 4ceb6d4bbb8d986ad11c093b4e7f71954a2600fe Mon Sep 17 00:00:00 2001 From: infacc Date: Thu, 13 Jul 2023 13:28:00 +0200 Subject: [PATCH 10/29] catch JSON paring errors --- .../plugin-filter-editor.component.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts index 8e889b3..e0d853e 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts @@ -50,8 +50,12 @@ export class PluginFilterEditorComponent implements OnInit { return; } if (this.filterControl.valid) { - this.filterObject = JSON.parse(filterValue); - this.filterString = filterValue; + try { + this.filterObject = JSON.parse(filterValue); + this.filterString = filterValue; + } catch (e) { + console.warn("Invalid filter string", this.filterObject, "\nError:", e); + } } } From 183ced2f88ed8df326527410660c12290bea7c81 Mon Sep 17 00:00:00 2001 From: infacc Date: Thu, 13 Jul 2023 13:29:10 +0200 Subject: [PATCH 11/29] separate ternary operator to improve readability --- .../plugin-filter-node/plugin-filter-node.component.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts index 24260d4..8b7d482 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts @@ -78,9 +78,8 @@ export class PluginFilterNodeComponent implements OnInit { console.warn("Cannot add child filter because the plugin filter node component is a leaf node!"); return; } - this.children.push({ - [type]: type === 'and' || type === 'or' ? [] : "" - }); + const filterValue = (type === 'and' || type === 'or') ? [] : ""; + this.children.push({ [type]: filterValue }); this.updateFilterObject(); } From a1fe552b5eb6fc13d09472f4afbbf544c0073029 Mon Sep 17 00:00:00 2001 From: infacc Date: Thu, 13 Jul 2023 13:38:16 +0200 Subject: [PATCH 12/29] add inverted indication for and/or filters --- .../plugin-filter-node/plugin-filter-node.component.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html index c202c7a..ab2b594 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html @@ -43,11 +43,12 @@
+ Not:
-

Not:

+ Not: Filter Type: From 05d026bfbee86c578806409c29b31d6da17d264e Mon Sep 17 00:00:00 2001 From: infacc Date: Mon, 17 Jul 2023 13:19:02 +0200 Subject: [PATCH 13/29] remove index property child components should not know their index in a list of their parent --- .../plugin-filter-editor.component.html | 2 +- .../plugin-filter-editor.component.ts | 5 ++--- .../plugin-filter-node.component.html | 8 ++++---- .../plugin-filter-node.component.ts | 19 ++++++++++--------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html index 5eb2532..d712578 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html @@ -3,7 +3,7 @@

Filter Editor

Editor Mode {{ showEditor ? '(UI)' : '(JSON)' }} - + Filter String: diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts index e0d853e..0aae1f4 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts @@ -32,8 +32,7 @@ export class PluginFilterEditorComponent implements OnInit { }); } - updateFilter(event: [number, any]) { - const [index, value] = event; + updateFilter(value: any) { this.filterObject = value; this.filterString = JSON.stringify(this.filterObject, null, 2); this.filterControl.setValue(this.filterString); @@ -59,7 +58,7 @@ export class PluginFilterEditorComponent implements OnInit { } } - deleteFilter(index: number) { + deleteFilter() { this.filterString = "{}"; this.filterControl.setValue(this.filterString); this.updateFilterEditor(); diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html index ab2b594..68ac1de 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html @@ -34,7 +34,7 @@ add Filter -
@@ -44,8 +44,8 @@ Not: - +
Not: @@ -62,7 +62,7 @@ -
diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts index 8b7d482..dc2e6a9 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts @@ -9,10 +9,9 @@ type FilterType = 'and' | 'or' | 'name' | 'tag' | 'version'; }) export class PluginFilterNodeComponent implements OnInit { @Input() filterObject: any; - @Input() index: number = 0; @Input() depth: number = 0; - @Output() childChange = new EventEmitter<[number, any]>(); - @Output() delete = new EventEmitter(); + @Output() childChange = new EventEmitter(); + @Output() delete = new EventEmitter(); type: FilterType | null = null; children: any[] | null = null; @@ -32,6 +31,9 @@ export class PluginFilterNodeComponent implements OnInit { } setupFilter() { + if (Object.keys(this.filterObject).length === 0) { + return; + } const currentValue = this.filterObject; const filter = currentValue.not ?? this.filterObject; this.inverted = currentValue.not != null; @@ -53,7 +55,7 @@ export class PluginFilterNodeComponent implements OnInit { this.children = filter[this.type]; } this.filterObject = currentValue; - this.childChange.emit([this.index, this.filterObject]); + this.childChange.emit(this.filterObject); } setFilter(type: FilterType) { @@ -90,15 +92,14 @@ export class PluginFilterNodeComponent implements OnInit { } const filter = this.filterObject.not ?? this.filterObject; filter[this.type] = this.children ?? this.value; - this.childChange.emit([this.index, this.filterObject]); + this.childChange.emit(this.filterObject); } - updateChild(event: [number, any]) { + updateChild(value: any, index: number) { if (this.children == null) { console.warn("No children provided to plugin filter node component"); return; } - const [index, value] = event; if (value == null) { this.children.splice(index, 1); } else { @@ -124,7 +125,7 @@ export class PluginFilterNodeComponent implements OnInit { this.value = null; filter[type] = this.children ?? []; } - this.childChange.emit([this.index, this.filterObject]); + this.childChange.emit(this.filterObject); } updateFilterString(event: any) { @@ -139,7 +140,7 @@ export class PluginFilterNodeComponent implements OnInit { } else { this.filterObject = this.filterObject.not; } - this.childChange.emit([this.index, this.filterObject]); + this.childChange.emit(this.filterObject); } isFilterEmpty(): boolean { From f62ff31d8192d19e0d419c911ade533ecc0144b4 Mon Sep 17 00:00:00 2001 From: infacc Date: Mon, 17 Jul 2023 13:25:37 +0200 Subject: [PATCH 14/29] store info whether filter is empty in property --- .../plugin-filter-node.component.html | 4 ++-- .../plugin-filter-node/plugin-filter-node.component.ts | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html index 68ac1de..9fdb165 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html @@ -1,5 +1,5 @@
-
+
- +
diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts index dc2e6a9..45939a6 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts @@ -17,6 +17,7 @@ export class PluginFilterNodeComponent implements OnInit { children: any[] | null = null; value: string | null = null; inverted: boolean = false; + isEmpty: boolean = true; constructor() { } @@ -31,7 +32,8 @@ export class PluginFilterNodeComponent implements OnInit { } setupFilter() { - if (Object.keys(this.filterObject).length === 0) { + this.isEmpty = Object.keys(this.filterObject).length === 0; + if (this.isEmpty) { return; } const currentValue = this.filterObject; @@ -64,6 +66,7 @@ export class PluginFilterNodeComponent implements OnInit { this.children = isLeaf ? null : []; this.value = isLeaf ? "" : null; this.updateFilterObject(); + this.isEmpty = false; } deleteChild(index: number) { @@ -142,9 +145,4 @@ export class PluginFilterNodeComponent implements OnInit { } this.childChange.emit(this.filterObject); } - - isFilterEmpty(): boolean { - const filter = this.filterObject.not ?? this.filterObject; - return Object.keys(filter).length === 0; - } } From 334189a630013be3d11c9d9f1e2ac31f54f85f3c Mon Sep 17 00:00:00 2001 From: infacc Date: Tue, 18 Jul 2023 11:47:51 +0200 Subject: [PATCH 15/29] refactor: separate input and output objects --- .../plugin-filter-editor.component.html | 2 +- .../plugin-filter-editor.component.ts | 1 + .../plugin-filter-node.component.html | 6 +- .../plugin-filter-node.component.ts | 57 ++++++++++--------- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html index d712578..b364d21 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html @@ -3,7 +3,7 @@

Filter Editor

Editor Mode {{ showEditor ? '(UI)' : '(JSON)' }} - + Filter String: diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts index 0aae1f4..9f329dc 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts @@ -29,6 +29,7 @@ export class PluginFilterEditorComponent implements OnInit { this.registry.getByApiLink(this.tabLink).then(response => { this.filterString = response?.data?.filterString ?? this.filterString; this.filterObject = JSON.parse(this.filterString); + this.updateFilter(this.filterObject); }); } diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html index 9fdb165..ef64aa9 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html @@ -44,8 +44,8 @@ Not: - +
Not: @@ -66,7 +66,7 @@ delete
- Invert - Negate this filter + Invert - Negate this filter
\ No newline at end of file diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts index 45939a6..16ced66 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts @@ -8,11 +8,12 @@ type FilterType = 'and' | 'or' | 'name' | 'tag' | 'version'; styleUrls: ['./plugin-filter-node.component.sass'] }) export class PluginFilterNodeComponent implements OnInit { - @Input() filterObject: any; + @Input() filterIn: any; @Input() depth: number = 0; - @Output() childChange = new EventEmitter(); + @Output() filterOut = new EventEmitter(); @Output() delete = new EventEmitter(); + filterObject: any = {}; type: FilterType | null = null; children: any[] | null = null; value: string | null = null; @@ -26,25 +27,23 @@ export class PluginFilterNodeComponent implements OnInit { } ngOnChanges(changes: SimpleChanges) { - if (changes.filterObject != null) { + if (changes.filterIn.previousValue != null) { this.setupFilter(); } } setupFilter() { - this.isEmpty = Object.keys(this.filterObject).length === 0; - if (this.isEmpty) { - return; - } - const currentValue = this.filterObject; - const filter = currentValue.not ?? this.filterObject; - this.inverted = currentValue.not != null; + this.isEmpty = Object.keys(this.filterIn).length === 0; + this.filterObject = { ...this.filterIn }; + const filter = this.filterObject.not ?? this.filterObject; + this.inverted = this.filterObject.not != null; if (filter == null) { console.warn("No filter object provided to plugin filter node component"); return; } // Should filters have multiple attributes at some point, this must be changed const type = Object.keys(filter)[0]; + // TODO: check for empty filter (currently prints warning) if (type !== 'and' && type !== 'or' && type !== 'name' && type !== 'tag' && type !== 'version') { console.warn("Invalid filter type provided to plugin filter node component"); return; @@ -56,8 +55,20 @@ export class PluginFilterNodeComponent implements OnInit { } else { this.children = filter[this.type]; } - this.filterObject = currentValue; - this.childChange.emit(this.filterObject); + } + + updateFilterObject() { + if (this.type == null) { + console.warn("No type provided to plugin filter node component"); + return; + } + const filter = { ...this.filterObject }; + if (this.inverted) { + filter.not[this.type] = this.children ?? this.value; + } else { + filter[this.type] = this.children ?? this.value; + } + this.filterOut.emit(filter); } setFilter(type: FilterType) { @@ -88,16 +99,6 @@ export class PluginFilterNodeComponent implements OnInit { this.updateFilterObject(); } - updateFilterObject() { - if (this.type == null) { - console.warn("No type provided to plugin filter node component"); - return; - } - const filter = this.filterObject.not ?? this.filterObject; - filter[this.type] = this.children ?? this.value; - this.childChange.emit(this.filterObject); - } - updateChild(value: any, index: number) { if (this.children == null) { console.warn("No children provided to plugin filter node component"); @@ -112,10 +113,10 @@ export class PluginFilterNodeComponent implements OnInit { } changeType(type: FilterType) { - if (type == null) { + if (type == null || this.type === type) { return; } - const filter = this.filterObject.not ?? this.filterObject; + const filter = { ...(this.filterObject.not ?? this.filterObject) }; const isLeaf = type !== 'and' && type !== 'or'; if (this.type != null) { delete filter[this.type] @@ -128,7 +129,7 @@ export class PluginFilterNodeComponent implements OnInit { this.value = null; filter[type] = this.children ?? []; } - this.childChange.emit(this.filterObject); + this.filterOut.emit(filter); } updateFilterString(event: any) { @@ -136,13 +137,13 @@ export class PluginFilterNodeComponent implements OnInit { this.updateFilterObject(); } - invertFilter() { - this.inverted = !this.inverted; + invertFilter(event: any) { + this.inverted = event.checked; if (this.inverted) { this.filterObject = { not: this.filterObject }; } else { this.filterObject = this.filterObject.not; } - this.childChange.emit(this.filterObject); + this.filterOut.emit({ ...this.filterObject }); } } From c0ee392940253b5f1b1afc7960e31335fde6753c Mon Sep 17 00:00:00 2001 From: infacc Date: Tue, 18 Jul 2023 11:52:14 +0200 Subject: [PATCH 16/29] check for empty filter --- .../plugin-filter-node/plugin-filter-node.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts index 16ced66..96ab385 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts @@ -41,9 +41,11 @@ export class PluginFilterNodeComponent implements OnInit { console.warn("No filter object provided to plugin filter node component"); return; } + if (this.isEmpty) { + return; + } // Should filters have multiple attributes at some point, this must be changed const type = Object.keys(filter)[0]; - // TODO: check for empty filter (currently prints warning) if (type !== 'and' && type !== 'or' && type !== 'name' && type !== 'tag' && type !== 'version') { console.warn("Invalid filter type provided to plugin filter node component"); return; From 51171f82d926e401d69a408f2a1418ae8601d95a Mon Sep 17 00:00:00 2001 From: infacc Date: Tue, 18 Jul 2023 12:01:35 +0200 Subject: [PATCH 17/29] fix bug: keep input focus when typing --- .../plugin-filter-node/plugin-filter-node.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html index ef64aa9..47b6616 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html @@ -59,7 +59,7 @@ Filter String: - \ No newline at end of file diff --git a/src/app/components-small/template-details/template-details.component.ts b/src/app/components-small/template-details/template-details.component.ts index c27deca..0f1af52 100644 --- a/src/app/components-small/template-details/template-details.component.ts +++ b/src/app/components-small/template-details/template-details.component.ts @@ -93,8 +93,4 @@ export class TemplateDetailsComponent implements OnInit { } } } - - onFilterChange(filterString: string) { - this.filterString = filterString; - } } From ec041cff913d6bd2ae65996e130113a94043924d Mon Sep 17 00:00:00 2001 From: infacc Date: Tue, 18 Jul 2023 12:37:43 +0200 Subject: [PATCH 21/29] refactor: simplify type check for filter keys --- .../plugin-filter-node.component.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts index a6e4a35..a00d748 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.ts @@ -1,6 +1,11 @@ import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; -type FilterType = 'and' | 'or' | 'name' | 'tag' | 'version'; +// Define filter types ('not' excluded) +// The PluginFilterNodeComponent component is designed to encapsulate a filter object and the information wether the filter is inverted ('not'). +// When the filter is inverted, the filter object is wrapped in a 'not' object and the 'inverted' property is set to true. +const filterTypes = ['and', 'or', 'name', 'tag', 'version'] as const; +type FilterType = (typeof filterTypes)[number]; +const isFilterType = (x: any): x is FilterType => filterTypes.includes(x); @Component({ selector: 'qhana-plugin-filter-node', @@ -46,11 +51,11 @@ export class PluginFilterNodeComponent implements OnInit { } const filterKeys = Object.keys(filter) if (filterKeys.length != 1) { - console.error("Filters with more than one attribute are not supported!"); + console.error("Filters with more than one attribute are not supported! ", filterKeys); } const type = filterKeys[0]; - if (type !== 'and' && type !== 'or' && type !== 'name' && type !== 'tag' && type !== 'version') { - console.warn("Invalid filter type provided to plugin filter node component"); + if (!isFilterType(type)) { + console.warn("Invalid filter type provided to plugin filter node component: ", type); return; } this.type = type as FilterType; From b8b6cf6dce4aec46f966776860f5ac3ff9b317ff Mon Sep 17 00:00:00 2001 From: infacc Date: Tue, 18 Jul 2023 19:01:38 +0200 Subject: [PATCH 22/29] rename HTML class to avoid confusion --- .../plugin-filter-node/plugin-filter-node.component.html | 2 +- .../plugin-filter-node/plugin-filter-node.component.sass | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html index 47b6616..88c1d46 100644 --- a/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html +++ b/src/app/components-small/plugin-filter-node/plugin-filter-node.component.html @@ -1,5 +1,5 @@
-
+
+
diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.sass b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.sass index 8dfa516..8540462 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.sass +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.sass @@ -13,4 +13,9 @@ padding-bottom: 0px .editor-description - margin: 0 \ No newline at end of file + margin: 0 + +.editor-header + display: flex + justify-content: space-between + align-items: center \ No newline at end of file From 37526f4a4d7091d78e725f26e60d61f97a9c55c7 Mon Sep 17 00:00:00 2001 From: infacc Date: Thu, 20 Jul 2023 22:45:57 +0200 Subject: [PATCH 26/29] remove unused location interface --- .../template-details/template-details.component.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/app/components-small/template-details/template-details.component.ts b/src/app/components-small/template-details/template-details.component.ts index fd7e54d..932622b 100644 --- a/src/app/components-small/template-details/template-details.component.ts +++ b/src/app/components-small/template-details/template-details.component.ts @@ -14,12 +14,6 @@ export function isInSetValidator(validValues: any[]): Validators { }; } -export interface Location { - value: string; - description: string; -} - - @Component({ selector: 'qhana-template-details', templateUrl: './template-details.component.html', From 6a332026eaa9bd32bc5cfa0ca10dac7350dd55a6 Mon Sep 17 00:00:00 2001 From: infacc Date: Thu, 20 Jul 2023 22:56:32 +0200 Subject: [PATCH 27/29] add JSON validator to filter string textarea input --- .../plugin-filter-editor.component.html | 1 + .../plugin-filter-editor.component.ts | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html index d943f68..4e8beaf 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html @@ -19,6 +19,7 @@

Filter Editor

+ Invalid JSON
Filter String Info diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts index 9beb1bc..51eb9b0 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts @@ -1,9 +1,20 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { FormControl, Validators } from '@angular/forms'; +import { AbstractControl, FormControl, Validators, ValidatorFn } from '@angular/forms'; import { ApiLink } from 'src/app/services/api-data-types'; import { PluginRegistryBaseService } from 'src/app/services/registry.service'; import { TemplateTabApiObject } from 'src/app/services/templates.service'; +export function isJSONValidator(): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + try { + JSON.parse(control.value); + return null; + } catch (e) { + return { invalidJSON: true }; + } + }; +} + @Component({ selector: 'qhana-plugin-filter-editor', templateUrl: './plugin-filter-editor.component.html', @@ -14,7 +25,8 @@ export class PluginFilterEditorComponent implements OnInit { @Output() filterEmitter: EventEmitter = new EventEmitter(); filterString: string = "{}"; - filterControl = new FormControl(this.filterString, [Validators.required, Validators.minLength(2)]); // TODO: Add validator for JSON + // TODO: Add JSON validator for filter strings + filterControl = new FormControl(this.filterString, [isJSONValidator()]); filterObject: any = {}; From f5423d55382796494b876692c3ad7646249262b8 Mon Sep 17 00:00:00 2001 From: infacc <33042539+infacc@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:38:01 +0200 Subject: [PATCH 28/29] rename revert filter button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fabian Bühler --- .../plugin-filter-editor/plugin-filter-editor.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html index 4e8beaf..42a4f9d 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html @@ -6,7 +6,7 @@

Filter Editor

From eb4ebca8c45c966e5501fc1e6d45c330831bbe9f Mon Sep 17 00:00:00 2001 From: infacc Date: Fri, 21 Jul 2023 13:47:48 +0200 Subject: [PATCH 29/29] hide button from screen readers --- .../plugin-filter-editor/plugin-filter-editor.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html index 42a4f9d..2f10e4a 100644 --- a/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html +++ b/src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html @@ -5,7 +5,7 @@

Filter Editor

Editor Mode {{ showEditor ? '(UI)' : '(JSON)' }}