UI: Improve data keys config. Fix datasource type processing.

This commit is contained in:
Igor Kulikov 2023-06-20 18:52:13 +03:00
parent 6b8cbbd3c9
commit 1ec37d7685
20 changed files with 305 additions and 176 deletions

View File

@ -252,10 +252,9 @@ export class AliasController implements IAliasController {
private resolveDatasource(datasource: Datasource, forceFilter = false): Observable<Datasource> { private resolveDatasource(datasource: Datasource, forceFilter = false): Observable<Datasource> {
const newDatasource = deepClone(datasource); const newDatasource = deepClone(datasource);
if (newDatasource.type === DatasourceType.device) { if (newDatasource.type === DatasourceType.entity
newDatasource.type = DatasourceType.entity; || newDatasource.type === DatasourceType.device
} || newDatasource.type === DatasourceType.entityCount
if (newDatasource.type === DatasourceType.entity || newDatasource.type === DatasourceType.entityCount
|| newDatasource.type === DatasourceType.alarmCount) { || newDatasource.type === DatasourceType.alarmCount) {
if (newDatasource.filterId) { if (newDatasource.filterId) {
newDatasource.keyFilters = this.getKeyFilters(newDatasource.filterId); newDatasource.keyFilters = this.getKeyFilters(newDatasource.filterId);
@ -263,7 +262,8 @@ export class AliasController implements IAliasController {
if (newDatasource.type === DatasourceType.alarmCount) { if (newDatasource.type === DatasourceType.alarmCount) {
newDatasource.alarmFilter = this.entityService.resolveAlarmFilter(newDatasource.alarmFilterConfig, false); newDatasource.alarmFilter = this.entityService.resolveAlarmFilter(newDatasource.alarmFilterConfig, false);
} }
if (newDatasource.deviceId) { if (newDatasource.type === DatasourceType.device) {
newDatasource.type = DatasourceType.entity;
newDatasource.entityFilter = singleEntityFilterFromDeviceId(newDatasource.deviceId); newDatasource.entityFilter = singleEntityFilterFromDeviceId(newDatasource.deviceId);
if (forceFilter) { if (forceFilter) {
return this.entityService.findSingleEntityInfoByEntityFilter(newDatasource.entityFilter, return this.entityService.findSingleEntityInfoByEntityFilter(newDatasource.entityFilter,

View File

@ -282,13 +282,9 @@ export class UtilsService {
public validateDatasources(datasources: Array<Datasource>): Array<Datasource> { public validateDatasources(datasources: Array<Datasource>): Array<Datasource> {
datasources.forEach((datasource) => { datasources.forEach((datasource) => {
// @ts-ignore if (datasource.type === DatasourceType.device) {
if (datasource.type === 'device') { if (datasource.deviceAliasId) {
datasource.type = DatasourceType.entity; datasource.type = DatasourceType.entity;
datasource.entityType = EntityType.DEVICE;
if (datasource.deviceId) {
datasource.entityId = datasource.deviceId;
} else if (datasource.deviceAliasId) {
datasource.entityAliasId = datasource.deviceAliasId; datasource.entityAliasId = datasource.deviceAliasId;
} }
if (datasource.deviceName) { if (datasource.deviceName) {

View File

@ -97,7 +97,9 @@ export abstract class WebsocketService<T extends WsSubscriber> implements WsServ
this.dataStream.next(this.cmdWrapper.preparePublishCommands(MAX_PUBLISH_COMMANDS)); this.dataStream.next(this.cmdWrapper.preparePublishCommands(MAX_PUBLISH_COMMANDS));
this.checkToClose(); this.checkToClose();
} }
this.tryOpenSocket(); if (this.subscribersCount > 0) {
this.tryOpenSocket();
}
} }
private checkToClose() { private checkToClose() {

View File

@ -28,6 +28,7 @@
<tb-data-keys-panel <tb-data-keys-panel
panelTitle="{{ 'widgets.table.columns' | translate }}" panelTitle="{{ 'widgets.table.columns' | translate }}"
addKeyTitle="{{ 'widgets.table.add-column' | translate }}" addKeyTitle="{{ 'widgets.table.add-column' | translate }}"
keySettingsTitle="{{ 'widgets.table.column-settings' | translate }}"
removeKeyTitle="{{ 'widgets.table.remove-column' | translate }}" removeKeyTitle="{{ 'widgets.table.remove-column' | translate }}"
noKeysText="{{ 'widgets.table.no-columns' | translate }}" noKeysText="{{ 'widgets.table.no-columns' | translate }}"
hideDataKeyColor hideDataKeyColor

View File

@ -151,6 +151,22 @@
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
</div> </div>
<div class="tb-data-keys-table-row-buttons">
<button type="button"
mat-icon-button
(click)="editKey(true)"
[matTooltip]="keySettingsTitle"
matTooltipPosition="above">
<mat-icon>settings</mat-icon>
</button>
<button type="button"
mat-icon-button
(click)="keyRemoved.emit()"
[matTooltip]="removeKeyTitle"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</div> </div>
<ng-template #keyName> <ng-template #keyName>
<ng-container *ngIf="dataKeyHasPostprocessing(); else keyName"> <ng-container *ngIf="dataKeyHasPostprocessing(); else keyName">

View File

@ -51,3 +51,19 @@
align-items: center; align-items: center;
} }
} }
.tb-data-keys-table-row-buttons {
display: flex;
flex-direction: row;
button.mat-mdc-icon-button.mat-mdc-button-base {
padding: 7px;
width: 38px;
height: 38px;
.mat-icon {
color: rgba(0, 0, 0, 0.38);
}
&.tb-hidden {
visibility: hidden;
}
}
}

View File

@ -18,10 +18,12 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
ElementRef, ElementRef,
EventEmitter,
forwardRef, forwardRef,
Input, Input,
OnChanges, OnChanges,
OnInit, OnInit,
Output,
SimpleChanges, SimpleChanges,
ViewChild, ViewChild,
ViewEncapsulation ViewEncapsulation
@ -37,7 +39,14 @@ import {
} from '@angular/forms'; } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKey, DatasourceType, JsonSettingsSchema, Widget, widgetType } from '@shared/models/widget.models'; import {
DataKey,
DataKeyConfigMode,
DatasourceType,
JsonSettingsSchema,
Widget,
widgetType
} from '@shared/models/widget.models';
import { DataKeysPanelComponent } from '@home/components/widget/config/basic/common/data-keys-panel.component'; import { DataKeysPanelComponent } from '@home/components/widget/config/basic/common/data-keys-panel.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { AggregationType } from '@shared/models/time/time.models'; import { AggregationType } from '@shared/models/time/time.models';
@ -104,6 +113,9 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
@Input() @Input()
deviceId: string; deviceId: string;
@Output()
keyRemoved = new EventEmitter();
keyFormControl: UntypedFormControl; keyFormControl: UntypedFormControl;
keyRowFormGroup: UntypedFormGroup; keyRowFormGroup: UntypedFormGroup;
@ -169,6 +181,18 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
return this.modelValue.type && ![ DataKeyType.alarm, DataKeyType.entityField, DataKeyType.count ].includes(this.modelValue.type); return this.modelValue.type && ![ DataKeyType.alarm, DataKeyType.entityField, DataKeyType.count ].includes(this.modelValue.type);
} }
get keySettingsTitle(): string {
return this.dataKeysPanelComponent.keySettingsTitle;
}
get removeKeyTitle(): string {
return this.dataKeysPanelComponent.removeKeyTitle;
}
get dragEnabled(): boolean {
return this.dataKeysPanelComponent.dragEnabled;
}
private propagateChange = (_val: any) => {}; private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder, constructor(private fb: UntypedFormBuilder,
@ -291,13 +315,14 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
} }
} }
editKey() { editKey(advanced = false) {
this.dialog.open<DataKeyConfigDialogComponent, DataKeyConfigDialogData, DataKey>(DataKeyConfigDialogComponent, this.dialog.open<DataKeyConfigDialogComponent, DataKeyConfigDialogData, DataKey>(DataKeyConfigDialogComponent,
{ {
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
dataKey: deepClone(this.modelValue), dataKey: deepClone(this.modelValue),
dataKeyConfigMode: advanced ? DataKeyConfigMode.advanced : DataKeyConfigMode.general,
dataKeySettingsSchema: this.datakeySettingsSchema, dataKeySettingsSchema: this.datakeySettingsSchema,
dataKeySettingsDirective: this.dataKeySettingsDirective, dataKeySettingsDirective: this.dataKeySettingsDirective,
dashboard: this.dashboard, dashboard: this.dashboard,

View File

@ -26,27 +26,25 @@
<div class="tb-data-keys-header-cell tb-decimals-header" translate>widget-config.decimals-short</div> <div class="tb-data-keys-header-cell tb-decimals-header" translate>widget-config.decimals-short</div>
<div class="tb-data-keys-header-cell tb-actions-header"></div> <div class="tb-data-keys-header-cell tb-actions-header"></div>
</div> </div>
<div *ngIf="keysFormArray().controls.length; else noKeys" class="tb-data-keys-body tb-drop-list" cdkDropList cdkDropListOrientation="vertical" <div *ngIf="keysFormArray().controls.length; else noKeys" class="tb-data-keys-body tb-drop-list"
cdkDropList cdkDropListOrientation="vertical"
[cdkDropListDisabled]="!dragEnabled"
(cdkDropListDropped)="keyDrop($event)"> (cdkDropListDropped)="keyDrop($event)">
<div cdkDrag class="tb-data-keys-table-row tb-draggable" *ngFor="let keyControl of keysFormArray().controls; trackBy: trackByKey; <div cdkDrag [cdkDragDisabled]="!dragEnabled"
let $index = index;"> class="tb-data-keys-table-row tb-draggable"
*ngFor="let keyControl of keysFormArray().controls; trackBy: trackByKey; let $index = index;">
<tb-data-key-row fxFlex <tb-data-key-row fxFlex
[formControl]="keyControl" [formControl]="keyControl"
[datasourceType]="datasourceType" [datasourceType]="datasourceType"
[deviceId]="deviceId" [deviceId]="deviceId"
[entityAliasId]="entityAliasId"> [entityAliasId]="entityAliasId"
(keyRemoved)="removeKey($index)">
</tb-data-key-row> </tb-data-key-row>
<div class="tb-data-keys-table-row-buttons"> <div class="tb-data-keys-table-row-buttons">
<button type="button"
mat-icon-button
(click)="removeKey($index)"
[matTooltip]="removeKeyTitle"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
<button mat-icon-button <button mat-icon-button
type="button" type="button"
cdkDragHandle cdkDragHandle
[ngClass]="{'tb-hidden': !dragEnabled}"
matTooltip="{{ 'action.drag' | translate }}" matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above"> matTooltipPosition="above">
<mat-icon>drag_indicator</mat-icon> <mat-icon>drag_indicator</mat-icon>

View File

@ -39,7 +39,7 @@
width: 60px; width: 60px;
} }
&.tb-actions-header { &.tb-actions-header {
width: 76px; width: 114px;
} }
} }
} }
@ -57,20 +57,5 @@
height: 38px; height: 38px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: 12px;
background: #fff; background: #fff;
.tb-data-keys-table-row-buttons {
display: flex;
flex-direction: row;
button.mat-mdc-icon-button.mat-mdc-button-base {
padding: 7px;
width: 38px;
height: 38px;
.mat-icon {
color: rgba(0, 0, 0, 0.38);
}
}
}
} }

View File

@ -75,6 +75,9 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
@Input() @Input()
addKeyTitle: string; addKeyTitle: string;
@Input()
keySettingsTitle: string;
@Input() @Input()
removeKeyTitle: string; removeKeyTitle: string;
@ -112,6 +115,10 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema; return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema;
} }
get dragEnabled(): boolean {
return this.keysFormArray().controls.length > 1;
}
private propagateChange = (_val: any) => {}; private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder, constructor(private fb: UntypedFormBuilder,

View File

@ -19,6 +19,10 @@
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2>{{ 'datakey.configuration' | translate }}</h2> <h2>{{ 'datakey.configuration' | translate }}</h2>
<span fxFlex></span> <span fxFlex></span>
<tb-toggle-header *ngIf="hasAdvanced" (valueChange)="dataKeyConfigMode = $event" ignoreMdLgSize="true"
appearance="fill-invert" [options]="dataKeyConfigHeaderOptions"
[value]="dataKeyConfigMode" name="dataKeyConfigModeHeader" useSelectOnMdLg="false">
</tb-toggle-header>
<button mat-icon-button <button mat-icon-button
(click)="cancel()" (click)="cancel()"
type="button"> type="button">
@ -28,8 +32,9 @@
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar> </mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content> <div mat-dialog-content style="padding: 0; height: 800px; overflow: auto;">
<tb-data-key-config #dataKeyConfig <tb-data-key-config #dataKeyConfig
[dataKeyConfigMode]="dataKeyConfigMode"
[dataKeySettingsSchema]="data.dataKeySettingsSchema" [dataKeySettingsSchema]="data.dataKeySettingsSchema"
[dataKeySettingsDirective]="data.dataKeySettingsDirective" [dataKeySettingsDirective]="data.dataKeySettingsDirective"
[deviceId]="data.deviceId" [deviceId]="data.deviceId"

View File

@ -19,17 +19,27 @@ import { ErrorStateMatcher } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import {
FormGroupDirective,
NgForm,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validators
} from '@angular/forms';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { DialogComponent } from '@shared/components/dialog.component'; import { DialogComponent } from '@shared/components/dialog.component';
import { DataKey, Widget, widgetType } from '@shared/models/widget.models'; import { DataKey, DataKeyConfigMode, Widget, widgetType } from '@shared/models/widget.models';
import { DataKeysCallbacks } from './data-keys.component.models'; import { DataKeysCallbacks } from './data-keys.component.models';
import { DataKeyConfigComponent } from '@home/components/widget/config/data-key-config.component'; import { DataKeyConfigComponent } from '@home/components/widget/config/data-key-config.component';
import { Dashboard } from '@shared/models/dashboard.models'; import { Dashboard } from '@shared/models/dashboard.models';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
import { TranslateService } from '@ngx-translate/core';
export interface DataKeyConfigDialogData { export interface DataKeyConfigDialogData {
dataKey: DataKey; dataKey: DataKey;
dataKeyConfigMode?: DataKeyConfigMode;
dataKeySettingsSchema: any; dataKeySettingsSchema: any;
dataKeySettingsDirective: string; dataKeySettingsDirective: string;
dashboard: Dashboard; dashboard: Dashboard;
@ -57,6 +67,12 @@ export class DataKeyConfigDialogComponent extends DialogComponent<DataKeyConfigD
@ViewChild('dataKeyConfig', {static: true}) dataKeyConfig: DataKeyConfigComponent; @ViewChild('dataKeyConfig', {static: true}) dataKeyConfig: DataKeyConfigComponent;
hasAdvanced = false;
dataKeyConfigHeaderOptions: ToggleHeaderOption[];
dataKeyConfigMode: DataKeyConfigMode = DataKeyConfigMode.general;
dataKeyFormGroup: UntypedFormGroup; dataKeyFormGroup: UntypedFormGroup;
submitted = false; submitted = false;
@ -66,6 +82,7 @@ export class DataKeyConfigDialogComponent extends DialogComponent<DataKeyConfigD
@Inject(MAT_DIALOG_DATA) public data: DataKeyConfigDialogData, @Inject(MAT_DIALOG_DATA) public data: DataKeyConfigDialogData,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<DataKeyConfigDialogComponent, DataKey>, public dialogRef: MatDialogRef<DataKeyConfigDialogComponent, DataKey>,
private translate: TranslateService,
public fb: UntypedFormBuilder) { public fb: UntypedFormBuilder) {
super(store, router, dialogRef); super(store, router, dialogRef);
} }
@ -74,6 +91,23 @@ export class DataKeyConfigDialogComponent extends DialogComponent<DataKeyConfigD
this.dataKeyFormGroup = this.fb.group({ this.dataKeyFormGroup = this.fb.group({
dataKey: [this.data.dataKey, [Validators.required]] dataKey: [this.data.dataKey, [Validators.required]]
}); });
if (this.data.dataKeySettingsSchema && this.data.dataKeySettingsSchema.schema ||
this.data.dataKeySettingsDirective && this.data.dataKeySettingsDirective.length) {
this.hasAdvanced = true;
this.dataKeyConfigHeaderOptions = [
{
name: this.translate.instant('datakey.general'),
value: DataKeyConfigMode.general
},
{
name: this.translate.instant('datakey.advanced'),
value: DataKeyConfigMode.advanced
}
];
if (this.data.dataKeyConfigMode) {
this.dataKeyConfigMode = this.data.dataKeyConfigMode;
}
}
} }
isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean {

View File

@ -15,10 +15,12 @@
limitations under the License. limitations under the License.
--> -->
<mat-tab-group class="tb-datakey-config" [ngClass]="{'tb-headless': !displayAdvanced}"> <div class="tb-datakey-config" *ngIf="dataKeyConfigMode === dataKeyConfigModes.general; else advanced"
<mat-tab [formGroup]="dataKeyFormGroup" label="{{ 'datakey.settings' | translate }}"> [formGroup]="dataKeyFormGroup">
<div class="mat-padding settings" fxLayout="column"> <div class="mat-padding" fxLayout="column" style="gap: 16px;">
<mat-form-field class="mat-block" *ngIf="modelValue.type !== dataKeyTypes.function"> <div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>datakey.general</div>
<mat-form-field *ngIf="modelValue.type !== dataKeyTypes.function" subscriptSizing="dynamic">
<mat-label>{{ 'entity.key' | translate }}</mat-label> <mat-label>{{ 'entity.key' | translate }}</mat-label>
<input matInput type="text" placeholder="{{ 'entity.key-name' | translate }}" <input matInput type="text" placeholder="{{ 'entity.key-name' | translate }}"
#keyInput #keyInput
@ -39,31 +41,35 @@
</mat-option> </mat-option>
</mat-autocomplete> </mat-autocomplete>
</mat-form-field> </mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start center" fxLayoutAlign.xs fxLayoutGap="8px"> <mat-form-field *ngIf="!hideDataKeyLabel" subscriptSizing="dynamic">
<mat-form-field *ngIf="!hideDataKeyLabel" fxFlex class="mat-block"> <mat-label translate>datakey.label</mat-label>
<mat-label translate>datakey.label</mat-label> <input matInput formControlName="label" required>
<input matInput formControlName="label" required> </mat-form-field>
</mat-form-field> <ng-container *ngIf="modelValue.type !== dataKeyTypes.alarm">
<tb-color-input *ngIf="!hideDataKeyColor" fxFlex <div class="tb-widget-config-row space-between" *ngIf="!hideDataKeyUnits">
required <div translate>widget-config.units-short</div>
label="{{'datakey.color' | translate}}" <tb-widget-units
icon="format_color_fill" formControlName="units">
openOnInput </tb-widget-units>
formControlName="color"> </div>
</tb-color-input> <div class="tb-widget-config-row space-between" *ngIf="!hideDataKeyDecimals">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</ng-container>
<div class="tb-widget-config-row space-between same-padding" *ngIf="!hideDataKeyColor">
<div>{{ 'datakey.color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div> </div>
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start center" fxLayoutAlign.xs fxLayoutGap="8px" *ngIf="modelValue.type !== dataKeyTypes.alarm"> <ng-container *ngIf="widgetType === widgetTypes.latest && modelValue.type === dataKeyTypes.timeseries">
<mat-form-field *ngIf="!hideDataKeyUnits" fxFlex> <mat-form-field subscriptSizing="dynamic">
<mat-label translate>datakey.units</mat-label>
<input matInput formControlName="units">
</mat-form-field>
<mat-form-field *ngIf="!hideDataKeyDecimals" fxFlex>
<mat-label translate>datakey.decimals</mat-label>
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1">
</mat-form-field>
</div>
<section *ngIf="widgetType === widgetTypes.latest && modelValue.type === dataKeyTypes.timeseries" fxLayout="column">
<mat-form-field>
<mat-label translate>datakey.aggregation</mat-label> <mat-label translate>datakey.aggregation</mat-label>
<mat-select formControlName="aggregationType" style="min-width: 150px;"> <mat-select formControlName="aggregationType" style="min-width: 150px;">
<mat-option *ngFor="let aggregation of aggregations" [value]="aggregation"> <mat-option *ngFor="let aggregation of aggregations" [value]="aggregation">
@ -80,7 +86,7 @@
<fieldset *ngIf="dataKeyFormGroup.get('aggregationType').value && dataKeyFormGroup.get('aggregationType').value !== aggregationTypes.NONE" class="fields-group fields-group-slider"> <fieldset *ngIf="dataKeyFormGroup.get('aggregationType').value && dataKeyFormGroup.get('aggregationType').value !== aggregationTypes.NONE" class="fields-group fields-group-slider">
<legend class="group-title" translate>datakey.delta-calculation</legend> <legend class="group-title" translate>datakey.delta-calculation</legend>
<mat-expansion-panel class="tb-settings comparison" [expanded]="dataKeyFormGroup.get('comparisonEnabled').value" [disabled]="!dataKeyFormGroup.get('comparisonEnabled').value"> <mat-expansion-panel class="tb-settings comparison" [expanded]="dataKeyFormGroup.get('comparisonEnabled').value" [disabled]="!dataKeyFormGroup.get('comparisonEnabled').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxLayout="column" fxLayoutAlign="center start"> <mat-panel-title fxLayout="column" fxLayoutAlign="center start">
<mat-slide-toggle formControlName="comparisonEnabled" (click)="$event.stopPropagation()"> <mat-slide-toggle formControlName="comparisonEnabled" (click)="$event.stopPropagation()">
{{ 'datakey.enable-delta-calculation' | translate }} {{ 'datakey.enable-delta-calculation' | translate }}
@ -129,42 +135,54 @@
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</fieldset> </fieldset>
</section> </ng-container>
<section fxLayout="column" *ngIf="modelValue.type === dataKeyTypes.function"> <section fxLayout="column" *ngIf="modelValue.type === dataKeyTypes.function">
<span translate>datakey.data-generation-func</span> <span translate>datakey.data-generation-func</span>
<br/> <br/>
<tb-js-func #funcBodyEdit <tb-js-func #funcBodyEdit
[functionArgs]="['time', 'prevValue']" [functionArgs]="['time', 'prevValue']"
[globalVariables]="functionScopeVariables"
[validationArgs]="[[1, 1],[1, '1']]"
resultType="any"
helpId="widget/config/datakey_generation_fn"
formControlName="funcBody">
</tb-js-func>
</section>
<section fxLayout="column" *ngIf="(modelValue.type === dataKeyTypes.timeseries || modelValue.type === dataKeyTypes.attribute || modelValue.type === dataKeyTypes.count) && showPostProcessing">
<mat-checkbox formControlName="usePostProcessing">
{{ 'datakey.use-data-post-processing-func' | translate }}
</mat-checkbox>
<tb-js-func *ngIf="dataKeyFormGroup.get('usePostProcessing').value" #postFuncBodyEdit
[functionArgs]="['time', 'value', 'prevValue', 'timePrev', 'prevOrigValue']"
[globalVariables]="functionScopeVariables" [globalVariables]="functionScopeVariables"
[validationArgs]="[[1, 1, 1, 1, 1],[1, '1', '1', 1, '1']]" [validationArgs]="[[1, 1],[1, '1']]"
resultType="any" resultType="any"
helpId="widget/config/datakey_postprocess_fn" helpId="widget/config/datakey_generation_fn"
formControlName="postFuncBody"> formControlName="funcBody">
</tb-js-func> </tb-js-func>
<label *ngIf="dataKeyFormGroup.get('usePostProcessing').value" class="tb-title" style="margin-left: 15px;">
time - {{ 'datakey.time-description' | translate }}<br/>
value - {{ 'datakey.value-description' | translate }}<br/>
prevValue - {{ 'datakey.prev-value-description' | translate }}<br/>
timePrev - {{ 'datakey.time-prev-description' | translate }}<br/>
prevOrigValue - {{ 'datakey.prev-orig-value-description' | translate }}
</label>
</section> </section>
</div> </div>
</mat-tab> <div class="tb-widget-config-panel tb-slide-toggle"
<mat-tab [formGroup]="dataKeySettingsFormGroup" label="{{ 'datakey.advanced' | translate }}" *ngIf="displayAdvanced"> *ngIf="(modelValue.type === dataKeyTypes.timeseries || modelValue.type === dataKeyTypes.attribute || modelValue.type === dataKeyTypes.count) && showPostProcessing">
<mat-expansion-panel class="tb-settings" [expanded]="dataKeyFormGroup.get('usePostProcessing').value" [disabled]="!dataKeyFormGroup.get('usePostProcessing').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="usePostProcessing" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'datakey.use-data-post-processing-func' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-js-func *ngIf="dataKeyFormGroup.get('usePostProcessing').value" #postFuncBodyEdit
[functionArgs]="['time', 'value', 'prevValue', 'timePrev', 'prevOrigValue']"
[globalVariables]="functionScopeVariables"
[validationArgs]="[[1, 1, 1, 1, 1],[1, '1', '1', 1, '1']]"
resultType="any"
helpId="widget/config/datakey_postprocess_fn"
formControlName="postFuncBody">
</tb-js-func>
<label *ngIf="dataKeyFormGroup.get('usePostProcessing').value" class="tb-title" style="margin-left: 15px;">
time - {{ 'datakey.time-description' | translate }}<br/>
value - {{ 'datakey.value-description' | translate }}<br/>
prevValue - {{ 'datakey.prev-value-description' | translate }}<br/>
timePrev - {{ 'datakey.time-prev-description' | translate }}<br/>
prevOrigValue - {{ 'datakey.prev-orig-value-description' | translate }}
</label>
</ng-template>
</mat-expansion-panel>
</div>
</div>
</div>
<ng-template #advanced>
<div class="tb-datakey-config" [formGroup]="dataKeySettingsFormGroup">
<div class="mat-padding" fxLayout="column"> <div class="mat-padding" fxLayout="column">
<tb-widget-settings <tb-widget-settings
[dashboard]="dashboard" [dashboard]="dashboard"
@ -173,5 +191,5 @@
formControlName="settings"> formControlName="settings">
</tb-widget-settings> </tb-widget-settings>
</div> </div>
</mat-tab> </div>
</mat-tab-group> </ng-template>

View File

@ -17,18 +17,6 @@
:host { :host {
.tb-datakey-config { .tb-datakey-config {
height: 100%;
.settings{
&> :first-child{
padding-top: 12px;
}
&> :nth-child(n){
padding-right: 12px;
padding-left: 12px;
}
}
.fields-group { .fields-group {
padding: 0 16px 8px; padding: 0 16px 8px;
margin-bottom: 10px; margin-bottom: 10px;
@ -60,7 +48,7 @@
} }
.tb-hint.after-fields { .tb-hint.after-fields {
margin-top: -1.25em; margin-top: -0.75em;
max-width: fit-content; max-width: fit-content;
line-height: 15px; line-height: 15px;
} }
@ -69,15 +57,6 @@
:host ::ng-deep { :host ::ng-deep {
.tb-datakey-config { .tb-datakey-config {
.mat-mdc-tab-body.mat-mdc-tab-body-active {
.mat-mdc-tab-body-content > div {
height: calc(65vh - 50px);
overflow: initial;
@media #{$mat-xs} {
height: 500px;
}
}
}
.mat-expansion-panel { .mat-expansion-panel {
&.tb-settings { &.tb-settings {
box-shadow: none; box-shadow: none;

View File

@ -22,7 +22,7 @@ import {
ComparisonResultType, ComparisonResultType,
comparisonResultTypeTranslationMap, comparisonResultTypeTranslationMap,
DataKey, DataKey,
dataKeyAggregationTypeHintTranslationMap, dataKeyAggregationTypeHintTranslationMap, DataKeyConfigMode,
Widget, Widget,
widgetType widgetType
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
@ -73,6 +73,8 @@ import { coerceBoolean } from '@shared/decorators/coercion';
}) })
export class DataKeyConfigComponent extends PageComponent implements OnInit, ControlValueAccessor, Validator { export class DataKeyConfigComponent extends PageComponent implements OnInit, ControlValueAccessor, Validator {
dataKeyConfigModes = DataKeyConfigMode;
dataKeyTypes = DataKeyType; dataKeyTypes = DataKeyType;
widgetTypes = widgetType; widgetTypes = widgetType;
@ -91,6 +93,9 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
comparisonResultTypeTranslations = comparisonResultTypeTranslationMap; comparisonResultTypeTranslations = comparisonResultTypeTranslationMap;
@Input()
dataKeyConfigMode: DataKeyConfigMode;
@Input() @Input()
deviceId: string; deviceId: string;
@ -142,7 +147,7 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
@ViewChild('funcBodyEdit') funcBodyEdit: JsFuncComponent; @ViewChild('funcBodyEdit') funcBodyEdit: JsFuncComponent;
@ViewChild('postFuncBodyEdit') postFuncBodyEdit: JsFuncComponent; @ViewChild('postFuncBodyEdit') postFuncBodyEdit: JsFuncComponent;
displayAdvanced = false; hasAdvanced = false;
modelValue: DataKey; modelValue: DataKey;
@ -193,7 +198,7 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
} }
if (this.dataKeySettingsSchema && this.dataKeySettingsSchema.schema || if (this.dataKeySettingsSchema && this.dataKeySettingsSchema.schema ||
this.dataKeySettingsDirective && this.dataKeySettingsDirective.length) { this.dataKeySettingsDirective && this.dataKeySettingsDirective.length) {
this.displayAdvanced = true; this.hasAdvanced = true;
this.dataKeySettingsData = { this.dataKeySettingsData = {
schema: this.dataKeySettingsSchema?.schema || { schema: this.dataKeySettingsSchema?.schema || {
type: 'object', type: 'object',
@ -291,7 +296,7 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
} }
this.dataKeyFormGroup.patchValue(this.modelValue, {emitEvent: false}); this.dataKeyFormGroup.patchValue(this.modelValue, {emitEvent: false});
this.updateValidators(); this.updateValidators();
if (this.displayAdvanced) { if (this.hasAdvanced) {
this.dataKeySettingsData.model = this.modelValue.settings; this.dataKeySettingsData.model = this.modelValue.settings;
this.dataKeySettingsFormGroup.patchValue({ this.dataKeySettingsFormGroup.patchValue({
settings: this.dataKeySettingsData settings: this.dataKeySettingsData
@ -370,7 +375,7 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
private updateModel() { private updateModel() {
this.modelValue = {...this.modelValue, ...this.dataKeyFormGroup.value}; this.modelValue = {...this.modelValue, ...this.dataKeyFormGroup.value};
if (this.displayAdvanced) { if (this.hasAdvanced) {
this.modelValue.settings = this.dataKeySettingsFormGroup.get('settings').value.model; this.modelValue.settings = this.dataKeySettingsFormGroup.get('settings').value.model;
} }
if (this.modelValue.name) { if (this.modelValue.name) {
@ -449,7 +454,7 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
} }
}; };
} }
if (this.displayAdvanced && (!this.dataKeySettingsFormGroup.valid || !this.modelValue.settings)) { if (this.hasAdvanced && (!this.dataKeySettingsFormGroup.valid || !this.modelValue.settings)) {
return { return {
dataKeySettings: { dataKeySettings: {
valid: false valid: false

View File

@ -15,21 +15,56 @@
limitations under the License. limitations under the License.
--> -->
<section class="tb-widget-settings" [formGroup]="entitiesTableKeySettingsForm" fxLayout="column"> <ng-container [formGroup]="entitiesTableKeySettingsForm">
<mat-form-field fxFlex class="mat-block"> <div class="tb-widget-config-panel">
<mat-label translate>widgets.table.custom-title</mat-label> <div class="tb-widget-config-panel-title" translate>widgets.table.column-settings</div>
<input matInput formControlName="customTitle"> <div class="tb-widget-config-row">
</mat-form-field> <div>{{ 'widgets.table.custom-title' | translate }}</div>
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<mat-label translate>widgets.table.column-width</mat-label> <input matInput formControlName="customTitle" placeholder="{{ 'widget-config.set' | translate }}">
<input matInput formControlName="columnWidth"> </mat-form-field>
</mat-form-field> </div>
<fieldset class="fields-group fields-group-slider"> <div class="tb-widget-config-row space-between">
<legend class="group-title" translate>widgets.table.cell-style</legend> <div>{{ 'widgets.table.column-width' | translate }}</div>
<mat-form-field appearance="outline" class="center" subscriptSizing="dynamic">
<input matInput formControlName="columnWidth" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div>{{ 'widgets.table.default-column-visibility' | translate }}</div>
<mat-form-field style="width: 220px;" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="defaultColumnVisibility">
<mat-option [value]="'visible'">
{{ 'widgets.table.column-visibility-visible' | translate }}
</mat-option>
<mat-option [value]="'hidden'">
{{ 'widgets.table.column-visibility-hidden' | translate }}
</mat-option>
<mat-option [value]="'hidden-mobile'">
{{ 'widgets.table.column-visibility-hidden-mobile' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div>{{ 'widgets.table.column-selection-to-display' | translate }}</div>
<mat-form-field style="width: 220px;" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="columnSelectionToDisplay">
<mat-option [value]="'enabled'">
{{ 'widgets.table.column-selection-to-display-enabled' | translate }}
</mat-option>
<mat-option [value]="'disabled'">
{{ 'widgets.table.column-selection-to-display-disabled' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="tb-widget-config-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="entitiesTableKeySettingsForm.get('useCellStyleFunction').value"> <mat-expansion-panel class="tb-settings" [expanded]="entitiesTableKeySettingsForm.get('useCellStyleFunction').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60"> <mat-panel-title fxFlex="60">
<mat-slide-toggle formControlName="useCellStyleFunction" (click)="$event.stopPropagation()" <mat-slide-toggle class="mat-slide" formControlName="useCellStyleFunction" (click)="$event.stopPropagation()"
fxLayoutAlign="center"> fxLayoutAlign="center">
{{ 'widgets.table.use-cell-style-function' | translate }} {{ 'widgets.table.use-cell-style-function' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
@ -48,13 +83,12 @@
</tb-js-func> </tb-js-func>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</fieldset> </div>
<fieldset class="fields-group fields-group-slider"> <div class="tb-widget-config-panel tb-slide-toggle">
<legend class="group-title" translate>widgets.table.cell-content</legend>
<mat-expansion-panel class="tb-settings" [expanded]="entitiesTableKeySettingsForm.get('useCellContentFunction').value"> <mat-expansion-panel class="tb-settings" [expanded]="entitiesTableKeySettingsForm.get('useCellContentFunction').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60"> <mat-panel-title fxFlex="60">
<mat-slide-toggle formControlName="useCellContentFunction" (click)="$event.stopPropagation()" <mat-slide-toggle class="mat-slide" formControlName="useCellContentFunction" (click)="$event.stopPropagation()"
fxLayoutAlign="center"> fxLayoutAlign="center">
{{ 'widgets.table.use-cell-content-function' | translate }} {{ 'widgets.table.use-cell-content-function' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
@ -73,30 +107,5 @@
</tb-js-func> </tb-js-func>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</fieldset> </div>
<mat-form-field fxFlex class="mat-block"> </ng-container>
<mat-label translate>widgets.table.default-column-visibility</mat-label>
<mat-select formControlName="defaultColumnVisibility">
<mat-option [value]="'visible'">
{{ 'widgets.table.column-visibility-visible' | translate }}
</mat-option>
<mat-option [value]="'hidden'">
{{ 'widgets.table.column-visibility-hidden' | translate }}
</mat-option>
<mat-option [value]="'hidden-mobile'">
{{ 'widgets.table.column-visibility-hidden-mobile' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.table.column-selection-to-display</mat-label>
<mat-select formControlName="columnSelectionToDisplay">
<mat-option [value]="'enabled'">
{{ 'widgets.table.column-selection-to-display-enabled' | translate }}
</mat-option>
<mat-option [value]="'disabled'">
{{ 'widgets.table.column-selection-to-display-disabled' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</section>

View File

@ -16,6 +16,10 @@
@import '../../../../../../../scss/constants'; @import '../../../../../../../scss/constants';
:host { :host {
display: flex;
flex-direction: column;
gap: 16px;
.tb-widget-settings { .tb-widget-settings {
.fields-group { .fields-group {
padding: 0 16px 8px; padding: 0 16px 8px;

View File

@ -318,6 +318,11 @@ export interface DataKey extends KeyInfo {
_hash?: number; _hash?: number;
} }
export enum DataKeyConfigMode {
general = 'general',
advanced = 'advanced'
}
export enum DatasourceType { export enum DatasourceType {
function = 'function', function = 'function',
device = 'device', device = 'device',

View File

@ -1130,6 +1130,7 @@
}, },
"datakey": { "datakey": {
"settings": "Settings", "settings": "Settings",
"general": "General",
"advanced": "Advanced", "advanced": "Advanced",
"key": "Key", "key": "Key",
"label": "Label", "label": "Label",
@ -5231,6 +5232,7 @@
"display-alarm-activity": "Display alarm activity", "display-alarm-activity": "Display alarm activity",
"allow-alarms-assign": "Allow alarms assignment", "allow-alarms-assign": "Allow alarms assignment",
"columns": "Columns", "columns": "Columns",
"column-settings": "Column settings",
"remove-column": "Remove column", "remove-column": "Remove column",
"add-column": "Add column", "add-column": "Add column",
"no-columns": "No columns configured" "no-columns": "No columns configured"

View File

@ -224,6 +224,8 @@ div {
line-height: var(--mdc-typography-caption-line-height, 20px); line-height: var(--mdc-typography-caption-line-height, 20px);
font-weight: var(--mdc-typography-caption-font-weight, 400); font-weight: var(--mdc-typography-caption-font-weight, 400);
letter-spacing: var(--mdc-typography-caption-letter-spacing, 0.0333333333em); letter-spacing: var(--mdc-typography-caption-letter-spacing, 0.0333333333em);
color: rgba(0, 0, 0, 0.6);
white-space: normal;
} }
.mat-caption { .mat-caption {
@ -1199,11 +1201,26 @@ mat-label {
&.no-padding-bottom { &.no-padding-bottom {
padding-bottom: 0; padding-bottom: 0;
} }
&.no-padding {
padding: 0;
}
&.stroked { &.stroked {
box-shadow: none; box-shadow: none;
border: 1px solid rgba(0, 0, 0, 0.12); border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 6px; border-radius: 6px;
} }
&.tb-slide-toggle {
padding: 0;
.mat-expansion-panel {
padding: 16px;
.mat-expansion-panel-header {
height: 32px;
.mat-slide {
margin: 0;
}
}
}
}
.mat-expansion-panel { .mat-expansion-panel {
&.tb-settings { &.tb-settings {
box-shadow: none; box-shadow: none;
@ -1220,6 +1237,11 @@ mat-label {
flex: 0; flex: 0;
white-space: nowrap; white-space: nowrap;
} }
&.fill-width {
.mat-content {
flex: 1;
}
}
&:hover { &:hover {
background: none; background: none;
} }