-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #48 from UST-QuAntiL/feature/plugin-filter-editor
Feature/plugin filter editor
- Loading branch information
Showing
11 changed files
with
510 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<div class="filter-editor mat-elevation-z0"> | ||
<h3 class="editor-description">Filter Editor</h3> | ||
<div class="editor-header"> | ||
<mat-slide-toggle [(ngModel)]="showEditor"> | ||
Editor Mode {{ showEditor ? '(UI)' : '(JSON)' }} | ||
</mat-slide-toggle> | ||
<button mat-raised-button type="button" (click)="ngOnInit()"> | ||
<mat-icon aria-hidden="true">refresh</mat-icon> | ||
Revert Changes | ||
</button> | ||
</div> | ||
<qhana-plugin-filter-node *ngIf="filterObject && showEditor" [filterIn]="filterObject" (filterOut)="updateFilter($event)" (delete)="deleteFilter()"></qhana-plugin-filter-node> | ||
<ng-container *ngIf="!showEditor"> | ||
<mat-form-field class="form-field"> | ||
<mat-label>Filter String:</mat-label> | ||
<textarea matInput [formControl]="filterControl" cdkTextareaAutosize #autosize="cdkTextareaAutosize" | ||
cdkAutosizeMinRows="1" cdkAutosizeMaxRows="20" (focusout)="updateFilterEditor()"> | ||
</textarea> | ||
<button mat-raised-button class="copy-button" type="button" (click)="copyFilterString()"> | ||
<mat-icon>content_copy</mat-icon> | ||
</button> | ||
<mat-error *ngIf="filterControl.hasError('invalidJSON')">Invalid JSON</mat-error> | ||
</mat-form-field> | ||
<details> | ||
<summary>Filter String Info</summary> | ||
|
||
<i>Enter a filter string as JSON object. Filter strings have the following keys:</i> | ||
<dl> | ||
<dt>name</dt> <dd>Represents the name of a plugin.</dd> | ||
<dt>tag</dt> <dd>Allows filtering elements by their assigned tags.</dd> | ||
<dt>version</dt> <dd>Uses PEP 440 version specifier to filter elements based on specific versions or version ranges.</dd> | ||
<dt>not</dt> <dd>Specifies a filter string to exclude certain elements.</dd> | ||
<dt>and</dt> <dd>Includes multiple filter strings, with elements passing all conditions included in the filtered results (intersection).</dd> | ||
<dt>or</dt> <dd>Includes multiple filter strings, with elements meeting at least one condition included in the filtered results (union).</dd> | ||
</dl> | ||
<mat-divider></mat-divider> | ||
<p>Examples:</p> | ||
<mat-form-field class="form-field"> | ||
<textarea class="filter-example" matInput disabled rows="3"> | ||
{ | ||
"name": "hello-world" | ||
} | ||
</textarea> | ||
</mat-form-field> | ||
<mat-form-field class="form-field"> | ||
<textarea class="filter-example" matInput disabled rows="3"> | ||
{ | ||
"not": { "name": "hello-world" } | ||
} | ||
</textarea> | ||
</mat-form-field> | ||
<mat-form-field class="form-field"> | ||
<textarea class="filter-example" matInput disabled rows="6"> | ||
{ | ||
"and": [ | ||
{ "tag": "data-loading" }, | ||
{ "version": ">=0.2.0" } | ||
] | ||
} | ||
</textarea> | ||
</mat-form-field> | ||
</details> | ||
</ng-container> | ||
</div> |
21 changes: 21 additions & 0 deletions
21
src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.sass
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
.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 | ||
|
||
.editor-header | ||
display: flex | ||
justify-content: space-between | ||
align-items: center |
23 changes: 23 additions & 0 deletions
23
src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<PluginFilterEditorComponent>; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
declarations: [ PluginFilterEditorComponent ] | ||
}) | ||
.compileComponents(); | ||
|
||
fixture = TestBed.createComponent(PluginFilterEditorComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
80 changes: 80 additions & 0 deletions
80
src/app/components-small/plugin-filter-editor/plugin-filter-editor.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; | ||
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', | ||
styleUrls: ['./plugin-filter-editor.component.sass'] | ||
}) | ||
export class PluginFilterEditorComponent implements OnInit { | ||
@Input() tabLink: ApiLink | null = null; | ||
@Output() filterEmitter: EventEmitter<string> = new EventEmitter<string>(); | ||
|
||
filterString: string = "{}"; | ||
// TODO: Add JSON validator for filter strings | ||
filterControl = new FormControl(this.filterString, [isJSONValidator()]); | ||
|
||
filterObject: any = {}; | ||
|
||
showEditor: boolean = true; | ||
|
||
constructor(private registry: PluginRegistryBaseService) { } | ||
|
||
ngOnInit(): void { | ||
if (this.tabLink == null) { | ||
return; | ||
} | ||
this.registry.getByApiLink<TemplateTabApiObject>(this.tabLink).then(response => { | ||
this.filterString = response?.data?.filterString ?? this.filterString; | ||
this.filterObject = JSON.parse(this.filterString); | ||
this.updateFilter(this.filterObject); | ||
}); | ||
} | ||
|
||
updateFilter(value: any) { | ||
this.filterObject = value; | ||
this.filterString = JSON.stringify(this.filterObject, null, 2); | ||
this.filterControl.setValue(this.filterString); | ||
this.filterEmitter.emit(this.filterString); | ||
} | ||
|
||
copyFilterString() { | ||
navigator.clipboard.writeText(this.filterString); | ||
} | ||
|
||
updateFilterEditor() { | ||
const filterValue = this.filterControl.value; | ||
if (filterValue == null) { | ||
return; | ||
} | ||
if (this.filterControl.valid) { | ||
try { | ||
this.filterObject = JSON.parse(filterValue); | ||
this.filterString = filterValue; | ||
this.filterEmitter.emit(this.filterString); | ||
} catch (e) { | ||
console.warn("Invalid filter string", this.filterObject, "\nError:", e); | ||
} | ||
} | ||
} | ||
|
||
deleteFilter() { | ||
this.filterString = "{}"; | ||
this.filterControl.setValue(this.filterString); | ||
this.updateFilterEditor(); | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
src/app/components-small/plugin-filter-node/plugin-filter-node.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<div> | ||
<div *ngIf="isEmpty" class="set-buttons"> | ||
<button mat-raised-button type="button" (click)="setFilter('and')" color="primary"> | ||
<mat-icon>add</mat-icon> | ||
AND | ||
</button> | ||
<button mat-raised-button type="button" (click)="setFilter('or')" color="primary"> | ||
<mat-icon>add</mat-icon> | ||
OR | ||
</button> | ||
<button mat-raised-button type="button" (click)="setFilter('name')" color="primary"> | ||
<mat-icon>add</mat-icon> | ||
Filter | ||
</button> | ||
</div> | ||
<mat-card *ngIf="!isEmpty" class="filter-node mat-elevation-z0" [ngClass]="{'filter-leaf' : type !== 'and' && type !== 'or'}"> | ||
<mat-card-header> | ||
<ng-container *ngIf="children != null"> | ||
<div class="config-header"> | ||
<mat-button-toggle-group [value]="type" (valueChange)="changeType($event)"> | ||
<mat-button-toggle value="and">AND</mat-button-toggle> | ||
<mat-button-toggle value="or">OR</mat-button-toggle> | ||
</mat-button-toggle-group> | ||
<div class="config-header-buttons"> | ||
<button mat-raised-button *ngIf="depth < 2" type="button" (click)="addFilter('and')" color="primary"> | ||
<mat-icon>add</mat-icon> | ||
AND | ||
</button> | ||
<button mat-raised-button *ngIf="depth < 2" type="button" (click)="addFilter('or')" color="primary"> | ||
<mat-icon>add</mat-icon> | ||
OR | ||
</button> | ||
<button mat-raised-button type="button" (click)="addFilter('name')" color="primary"> | ||
<mat-icon>add</mat-icon> | ||
Filter | ||
</button> | ||
<button mat-raised-button type="button" (click)="delete.emit()"> | ||
<mat-icon>delete</mat-icon> | ||
</button> | ||
</div> | ||
</div> | ||
</ng-container> | ||
</mat-card-header> | ||
<mat-card-content> | ||
<ng-container *ngIf="children != null"> | ||
<span *ngIf="inverted" class="t-subheading">Not:</span> | ||
<qhana-plugin-filter-node *ngFor="let child of children; let i = index" [filterIn]="children[i]" | ||
[depth]="depth + 1" (filterOut)="updateChild($event, i)" (delete)="deleteChild(i)"></qhana-plugin-filter-node> | ||
</ng-container> | ||
<div class="config-filter" *ngIf="value != null"> | ||
<span *ngIf="inverted" class="t-subheading">Not:</span> | ||
<mat-form-field class="inner-form-field"> | ||
<mat-label>Filter Type:</mat-label> | ||
<mat-select [value]="type" (valueChange)="changeType($event)"> | ||
<mat-option value="name">Name</mat-option> | ||
<mat-option value="tag">Tag</mat-option> | ||
<mat-option value="version">Version</mat-option> | ||
</mat-select> | ||
</mat-form-field> | ||
<mat-form-field class="form-field"> | ||
<mat-label>Filter String:</mat-label> | ||
<textarea matInput [value]="value" (focusout)="updateFilterString($event)" cdkTextareaAutosize #autosize="cdkTextareaAutosize" | ||
cdkAutosizeMinRows="1" cdkAutosizeMaxRows="15"></textarea> | ||
</mat-form-field> | ||
<button mat-raised-button type="button" (click)="delete.emit()"> | ||
<mat-icon>delete</mat-icon> | ||
</button> | ||
</div> | ||
<mat-checkbox [checked]="inverted" (change)="invertFilter($event)">Invert - <i>Negate this filter</i></mat-checkbox> | ||
</mat-card-content> | ||
</mat-card> | ||
</div> |
57 changes: 57 additions & 0 deletions
57
src/app/components-small/plugin-filter-node/plugin-filter-node.component.sass
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
.set-buttons | ||
display: flex | ||
flex-direction: row | ||
justify-content: flex-start | ||
gap: 10px | ||
|
||
.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 |
23 changes: 23 additions & 0 deletions
23
src/app/components-small/plugin-filter-node/plugin-filter-node.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<PluginFilterNodeComponent>; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
declarations: [ PluginFilterNodeComponent ] | ||
}) | ||
.compileComponents(); | ||
|
||
fixture = TestBed.createComponent(PluginFilterNodeComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
Oops, something went wrong.