-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/plugin filter editor #48
Merged
Merged
Changes from 13 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
01bf1bb
add plugin filter editor
infacc e55a407
show plugin editor when no tab link is provided
infacc 8b1fe43
hide mat-card if filter is empty
infacc 5616886
refactor: improve filter editor usability
infacc 76d7744
refactor: avoid ViewChild decorator in filter editor
infacc 4e2edb3
simplify isLeaf check
infacc 38879c2
rectify warning message
infacc e71700b
refactor: simplify filter setup
infacc d81143a
Merge branch 'feature/plugin-filter-editor' of github.com:UST-QuAntiL…
infacc fae56c0
handle filter deletion by parent
infacc 4ceb6d4
catch JSON paring errors
infacc 183ced2
separate ternary operator to improve readability
infacc a1fe552
add inverted indication for and/or filters
infacc 05d026b
remove index property
infacc f62ff31
store info whether filter is empty in property
infacc 334189a
refactor: separate input and output objects
infacc c0ee392
check for empty filter
infacc 51171f8
fix bug: keep input focus when typing
infacc fff9496
fix bug: use JSON editor input on tab change
infacc be07ea4
add active warning for filters with multiple attributes
infacc 9622400
set filter string via template expression
infacc ec041cf
refactor: simplify type check for filter keys
infacc 62344c6
Merge branch 'main' into feature/plugin-filter-editor
infacc b8b6cf6
rename HTML class to avoid confusion
infacc 16dd6a5
use deep copies to emit filters
infacc 8ea5eea
remove filter string from template form
infacc af7ad6b
add reload filter button
infacc 37526f4
remove unused location interface
infacc 6a33202
add JSON validator to filter string textarea input
infacc f5423d5
rename revert filter button
infacc eb4ebca
hide button from screen readers
infacc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
57 changes: 57 additions & 0 deletions
57
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,57 @@ | ||
<div class="filter-editor mat-elevation-z0"> | ||
<h3 class="editor-description">Filter Editor</h3> | ||
<mat-slide-toggle [(ngModel)]="showEditor"> | ||
Editor Mode {{ showEditor ? '(UI)' : '(JSON)' }} | ||
</mat-slide-toggle> | ||
<qhana-plugin-filter-node *ngIf="filterObject && showEditor" [filterObject]="filterObject" (childChange)="updateFilter($event)" (delete)="deleteFilter($event)"></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" (input)="updateFilterEditor()"> | ||
</textarea> | ||
<button mat-raised-button class="copy-button" type="button" (click)="copyFilterString()"> | ||
<mat-icon>content_copy</mat-icon> | ||
</button> | ||
</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> |
16 changes: 16 additions & 0 deletions
16
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,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 |
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(); | ||
}); | ||
}); |
67 changes: 67 additions & 0 deletions
67
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,67 @@ | ||
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'; | ||
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; | ||
@Output() filterEmitter: EventEmitter<string> = new EventEmitter<string>(); | ||
|
||
filterString: string = "{}"; | ||
filterControl = new FormControl(this.filterString, [Validators.required, Validators.minLength(2)]); // TODO: Add validator for JSON | ||
|
||
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); | ||
}); | ||
} | ||
|
||
updateFilter(event: [number, any]) { | ||
const [index, value] = event; | ||
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; | ||
} catch (e) { | ||
console.warn("Invalid filter string", this.filterObject, "\nError:", e); | ||
} | ||
} | ||
} | ||
|
||
deleteFilter(index: number) { | ||
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="isFilterEmpty()" class="create-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="!isFilterEmpty()" 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(index)"> | ||
<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" [filterObject]="children[i]" [index]="i" | ||
[depth]="depth + 1" (childChange)="updateChild($event)" (delete)="deleteChild($event)"></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" (input)="updateFilterString($event)" cdkTextareaAutosize #autosize="cdkTextareaAutosize" | ||
cdkAutosizeMinRows="1" cdkAutosizeMaxRows="15"></textarea> | ||
</mat-form-field> | ||
<button mat-raised-button type="button" (click)="delete.emit(index)"> | ||
<mat-icon>delete</mat-icon> | ||
</button> | ||
</div> | ||
<mat-checkbox [checked]="inverted" (change)="invertFilter()">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 @@ | ||
.create-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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be handled in the parent component (a node cannot create or delete itself normally...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is meant for empty filters (
{}
). I.e. when a tab is newly created. The HTML class namecreate-buttons
is misleading, I changed it toset-buttons
.