Big refactor of layout etc

This commit is contained in:
shamoon 2024-09-04 14:27:31 -07:00
parent 30fb5a96ce
commit b6a3c7b58b
3 changed files with 113 additions and 90 deletions

View File

@ -1,20 +1,12 @@
<div class="btn-group w-100" ngbDropdown role="group" #dropdown="ngbDropdown">
<div class="btn-group w-100" ngbDropdown role="group" #dropdown="ngbDropdown" (openChange)="onOpenChange($event)">
<button class="btn btn-sm btn-outline-primary" id="dropdown_{{name}}" ngbDropdownToggle [disabled]="disabled">
<i-bs name="{{icon}}"></i-bs>
<div class="d-none d-sm-inline">&nbsp;{{title}}</div>
@if (selectionModel.queries.length > 0) {
<pngx-clearable-badge [selected]="selectionModel.queries.length > 0" (cleared)="reset()"></pngx-clearable-badge>
@if (isActive) {
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge>
}
</button>
<div class="dropdown-menu px-3 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}">
<div class="btn-group my-2 w-100">
<button type="button" class="btn btn-sm btn-outline-primary" (click)="addAtom()" [disabled]="disabled">
<i-bs name="node-plus"></i-bs>&nbsp;Add query
</button>
<button type="button" class="btn btn-sm btn-outline-primary" (click)="addExpression()" [disabled]="disabled">
<i-bs name="braces"></i-bs>&nbsp;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 px-0 d-flex flex-nowrap">
@ -61,36 +53,46 @@
<input class="w-25 form-control rounded-end" type="text" [(ngModel)]="query.value" [disabled]="disabled">
}
}
<button class="btn btn-link btn-sm text-danger pe-0" type="button" (click)="removeComponent(query)" [disabled]="disabled">
<button class="btn btn-link btn-sm text-danger pe-0" type="button" (click)="removeElement(query)" [disabled]="disabled">
<i-bs name="x-circle"></i-bs>
</button>
</div>
</ng-template>
<ng-template #queryExpression let-query="query">
<div class="d-flex flex-column border border-primary rounded px-2 pt-2 w-100">
<div class="btn-group btn-group-xs" 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>
<div class="d-flex w-100">
<div class="d-flex flex-grow-1 flex-column">
<div class="btn-group btn-group-xs" role="group">
<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>Any</label>
<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>All</label>
</div>
<div class="list-group list-group-flush mb-n2">
@for (subquery of query.value; track subquery; let i = $index) {
<div class="list-group-item px-0 d-flex flex-nowrap">
@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>
}
}
</div>
}
</div>
</div>
<div class="list-group list-group-flush">
@for (subquery of query.value; track subquery; let i = $index) {
<div class="list-group-item px-0 d-flex flex-nowrap">
@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>
}
}
</div>
}
<div class="btn-group-vertical ms-2 ps-2 border-start" role="group" aria-label="Vertical button group">
<button type="button" class="btn btn-sm btn-outline-secondary text-primary" title="Add query" i18n-title (click)="addAtom(query)" [disabled]="disabled">
<i-bs name="node-plus"></i-bs>
</button>
<button type="button" class="btn btn-sm btn-outline-secondary text-primary" title="Add expression" i18n-title (click)="addExpression(query)" [disabled]="disabled">
<i-bs name="braces"></i-bs>
</button>
<button type="button" class="btn btn-sm btn-outline-secondary text-danger" (click)="removeElement(query)" [disabled]="disabled">
<i-bs name="x-circle"></i-bs>
</button>
</div>
</div>
<button class="btn btn-link btn-sm text-danger pe-0" type="button" (click)="removeComponent(query)" [disabled]="disabled">
<i-bs name="x-circle"></i-bs>
</button>
</ng-template>

View File

@ -10,11 +10,12 @@ import {
CUSTOM_FIELD_QUERY_OPERATORS_BY_GROUP,
CustomFieldQueryOperatorGroups,
CUSTOM_FIELD_QUERY_OPERATOR_LABELS,
CustomFieldQueryElement,
} from 'src/app/data/custom-field-query'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
export class CustomFieldQueriesModel {
public queries: Array<CustomFieldQueryAtom | CustomFieldQueryExpression> = []
public queries: CustomFieldQueryElement[] = []
public readonly changed = new Subject<CustomFieldQueriesModel>()
@ -25,22 +26,11 @@ export class CustomFieldQueriesModel {
}
}
public addQuery(
query: CustomFieldQueryAtom = new CustomFieldQueryAtom([
null,
CustomFieldQueryOperator.Exists,
'true',
])
) {
if (this.queries.length > 0) {
if (this.queries[0].type === CustomFieldQueryElementType.Expression) {
;(this.queries[0].value as Array<any>).push(query)
} else {
this.queries.push(query)
}
} else {
this.queries.push(query)
public addQuery(query: CustomFieldQueryAtom) {
if (this.queries.length === 0) {
this.addExpression()
}
;(this.queries[0].value as CustomFieldQueryElement[]).push(query)
query.changed.subscribe(() => {
if (query.field && query.operator && query.value) {
this.changed.next(this)
@ -52,15 +42,10 @@ export class CustomFieldQueriesModel {
expression: CustomFieldQueryExpression = new CustomFieldQueryExpression()
) {
if (this.queries.length > 0) {
if (this.queries[0].type === CustomFieldQueryElementType.Atom) {
expression.value = this.queries as CustomFieldQueryAtom[]
this.queries = []
this.queries.push(expression)
} else {
;((this.queries[0] as CustomFieldQueryExpression).value as any[]).push(
expression
)
}
;(
(this.queries[0] as CustomFieldQueryExpression)
.value as CustomFieldQueryElement[]
).push(expression)
} else {
this.queries.push(expression)
}
@ -69,20 +54,12 @@ export class CustomFieldQueriesModel {
})
}
private findComponent(
queryComponent: CustomFieldQueryAtom | CustomFieldQueryExpression,
components: any[]
) {
for (let i = 0; i < components.length; i++) {
if (components[i] === queryComponent) {
return components.splice(i, 1)[0]
} else if (
components[i].type === CustomFieldQueryElementType.Expression
) {
let found = this.findComponent(
queryComponent,
components[i].value as any[]
)
private findElement(queryElement: CustomFieldQueryElement, elements: any[]) {
for (let i = 0; i < elements.length; i++) {
if (elements[i] === queryElement) {
return elements.splice(i, 1)[0]
} else if (elements[i].type === CustomFieldQueryElementType.Expression) {
let found = this.findElement(queryElement, elements[i].value as any[])
if (found !== undefined) {
return found
}
@ -91,17 +68,15 @@ export class CustomFieldQueriesModel {
return undefined
}
public removeComponent(
queryComponent: CustomFieldQueryAtom | CustomFieldQueryExpression
) {
public removeElement(queryElement: CustomFieldQueryElement) {
let foundComponent
for (let i = 0; i < this.queries.length; i++) {
let query = this.queries[i]
if (query === queryComponent) {
if (query === queryElement) {
foundComponent = this.queries.splice(i, 1)[0]
break
} else if (query.type === CustomFieldQueryElementType.Expression) {
let found = this.findComponent(queryComponent, query.value as any[])
let found = this.findElement(queryElement, query.value as any[])
if (found !== undefined) {
foundComponent = found
}
@ -149,10 +124,14 @@ export class CustomFieldsQueryDropdownComponent {
@Input()
disabled: boolean = false
_selectionModel: CustomFieldQueriesModel = new CustomFieldQueriesModel()
private _selectionModel: CustomFieldQueriesModel =
new CustomFieldQueriesModel()
@Input()
set selectionModel(model: CustomFieldQueriesModel) {
if (this._selectionModel) {
this._selectionModel.changed.complete()
}
model.changed.subscribe((updatedModel) => {
this.selectionModelChange.next(updatedModel)
})
@ -172,6 +151,7 @@ export class CustomFieldsQueryDropdownComponent {
constructor(protected customFieldsService: CustomFieldsService) {
this.getFields()
this.reset()
}
ngOnDestroy(): void {
@ -179,6 +159,19 @@ export class CustomFieldsQueryDropdownComponent {
this.unsubscribeNotifier.complete()
}
public onOpenChange(open: boolean) {
if (open && this.selectionModel.queries.length === 0) {
this.selectionModel.addExpression()
}
}
public get isActive(): boolean {
return (
(this.selectionModel.queries[0] as CustomFieldQueryExpression)?.value
?.length > 0
)
}
private getFields() {
this.customFieldsService
.listAll()
@ -188,22 +181,23 @@ export class CustomFieldsQueryDropdownComponent {
})
}
public addAtom() {
this.selectionModel.addQuery()
public addAtom(expression: CustomFieldQueryExpression) {
expression.addAtom()
}
public addExpression() {
this.selectionModel.addExpression()
public addExpression(expression: CustomFieldQueryExpression) {
expression.addExpression()
}
public removeComponent(
component: CustomFieldQueryAtom | CustomFieldQueryExpression
) {
this.selectionModel.removeComponent(component)
this.selectionModel.removeElement(component)
}
public reset() {
this.selectionModel.clear()
this.selectionModel.clear(false)
this.selectionModel.changed.next(this.selectionModel)
}
getOperatorsForField(

View File

@ -44,6 +44,7 @@ export enum CustomFieldQueryOperatorGroups {
String = 'string',
Arithmetic = 'arithmetic',
Containment = 'containment',
Date = 'date',
}
export const CUSTOM_FIELD_QUERY_OPERATORS_BY_GROUP = {
@ -63,11 +64,15 @@ export const CUSTOM_FIELD_QUERY_OPERATORS_BY_GROUP = {
CustomFieldQueryOperator.GreaterThanOrEqual,
CustomFieldQueryOperator.LessThan,
CustomFieldQueryOperator.LessThanOrEqual,
CustomFieldQueryOperator.Range,
// CustomFieldQueryOperator.Range,
],
[CustomFieldQueryOperatorGroups.Containment]: [
CustomFieldQueryOperator.Contains,
],
[CustomFieldQueryOperatorGroups.Date]: [
CustomFieldQueryOperator.GreaterThanOrEqual,
CustomFieldQueryOperator.LessThanOrEqual,
],
}
// filters.py > SUPPORTED_EXPR_CATEGORIES
@ -82,7 +87,7 @@ export const CUSTOM_FIELD_QUERY_OPERATOR_GROUPS_BY_TYPE = {
],
[CustomFieldDataType.Date]: [
CustomFieldQueryOperatorGroups.Basic,
CustomFieldQueryOperatorGroups.Arithmetic,
CustomFieldQueryOperatorGroups.Date,
],
[CustomFieldDataType.Boolean]: [CustomFieldQueryOperatorGroups.Basic],
[CustomFieldDataType.Integer]: [
@ -232,13 +237,13 @@ export class CustomFieldQueryAtom extends CustomFieldQueryElement {
export class CustomFieldQueryExpression extends CustomFieldQueryElement {
constructor(
expressionArray: [CustomFieldQueryLogicalOperator, any[]] = [
CustomFieldQueryLogicalOperator.And,
CustomFieldQueryLogicalOperator.Or,
null,
]
) {
super(CustomFieldQueryElementType.Expression)
;[this._operator] = expressionArray
let values = expressionArray[1]
let values
;[this._operator, values] = expressionArray
if (!values) {
this._value = []
} else if (values?.length > 0 && values[0] instanceof Array) {
@ -279,4 +284,26 @@ export class CustomFieldQueryExpression extends CustomFieldQueryElement {
(this._value as any[]).every((v) => v.isValid)
)
}
public addAtom(
atom: CustomFieldQueryAtom = new CustomFieldQueryAtom([
null,
CustomFieldQueryOperator.Exists,
'true',
])
) {
;(this._value as CustomFieldQueryElement[]).push(atom)
atom.changed.subscribe(() => {
this.changed.next(this)
})
}
public addExpression(
expression: CustomFieldQueryExpression = new CustomFieldQueryExpression()
) {
;(this._value as CustomFieldQueryElement[]).push(expression)
expression.changed.subscribe(() => {
this.changed.next(this)
})
}
}