UI: Dashboard filters - user mode
This commit is contained in:
parent
25dae17671
commit
c6f7862cbf
@ -576,7 +576,7 @@ export class EntityService {
|
||||
}
|
||||
|
||||
private getEntityFieldKeys (entityType: EntityType, searchText: string): Array<string> {
|
||||
const entityFieldKeys: string[] = [];
|
||||
const entityFieldKeys: string[] = [entityFields.createdTime.keyName];
|
||||
const query = searchText.toLowerCase();
|
||||
switch(entityType) {
|
||||
case EntityType.USER:
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
-->
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="booleanFilterPredicateFormGroup">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex="40" class="mat-block">
|
||||
<mat-label></mat-label>
|
||||
<mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
|
||||
<mat-option *ngFor="let operation of booleanOperations" [value]="operation">
|
||||
@ -24,7 +24,7 @@
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-checkbox fxFlex formControlName="value">
|
||||
<mat-checkbox fxFlex="60" formControlName="value">
|
||||
{{ (booleanFilterPredicateFormGroup.get('value').value ? 'value.true' : 'value.false') | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
@ -39,8 +39,6 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, On
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@Input() userMode: boolean;
|
||||
|
||||
booleanFilterPredicateFormGroup: FormGroup;
|
||||
|
||||
booleanOperations = Object.keys(BooleanOperation);
|
||||
@ -57,9 +55,6 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, On
|
||||
operation: [BooleanOperation.EQUAL, [Validators.required]],
|
||||
value: [false]
|
||||
});
|
||||
if (this.userMode) {
|
||||
this.booleanFilterPredicateFormGroup.get('operation').disable({emitEvent: false});
|
||||
}
|
||||
this.booleanFilterPredicateFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
|
||||
@ -36,9 +36,9 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<tb-filter-predicate-list
|
||||
[userMode]="data.userMode"
|
||||
[valueType]="data.valueType"
|
||||
[operation]="complexFilterFormGroup.get('operation').value"
|
||||
[key]="data.key"
|
||||
formControlName="predicates">
|
||||
</tb-filter-predicate-list>
|
||||
</fieldset>
|
||||
|
||||
@ -24,14 +24,14 @@ import { Router } from '@angular/router';
|
||||
import { DialogComponent } from '@app/shared/components/dialog.component';
|
||||
import {
|
||||
BooleanOperation, booleanOperationTranslationMap,
|
||||
ComplexFilterPredicate, ComplexOperation, complexOperationTranslationMap,
|
||||
ComplexFilterPredicate, ComplexFilterPredicateInfo, ComplexOperation, complexOperationTranslationMap,
|
||||
EntityKeyValueType,
|
||||
FilterPredicateType
|
||||
FilterPredicateType, KeyFilterPredicateInfo
|
||||
} from '@shared/models/query/query.models';
|
||||
|
||||
export interface ComplexFilterPredicateDialogData {
|
||||
complexPredicate: ComplexFilterPredicate;
|
||||
userMode: boolean;
|
||||
complexPredicate: ComplexFilterPredicateInfo;
|
||||
key: string;
|
||||
disabled: boolean;
|
||||
isAdd: boolean;
|
||||
valueType: EntityKeyValueType;
|
||||
@ -44,7 +44,7 @@ export interface ComplexFilterPredicateDialogData {
|
||||
styleUrls: []
|
||||
})
|
||||
export class ComplexFilterPredicateDialogComponent extends
|
||||
DialogComponent<ComplexFilterPredicateDialogComponent, ComplexFilterPredicate>
|
||||
DialogComponent<ComplexFilterPredicateDialogComponent, ComplexFilterPredicateInfo>
|
||||
implements OnInit, ErrorStateMatcher {
|
||||
|
||||
complexFilterFormGroup: FormGroup;
|
||||
@ -61,7 +61,7 @@ export class ComplexFilterPredicateDialogComponent extends
|
||||
protected router: Router,
|
||||
@Inject(MAT_DIALOG_DATA) public data: ComplexFilterPredicateDialogData,
|
||||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
|
||||
public dialogRef: MatDialogRef<ComplexFilterPredicateDialogComponent, ComplexFilterPredicate>,
|
||||
public dialogRef: MatDialogRef<ComplexFilterPredicateDialogComponent, ComplexFilterPredicateInfo>,
|
||||
private fb: FormBuilder) {
|
||||
super(store, router, dialogRef);
|
||||
|
||||
@ -91,7 +91,7 @@ export class ComplexFilterPredicateDialogComponent extends
|
||||
save(): void {
|
||||
this.submitted = true;
|
||||
if (this.complexFilterFormGroup.valid) {
|
||||
const predicate: ComplexFilterPredicate = this.complexFilterFormGroup.getRawValue();
|
||||
const predicate: ComplexFilterPredicateInfo = this.complexFilterFormGroup.getRawValue();
|
||||
predicate.type = FilterPredicateType.COMPLEX;
|
||||
this.dialogRef.close(predicate);
|
||||
}
|
||||
|
||||
@ -16,7 +16,11 @@
|
||||
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ComplexFilterPredicate, EntityKeyValueType } from '@shared/models/query/query.models';
|
||||
import {
|
||||
ComplexFilterPredicate,
|
||||
ComplexFilterPredicateInfo,
|
||||
EntityKeyValueType
|
||||
} from '@shared/models/query/query.models';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import {
|
||||
ComplexFilterPredicateDialogComponent,
|
||||
@ -40,13 +44,13 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@Input() userMode: boolean;
|
||||
|
||||
@Input() valueType: EntityKeyValueType;
|
||||
|
||||
@Input() key: string;
|
||||
|
||||
private propagateChange = null;
|
||||
|
||||
private complexFilterPredicate: ComplexFilterPredicate;
|
||||
private complexFilterPredicate: ComplexFilterPredicateInfo;
|
||||
|
||||
constructor(private dialog: MatDialog) {
|
||||
}
|
||||
@ -65,21 +69,21 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
writeValue(predicate: ComplexFilterPredicate): void {
|
||||
writeValue(predicate: ComplexFilterPredicateInfo): void {
|
||||
this.complexFilterPredicate = predicate;
|
||||
}
|
||||
|
||||
private openComplexFilterDialog() {
|
||||
this.dialog.open<ComplexFilterPredicateDialogComponent, ComplexFilterPredicateDialogData,
|
||||
ComplexFilterPredicate>(ComplexFilterPredicateDialogComponent, {
|
||||
ComplexFilterPredicateInfo>(ComplexFilterPredicateDialogComponent, {
|
||||
disableClose: true,
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
||||
data: {
|
||||
complexPredicate: deepClone(this.complexFilterPredicate),
|
||||
disabled: this.disabled,
|
||||
userMode: this.userMode,
|
||||
valueType: this.valueType,
|
||||
isAdd: false
|
||||
isAdd: false,
|
||||
key: this.key
|
||||
}
|
||||
}).afterClosed().subscribe(
|
||||
(result) => {
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
-->
|
||||
<form [formGroup]="filterFormGroup" (ngSubmit)="save()" style="width: 700px;">
|
||||
<mat-toolbar color="primary">
|
||||
<h2>{{ userMode ? filter.filter : ((isAdd ? 'filter.add' : 'filter.edit') | translate) }}</h2>
|
||||
<h2>{{ (isAdd ? 'filter.add' : 'filter.edit') | translate }}</h2>
|
||||
<span fxFlex></span>
|
||||
<button mat-icon-button
|
||||
(click)="cancel()"
|
||||
@ -30,7 +30,7 @@
|
||||
<div mat-dialog-content>
|
||||
<fieldset [disabled]="isLoading$ | async">
|
||||
<div fxFlex fxLayout="column">
|
||||
<div fxLayout="row" [fxShow]="!userMode">
|
||||
<div fxLayout="row">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>filter.name</mat-label>
|
||||
<input matInput formControlName="filter" required>
|
||||
@ -49,8 +49,7 @@
|
||||
</section>
|
||||
</div>
|
||||
<tb-key-filter-list
|
||||
formControlName="keyFilters"
|
||||
[userMode]="userMode">
|
||||
formControlName="keyFilters">
|
||||
</tb-key-filter-list>
|
||||
</div>
|
||||
</fieldset>
|
||||
@ -59,7 +58,7 @@
|
||||
<button mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="(isLoading$ | async) || filterFormGroup.invalid || !filterFormGroup.dirty">
|
||||
{{ (userMode ? 'action.update' : (isAdd ? 'action.add' : 'action.update')) | translate }}
|
||||
{{ (isAdd ? 'action.add' : 'action.update') | translate }}
|
||||
</button>
|
||||
<button mat-button color="primary"
|
||||
type="button"
|
||||
|
||||
@ -36,7 +36,6 @@ import { Filter, Filters } from '@shared/models/query/query.models';
|
||||
|
||||
export interface FilterDialogData {
|
||||
isAdd: boolean;
|
||||
userMode: boolean;
|
||||
filters: Filters | Array<Filter>;
|
||||
filter?: Filter;
|
||||
}
|
||||
@ -51,7 +50,6 @@ export class FilterDialogComponent extends DialogComponent<FilterDialogComponent
|
||||
implements OnInit, ErrorStateMatcher {
|
||||
|
||||
isAdd: boolean;
|
||||
userMode: boolean;
|
||||
filters: Array<Filter>;
|
||||
|
||||
filter: Filter;
|
||||
@ -70,7 +68,6 @@ export class FilterDialogComponent extends DialogComponent<FilterDialogComponent
|
||||
public translate: TranslateService) {
|
||||
super(store, router, dialogRef);
|
||||
this.isAdd = data.isAdd;
|
||||
this.userMode = data.userMode;
|
||||
if (Array.isArray(data.filters)) {
|
||||
this.filters = data.filters;
|
||||
} else {
|
||||
|
||||
@ -25,13 +25,16 @@
|
||||
<div fxLayout="row">
|
||||
<span fxFlex="8"></span>
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex="92">
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<label fxFlex translate class="tb-title no-padding">filter.operation.operation</label>
|
||||
<label *ngIf="valueType === valueTypeEnum.STRING"
|
||||
translate class="tb-title no-padding" style="min-width: 70px;">filter.ignore-case</label>
|
||||
<div fxFlex fxLayout="row" fxLayoutGap="8px">
|
||||
<div fxFlex="40" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<label fxFlex translate class="tb-title no-padding">filter.operation.operation</label>
|
||||
<label *ngIf="valueType === valueTypeEnum.STRING"
|
||||
translate class="tb-title no-padding" style="min-width: 70px;">filter.ignore-case</label>
|
||||
</div>
|
||||
<label fxFlex="60" translate class="tb-title no-padding">filter.value</label>
|
||||
</div>
|
||||
<label fxFlex translate class="tb-title no-padding">filter.value</label>
|
||||
<span [fxShow]="!disabled && !userMode" style="min-width: 40px;"> </span>
|
||||
<label translate class="tb-title no-padding" style="width: 60px;">filter.user-parameters</label>
|
||||
<span [fxShow]="!disabled" style="min-width: 40px;"> </span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
@ -46,12 +49,12 @@
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex>
|
||||
<tb-filter-predicate
|
||||
fxFlex
|
||||
[userMode]="userMode"
|
||||
[valueType]="valueType"
|
||||
[key]="key"
|
||||
[formControl]="predicateControl">
|
||||
</tb-filter-predicate>
|
||||
<button mat-icon-button color="primary"
|
||||
[fxShow]="!disabled && !userMode"
|
||||
[fxShow]="!disabled"
|
||||
type="button"
|
||||
(click)="removePredicate($index)"
|
||||
matTooltip="{{ 'filter.remove-filter' | translate }}"
|
||||
@ -67,7 +70,7 @@
|
||||
</div>
|
||||
<div style="margin-top: 16px;" fxLayout="row" fxLayoutGap="8px">
|
||||
<button mat-button mat-raised-button color="primary"
|
||||
[fxShow]="!disabled && !userMode"
|
||||
[fxShow]="!disabled"
|
||||
(click)="addPredicate(false)"
|
||||
type="button"
|
||||
matTooltip="{{ 'filter.add-filter' | translate }}"
|
||||
@ -75,7 +78,7 @@
|
||||
{{ 'action.add' | translate }}
|
||||
</button>
|
||||
<button mat-button mat-raised-button color="primary"
|
||||
[fxShow]="!disabled && !userMode"
|
||||
[fxShow]="!disabled"
|
||||
(click)="addPredicate(true)"
|
||||
type="button"
|
||||
matTooltip="{{ 'filter.add-complex-filter' | translate }}"
|
||||
|
||||
@ -26,17 +26,19 @@ import {
|
||||
} from '@angular/forms';
|
||||
import { Observable, of, Subscription } from 'rxjs';
|
||||
import {
|
||||
ComplexFilterPredicate,
|
||||
ComplexOperation, complexOperationTranslationMap,
|
||||
createDefaultFilterPredicate,
|
||||
ComplexFilterPredicateInfo,
|
||||
ComplexOperation,
|
||||
complexOperationTranslationMap,
|
||||
createDefaultFilterPredicateInfo,
|
||||
EntityKeyValueType,
|
||||
KeyFilterPredicate
|
||||
KeyFilterPredicateInfo
|
||||
} from '@shared/models/query/query.models';
|
||||
import {
|
||||
ComplexFilterPredicateDialogComponent,
|
||||
ComplexFilterPredicateDialogData
|
||||
} from '@home/components/filter/complex-filter-predicate-dialog.component';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-filter-predicate-list',
|
||||
@ -54,10 +56,10 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@Input() userMode: boolean;
|
||||
|
||||
@Input() valueType: EntityKeyValueType;
|
||||
|
||||
@Input() key: string;
|
||||
|
||||
@Input() operation: ComplexOperation = ComplexOperation.AND;
|
||||
|
||||
filterListFormGroup: FormGroup;
|
||||
@ -100,7 +102,7 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(predicates: Array<KeyFilterPredicate>): void {
|
||||
writeValue(predicates: Array<KeyFilterPredicateInfo>): void {
|
||||
if (this.valueChangeSubscription) {
|
||||
this.valueChangeSubscription.unsubscribe();
|
||||
}
|
||||
@ -127,10 +129,10 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
|
||||
|
||||
public addPredicate(complex: boolean) {
|
||||
const predicatesFormArray = this.filterListFormGroup.get('predicates') as FormArray;
|
||||
const predicate = createDefaultFilterPredicate(this.valueType, complex);
|
||||
let observable: Observable<KeyFilterPredicate>;
|
||||
const predicate = createDefaultFilterPredicateInfo(this.valueType, complex);
|
||||
let observable: Observable<KeyFilterPredicateInfo>;
|
||||
if (complex) {
|
||||
observable = this.openComplexFilterDialog(predicate as ComplexFilterPredicate);
|
||||
observable = this.openComplexFilterDialog(predicate);
|
||||
} else {
|
||||
observable = of(predicate);
|
||||
}
|
||||
@ -141,24 +143,33 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
|
||||
});
|
||||
}
|
||||
|
||||
private openComplexFilterDialog(predicate: ComplexFilterPredicate): Observable<KeyFilterPredicate> {
|
||||
private openComplexFilterDialog(predicate: KeyFilterPredicateInfo): Observable<KeyFilterPredicateInfo> {
|
||||
return this.dialog.open<ComplexFilterPredicateDialogComponent, ComplexFilterPredicateDialogData,
|
||||
ComplexFilterPredicate>(ComplexFilterPredicateDialogComponent, {
|
||||
ComplexFilterPredicateInfo>(ComplexFilterPredicateDialogComponent, {
|
||||
disableClose: true,
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
||||
data: {
|
||||
complexPredicate: predicate,
|
||||
complexPredicate: predicate.keyFilterPredicate as ComplexFilterPredicateInfo,
|
||||
disabled: this.disabled,
|
||||
userMode: this.userMode,
|
||||
valueType: this.valueType,
|
||||
key: this.key,
|
||||
isAdd: true
|
||||
}
|
||||
}).afterClosed();
|
||||
}).afterClosed().pipe(
|
||||
map((result) => {
|
||||
if (result) {
|
||||
predicate.keyFilterPredicate = result;
|
||||
return predicate;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
const predicates: Array<KeyFilterPredicate> = this.filterListFormGroup.getRawValue().predicates;
|
||||
if (predicates.length) {
|
||||
const predicates: Array<KeyFilterPredicateInfo> = this.filterListFormGroup.getRawValue().predicates;
|
||||
if (this.filterListFormGroup.valid && predicates.length) {
|
||||
this.propagateChange(predicates);
|
||||
} else {
|
||||
this.propagateChange(null);
|
||||
|
||||
@ -15,28 +15,36 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div fxLayout="column" [formGroup]="filterPredicateFormGroup"
|
||||
[ngSwitch]="type">
|
||||
<ng-template [ngSwitchCase]="filterPredicateType.STRING">
|
||||
<tb-string-filter-predicate [userMode]="userMode"
|
||||
formControlName="predicate">
|
||||
</tb-string-filter-predicate>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="filterPredicateType.NUMERIC">
|
||||
<tb-numeric-filter-predicate [userMode]="userMode"
|
||||
formControlName="predicate">
|
||||
</tb-numeric-filter-predicate>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="filterPredicateType.BOOLEAN">
|
||||
<tb-boolean-filter-predicate [userMode]="userMode"
|
||||
formControlName="predicate">
|
||||
</tb-boolean-filter-predicate>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="filterPredicateType.COMPLEX">
|
||||
<tb-complex-filter-predicate
|
||||
[valueType]="valueType"
|
||||
[userMode]="userMode"
|
||||
formControlName="predicate">
|
||||
</tb-complex-filter-predicate>
|
||||
</ng-template>
|
||||
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="filterPredicateFormGroup">
|
||||
<div fxFlex fxLayout="column" [ngSwitch]="type">
|
||||
<ng-template [ngSwitchCase]="filterPredicateType.STRING">
|
||||
<tb-string-filter-predicate formControlName="predicate">
|
||||
</tb-string-filter-predicate>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="filterPredicateType.NUMERIC">
|
||||
<tb-numeric-filter-predicate [valueType]="valueType"
|
||||
formControlName="predicate">
|
||||
</tb-numeric-filter-predicate>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="filterPredicateType.BOOLEAN">
|
||||
<tb-boolean-filter-predicate formControlName="predicate">
|
||||
</tb-boolean-filter-predicate>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="filterPredicateType.COMPLEX">
|
||||
<tb-complex-filter-predicate
|
||||
[key]="key"
|
||||
[valueType]="valueType"
|
||||
formControlName="predicate">
|
||||
</tb-complex-filter-predicate>
|
||||
</ng-template>
|
||||
</div>
|
||||
<tb-filter-user-info *ngIf="type !== filterPredicateType.COMPLEX"
|
||||
style="width: 60px;"
|
||||
fxLayout="row" fxLayoutAlign="center"
|
||||
[valueType]="valueType"
|
||||
[operation]="filterPredicateFormGroup.get('predicate').value?.operation"
|
||||
[key]="key"
|
||||
formControlName="userInfo">
|
||||
</tb-filter-user-info>
|
||||
</div>
|
||||
|
||||
@ -18,7 +18,7 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
|
||||
import {
|
||||
EntityKeyValueType,
|
||||
FilterPredicateType, KeyFilterPredicate
|
||||
FilterPredicateType, KeyFilterPredicate, KeyFilterPredicateInfo
|
||||
} from '@shared/models/query/query.models';
|
||||
|
||||
@Component({
|
||||
@ -37,10 +37,10 @@ export class FilterPredicateComponent implements ControlValueAccessor, OnInit {
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@Input() userMode: boolean;
|
||||
|
||||
@Input() valueType: EntityKeyValueType;
|
||||
|
||||
@Input() key: string;
|
||||
|
||||
filterPredicateFormGroup: FormGroup;
|
||||
|
||||
type: FilterPredicateType;
|
||||
@ -54,7 +54,8 @@ export class FilterPredicateComponent implements ControlValueAccessor, OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.filterPredicateFormGroup = this.fb.group({
|
||||
predicate: [null, [Validators.required]]
|
||||
predicate: [null, [Validators.required]],
|
||||
userInfo: [null, []]
|
||||
});
|
||||
this.filterPredicateFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
@ -77,15 +78,19 @@ export class FilterPredicateComponent implements ControlValueAccessor, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(predicate: KeyFilterPredicate): void {
|
||||
this.type = predicate.type;
|
||||
this.filterPredicateFormGroup.get('predicate').patchValue(predicate, {emitEvent: false});
|
||||
writeValue(predicate: KeyFilterPredicateInfo): void {
|
||||
this.type = predicate.keyFilterPredicate.type;
|
||||
this.filterPredicateFormGroup.get('predicate').patchValue(predicate.keyFilterPredicate, {emitEvent: false});
|
||||
this.filterPredicateFormGroup.get('userInfo').patchValue(predicate.userInfo, {emitEvent: false});
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
let predicate: KeyFilterPredicate = null;
|
||||
let predicate: KeyFilterPredicateInfo = null;
|
||||
if (this.filterPredicateFormGroup.valid) {
|
||||
predicate = this.filterPredicateFormGroup.getRawValue().predicate;
|
||||
predicate = {
|
||||
keyFilterPredicate: this.filterPredicateFormGroup.getRawValue().predicate,
|
||||
userInfo: this.filterPredicateFormGroup.getRawValue().userInfo
|
||||
};
|
||||
}
|
||||
this.propagateChange(predicate);
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
.mat-form-field-infix {
|
||||
border-top-width: 0.2em;
|
||||
width: auto;
|
||||
min-width: auto;
|
||||
}
|
||||
.mat-form-field-underline {
|
||||
bottom: 0;
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2020 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<form [formGroup]="filterUserInfoFormGroup" (ngSubmit)="save()" style="width: 500px;">
|
||||
<mat-toolbar color="primary">
|
||||
<h2 translate>filter.edit-filter-user-params</h2>
|
||||
<span fxFlex></span>
|
||||
<button mat-icon-button
|
||||
(click)="cancel()"
|
||||
type="button">
|
||||
<mat-icon class="material-icons">close</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
<div mat-dialog-content>
|
||||
<fieldset [disabled]="isLoading$ | async" fxLayout="column">
|
||||
<mat-checkbox fxFlex formControlName="editable" style="margin-bottom: 16px;">
|
||||
{{ 'filter.editable' | translate }}
|
||||
</mat-checkbox>
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>filter.display-label</mat-label>
|
||||
<input matInput formControlName="label">
|
||||
</mat-form-field>
|
||||
<mat-checkbox formControlName="autogeneratedLabel">
|
||||
{{ 'filter.autogenerated-label' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>filter.order-priority</mat-label>
|
||||
<input matInput type="number" formControlName="order">
|
||||
</mat-form-field>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayoutAlign="end center">
|
||||
<button mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="(isLoading$ | async) || filterUserInfoFormGroup.invalid || !filterUserInfoFormGroup.dirty">
|
||||
{{ 'action.update' | translate }}
|
||||
</button>
|
||||
<button mat-button color="primary"
|
||||
type="button"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()"
|
||||
cdkFocusInitial>
|
||||
{{ 'action.cancel' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -0,0 +1,108 @@
|
||||
///
|
||||
/// Copyright © 2016-2020 The Thingsboard Authors
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
|
||||
import { ErrorStateMatcher } from '@angular/material/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { DialogComponent } from '@app/shared/components/dialog.component';
|
||||
import {
|
||||
BooleanOperation,
|
||||
EntityKeyValueType, generateUserFilterValueLabel,
|
||||
KeyFilterPredicateUserInfo, NumericOperation,
|
||||
StringOperation
|
||||
} from '@shared/models/query/query.models';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
export interface FilterUserInfoDialogData {
|
||||
key: string;
|
||||
valueType: EntityKeyValueType;
|
||||
operation: StringOperation | BooleanOperation | NumericOperation;
|
||||
keyFilterPredicateUserInfo: KeyFilterPredicateUserInfo;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'tb-filter-user-info-dialog',
|
||||
templateUrl: './filter-user-info-dialog.component.html',
|
||||
providers: [{provide: ErrorStateMatcher, useExisting: FilterUserInfoDialogComponent}],
|
||||
styleUrls: []
|
||||
})
|
||||
export class FilterUserInfoDialogComponent extends
|
||||
DialogComponent<FilterUserInfoDialogComponent, KeyFilterPredicateUserInfo>
|
||||
implements OnInit, ErrorStateMatcher {
|
||||
|
||||
filterUserInfoFormGroup: FormGroup;
|
||||
|
||||
submitted = false;
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
protected router: Router,
|
||||
@Inject(MAT_DIALOG_DATA) public data: FilterUserInfoDialogData,
|
||||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
|
||||
public dialogRef: MatDialogRef<FilterUserInfoDialogComponent, KeyFilterPredicateUserInfo>,
|
||||
private fb: FormBuilder,
|
||||
private translate: TranslateService) {
|
||||
super(store, router, dialogRef);
|
||||
|
||||
this.filterUserInfoFormGroup = this.fb.group(
|
||||
{
|
||||
editable: [this.data.keyFilterPredicateUserInfo.editable],
|
||||
label: [this.data.keyFilterPredicateUserInfo.label],
|
||||
autogeneratedLabel: [this.data.keyFilterPredicateUserInfo.autogeneratedLabel],
|
||||
order: [this.data.keyFilterPredicateUserInfo.order]
|
||||
}
|
||||
);
|
||||
this.onAutogeneratedLabelChange();
|
||||
this.filterUserInfoFormGroup.get('autogeneratedLabel').valueChanges.subscribe(() => {
|
||||
this.onAutogeneratedLabelChange();
|
||||
});
|
||||
}
|
||||
|
||||
private onAutogeneratedLabelChange() {
|
||||
const autogeneratedLabel: boolean = this.filterUserInfoFormGroup.get('autogeneratedLabel').value;
|
||||
if (autogeneratedLabel) {
|
||||
const generatedLabel = generateUserFilterValueLabel(this.data.key, this.data.valueType, this.data.operation, this.translate);
|
||||
this.filterUserInfoFormGroup.get('label').patchValue(generatedLabel, {emitEvent: false});
|
||||
this.filterUserInfoFormGroup.get('label').disable({emitEvent: false});
|
||||
} else {
|
||||
this.filterUserInfoFormGroup.get('label').enable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
||||
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
|
||||
const customErrorState = !!(control && control.invalid && this.submitted);
|
||||
return originalErrorState || customErrorState;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(null);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.submitted = true;
|
||||
if (this.filterUserInfoFormGroup.valid) {
|
||||
const keyFilterPredicateUserInfo: KeyFilterPredicateUserInfo = this.filterUserInfoFormGroup.getRawValue();
|
||||
this.dialogRef.close(keyFilterPredicateUserInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2020 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<button mat-icon-button color="primary"
|
||||
class="tb-mat-32"
|
||||
[fxShow]="!disabled"
|
||||
type="button"
|
||||
(click)="openFilterUserInfoDialog()"
|
||||
matTooltip="{{ 'filter.edit-filter-user-params' | translate }}"
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>settings</mat-icon>
|
||||
</button>
|
||||
@ -0,0 +1,104 @@
|
||||
///
|
||||
/// Copyright © 2016-2020 The Thingsboard Authors
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import {
|
||||
BooleanOperation,
|
||||
EntityKeyValueType,
|
||||
KeyFilterPredicateUserInfo, NumericOperation,
|
||||
StringOperation
|
||||
} from '@shared/models/query/query.models';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import {
|
||||
FilterUserInfoDialogComponent,
|
||||
FilterUserInfoDialogData
|
||||
} from '@home/components/filter/filter-user-info-dialog.component';
|
||||
import { deepClone } from '@core/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-filter-user-info',
|
||||
templateUrl: './filter-user-info.component.html',
|
||||
styleUrls: [],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => FilterUserInfoComponent),
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class FilterUserInfoComponent implements ControlValueAccessor, OnInit {
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@Input() key: string;
|
||||
|
||||
@Input() operation: StringOperation | BooleanOperation | NumericOperation;
|
||||
|
||||
@Input() valueType: EntityKeyValueType;
|
||||
|
||||
private propagateChange = null;
|
||||
|
||||
private keyFilterPredicateUserInfo: KeyFilterPredicateUserInfo;
|
||||
|
||||
constructor(private dialog: MatDialog) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
}
|
||||
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
writeValue(keyFilterPredicateUserInfo: KeyFilterPredicateUserInfo): void {
|
||||
this.keyFilterPredicateUserInfo = keyFilterPredicateUserInfo;
|
||||
}
|
||||
|
||||
private openFilterUserInfoDialog() {
|
||||
this.dialog.open<FilterUserInfoDialogComponent, FilterUserInfoDialogData,
|
||||
KeyFilterPredicateUserInfo>(FilterUserInfoDialogComponent, {
|
||||
disableClose: true,
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
||||
data: {
|
||||
keyFilterPredicateUserInfo: deepClone(this.keyFilterPredicateUserInfo),
|
||||
valueType: this.valueType,
|
||||
key: this.key,
|
||||
operation: this.operation
|
||||
}
|
||||
}).afterClosed().subscribe(
|
||||
(result) => {
|
||||
if (result) {
|
||||
this.keyFilterPredicateUserInfo = result;
|
||||
this.updateModel();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
this.propagateChange(this.keyFilterPredicateUserInfo);
|
||||
}
|
||||
|
||||
}
|
||||
@ -184,8 +184,7 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
|
||||
data: {
|
||||
isAdd,
|
||||
filters: filtersArray,
|
||||
filter: isAdd ? null : deepClone(filter),
|
||||
userMode: false
|
||||
filter: isAdd ? null : deepClone(filter)
|
||||
}
|
||||
}).afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
|
||||
@ -18,8 +18,8 @@ import { Component, Inject, InjectionToken } from '@angular/core';
|
||||
import { IAliasController } from '@core/api/widget-api.models';
|
||||
import { Filter, FilterInfo } from '@shared/models/query/query.models';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component';
|
||||
import { deepClone } from '@core/utils';
|
||||
import { UserFilterDialogComponent, UserFilterDialogData } from '@home/components/filter/user-filter-dialog.component';
|
||||
|
||||
export const FILTER_EDIT_PANEL_DATA = new InjectionToken<any>('FiltersEditPanelData');
|
||||
|
||||
@ -44,15 +44,12 @@ export class FiltersEditPanelComponent {
|
||||
|
||||
public editFilter(filterId: string, filter: FilterInfo) {
|
||||
const singleFilter: Filter = {id: filterId, ...deepClone(filter)};
|
||||
this.dialog.open<FilterDialogComponent, FilterDialogData,
|
||||
Filter>(FilterDialogComponent, {
|
||||
this.dialog.open<UserFilterDialogComponent, UserFilterDialogData,
|
||||
Filter>(UserFilterDialogComponent, {
|
||||
disableClose: true,
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
||||
data: {
|
||||
isAdd: false,
|
||||
filters: [],
|
||||
filter: singleFilter,
|
||||
userMode: true
|
||||
filter: singleFilter
|
||||
}
|
||||
}).afterClosed().subscribe(
|
||||
(result) => {
|
||||
|
||||
@ -22,7 +22,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
import { deepClone } from '@core/utils';
|
||||
import { FilterInfo } from '@shared/models/query/query.models';
|
||||
import { FilterInfo, isFilterEditable } from '@shared/models/query/query.models';
|
||||
import {
|
||||
FILTER_EDIT_PANEL_DATA,
|
||||
FiltersEditPanelComponent,
|
||||
@ -144,7 +144,7 @@ export class FiltersEditComponent implements OnInit, OnDestroy {
|
||||
this.hasEditableFilters = false;
|
||||
for (const filterId of Object.keys(allFilters)) {
|
||||
const filterInfo = this.aliasController.getFilterInfo(filterId);
|
||||
if (filterInfo && filterInfo.editable) {
|
||||
if (filterInfo && isFilterEditable(filterInfo)) {
|
||||
this.filtersInfo[filterId] = deepClone(filterInfo);
|
||||
this.hasEditableFilters = true;
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
-->
|
||||
<form [formGroup]="keyFilterFormGroup" (ngSubmit)="save()" style="width: 700px;">
|
||||
<mat-toolbar color="primary">
|
||||
<h2>{{data.userMode ? data.keyFilter.key.key : ((data.isAdd ? 'filter.add-key-filter' : 'filter.edit-key-filter') | translate)}}</h2>
|
||||
<h2>{{(data.isAdd ? 'filter.add-key-filter' : 'filter.edit-key-filter') | translate}}</h2>
|
||||
<span fxFlex></span>
|
||||
<button mat-icon-button
|
||||
(click)="cancel()"
|
||||
@ -27,7 +27,7 @@
|
||||
</mat-toolbar>
|
||||
<div mat-dialog-content>
|
||||
<fieldset [disabled]="isLoading$ | async" fxLayout="column">
|
||||
<section fxLayout="row" fxLayoutGap="8px" class="entity-key" [fxShow]="!data.userMode">
|
||||
<section fxLayout="row" fxLayoutGap="8px" class="entity-key">
|
||||
<section fxFlex="70" fxLayout="row" formGroupName="key" fxLayoutGap="8px">
|
||||
<mat-form-field fxFlex="60" class="mat-block">
|
||||
<mat-label translate>filter.key-name</mat-label>
|
||||
@ -63,8 +63,8 @@
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<tb-filter-predicate-list *ngIf="keyFilterFormGroup.get('valueType').value"
|
||||
[userMode]="data.userMode"
|
||||
[valueType]="keyFilterFormGroup.get('valueType').value"
|
||||
[key]="keyFilterFormGroup.get('key.key').value"
|
||||
formControlName="predicates">
|
||||
</tb-filter-predicate-list>
|
||||
</fieldset>
|
||||
|
||||
@ -34,7 +34,6 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
export interface KeyFilterDialogData {
|
||||
keyFilter: KeyFilterInfo;
|
||||
userMode: boolean;
|
||||
isAdd: boolean;
|
||||
}
|
||||
|
||||
|
||||
@ -27,8 +27,8 @@
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxFlex="92">
|
||||
<label fxFlex translate class="tb-title no-padding">filter.key-name</label>
|
||||
<label fxFlex translate class="tb-title no-padding">filter.key-type.key-type</label>
|
||||
<span [fxShow]="!disabled && !userMode" style="min-width: 80px;"> </span>
|
||||
<span [fxShow]="disabled || userMode" style="min-width: 40px;"> </span>
|
||||
<span [fxShow]="!disabled" style="min-width: 80px;"> </span>
|
||||
<span [fxShow]="disabled" style="min-width: 40px;"> </span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
@ -51,7 +51,7 @@
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="primary"
|
||||
[fxShow]="!disabled && !userMode"
|
||||
[fxShow]="!disabled"
|
||||
type="button"
|
||||
(click)="removeKeyFilter($index)"
|
||||
matTooltip="{{ 'filter.remove-key-filter' | translate }}"
|
||||
@ -68,7 +68,7 @@
|
||||
</div>
|
||||
<div style="margin-top: 16px;">
|
||||
<button mat-button mat-raised-button color="primary"
|
||||
[fxShow]="!disabled && !userMode"
|
||||
[fxShow]="!disabled"
|
||||
(click)="addKeyFilter()"
|
||||
type="button"
|
||||
matTooltip="{{ 'filter.add-key-filter' | translate }}"
|
||||
|
||||
@ -46,8 +46,6 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@Input() userMode: boolean;
|
||||
|
||||
keyFilterListFormGroup: FormGroup;
|
||||
|
||||
entityKeyTypeTranslations = entityKeyTypeTranslationMap;
|
||||
@ -150,7 +148,6 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
||||
data: {
|
||||
keyFilter: keyFilter ? deepClone(keyFilter): null,
|
||||
userMode: this.userMode,
|
||||
isAdd
|
||||
}
|
||||
}).afterClosed();
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
-->
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="numericFilterPredicateFormGroup">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex="40" class="mat-block">
|
||||
<mat-label></mat-label>
|
||||
<mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
|
||||
<mat-option *ngFor="let operation of numericOperations" [value]="operation">
|
||||
@ -24,9 +24,19 @@
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
|
||||
<mat-label></mat-label>
|
||||
<input required type="number" matInput formControlName="value"
|
||||
placeholder="{{'filter.value' | translate}}">
|
||||
</mat-form-field>
|
||||
<div fxFlex="60" fxLayout="column" [ngSwitch]="valueType">
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
|
||||
<mat-label></mat-label>
|
||||
<input required type="number" matInput formControlName="value"
|
||||
placeholder="{{'filter.value' | translate}}">
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
|
||||
<tb-datetime fxFlex formControlName="value"
|
||||
dateText="filter.date"
|
||||
timeText="filter.time"
|
||||
required [showLabel]="false"></tb-datetime>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
|
||||
import {
|
||||
EntityKeyValueType,
|
||||
FilterPredicateType, NumericFilterPredicate, NumericOperation, numericOperationTranslationMap,
|
||||
} from '@shared/models/query/query.models';
|
||||
import { isDefined } from '@core/utils';
|
||||
@ -37,10 +38,12 @@ export class NumericFilterPredicateComponent implements ControlValueAccessor, On
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@Input() userMode: boolean;
|
||||
@Input() valueType: EntityKeyValueType;
|
||||
|
||||
numericFilterPredicateFormGroup: FormGroup;
|
||||
|
||||
valueTypeEnum = EntityKeyValueType;
|
||||
|
||||
numericOperations = Object.keys(NumericOperation);
|
||||
numericOperationEnum = NumericOperation;
|
||||
numericOperationTranslations = numericOperationTranslationMap;
|
||||
@ -55,9 +58,6 @@ export class NumericFilterPredicateComponent implements ControlValueAccessor, On
|
||||
operation: [NumericOperation.EQUAL, [Validators.required]],
|
||||
value: [0, [Validators.required]]
|
||||
});
|
||||
if (this.userMode) {
|
||||
this.numericFilterPredicateFormGroup.get('operation').disable({emitEvent: false});
|
||||
}
|
||||
this.numericFilterPredicateFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
-->
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="stringFilterPredicateFormGroup">
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<div fxFlex="40" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
|
||||
<mat-label></mat-label>
|
||||
<mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
|
||||
@ -28,7 +28,7 @@
|
||||
<mat-checkbox fxLayout="row" fxLayoutAlign="center" formControlName="ignoreCase" style="min-width: 70px;">
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex="60" class="mat-block">
|
||||
<mat-label></mat-label>
|
||||
<input matInput formControlName="value" placeholder="{{'filter.value' | translate}}">
|
||||
</mat-form-field>
|
||||
|
||||
@ -39,8 +39,6 @@ export class StringFilterPredicateComponent implements ControlValueAccessor, OnI
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@Input() userMode: boolean;
|
||||
|
||||
stringFilterPredicateFormGroup: FormGroup;
|
||||
|
||||
stringOperations = Object.keys(StringOperation);
|
||||
@ -58,10 +56,6 @@ export class StringFilterPredicateComponent implements ControlValueAccessor, OnI
|
||||
value: [''],
|
||||
ignoreCase: [false]
|
||||
});
|
||||
if (this.userMode) {
|
||||
this.stringFilterPredicateFormGroup.get('operation').disable({emitEvent: false});
|
||||
this.stringFilterPredicateFormGroup.get('ignoreCase').disable({emitEvent: false});
|
||||
}
|
||||
this.stringFilterPredicateFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2020 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<form [formGroup]="userFilterFormGroup" (ngSubmit)="save()" style="width: 400px;">
|
||||
<mat-toolbar color="primary">
|
||||
<h2>{{ filter.filter }}</h2>
|
||||
<span fxFlex></span>
|
||||
<button mat-icon-button
|
||||
(click)="cancel()"
|
||||
type="button">
|
||||
<mat-icon class="material-icons">close</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
|
||||
</mat-progress-bar>
|
||||
<div mat-dialog-content>
|
||||
<fieldset [disabled]="isLoading$ | async">
|
||||
<div fxFlex fxLayout="column">
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center"
|
||||
formArrayName="userInputs"
|
||||
*ngFor="let userInputControl of userInputsFormArray().controls; let $index = index">
|
||||
<div fxFlex fxLayout="column"
|
||||
[ngSwitch]="userInputControl.get('valueType').value">
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.STRING">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label>{{ userInputControl.get('label').value }}</mat-label>
|
||||
<input matInput [formControl]="userInputControl.get('value')">
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label>{{ userInputControl.get('label').value }}</mat-label>
|
||||
<input required type="number" matInput [formControl]="userInputControl.get('value')">
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
|
||||
<label class="tb-title no-padding tb-required">{{ userInputControl.get('label').value }}</label>
|
||||
<tb-datetime fxFlex [formControl]="userInputControl.get('value')"
|
||||
dateText="filter.date"
|
||||
timeText="filter.time"
|
||||
required [showLabel]="false"></tb-datetime>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
|
||||
<mat-checkbox labelPosition="before" fxFlex [formControl]="userInputControl.get('value')">
|
||||
{{ userInputControl.get('label').value }}
|
||||
</mat-checkbox>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayoutAlign="end center">
|
||||
<button mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="(isLoading$ | async) || userFilterFormGroup.invalid || !userFilterFormGroup.dirty">
|
||||
{{ 'action.update' | translate }}
|
||||
</button>
|
||||
<button mat-button color="primary"
|
||||
type="button"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()" cdkFocusInitial>
|
||||
{{ 'action.cancel' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -0,0 +1,118 @@
|
||||
///
|
||||
/// Copyright © 2016-2020 The Thingsboard Authors
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
|
||||
import { ErrorStateMatcher } from '@angular/material/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import {
|
||||
AbstractControl, FormArray,
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
FormGroupDirective,
|
||||
NgForm,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { DialogComponent } from '@app/shared/components/dialog.component';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
EntityKeyValueType,
|
||||
Filter,
|
||||
filterToUserFilterInfoList,
|
||||
UserFilterInputInfo
|
||||
} from '@shared/models/query/query.models';
|
||||
|
||||
export interface UserFilterDialogData {
|
||||
filter: Filter;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'tb-user-filter-dialog',
|
||||
templateUrl: './user-filter-dialog.component.html',
|
||||
providers: [{provide: ErrorStateMatcher, useExisting: UserFilterDialogComponent}],
|
||||
styleUrls: []
|
||||
})
|
||||
export class UserFilterDialogComponent extends DialogComponent<UserFilterDialogComponent, Filter>
|
||||
implements OnInit, ErrorStateMatcher {
|
||||
|
||||
filter: Filter;
|
||||
|
||||
userFilterFormGroup: FormGroup;
|
||||
|
||||
valueTypeEnum = EntityKeyValueType;
|
||||
|
||||
submitted = false;
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
protected router: Router,
|
||||
@Inject(MAT_DIALOG_DATA) public data: UserFilterDialogData,
|
||||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
|
||||
public dialogRef: MatDialogRef<UserFilterDialogComponent, Filter>,
|
||||
private fb: FormBuilder,
|
||||
public translate: TranslateService) {
|
||||
super(store, router, dialogRef);
|
||||
this.filter = data.filter;
|
||||
const userInputs = filterToUserFilterInfoList(this.filter, translate);
|
||||
|
||||
const userInputControls: Array<AbstractControl> = [];
|
||||
for (const userInput of userInputs) {
|
||||
userInputControls.push(this.createUserInputFormControl(userInput));
|
||||
}
|
||||
|
||||
this.userFilterFormGroup = this.fb.group({
|
||||
userInputs: this.fb.array(userInputControls)
|
||||
});
|
||||
}
|
||||
|
||||
private createUserInputFormControl(userInput: UserFilterInputInfo): AbstractControl {
|
||||
const userInputControl = this.fb.group({
|
||||
label: [userInput.label],
|
||||
valueType: [userInput.valueType],
|
||||
value: [(userInput.info.keyFilterPredicate as any).value,
|
||||
userInput.valueType === EntityKeyValueType.NUMERIC ||
|
||||
userInput.valueType === EntityKeyValueType.DATE_TIME ? [Validators.required] : []]
|
||||
});
|
||||
userInputControl.get('value').valueChanges.subscribe(value => {
|
||||
(userInput.info.keyFilterPredicate as any).value = value;
|
||||
});
|
||||
return userInputControl;
|
||||
}
|
||||
|
||||
userInputsFormArray(): FormArray {
|
||||
return this.userFilterFormGroup.get('userInputs') as FormArray;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
||||
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
|
||||
const customErrorState = !!(control && control.invalid && this.submitted);
|
||||
return originalErrorState || customErrorState;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(null);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.submitted = true;
|
||||
this.dialogRef.close(this.filter);
|
||||
}
|
||||
}
|
||||
@ -80,6 +80,9 @@ import { FilterDialogComponent } from '@home/components/filter/filter-dialog.com
|
||||
import { FilterSelectComponent } from './filter/filter-select.component';
|
||||
import { FiltersEditComponent } from '@home/components/filter/filters-edit.component';
|
||||
import { FiltersEditPanelComponent } from '@home/components/filter/filters-edit-panel.component';
|
||||
import { UserFilterDialogComponent } from '@home/components/filter/user-filter-dialog.component';
|
||||
import { FilterUserInfoComponent } from './filter/filter-user-info.component';
|
||||
import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component';
|
||||
|
||||
@NgModule({
|
||||
declarations:
|
||||
@ -142,7 +145,10 @@ import { FiltersEditPanelComponent } from '@home/components/filter/filters-edit-
|
||||
FiltersDialogComponent,
|
||||
FilterSelectComponent,
|
||||
FiltersEditComponent,
|
||||
FiltersEditPanelComponent
|
||||
FiltersEditPanelComponent,
|
||||
UserFilterDialogComponent,
|
||||
FilterUserInfoComponent,
|
||||
FilterUserInfoDialogComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -197,7 +203,8 @@ import { FiltersEditPanelComponent } from '@home/components/filter/filters-edit-
|
||||
FilterDialogComponent,
|
||||
FiltersDialogComponent,
|
||||
FilterSelectComponent,
|
||||
FiltersEditComponent
|
||||
FiltersEditComponent,
|
||||
UserFilterDialogComponent
|
||||
],
|
||||
providers: [
|
||||
WidgetComponentService,
|
||||
|
||||
@ -633,7 +633,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
|
||||
} else {
|
||||
let label: string = chip;
|
||||
if (type === DataKeyType.alarm || type === DataKeyType.entityField) {
|
||||
const keyField = type === DataKeyType.alarm ? alarmFields[label] : entityFields[chip];;
|
||||
const keyField = type === DataKeyType.alarm ? alarmFields[label] : entityFields[chip];
|
||||
if (keyField) {
|
||||
label = this.translate.instant(keyField.name);
|
||||
}
|
||||
@ -729,8 +729,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
|
||||
data: {
|
||||
isAdd: true,
|
||||
filters: this.filters,
|
||||
filter: singleFilter,
|
||||
userMode: false
|
||||
filter: singleFilter
|
||||
}
|
||||
}).afterClosed().pipe(
|
||||
tap((result) => {
|
||||
|
||||
@ -16,8 +16,8 @@
|
||||
|
||||
-->
|
||||
<section fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="16px">
|
||||
<mat-form-field>
|
||||
<mat-placeholder>{{ dateText | translate }}</mat-placeholder>
|
||||
<mat-form-field [floatLabel]="showLabel ? 'auto' : 'always'" [hideRequiredMarker]="!showLabel" [ngClass]="{'no-label': !showLabel}">
|
||||
<mat-placeholder *ngIf="showLabel">{{ dateText | translate }}</mat-placeholder>
|
||||
<mat-datetimepicker-toggle [for]="datePicker" matPrefix></mat-datetimepicker-toggle>
|
||||
<mat-datetimepicker #datePicker type="date" openOnFocus="true"></mat-datetimepicker>
|
||||
<input [min]="minDateValue" [max]="maxDateValue"
|
||||
@ -26,8 +26,8 @@
|
||||
matInput [(ngModel)]="date"
|
||||
[matDatetimepicker]="datePicker" (ngModelChange)="onDateChange()">
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-placeholder>{{ timeText | translate }}</mat-placeholder>
|
||||
<mat-form-field [floatLabel]="showLabel ? 'auto' : 'always'" [hideRequiredMarker]="!showLabel" [ngClass]="{'no-label': !showLabel}">
|
||||
<mat-placeholder *ngIf="showLabel">{{ timeText | translate }}</mat-placeholder>
|
||||
<mat-datetimepicker-toggle [for]="timePicker" matPrefix></mat-datetimepicker-toggle>
|
||||
<mat-datetimepicker #timePicker type="time" openOnFocus="true"></mat-datetimepicker>
|
||||
<input [min]="minDateValue" [max]="maxDateValue"
|
||||
|
||||
@ -24,4 +24,11 @@
|
||||
width: auto;
|
||||
min-width: 100px;
|
||||
}
|
||||
mat-form-field {
|
||||
&.no-label {
|
||||
.mat-form-field-infix {
|
||||
border-top-width: 0.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +50,9 @@ export class DatetimeComponent implements OnInit, ControlValueAccessor {
|
||||
@Input()
|
||||
timeText: string;
|
||||
|
||||
@Input()
|
||||
showLabel = true;
|
||||
|
||||
minDateValue: Date | null;
|
||||
|
||||
@Input()
|
||||
|
||||
@ -71,10 +71,10 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
|
||||
if (this.shouldDisplayMessage(notificationMessage)) {
|
||||
this.currentMessage = notificationMessage;
|
||||
const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']);
|
||||
if (isGtSm) {
|
||||
if (isGtSm && this.toastTarget !== 'root') {
|
||||
this.showToastPanel(notificationMessage);
|
||||
} else {
|
||||
this.showSnackBar(notificationMessage);
|
||||
this.showSnackBar(notificationMessage, isGtSm);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -180,13 +180,14 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private showSnackBar(notificationMessage: NotificationMessage) {
|
||||
private showSnackBar(notificationMessage: NotificationMessage, isGtSm: boolean) {
|
||||
const data: ToastPanelData = {
|
||||
notification: notificationMessage
|
||||
notification: notificationMessage,
|
||||
parent: this.elementRef
|
||||
};
|
||||
const config: MatSnackBarConfig = {
|
||||
horizontalPosition: notificationMessage.horizontalPosition || 'left',
|
||||
verticalPosition: 'bottom',
|
||||
verticalPosition: !isGtSm ? 'bottom' : (notificationMessage.verticalPosition || 'top'),
|
||||
viewContainerRef: this.viewContainerRef,
|
||||
duration: notificationMessage.duration,
|
||||
panelClass: notificationMessage.panelClass,
|
||||
@ -248,6 +249,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
|
||||
|
||||
interface ToastPanelData {
|
||||
notification: NotificationMessage;
|
||||
parent?: ElementRef;
|
||||
}
|
||||
|
||||
import {
|
||||
@ -259,6 +261,7 @@ import {
|
||||
style,
|
||||
animate,
|
||||
} from '@angular/animations';
|
||||
import { onParentScrollOrWindowResize } from '@core/utils';
|
||||
|
||||
export const toastAnimations: {
|
||||
readonly showHideToast: AnimationTriggerMetadata;
|
||||
@ -285,6 +288,10 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
@ViewChild('actionButton', {static: true}) actionButton: MatButton;
|
||||
|
||||
private parentEl: HTMLElement;
|
||||
private snackBarContainerEl: HTMLElement;
|
||||
private parentScrollSubscription: Subscription = null;
|
||||
|
||||
public notification: NotificationMessage;
|
||||
|
||||
animationState: ToastAnimationState;
|
||||
@ -296,6 +303,7 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
constructor(@Inject(MAT_SNACK_BAR_DATA)
|
||||
private data: ToastPanelData,
|
||||
private elementRef: ElementRef,
|
||||
@Optional()
|
||||
private snackBarRef: MatSnackBarRef<TbSnackBarComponent>,
|
||||
@Optional()
|
||||
@ -305,9 +313,51 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (this.snackBarRef) {
|
||||
this.parentEl = this.data.parent.nativeElement;
|
||||
this.snackBarContainerEl = this.elementRef.nativeElement.parentNode;
|
||||
this.snackBarContainerEl.style.position = 'absolute';
|
||||
this.updateContainerRect();
|
||||
this.updatePosition(this.snackBarRef.containerInstance.snackBarConfig);
|
||||
const snackBarComponent = this;
|
||||
this.parentScrollSubscription = onParentScrollOrWindowResize(this.parentEl).subscribe(() => {
|
||||
snackBarComponent.updateContainerRect();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private updatePosition(config: MatSnackBarConfig) {
|
||||
const isRtl = config.direction === 'rtl';
|
||||
const isLeft = (config.horizontalPosition === 'left' ||
|
||||
(config.horizontalPosition === 'start' && !isRtl) ||
|
||||
(config.horizontalPosition === 'end' && isRtl));
|
||||
const isRight = !isLeft && config.horizontalPosition !== 'center';
|
||||
if (isLeft) {
|
||||
this.snackBarContainerEl.style.justifyContent = 'flex-start';
|
||||
} else if (isRight) {
|
||||
this.snackBarContainerEl.style.justifyContent = 'flex-end';
|
||||
} else {
|
||||
this.snackBarContainerEl.style.justifyContent = 'center';
|
||||
}
|
||||
if (config.verticalPosition === 'top') {
|
||||
this.snackBarContainerEl.style.alignItems = 'flex-start';
|
||||
} else {
|
||||
this.snackBarContainerEl.style.alignItems = 'flex-end';
|
||||
}
|
||||
}
|
||||
|
||||
private updateContainerRect() {
|
||||
const viewportOffset = this.parentEl.getBoundingClientRect();
|
||||
this.snackBarContainerEl.style.top = viewportOffset.top + 'px';
|
||||
this.snackBarContainerEl.style.left = viewportOffset.left + 'px';
|
||||
this.snackBarContainerEl.style.width = viewportOffset.width + 'px';
|
||||
this.snackBarContainerEl.style.height = viewportOffset.height + 'px';
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.parentScrollSubscription) {
|
||||
this.parentScrollSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
action(): void {
|
||||
|
||||
@ -22,7 +22,8 @@ import { EntityInfo } from '@shared/models/entity.models';
|
||||
import { EntityType } from '@shared/models/entity-type.models';
|
||||
import { Datasource, DatasourceType } from '@shared/models/widget.models';
|
||||
import { PageData } from '@shared/models/page/page-data';
|
||||
import { isEqual } from '@core/utils';
|
||||
import { isDefined, isEqual } from '@core/utils';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
export enum EntityKeyType {
|
||||
ATTRIBUTE = 'ATTRIBUTE',
|
||||
@ -63,7 +64,8 @@ export interface EntityKey {
|
||||
export enum EntityKeyValueType {
|
||||
STRING = 'STRING',
|
||||
NUMERIC = 'NUMERIC',
|
||||
BOOLEAN = 'BOOLEAN'
|
||||
BOOLEAN = 'BOOLEAN',
|
||||
DATE_TIME = 'DATE_TIME'
|
||||
}
|
||||
|
||||
export interface EntityKeyValueTypeData {
|
||||
@ -93,6 +95,13 @@ export const entityKeyValueTypesMap = new Map<EntityKeyValueType, EntityKeyValue
|
||||
name: 'filter.value-type.boolean',
|
||||
icon: 'mdi:checkbox-marked-outline'
|
||||
}
|
||||
],
|
||||
[
|
||||
EntityKeyValueType.DATE_TIME,
|
||||
{
|
||||
name: 'filter.value-type.date-time',
|
||||
icon: 'mdi:calendar-clock'
|
||||
}
|
||||
]
|
||||
]
|
||||
);
|
||||
@ -102,12 +111,26 @@ export function entityKeyValueTypeToFilterPredicateType(valueType: EntityKeyValu
|
||||
case EntityKeyValueType.STRING:
|
||||
return FilterPredicateType.STRING;
|
||||
case EntityKeyValueType.NUMERIC:
|
||||
case EntityKeyValueType.DATE_TIME:
|
||||
return FilterPredicateType.NUMERIC;
|
||||
case EntityKeyValueType.BOOLEAN:
|
||||
return FilterPredicateType.BOOLEAN;
|
||||
}
|
||||
}
|
||||
|
||||
export function createDefaultFilterPredicateInfo(valueType: EntityKeyValueType, complex: boolean): KeyFilterPredicateInfo {
|
||||
const predicate = createDefaultFilterPredicate(valueType, complex);
|
||||
return {
|
||||
keyFilterPredicate: predicate,
|
||||
userInfo: {
|
||||
editable: true,
|
||||
label: '',
|
||||
autogeneratedLabel: true,
|
||||
order: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function createDefaultFilterPredicate(valueType: EntityKeyValueType, complex: boolean): KeyFilterPredicate {
|
||||
const predicate = {
|
||||
type: complex ? FilterPredicateType.COMPLEX : entityKeyValueTypeToFilterPredicateType(valueType)
|
||||
@ -120,7 +143,7 @@ export function createDefaultFilterPredicate(valueType: EntityKeyValueType, comp
|
||||
break;
|
||||
case FilterPredicateType.NUMERIC:
|
||||
predicate.operation = NumericOperation.EQUAL;
|
||||
predicate.value = 0;
|
||||
predicate.value = valueType === EntityKeyValueType.DATE_TIME ? Date.now() : 0;
|
||||
break;
|
||||
case FilterPredicateType.BOOLEAN:
|
||||
predicate.operation = BooleanOperation.EQUAL;
|
||||
@ -224,16 +247,33 @@ export interface BooleanFilterPredicate {
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
export interface ComplexFilterPredicate {
|
||||
export interface BaseComplexFilterPredicate<T extends KeyFilterPredicate | KeyFilterPredicateInfo> {
|
||||
type: FilterPredicateType.COMPLEX,
|
||||
operation: ComplexOperation;
|
||||
predicates: Array<KeyFilterPredicate>;
|
||||
predicates: Array<T>;
|
||||
}
|
||||
|
||||
export type ComplexFilterPredicate = BaseComplexFilterPredicate<KeyFilterPredicate>;
|
||||
|
||||
export type ComplexFilterPredicateInfo = BaseComplexFilterPredicate<KeyFilterPredicateInfo>;
|
||||
|
||||
export type KeyFilterPredicate = StringFilterPredicate |
|
||||
NumericFilterPredicate |
|
||||
BooleanFilterPredicate |
|
||||
ComplexFilterPredicate;
|
||||
ComplexFilterPredicate |
|
||||
ComplexFilterPredicateInfo;
|
||||
|
||||
export interface KeyFilterPredicateUserInfo {
|
||||
editable: boolean;
|
||||
label: string;
|
||||
autogeneratedLabel: boolean;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
export interface KeyFilterPredicateInfo {
|
||||
keyFilterPredicate: KeyFilterPredicate;
|
||||
userInfo: KeyFilterPredicateUserInfo;
|
||||
}
|
||||
|
||||
export interface KeyFilter {
|
||||
key: EntityKey;
|
||||
@ -243,7 +283,7 @@ export interface KeyFilter {
|
||||
export interface KeyFilterInfo {
|
||||
key: EntityKey;
|
||||
valueType: EntityKeyValueType;
|
||||
predicates: Array<KeyFilterPredicate>;
|
||||
predicates: Array<KeyFilterPredicateInfo>;
|
||||
}
|
||||
|
||||
export interface FilterInfo {
|
||||
@ -264,7 +304,7 @@ export function filterInfoToKeyFilters(filter: FilterInfo): Array<KeyFilter> {
|
||||
for (const predicate of keyFilterInfo.predicates) {
|
||||
const keyFilter: KeyFilter = {
|
||||
key,
|
||||
predicate
|
||||
predicate: keyFilterPredicateInfoToKeyFilterPredicate(predicate)
|
||||
};
|
||||
keyFilters.push(keyFilter);
|
||||
}
|
||||
@ -272,6 +312,112 @@ export function filterInfoToKeyFilters(filter: FilterInfo): Array<KeyFilter> {
|
||||
return keyFilters;
|
||||
}
|
||||
|
||||
export function keyFilterPredicateInfoToKeyFilterPredicate(keyFilterPredicateInfo: KeyFilterPredicateInfo): KeyFilterPredicate {
|
||||
let keyFilterPredicate = keyFilterPredicateInfo.keyFilterPredicate;
|
||||
if (keyFilterPredicate.type === FilterPredicateType.COMPLEX) {
|
||||
const complexInfo = keyFilterPredicate as ComplexFilterPredicateInfo;
|
||||
const predicates = complexInfo.predicates.map((predicateInfo => keyFilterPredicateInfoToKeyFilterPredicate(predicateInfo)));
|
||||
keyFilterPredicate = {
|
||||
type: FilterPredicateType.COMPLEX,
|
||||
operation: complexInfo.operation,
|
||||
predicates
|
||||
} as ComplexFilterPredicate;
|
||||
}
|
||||
return keyFilterPredicate;
|
||||
}
|
||||
|
||||
export function isFilterEditable(filter: FilterInfo): boolean {
|
||||
if (filter.editable) {
|
||||
return filter.keyFilters.some(value => isKeyFilterInfoEditable(value));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isKeyFilterInfoEditable(keyFilterInfo: KeyFilterInfo): boolean {
|
||||
return keyFilterInfo.predicates.some(value => isPredicateInfoEditable(value));
|
||||
}
|
||||
|
||||
export function isPredicateInfoEditable(predicateInfo: KeyFilterPredicateInfo): boolean {
|
||||
if (predicateInfo.keyFilterPredicate.type === FilterPredicateType.COMPLEX) {
|
||||
const complexFilterPredicateInfo: ComplexFilterPredicateInfo = predicateInfo.keyFilterPredicate as ComplexFilterPredicateInfo;
|
||||
return complexFilterPredicateInfo.predicates.some(value => isPredicateInfoEditable(value));
|
||||
} else {
|
||||
return predicateInfo.userInfo.editable;
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserFilterInputInfo {
|
||||
label: string;
|
||||
valueType: EntityKeyValueType;
|
||||
info: KeyFilterPredicateInfo;
|
||||
}
|
||||
|
||||
export function filterToUserFilterInfoList(filter: Filter, translate: TranslateService): Array<UserFilterInputInfo> {
|
||||
const result = filter.keyFilters.map((keyFilterInfo => keyFilterInfoToUserFilterInfoList(keyFilterInfo, translate)));
|
||||
let userInputs: Array<UserFilterInputInfo> = [].concat.apply([], result);
|
||||
userInputs = userInputs.sort((input1, input2) => {
|
||||
const order1 = isDefined(input1.info.userInfo.order) ? input1.info.userInfo.order : 0;
|
||||
const order2 = isDefined(input2.info.userInfo.order) ? input2.info.userInfo.order : 0;
|
||||
return order1 - order2;
|
||||
});
|
||||
return userInputs;
|
||||
}
|
||||
|
||||
export function keyFilterInfoToUserFilterInfoList(keyFilterInfo: KeyFilterInfo, translate: TranslateService): Array<UserFilterInputInfo> {
|
||||
const result = keyFilterInfo.predicates.map((predicateInfo => predicateInfoToUserFilterInfoList(keyFilterInfo.key,
|
||||
keyFilterInfo.valueType, predicateInfo, translate)));
|
||||
return [].concat.apply([], result);
|
||||
}
|
||||
|
||||
export function predicateInfoToUserFilterInfoList(key: EntityKey,
|
||||
valueType: EntityKeyValueType,
|
||||
predicateInfo: KeyFilterPredicateInfo,
|
||||
translate: TranslateService): Array<UserFilterInputInfo> {
|
||||
if (predicateInfo.keyFilterPredicate.type === FilterPredicateType.COMPLEX) {
|
||||
const complexFilterPredicateInfo: ComplexFilterPredicateInfo = predicateInfo.keyFilterPredicate as ComplexFilterPredicateInfo;
|
||||
const result = complexFilterPredicateInfo.predicates.map((predicateInfo1 =>
|
||||
predicateInfoToUserFilterInfoList(key, valueType, predicateInfo1, translate)));
|
||||
return [].concat.apply([], result);
|
||||
} else {
|
||||
if (predicateInfo.userInfo.editable) {
|
||||
const userInput: UserFilterInputInfo = {
|
||||
info: predicateInfo,
|
||||
label: predicateInfo.userInfo.label,
|
||||
valueType
|
||||
};
|
||||
if (predicateInfo.userInfo.autogeneratedLabel) {
|
||||
userInput.label = generateUserFilterValueLabel(key.key, valueType,
|
||||
predicateInfo.keyFilterPredicate.operation, translate);
|
||||
}
|
||||
return [userInput];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function generateUserFilterValueLabel(key: string, valueType: EntityKeyValueType,
|
||||
operation: StringOperation | BooleanOperation | NumericOperation,
|
||||
translate: TranslateService) {
|
||||
let label = key;
|
||||
let operationTranslationKey: string;
|
||||
switch (valueType) {
|
||||
case EntityKeyValueType.STRING:
|
||||
operationTranslationKey = stringOperationTranslationMap.get(operation as StringOperation);
|
||||
break;
|
||||
case EntityKeyValueType.NUMERIC:
|
||||
case EntityKeyValueType.DATE_TIME:
|
||||
operationTranslationKey = numericOperationTranslationMap.get(operation as NumericOperation);
|
||||
break;
|
||||
case EntityKeyValueType.BOOLEAN:
|
||||
operationTranslationKey = booleanOperationTranslationMap.get(operation as BooleanOperation);
|
||||
break;
|
||||
}
|
||||
label += ' ' + translate.instant(operationTranslationKey);
|
||||
return label;
|
||||
}
|
||||
|
||||
export interface Filter extends FilterInfo {
|
||||
id: string;
|
||||
}
|
||||
|
||||
@ -1174,18 +1174,18 @@
|
||||
"filter-required": "Filter is required.",
|
||||
"operation": {
|
||||
"operation": "Operation",
|
||||
"equal": "Equal",
|
||||
"not-equal": "Not equal",
|
||||
"starts-with": "Starts with",
|
||||
"ends-with": "Ends with",
|
||||
"contains": "Contains",
|
||||
"not-contain": "Not contain",
|
||||
"greater": "Greater than",
|
||||
"less": "Less than",
|
||||
"greater-or-equal": "Greater or equal",
|
||||
"less-or-equal": "Less or equal",
|
||||
"and": "And",
|
||||
"or": "Or"
|
||||
"equal": "equal",
|
||||
"not-equal": "not equal",
|
||||
"starts-with": "starts with",
|
||||
"ends-with": "ends with",
|
||||
"contains": "contains",
|
||||
"not-contain": "not contain",
|
||||
"greater": "greater than",
|
||||
"less": "less than",
|
||||
"greater-or-equal": "greater or equal",
|
||||
"less-or-equal": "less or equal",
|
||||
"and": "and",
|
||||
"or": "or"
|
||||
},
|
||||
"ignore-case": "Ignore case",
|
||||
"value": "Value",
|
||||
@ -1196,6 +1196,11 @@
|
||||
"add-complex": "Add complex",
|
||||
"complex-filter": "Complex filter",
|
||||
"edit-complex-filter": "Edit complex filter",
|
||||
"edit-filter-user-params": "Edit filter predicate user parameters",
|
||||
"user-parameters": "User parameters",
|
||||
"display-label": "Label to display",
|
||||
"autogenerated-label": "Auto generate label",
|
||||
"order-priority": "Field order priority",
|
||||
"key-filter": "Key filter",
|
||||
"key-filters": "Key filters",
|
||||
"key-name": "Key name",
|
||||
@ -1210,7 +1215,8 @@
|
||||
"value-type": "Value type",
|
||||
"string": "String",
|
||||
"numeric": "Numeric",
|
||||
"boolean": "Boolean"
|
||||
"boolean": "Boolean",
|
||||
"date-time": "Datetime"
|
||||
},
|
||||
"value-type-required": "Key value type is required.",
|
||||
"key-value-type-change-title": "Are you sure you want to change key value type?",
|
||||
@ -1218,7 +1224,9 @@
|
||||
"no-key-filters": "No key filters configured",
|
||||
"add-key-filter": "Add key filter",
|
||||
"remove-key-filter": "Remove key filter",
|
||||
"edit-key-filter": "Edit key filter"
|
||||
"edit-key-filter": "Edit key filter",
|
||||
"date": "Date",
|
||||
"time": "Time"
|
||||
},
|
||||
"fullscreen": {
|
||||
"expand": "Expand to fullscreen",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user