Rudimentary nesting / expressions
This commit is contained in:
parent
96546af95f
commit
9102ddb362
@ -3,37 +3,26 @@
|
||||
<i-bs name="{{icon}}"></i-bs>
|
||||
<div class="d-none d-sm-inline"> {{title}}</div>
|
||||
</button>
|
||||
<div class="dropdown-menu shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-3" (click)="addQuery()" [disabled]="disabled">
|
||||
<i-bs name="plus"></i-bs> Add query
|
||||
</button>
|
||||
<div class="dropdown-menu px-3 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}">
|
||||
<div class="btn-group mb-2 w-100">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="addAtom()" [disabled]="disabled">
|
||||
<i-bs name="plus"></i-bs> Add query
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="addExpression()" [disabled]="disabled">
|
||||
<i-bs name="plus"></i-bs> Add expression
|
||||
</button>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
@for (query of selectionModel.queries; track query; let i = $index) {
|
||||
<div class="list-group-item d-flex">
|
||||
<div class="input-group input-group-sm flex-shrink-1 flex-nowrap">
|
||||
<ng-select
|
||||
class="w-50"
|
||||
[items]="customFields"
|
||||
[(ngModel)]="query.field"
|
||||
[disabled]="disabled"
|
||||
bindLabel="name"
|
||||
bindValue="id"
|
||||
></ng-select>
|
||||
<select class="w-25 form-control" [(ngModel)]="query.operator" [disabled]="disabled">
|
||||
<option *ngFor="let operator of getOperatorsForField(query.field)" [ngValue]="operator">{{operator}}</option>
|
||||
</select>
|
||||
@switch (query.operator) {
|
||||
@case ('exists') {
|
||||
<select class="w-25 form-control" [(ngModel)]="query.value" [disabled]="disabled">
|
||||
<option value="true" i18n>true</option>
|
||||
<option value="false" i18n>false</option>
|
||||
</select>
|
||||
}
|
||||
@default {
|
||||
<input class="w-25 form-control" type="text" [(ngModel)]="query.value" [disabled]="disabled">
|
||||
}
|
||||
<div class="list-group-item px-0 d-flex">
|
||||
@switch (query.type) {
|
||||
@case (CustomFieldQueryComponentType.Atom) {
|
||||
<ng-container *ngTemplateOutlet="queryAtom; context: { query: query }"></ng-container>
|
||||
}
|
||||
</div>
|
||||
@case (CustomFieldQueryComponentType.Expression) {
|
||||
<ng-container *ngTemplateOutlet="queryExpression; context: { query: query }"></ng-container>
|
||||
}
|
||||
}
|
||||
<button class="btn btn-link btn-sm pe-0" type="button" (click)="removeQuery(i)" [disabled]="disabled">
|
||||
<i-bs name="x"></i-bs>
|
||||
</button>
|
||||
@ -42,3 +31,60 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #queryAtom let-query="query">
|
||||
<div class="input-group input-group-sm flex-shrink-1 flex-nowrap">
|
||||
<ng-select
|
||||
class="w-50"
|
||||
[items]="customFields"
|
||||
[(ngModel)]="query.field"
|
||||
[disabled]="disabled"
|
||||
bindLabel="name"
|
||||
bindValue="id"
|
||||
></ng-select>
|
||||
<select class="w-25 form-control" [(ngModel)]="query.operator" [disabled]="disabled">
|
||||
<option *ngFor="let operator of getOperatorsForField(query.field)" [ngValue]="operator">{{operator}}</option>
|
||||
</select>
|
||||
@switch (query.operator) {
|
||||
@case ('exists') {
|
||||
<select class="w-25 form-control" [(ngModel)]="query.value" [disabled]="disabled">
|
||||
<option value="true" i18n>true</option>
|
||||
<option value="false" i18n>false</option>
|
||||
</select>
|
||||
}
|
||||
@default {
|
||||
<input class="w-25 form-control" type="text" [(ngModel)]="query.value" [disabled]="disabled">
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #queryExpression let-query="query">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="btn-group btn-group-xs flex-fill" role="group">
|
||||
<input [(ngModel)]="query.operator" type="radio" class="btn-check" id="logicalOperatorAnd_{{query.field}}" name="logicalOperatorAnd_{{query.field}}" value="AND">
|
||||
<label class="btn btn-outline-primary" for="logicalOperatorAnd_{{query.field}}" i18n>And</label>
|
||||
<input [(ngModel)]="query.operator" type="radio" class="btn-check" id="logicalOperatorOr_{{query.field}}" name="logicalOperatorOr_{{query.field}}" value="OR">
|
||||
<label class="btn btn-outline-primary" for="logicalOperatorOr_{{query.field}}" i18n>Or</label>
|
||||
<input [(ngModel)]="query.operator" type="radio" class="btn-check" id="logicalOperatorNot_{{query.field}}" name="logicalOperatorNot_{{query.field}}" value="NOT">
|
||||
<label class="btn btn-outline-primary" for="logicalOperatorNot_{{query.field}}" i18n>Not</label>
|
||||
</div>
|
||||
<div class="border border-1 border-primary rounded p-2 mt-2">
|
||||
@for (subquery of query.value; track subquery; let i = $index) {
|
||||
<div class="list-group-item px-0 d-flex">
|
||||
@switch (subquery.type) {
|
||||
@case (CustomFieldQueryComponentType.Atom) {
|
||||
<ng-container *ngTemplateOutlet="queryAtom; context: { query: subquery }"></ng-container>
|
||||
}
|
||||
@case (CustomFieldQueryComponentType.Expression) {
|
||||
<ng-container *ngTemplateOutlet="queryExpression; context: { query: subquery }"></ng-container>
|
||||
}
|
||||
}
|
||||
<button class="btn btn-link btn-sm pe-0" type="button" (click)="removeQuery(i)" [disabled]="disabled">
|
||||
<i-bs name="x"></i-bs>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@ -3,19 +3,29 @@ import { Subject, first, takeUntil } from 'rxjs'
|
||||
import { CustomField } from 'src/app/data/custom-field'
|
||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||
|
||||
export class CustomFieldQuery {
|
||||
public changed = new Subject<CustomFieldQuery>()
|
||||
export enum CustomFieldQueryLogicalOperator {
|
||||
And = 'AND',
|
||||
Or = 'OR',
|
||||
Not = 'NOT',
|
||||
}
|
||||
|
||||
private _field: string
|
||||
set field(value: string) {
|
||||
this._field = value
|
||||
this.changed.next(this)
|
||||
}
|
||||
get field(): string {
|
||||
return this._field
|
||||
export enum CustomFieldQueryComponentType {
|
||||
Atom = 'Atom',
|
||||
Expression = 'Expression',
|
||||
}
|
||||
|
||||
export class CustomFieldQueryComponent {
|
||||
public readonly type: CustomFieldQueryComponentType
|
||||
public changed: Subject<CustomFieldQueryComponent>
|
||||
|
||||
constructor(type: CustomFieldQueryComponentType) {
|
||||
this.type = type
|
||||
this.changed = new Subject<CustomFieldQueryComponent>()
|
||||
}
|
||||
|
||||
private _operator: string
|
||||
public serialize() {}
|
||||
|
||||
protected _operator: string
|
||||
set operator(value: string) {
|
||||
this._operator = value
|
||||
this.changed.next(this)
|
||||
@ -24,31 +34,75 @@ export class CustomFieldQuery {
|
||||
return this._operator
|
||||
}
|
||||
|
||||
private _value: string
|
||||
set value(value: string) {
|
||||
protected _value: string | CustomFieldQueryAtom[] | CustomFieldQueryExpression
|
||||
set value(
|
||||
value: string | CustomFieldQueryAtom[] | CustomFieldQueryExpression
|
||||
) {
|
||||
this._value = value
|
||||
this.changed.next(this)
|
||||
}
|
||||
get value(): string {
|
||||
get value(): string | CustomFieldQueryAtom[] | CustomFieldQueryExpression {
|
||||
return this._value
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomFieldQueryAtom extends CustomFieldQueryComponent {
|
||||
protected _field: string
|
||||
set field(value: string) {
|
||||
this._field = value
|
||||
this.changed.next(this)
|
||||
}
|
||||
get field(): string {
|
||||
return this._field
|
||||
}
|
||||
|
||||
constructor(queryArray: [string, string, string] = [null, null, null]) {
|
||||
super(CustomFieldQueryComponentType.Atom)
|
||||
;[this._field, this._operator, this._value] = queryArray
|
||||
}
|
||||
|
||||
public serialize() {
|
||||
return [this._field, this._operator, this._value]
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomFieldQueryExpression extends CustomFieldQueryComponent {
|
||||
constructor(
|
||||
field: string = null,
|
||||
operator: string = null,
|
||||
value: string = null
|
||||
expressionArray: [
|
||||
CustomFieldQueryLogicalOperator,
|
||||
(
|
||||
| [string, string, string][]
|
||||
| [CustomFieldQueryLogicalOperator, [string, string, string][]]
|
||||
),
|
||||
] = [CustomFieldQueryLogicalOperator.And, null]
|
||||
) {
|
||||
this.field = field
|
||||
this.operator = operator
|
||||
this.value = value
|
||||
super(CustomFieldQueryComponentType.Expression)
|
||||
;[this._operator] = expressionArray
|
||||
let values = expressionArray[1]
|
||||
if (!values) {
|
||||
this._value = []
|
||||
} else if (values?.length > 0 && values[0] instanceof Array) {
|
||||
this._value = values.map((value) => new CustomFieldQueryAtom(value))
|
||||
} else {
|
||||
this._value = new CustomFieldQueryExpression(values as any)
|
||||
}
|
||||
}
|
||||
|
||||
public serialize() {
|
||||
let value
|
||||
if (this._value instanceof Array) {
|
||||
value = this._value.map((atom) => atom.serialize())
|
||||
} else {
|
||||
value = value.serialize()
|
||||
}
|
||||
return [this._operator, value]
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomFieldQueriesModel {
|
||||
// matchingModel: MatchingModel
|
||||
queries: CustomFieldQuery[] = []
|
||||
public queries: Array<CustomFieldQueryAtom | CustomFieldQueryExpression> = []
|
||||
|
||||
changed = new Subject<CustomFieldQueriesModel>()
|
||||
public readonly changed = new Subject<CustomFieldQueriesModel>()
|
||||
|
||||
public clear(fireEvent = true) {
|
||||
this.queries = []
|
||||
@ -57,8 +111,14 @@ export class CustomFieldQueriesModel {
|
||||
}
|
||||
}
|
||||
|
||||
public addQuery(query: CustomFieldQuery = new CustomFieldQuery()) {
|
||||
this.queries.push(query)
|
||||
public addQuery(query: CustomFieldQueryAtom = new CustomFieldQueryAtom()) {
|
||||
if (this.queries.length > 0) {
|
||||
if (this.queries[0].type === CustomFieldQueryComponentType.Expression) {
|
||||
;(this.queries[0].value as Array<any>).push(query)
|
||||
}
|
||||
} else {
|
||||
this.queries.push(query)
|
||||
}
|
||||
query.changed.subscribe(() => {
|
||||
if (query.field && query.operator && query.value) {
|
||||
this.changed.next(this)
|
||||
@ -71,6 +131,21 @@ export class CustomFieldQueriesModel {
|
||||
query.changed.complete()
|
||||
this.changed.next(this)
|
||||
}
|
||||
|
||||
public addExpression(
|
||||
expression: CustomFieldQueryExpression = new CustomFieldQueryExpression()
|
||||
) {
|
||||
if (this.queries.length > 0) {
|
||||
if (this.queries[0].type === CustomFieldQueryComponentType.Atom) {
|
||||
expression.value = this.queries as CustomFieldQueryAtom[]
|
||||
this.queries = []
|
||||
}
|
||||
}
|
||||
this.queries.push(expression)
|
||||
expression.changed.subscribe(() => {
|
||||
this.changed.next(this)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
@ -79,6 +154,8 @@ export class CustomFieldQueriesModel {
|
||||
styleUrls: ['./custom-fields-lookup-dropdown.component.scss'],
|
||||
})
|
||||
export class CustomFieldsLookupDropdownComponent {
|
||||
public CustomFieldQueryComponentType = CustomFieldQueryComponentType
|
||||
|
||||
@Input()
|
||||
title: string
|
||||
|
||||
@ -143,10 +220,14 @@ export class CustomFieldsLookupDropdownComponent {
|
||||
})
|
||||
}
|
||||
|
||||
public addQuery() {
|
||||
public addAtom() {
|
||||
this.selectionModel.addQuery()
|
||||
}
|
||||
|
||||
public addExpression() {
|
||||
this.selectionModel.addExpression()
|
||||
}
|
||||
|
||||
public removeQuery(index: number) {
|
||||
this.selectionModel.removeQuery(index)
|
||||
}
|
||||
|
@ -95,7 +95,8 @@ import { CustomField } from 'src/app/data/custom-field'
|
||||
import { SearchService } from 'src/app/services/rest/search.service'
|
||||
import {
|
||||
CustomFieldQueriesModel,
|
||||
CustomFieldQuery,
|
||||
CustomFieldQueryAtom,
|
||||
CustomFieldQueryExpression,
|
||||
} from '../../common/custom-fields-lookup-dropdown/custom-fields-lookup-dropdown.component'
|
||||
|
||||
const TEXT_FILTER_TARGET_TITLE = 'title'
|
||||
@ -523,11 +524,24 @@ export class FilterEditorComponent
|
||||
)
|
||||
break
|
||||
case FILTER_CUSTOM_FIELDS_LOOKUP:
|
||||
// TODO: fully implement
|
||||
const query = JSON.parse(rule.value)
|
||||
this.customFieldQueriesModel.addQuery(
|
||||
new CustomFieldQuery(query[0], query[1], query[2])
|
||||
)
|
||||
try {
|
||||
const query = JSON.parse(rule.value)
|
||||
if (Array.isArray(query)) {
|
||||
if (query.length === 2) {
|
||||
// expression
|
||||
this.customFieldQueriesModel.addExpression(
|
||||
new CustomFieldQueryExpression(query as any)
|
||||
)
|
||||
} else if (query.length === 3) {
|
||||
// atom
|
||||
this.customFieldQueriesModel.addQuery(
|
||||
new CustomFieldQueryAtom(query as any)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO: handle error?
|
||||
}
|
||||
break
|
||||
case FILTER_HAS_CUSTOM_FIELDS_ALL:
|
||||
console.log('FILTER_HAS_CUSTOM_FIELDS_ALL', rule.value)
|
||||
@ -752,8 +766,8 @@ export class FilterEditorComponent
|
||||
})
|
||||
}
|
||||
let queries = this.customFieldQueriesModel.queries
|
||||
.filter((query) => query.field && query.operator)
|
||||
.map((query) => [query.field, query.operator, query.value])
|
||||
.filter((query) => query.value && query.operator)
|
||||
.map((query) => query.serialize())
|
||||
console.log(
|
||||
'this.customFieldQueriesModel.queries',
|
||||
this.customFieldQueriesModel.queries
|
||||
@ -762,10 +776,7 @@ export class FilterEditorComponent
|
||||
if (queries.length > 0) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_CUSTOM_FIELDS_LOOKUP,
|
||||
value:
|
||||
queries.length === 1
|
||||
? JSON.stringify(queries[0])
|
||||
: JSON.stringify(queries),
|
||||
value: JSON.stringify(queries[0]),
|
||||
})
|
||||
}
|
||||
// TODO: fully implement custom fields
|
||||
|
Loading…
x
Reference in New Issue
Block a user