UI: Timeseries table basic config

This commit is contained in:
Igor Kulikov 2023-06-27 18:17:15 +03:00
parent 97831351a0
commit 987d41f329
17 changed files with 538 additions and 159 deletions

View File

@ -64,7 +64,9 @@
"settingsDirective": "tb-timeseries-table-widget-settings", "settingsDirective": "tb-timeseries-table-widget-settings",
"dataKeySettingsDirective": "tb-timeseries-table-key-settings", "dataKeySettingsDirective": "tb-timeseries-table-key-settings",
"latestDataKeySettingsDirective": "tb-timeseries-table-latest-key-settings", "latestDataKeySettingsDirective": "tb-timeseries-table-latest-key-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}],\"latestDataKeys\":null}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"displayTimewindow\":true}" "hasBasicMode": true,
"basicModeDirective": "tb-timeseries-table-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}],\"latestDataKeys\":null}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"displayTimewindow\":true,\"configMode\":\"basic\"}"
} }
}, },
{ {

View File

@ -1152,7 +1152,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.widgetComponentService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).subscribe( this.widgetComponentService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).subscribe(
(widgetTypeInfo) => { (widgetTypeInfo) => {
const config: WidgetConfig = this.dashboardUtils.widgetConfigFromWidgetType(widgetTypeInfo); const config: WidgetConfig = this.dashboardUtils.widgetConfigFromWidgetType(widgetTypeInfo);
if (!config.title) {
config.title = 'New ' + widgetTypeInfo.widgetName; config.title = 'New ' + widgetTypeInfo.widgetName;
}
let newWidget: Widget = { let newWidget: Widget = {
isSystemType: widget.isSystemType, isSystemType: widget.isSystemType,
bundleAlias: widget.bundleAlias, bundleAlias: widget.bundleAlias,

View File

@ -30,12 +30,16 @@ import {
} from '@home/components/widget/config/basic/cards/entities-table-basic-config.component'; } from '@home/components/widget/config/basic/cards/entities-table-basic-config.component';
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 { DataKeyRowComponent } from '@home/components/widget/config/basic/common/data-key-row.component'; import { DataKeyRowComponent } from '@home/components/widget/config/basic/common/data-key-row.component';
import {
TimeseriesTableBasicConfigComponent
} from '@home/components/widget/config/basic/cards/timeseries-table-basic-config.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
WidgetActionsPanelComponent, WidgetActionsPanelComponent,
SimpleCardBasicConfigComponent, SimpleCardBasicConfigComponent,
EntitiesTableBasicConfigComponent, EntitiesTableBasicConfigComponent,
TimeseriesTableBasicConfigComponent,
DataKeyRowComponent, DataKeyRowComponent,
DataKeysPanelComponent DataKeysPanelComponent
], ],
@ -48,6 +52,7 @@ import { DataKeyRowComponent } from '@home/components/widget/config/basic/common
WidgetActionsPanelComponent, WidgetActionsPanelComponent,
SimpleCardBasicConfigComponent, SimpleCardBasicConfigComponent,
EntitiesTableBasicConfigComponent, EntitiesTableBasicConfigComponent,
TimeseriesTableBasicConfigComponent,
DataKeyRowComponent, DataKeyRowComponent,
DataKeysPanelComponent DataKeysPanelComponent
] ]
@ -57,5 +62,6 @@ export class BasicWidgetConfigModule {
export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetConfigComponent>} = { export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetConfigComponent>} = {
'tb-simple-card-basic-config': SimpleCardBasicConfigComponent, 'tb-simple-card-basic-config': SimpleCardBasicConfigComponent,
'tb-entities-table-basic-config': EntitiesTableBasicConfigComponent 'tb-entities-table-basic-config': EntitiesTableBasicConfigComponent,
'tb-timeseries-table-basic-config': TimeseriesTableBasicConfigComponent
}; };

View File

@ -28,6 +28,7 @@ import {
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { isUndefined } from '@core/utils';
@Component({ @Component({
selector: 'tb-entities-table-basic-config', selector: 'tb-entities-table-basic-config',
@ -75,7 +76,7 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen
this.entitiesTableWidgetConfigForm = this.fb.group({ this.entitiesTableWidgetConfigForm = this.fb.group({
timewindowConfig: [{ timewindowConfig: [{
useDashboardTimewindow: configData.config.useDashboardTimewindow, useDashboardTimewindow: configData.config.useDashboardTimewindow,
displayTimewindow: configData.config.useDashboardTimewindow, displayTimewindow: configData.config.displayTimewindow,
timewindow: configData.config.timewindow timewindow: configData.config.timewindow
}, []], }, []],
datasources: [configData.config.datasources, []], datasources: [configData.config.datasources, []],
@ -155,13 +156,13 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen
private getCardButtons(config: WidgetConfig): string[] { private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = []; const buttons: string[] = [];
if (config.settings?.enableSearch) { if (isUndefined(config.settings?.enableSearch) || config.settings?.enableSearch) {
buttons.push('search'); buttons.push('search');
} }
if (config.settings?.enableSelectColumnDisplay) { if (isUndefined(config.settings?.enableSelectColumnDisplay) || config.settings?.enableSelectColumnDisplay) {
buttons.push('columnsToDisplay'); buttons.push('columnsToDisplay');
} }
if (config.enableFullscreen) { if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen'); buttons.push('fullscreen');
} }
return buttons; return buttons;

View File

@ -0,0 +1,94 @@
<!--
Copyright © 2016-2023 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.
-->
<ng-container [formGroup]="timeseriesTableWidgetConfigForm">
<tb-timewindow-config-panel formControlName="timewindowConfig">
</tb-timewindow-config-panel>
<tb-datasources
[configMode]="basicMode"
hideDataKeys
formControlName="datasources">
</tb-datasources>
<tb-data-keys-panel
panelTitle="{{ 'widgets.table.columns' | translate }}"
addKeyTitle="{{ 'widgets.table.add-column' | translate }}"
keySettingsTitle="{{ 'widgets.table.column-settings' | translate }}"
removeKeyTitle="{{ 'widgets.table.remove-column' | translate }}"
noKeysText="{{ 'widgets.table.no-columns' | translate }}"
requiredKeysText="{{ 'widgets.table.timeseries-column-error' | translate }}"
hideDataKeyColor
[datasourceType]="datasource?.type"
[deviceId]="datasource?.deviceId"
[entityAliasId]="datasource?.entityAliasId"
formControlName="columns">
</tb-data-keys-panel>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widget-config.appearance</div>
<div class="tb-widget-config-row">
<mat-slide-toggle class="mat-slide" formControlName="showTitle">
{{ 'widget-config.card-title' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between same-padding">
<mat-slide-toggle class="mat-slide" formControlName="showTitleIcon">
{{ 'widget-config.card-icon' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<tb-material-icon-select asBoxInput
[color]="timeseriesTableWidgetConfigForm.get('iconColor').value"
formControlName="titleIcon">
</tb-material-icon-select>
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="iconColor">
</tb-color-input>
</div>
</div>
<div class="tb-widget-config-row space-between">
<div translate>widgets.table.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="search">{{ 'action.search' | translate }}</mat-chip-option>
<mat-chip-option value="columnsToDisplay">{{ 'widgets.table.columns-to-display' | translate }}</mat-chip-option>
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div>{{ 'widget-config.text-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 class="tb-widget-config-row space-between same-padding">
<div>{{ 'widget-config.background-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="backgroundColor">
</tb-color-input>
</div>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">
</tb-widget-actions-panel>
</ng-container>

View File

@ -0,0 +1,181 @@
///
/// Copyright © 2016-2023 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 } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import {
DataKey,
Datasource,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation, WidgetConfig
} from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { deepClone, isUndefined } from '@core/utils';
@Component({
selector: 'tb-timeseries-table-basic-config',
templateUrl: './timeseries-table-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigComponent {
public get datasource(): Datasource {
const datasources: Datasource[] = this.timeseriesTableWidgetConfigForm.get('datasources').value;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
timeseriesTableWidgetConfigForm: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.timeseriesTableWidgetConfigForm;
}
protected setupDefaults(configData: WidgetConfigComponentData) {
this.setupDefaultDatasource(configData,
[{ name: 'temperature', label: 'Temperature', type: DataKeyType.timeseries, units: '°C', decimals: 0 }]);
}
protected onConfigSet(configData: WidgetConfigComponentData) {
this.timeseriesTableWidgetConfigForm = this.fb.group({
timewindowConfig: [{
useDashboardTimewindow: configData.config.useDashboardTimewindow,
displayTimewindow: configData.config.displayTimewindow,
timewindow: configData.config.timewindow
}, []],
datasources: [configData.config.datasources, []],
columns: [this.getColumns(configData.config.datasources), []],
showTitle: [configData.config.showTitle, []],
title: [configData.config.title, []],
showTitleIcon: [configData.config.showTitleIcon, []],
titleIcon: [configData.config.titleIcon, []],
iconColor: [configData.config.iconColor, []],
cardButtons: [this.getCardButtons(configData.config), []],
color: [configData.config.color, []],
backgroundColor: [configData.config.backgroundColor, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
this.widgetConfig.config.useDashboardTimewindow = config.timewindowConfig.useDashboardTimewindow;
this.widgetConfig.config.displayTimewindow = config.timewindowConfig.displayTimewindow;
this.widgetConfig.config.timewindow = config.timewindowConfig.timewindow;
this.widgetConfig.config.datasources = config.datasources;
this.setColumns(config.columns, this.widgetConfig.config.datasources);
this.widgetConfig.config.actions = config.actions;
this.widgetConfig.config.showTitle = config.showTitle;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.entitiesTitle = config.title;
this.widgetConfig.config.showTitleIcon = config.showTitleIcon;
this.widgetConfig.config.titleIcon = config.titleIcon;
this.widgetConfig.config.iconColor = config.iconColor;
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.color = config.color;
this.widgetConfig.config.backgroundColor = config.backgroundColor;
return this.widgetConfig;
}
protected validatorTriggers(): string[] {
return ['showTitle', 'showTitleIcon'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.timeseriesTableWidgetConfigForm.get('showTitle').value;
const showTitleIcon: boolean = this.timeseriesTableWidgetConfigForm.get('showTitleIcon').value;
if (showTitle) {
this.timeseriesTableWidgetConfigForm.get('title').enable();
this.timeseriesTableWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false});
if (showTitleIcon) {
this.timeseriesTableWidgetConfigForm.get('titleIcon').enable();
this.timeseriesTableWidgetConfigForm.get('iconColor').enable();
} else {
this.timeseriesTableWidgetConfigForm.get('titleIcon').disable();
this.timeseriesTableWidgetConfigForm.get('iconColor').disable();
}
} else {
this.timeseriesTableWidgetConfigForm.get('title').disable();
this.timeseriesTableWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false});
this.timeseriesTableWidgetConfigForm.get('titleIcon').disable();
this.timeseriesTableWidgetConfigForm.get('iconColor').disable();
}
this.timeseriesTableWidgetConfigForm.get('title').updateValueAndValidity({emitEvent});
this.timeseriesTableWidgetConfigForm.get('showTitleIcon').updateValueAndValidity({emitEvent: false});
this.timeseriesTableWidgetConfigForm.get('titleIcon').updateValueAndValidity({emitEvent});
this.timeseriesTableWidgetConfigForm.get('iconColor').updateValueAndValidity({emitEvent});
}
private getColumns(datasources?: Datasource[]): DataKey[] {
if (datasources && datasources.length) {
const dataKeys = deepClone(datasources[0].dataKeys) || [];
dataKeys.forEach(k => {
(k as any).latest = false;
});
const latestDataKeys = deepClone(datasources[0].latestDataKeys) || [];
latestDataKeys.forEach(k => {
(k as any).latest = true;
});
return dataKeys.concat(latestDataKeys);
}
return [];
}
private setColumns(columns: DataKey[], datasources?: Datasource[]) {
if (datasources && datasources.length) {
const dataKeys = deepClone(columns.filter(c => !(c as any).latest));
dataKeys.forEach(k => delete (k as any).latest);
const latestDataKeys = deepClone(columns.filter(c => (c as any).latest));
latestDataKeys.forEach(k => delete (k as any).latest);
datasources[0].dataKeys = dataKeys;
datasources[0].latestDataKeys = latestDataKeys;
}
}
private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = [];
if (isUndefined(config.settings?.enableSearch) || config.settings?.enableSearch) {
buttons.push('search');
}
if (isUndefined(config.settings?.enableSelectColumnDisplay) || config.settings?.enableSelectColumnDisplay) {
buttons.push('columnsToDisplay');
}
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen');
}
return buttons;
}
private setCardButtons(buttons: string[], config: WidgetConfig) {
config.settings.enableSearch = buttons.includes('search');
config.settings.enableSelectColumnDisplay = buttons.includes('columnsToDisplay');
config.enableFullscreen = buttons.includes('fullscreen');
}
}

View File

@ -16,6 +16,12 @@
--> -->
<div [formGroup]="keyRowFormGroup" class="tb-data-key-row"> <div [formGroup]="keyRowFormGroup" class="tb-data-key-row">
<mat-form-field *ngIf="hasAdditionalLatestDataKeys" class="tb-inline-field tb-source-field" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="latest">
<mat-option [value]="false">{{ 'datakey.timeseries' | translate }}</mat-option>
<mat-option [value]="true">{{ 'datakey.latest' | translate }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex class="tb-inline-field tb-key-field" subscriptSizing="dynamic"> <mat-form-field fxFlex class="tb-inline-field tb-key-field" subscriptSizing="dynamic">
<mat-chip-grid #chipList> <mat-chip-grid #chipList>
<mat-chip-row class="tb-datakey-chip" *ngIf="modelValue.type" <mat-chip-row class="tb-datakey-chip" *ngIf="modelValue.type"

View File

@ -43,6 +43,10 @@
} }
} }
.tb-source-field {
width: 140px;
}
.tb-color-field, .tb-units-field, .tb-decimals-field { .tb-color-field, .tb-units-field, .tb-decimals-field {
width: 60px; width: 60px;
display: flex; display: flex;

View File

@ -153,6 +153,11 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
return this.widgetConfigComponent.widgetConfigCallbacks; return this.widgetConfigComponent.widgetConfigCallbacks;
} }
get hasAdditionalLatestDataKeys(): boolean {
return this.widgetConfigComponent.widgetType === widgetType.timeseries &&
this.widgetConfigComponent.modelValue?.typeParameters?.hasAdditionalLatestDataKeys;
}
get widget(): Widget { get widget(): Widget {
return this.widgetConfigComponent.widget; return this.widgetConfigComponent.widget;
} }
@ -165,7 +170,7 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
return this.widgetConfigComponent.aliasController; return this.widgetConfigComponent.aliasController;
} }
get datakeySettingsSchema(): JsonSettingsSchema { get dataKeySettingsSchema(): JsonSettingsSchema {
return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema; return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema;
} }
@ -173,6 +178,14 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
return this.widgetConfigComponent.modelValue?.dataKeySettingsDirective; return this.widgetConfigComponent.modelValue?.dataKeySettingsDirective;
} }
get latestDataKeySettingsSchema(): JsonSettingsSchema {
return this.widgetConfigComponent.modelValue?.latestDataKeySettingsSchema;
}
get latestDataKeySettingsDirective(): string {
return this.widgetConfigComponent.modelValue?.latestDataKeySettingsDirective;
}
get isEntityDatasource(): boolean { get isEntityDatasource(): boolean {
return [DatasourceType.device, DatasourceType.entity].includes(this.datasourceType); return [DatasourceType.device, DatasourceType.entity].includes(this.datasourceType);
} }
@ -193,6 +206,10 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
return this.dataKeysPanelComponent.dragEnabled; return this.dataKeysPanelComponent.dragEnabled;
} }
get isLatestDataKeys(): boolean {
return this.hasAdditionalLatestDataKeys && this.keyRowFormGroup.get('latest').value === true;
}
private propagateChange = (_val: any) => {}; private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder, constructor(private fb: UntypedFormBuilder,
@ -212,6 +229,12 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
units: [null, []], units: [null, []],
decimals: [null, []], decimals: [null, []],
}); });
if (this.hasAdditionalLatestDataKeys) {
this.keyRowFormGroup.addControl('latest', this.fb.control(false));
this.keyRowFormGroup.valueChanges.subscribe(
() => this.clearKeySearchCache()
);
}
this.keyRowFormGroup.valueChanges.subscribe( this.keyRowFormGroup.valueChanges.subscribe(
() => this.updateModel() () => this.updateModel()
); );
@ -286,6 +309,11 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
decimals: value?.decimals decimals: value?.decimals
}, {emitEvent: false} }, {emitEvent: false}
); );
if (this.hasAdditionalLatestDataKeys) {
this.keyRowFormGroup.patchValue({
latest: (value as any)?.latest
}, {emitEvent: false});
}
this.cd.markForCheck(); this.cd.markForCheck();
} }
@ -323,8 +351,8 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
data: { data: {
dataKey: deepClone(this.modelValue), dataKey: deepClone(this.modelValue),
dataKeyConfigMode: advanced ? DataKeyConfigMode.advanced : DataKeyConfigMode.general, dataKeyConfigMode: advanced ? DataKeyConfigMode.advanced : DataKeyConfigMode.general,
dataKeySettingsSchema: this.datakeySettingsSchema, dataKeySettingsSchema: this.isLatestDataKeys ? this.latestDataKeySettingsSchema : this.dataKeySettingsSchema,
dataKeySettingsDirective: this.dataKeySettingsDirective, dataKeySettingsDirective: this.isLatestDataKeys ? this.latestDataKeySettingsDirective : this.dataKeySettingsDirective,
dashboard: this.dashboard, dashboard: this.dashboard,
aliasController: this.aliasController, aliasController: this.aliasController,
widget: this.widget, widget: this.widget,
@ -399,7 +427,7 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
} else if (this.datasourceType === DatasourceType.entity && this.entityAliasId || } else if (this.datasourceType === DatasourceType.entity && this.entityAliasId ||
this.datasourceType === DatasourceType.device && this.deviceId) { this.datasourceType === DatasourceType.device && this.deviceId) {
const dataKeyTypes = [DataKeyType.timeseries]; const dataKeyTypes = [DataKeyType.timeseries];
if (this.widgetType === widgetType.latest || this.widgetType === widgetType.alarm) { if (this.isLatestDataKeys || this.widgetType === widgetType.latest || this.widgetType === widgetType.alarm) {
dataKeyTypes.push(DataKeyType.attribute); dataKeyTypes.push(DataKeyType.attribute);
dataKeyTypes.push(DataKeyType.entityField); dataKeyTypes.push(DataKeyType.entityField);
if (this.widgetType === widgetType.alarm) { if (this.widgetType === widgetType.alarm) {
@ -428,7 +456,7 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
} }
private addKeyFromChipValue(chip: DataKey) { private addKeyFromChipValue(chip: DataKey) {
this.modelValue = this.callbacks.generateDataKey(chip.name, chip.type, this.datakeySettingsSchema); this.modelValue = this.callbacks.generateDataKey(chip.name, chip.type, this.dataKeySettingsSchema);
if (!this.keyRowFormGroup.get('label').value) { if (!this.keyRowFormGroup.get('label').value) {
this.keyRowFormGroup.get('label').patchValue(this.modelValue.label, {emitEvent: false}); this.keyRowFormGroup.get('label').patchValue(this.modelValue.label, {emitEvent: false});
} }

View File

@ -19,6 +19,7 @@
<div class="tb-widget-config-panel-title">{{ panelTitle }}</div> <div class="tb-widget-config-panel-title">{{ panelTitle }}</div>
<div class="tb-data-keys-table"> <div class="tb-data-keys-table">
<div class="tb-data-keys-header"> <div class="tb-data-keys-header">
<div *ngIf="hasAdditionalLatestDataKeys" class="tb-data-keys-header-cell tb-source-header" translate>datakey.source</div>
<div class="tb-data-keys-header-cell" fxFlex translate>datakey.key</div> <div class="tb-data-keys-header-cell" fxFlex translate>datakey.key</div>
<div class="tb-data-keys-header-cell" fxFlex translate>datakey.label</div> <div class="tb-data-keys-header-cell" fxFlex translate>datakey.label</div>
<div *ngIf="!hideDataKeyColor" class="tb-data-keys-header-cell tb-color-header" translate>datakey.color</div> <div *ngIf="!hideDataKeyColor" class="tb-data-keys-header-cell tb-color-header" translate>datakey.color</div>
@ -52,6 +53,7 @@
</div> </div>
</div> </div>
</div> </div>
<tb-error *ngIf="errorText" noMargin [error]="errorText" style="padding-left: 12px;"></tb-error>
</div> </div>
<div> <div>
<button type="button" mat-stroked-button color="primary" (click)="addKey()"> <button type="button" mat-stroked-button color="primary" (click)="addKey()">

View File

@ -35,6 +35,9 @@
line-height: 20px; line-height: 20px;
letter-spacing: 0.2px; letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.54); color: rgba(0, 0, 0, 0.54);
&.tb-source-header {
width: 140px;
}
&.tb-color-header, &.tb-units-header, &.tb-decimals-header { &.tb-color-header, &.tb-units-header, &.tb-decimals-header {
width: 60px; width: 60px;
} }

View File

@ -84,6 +84,9 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
@Input() @Input()
noKeysText: string; noKeysText: string;
@Input()
requiredKeysText: string;
@Input() @Input()
datasourceType: DatasourceType; datasourceType: DatasourceType;
@ -103,6 +106,8 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
keysListFormGroup: UntypedFormGroup; keysListFormGroup: UntypedFormGroup;
errorText = '';
get widgetType(): widgetType { get widgetType(): widgetType {
return this.widgetConfigComponent.widgetType; return this.widgetConfigComponent.widgetType;
} }
@ -111,6 +116,11 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
return this.widgetConfigComponent.widgetConfigCallbacks; return this.widgetConfigComponent.widgetConfigCallbacks;
} }
get hasAdditionalLatestDataKeys(): boolean {
return this.widgetConfigComponent.widgetType === widgetType.timeseries &&
this.widgetConfigComponent.modelValue?.typeParameters?.hasAdditionalLatestDataKeys;
}
get datakeySettingsSchema(): JsonSettingsSchema { get datakeySettingsSchema(): JsonSettingsSchema {
return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema; return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema;
} }
@ -119,6 +129,14 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
return this.keysFormArray().controls.length > 1; return this.keysFormArray().controls.length > 1;
} }
get noKeys(): boolean {
let keys: DataKey[] = this.keysListFormGroup.get('keys').value;
if (this.hasAdditionalLatestDataKeys) {
keys = keys.filter(k => !(k as any).latest);
}
return keys.length === 0;
}
private propagateChange = (_val: any) => {}; private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder, constructor(private fb: UntypedFormBuilder,
@ -196,7 +214,13 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
} }
public validate(c: UntypedFormControl) { public validate(c: UntypedFormControl) {
return this.keysListFormGroup.valid ? null : { this.errorText = '';
let valid = this.keysListFormGroup.valid;
if (this.noKeys && this.requiredKeysText) {
valid = false;
this.errorText = this.requiredKeysText;
}
return valid ? null : {
dataKeyRows: { dataKeyRows: {
valid: false, valid: false,
}, },
@ -226,6 +250,9 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
const dataKey = this.callbacks.generateDataKey('', null, this.datakeySettingsSchema); const dataKey = this.callbacks.generateDataKey('', null, this.datakeySettingsSchema);
dataKey.label = ''; dataKey.label = '';
dataKey.decimals = 0; dataKey.decimals = 0;
if (this.hasAdditionalLatestDataKeys) {
(dataKey as any).latest = false;
}
const keysArray = this.keysListFormGroup.get('keys') as UntypedFormArray; const keysArray = this.keysListFormGroup.get('keys') as UntypedFormArray;
const keyControl = this.fb.control(dataKey, [dataKeyRowValidator]); const keyControl = this.fb.control(dataKey, [dataKeyRowValidator]);
keysArray.push(keyControl); keysArray.push(keyControl);

View File

@ -15,18 +15,49 @@
limitations under the License. limitations under the License.
--> -->
<section class="tb-widget-settings" [formGroup]="timeseriesTableKeySettingsForm" fxLayout="column"> <ng-container [formGroup]="timeseriesTableKeySettingsForm">
<fieldset class="fields-group fields-group-slider"> <div class="tb-widget-config-panel">
<legend class="group-title" translate>widgets.table.cell-style</legend> <div class="tb-widget-config-panel-title" translate>widgets.table.column-settings</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]="timeseriesTableKeySettingsForm.get('useCellStyleFunction').value"> <mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableKeySettingsForm.get('useCellStyleFunction').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title> <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>
</mat-panel-title> </mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate> <mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings widget-config.advanced-settings
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
@ -40,18 +71,17 @@
</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]="timeseriesTableKeySettingsForm.get('useCellContentFunction').value"> <mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableKeySettingsForm.get('useCellContentFunction').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title> <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>
</mat-panel-title> </mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate> <mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings widget-config.advanced-settings
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
@ -65,30 +95,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

@ -15,25 +15,58 @@
limitations under the License. limitations under the License.
--> -->
<section class="tb-widget-settings" [formGroup]="timeseriesTableLatestKeySettingsForm" fxLayout="column"> <ng-container [formGroup]="timeseriesTableLatestKeySettingsForm">
<mat-slide-toggle class="mat-slide" formControlName="show"> <div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.column-settings</div>
<mat-slide-toggle class="mat-slide no-margin" formControlName="show">
{{ 'widgets.table.show-latest-data-column' | translate }} {{ 'widgets.table.show-latest-data-column' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<mat-form-field [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" fxFlex class="mat-block"> <div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-widget-config-row space-between">
<mat-label translate>widgets.table.latest-data-column-order</mat-label> <div translate>widgets.table.latest-data-column-order</div>
<input matInput type="number" step="1" formControlName="order"> <mat-form-field appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput formControlName="order" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
<fieldset [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="fields-group fields-group-slider"> </div>
<legend class="group-title" translate>widgets.table.cell-style</legend> <div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" 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 [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" 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 [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-widget-config-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableLatestKeySettingsForm.get('useCellStyleFunction').value"> <mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableLatestKeySettingsForm.get('useCellStyleFunction').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title> <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>
</mat-panel-title> </mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate> <mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings widget-config.advanced-settings
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
@ -47,18 +80,17 @@
</tb-js-func> </tb-js-func>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</fieldset> </div>
<fieldset [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="fields-group fields-group-slider"> <div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" 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]="timeseriesTableLatestKeySettingsForm.get('useCellContentFunction').value"> <mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableLatestKeySettingsForm.get('useCellContentFunction').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title> <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>
</mat-panel-title> </mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate> <mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings widget-config.advanced-settings
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
@ -72,30 +104,6 @@
</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

@ -15,28 +15,31 @@
limitations under the License. limitations under the License.
--> -->
<section class="tb-widget-settings" [formGroup]="timeseriesTableWidgetSettingsForm" fxLayout="column"> <ng-container [formGroup]="timeseriesTableWidgetSettingsForm">
<fieldset class="fields-group"> <div class="tb-widget-config-panel">
<legend class="group-title" translate>widgets.table.common-table-settings</legend> <div class="tb-widget-config-panel-title" translate>widgets.table.table-header</div>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px"> <mat-slide-toggle class="mat-slide no-margin" formControlName="enableStickyHeader">
<section fxLayout="column" fxFlex>
<mat-checkbox formControlName="enableSearch">
{{ 'widgets.table.enable-search' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="enableSelectColumnDisplay">
{{ 'widgets.table.enable-select-column-display' | translate }}
</mat-checkbox>
</section>
<section fxLayout="column" fxFlex>
<mat-checkbox formControlName="enableStickyHeader">
{{ 'widgets.table.enable-sticky-header' | translate }} {{ 'widgets.table.enable-sticky-header' | translate }}
</mat-checkbox> </mat-slide-toggle>
<mat-checkbox formControlName="enableStickyAction"> <mat-slide-toggle class="mat-slide no-margin" formControlName="enableSearch">
{{ 'widgets.table.enable-search' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide no-margin" formControlName="enableSelectColumnDisplay">
{{ 'widgets.table.enable-select-column-display' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.columns</div>
<mat-slide-toggle class="mat-slide no-margin" formControlName="showTimestamp">
{{ 'widgets.table.display-timestamp' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide no-margin" formControlName="showMilliseconds">
{{ 'widgets.table.display-milliseconds' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide no-margin" formControlName="enableStickyAction">
{{ 'widgets.table.enable-sticky-action' | translate }} {{ 'widgets.table.enable-sticky-action' | translate }}
</mat-checkbox> </mat-slide-toggle>
</section> <mat-form-field fxFlex subscriptSizing="dynamic">
</section>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.table.hidden-cell-button-display-mode</mat-label> <mat-label translate>widgets.table.hidden-cell-button-display-mode</mat-label>
<mat-select formControlName="reserveSpaceForHiddenAction"> <mat-select formControlName="reserveSpaceForHiddenAction">
<mat-option [value]="'true'"> <mat-option [value]="'true'">
@ -47,41 +50,39 @@
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<section fxLayout="column" fxLayoutGap="8px"> </div>
<mat-slide-toggle formControlName="showTimestamp"> <div class="tb-widget-config-panel">
{{ 'widgets.table.display-timestamp' | translate }} <div class="tb-widget-config-panel-title" translate>widgets.table.pagination</div>
</mat-slide-toggle> <mat-slide-toggle class="mat-slide no-margin" formControlName="displayPagination">
<mat-slide-toggle formControlName="showMilliseconds">
{{ 'widgets.table.display-milliseconds' | translate }}
</mat-slide-toggle>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap="8px" fxLayoutAlign.gt-xs="start center">
<mat-slide-toggle fxFlex formControlName="displayPagination">
{{ 'widgets.table.display-pagination' | translate }} {{ 'widgets.table.display-pagination' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<mat-form-field fxFlex class="mat-block"> <div class="tb-widget-config-row space-between">
<mat-label translate>widgets.table.default-page-size</mat-label> <div translate>widgets.table.default-page-size</div>
<input matInput type="number" min="1" step="1" formControlName="defaultPageSize"> <mat-form-field appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput formControlName="defaultPageSize" type="number" min="1" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
</section> </div>
<mat-slide-toggle formControlName="useEntityLabel"> </div>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.table-tabs</div>
<mat-slide-toggle class="mat-slide no-margin" formControlName="useEntityLabel">
{{ 'widgets.table.use-entity-label-tab-name' | translate }} {{ 'widgets.table.use-entity-label-tab-name' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<mat-slide-toggle formControlName="hideEmptyLines"> </div>
<div class="tb-widget-config-panel tb-slide-toggle" style="padding-bottom: 16px;">
<div class="tb-widget-config-panel-title" translate>widgets.table.rows</div>
<mat-slide-toggle class="mat-slide no-margin" style="padding-left: 16px;" formControlName="hideEmptyLines">
{{ 'widgets.table.hide-empty-lines' | translate }} {{ 'widgets.table.hide-empty-lines' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</section>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.table.row-style</legend>
<mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableWidgetSettingsForm.get('useRowStyleFunction').value"> <mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableWidgetSettingsForm.get('useRowStyleFunction').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title> <mat-panel-title fxFlex="60">
<mat-slide-toggle formControlName="useRowStyleFunction" (click)="$event.stopPropagation()" <mat-slide-toggle class="mat-slide" formControlName="useRowStyleFunction" (click)="$event.stopPropagation()"
fxLayoutAlign="center"> fxLayoutAlign="center">
{{ 'widgets.table.use-row-style-function' | translate }} {{ 'widgets.table.use-row-style-function' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</mat-panel-title> </mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate> <mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings widget-config.advanced-settings
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
@ -95,5 +96,5 @@
</tb-js-func> </tb-js-func>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</fieldset> </div>
</section> </ng-container>

View File

@ -16,11 +16,12 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations'; import { animate, state, style, transition, trigger } from '@angular/animations';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({ @Component({
selector: 'tb-error', selector: 'tb-error',
template: ` template: `
<div [@animation]="state" style="margin-top:0.5rem;font-size:.75rem"> <div [@animation]="state" [ngStyle]="{marginTop: noMargin ? '0' : '0.5rem', fontSize: '.75rem'}">
<mat-error > <mat-error >
{{message}} {{message}}
</mat-error> </mat-error>
@ -51,6 +52,10 @@ export class TbErrorComponent {
state: any; state: any;
message; message;
@Input()
@coerceBoolean()
noMargin = false;
@Input() @Input()
set error(value) { set error(value) {
if (value && !this.message) { if (value && !this.message) {

View File

@ -1188,7 +1188,9 @@
"delta-calculation-result": "Delta calculation result", "delta-calculation-result": "Delta calculation result",
"delta-calculation-result-previous-value": "Previous value", "delta-calculation-result-previous-value": "Previous value",
"delta-calculation-result-delta-absolute": "Delta (absolute)", "delta-calculation-result-delta-absolute": "Delta (absolute)",
"delta-calculation-result-delta-percent": "Delta (percent)" "delta-calculation-result-delta-percent": "Delta (percent)",
"source": "Source",
"latest": "Latest"
}, },
"datasource": { "datasource": {
"type": "Datasource type", "type": "Datasource type",
@ -5240,7 +5242,9 @@
"table-header": "Table header", "table-header": "Table header",
"header-buttons": "Header buttons", "header-buttons": "Header buttons",
"pagination": "Pagination", "pagination": "Pagination",
"rows": "Rows" "rows": "Rows",
"timeseries-column-error": "At least one timeseries column should be specified",
"table-tabs": "Table tabs"
}, },
"value-source": { "value-source": {
"value-source": "Value source", "value-source": "Value source",