Browse Source

UI: Implement 'Progress bar' widget.

pull/9447/head
Igor Kulikov 3 years ago
parent
commit
1320881a19
  1. 1
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 3
      application/src/main/data/json/system/widget_bundles/status_indicators.json
  3. 26
      application/src/main/data/json/system/widget_types/progress_bar.json
  4. 12
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  5. 171
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html
  6. 262
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.ts
  7. 42
      ui-ngx/src/app/modules/home/components/widget/lib/cards/progress-bar-widget.component.html
  8. 84
      ui-ngx/src/app/modules/home/components/widget/lib/cards/progress-bar-widget.component.scss
  9. 221
      ui-ngx/src/app/modules/home/components/widget/lib/cards/progress-bar-widget.component.ts
  10. 98
      ui-ngx/src/app/modules/home/components/widget/lib/cards/progress-bar-widget.models.ts
  11. 5
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss
  12. 1
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts
  13. 94
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html
  14. 133
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.ts
  15. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  16. 5
      ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.component.scss
  17. 7
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  18. 16
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  19. 38
      ui-ngx/src/assets/widget/progress-bar/default-layout.svg
  20. 36
      ui-ngx/src/assets/widget/progress-bar/simplified-layout.svg
  21. 7
      ui-ngx/src/form.scss

1
application/src/main/data/json/system/widget_bundles/cards.json

@ -12,6 +12,7 @@
"cards.horizontal_value_card",
"cards.aggregated_value_card",
"simple_value_and_chart_card",
"progress_bar",
"cards.label_widget",
"cards.dashboard_state_widget",
"cards.qr_code",

3
application/src/main/data/json/system/widget_bundles/status_indicators.json

@ -9,6 +9,7 @@
},
"widgetTypeFqns": [
"battery_level",
"signal_strength"
"signal_strength",
"progress_bar"
]
}

26
application/src/main/data/json/system/widget_types/progress_bar.json

@ -0,0 +1,26 @@
{
"fqn": "progress_bar",
"name": "Progress bar",
"deprecated": false,
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAeFBMVEXg4ODf39/g4ODg4OAAAAD////g4OD19fUhISE/Ut3j4+OsrKzHx8eQkJB0dHQ8PDzx8fEvLy+6urrV1dWCgoJYWFienp5KSkrv7++GhoZmZmaXl5f39/epqanLy8vCwsL29vZtfORiceJ+fn7m5ubU1NTn6fve4fIbcz38AAAABXRSTlPvIL+vAC9A4IoAAASZSURBVHja7d0NT9swEIDhMji4nb/jxEk/NmCM7f//w/niQLtJyxamtdfiF6EmDkh+1Di0IojVzdWH1e25d311s7paDXD2+cy49nABDfm0gotodSmQ2woRVoVIq0KkVSHSqhBpVYi0KkRaFSKtGUijctrDv6XQwEzHgDjkXLoAiKaoAurzhxAAGGxBm2gMgE+25SHeUNE0QKbRNo/otlWQo2QNQa6xrdpD1DSqW6vyBhmtrD86RKHNGIcOyOUHZJDDEEIe1xgQdT487gGF0CNmgUXnsH+BhPzhCKDFfNh50OgQ6dgQv0HFEB2hw8QYDQo3HlqGZFf0066GDiNQFmvcMEZNEMdH2zza8WhivopwVEjX9QF7yBCVSbwFCc04YdBoy4xhkw3QYAsWjQciiNhH8OT3a8RnXo4ooRm/M3dMSN91m+QnCM+6zN9h2ShTyq4xy08X9mpcVxgswR4yfktymDsJhICbIPEA4n+GJJXTfNS48atIWQzxABKQl0YEJQDiQ/Dl1LeoDyHtONWYj41TDtDYvJMwTZA2w7ADwwNJAAQS9sqgY4NLyb1CKGCrGNeh0QY7Pq6bHvUEQaMcKh5VJkiAgAmIHfGGw7B5hQD1iMHkR4vlC5TLA+l1jeS9dlo5LdojQeYj/7rBp8xrnvz+kSOCg349WjolpES9itphBEm9BeI3OJ46omLI8qhppP2e6z28sTqvKkRaFSKtCpFWhUirQqRVIdKqEGlViLQqRFoVIq0KkVaFSKtCpFUh0qoQaVWItCpEWhUirQqR1jzER609HEbSbhT4G4g3AXOWYKrpAnYgszlIjyVXJNTl7XOEJMQ+ArXT5L3LpBQJZDYD6RGJAQED5DKoBbnN3uTfvIAAgBB7EFyBzBbQlXsTNV/EQGh/hPgWUZW7e1PglR+hJOw6PA/RfCs2Gsg5nApFYvtO1L2A8xC1n3ne6DWQRewKEfQGSM7FeB5CSrUOUY0QN13D0DPEQuzANiAlhsxHAYNnSA9ci6gZ5Jq2VRbEVCCzWcRYrl2cKRAgk6gT9NPx9xBvbTqYeofBTyz9IlTaiDm3Zp6RckoxAePIScxzGKBEPTnVK5DRDMS+vtZyLMguBbTZv1BxpAw0UpbJDMQ7LAWCXIMl52EsGVAtKCmvv2YgQNYxY0PTbs97xh9SN07KemfITFHHw4nzntA3jO/kPfsZVSHSqhBpVYi0KkRaFSKtCpHWO4B8e/y4rC/f7/5/D8shXz4u7fHuCO0WQz4u7+4YLYY8LnZ8uTtCu+VrZOm59XiMNbJ7x1etM6tCpFUh0qoQac1A7s325L+P2t1/AoBhu90O04SWQ4b1ANsTP10Pa7XOD5/u4fMWbte74WlYDnlOAM3Jn5KBIesBHp7gXmXS5+WQz5/EQOz42TQVIgWyHgDyqfVGyPD0AOkZThxD+JrznBf7NpO+LodAszZbOHEF8nW9ZYEyZvnll9vtQErDNKH3/ZP9vKoQaVWItCpEWhUirQqRVoVIq0KkVSHSqhBpVYi0LgiygovodvXhAv71NIC/Xl2txPzpxNsbMuPm6vr23Ft9uLr5AdpbpSAPQyy7AAAAAElFTkSuQmCC",
"description": "Displays any value reading as a horizontal progress bar. Allows to configure value range, bar colors, and other settings.",
"descriptor": {
"type": "latest",
"sizeX": 3,
"sizeY": 2,
"resources": [],
"templateHtml": "<tb-progress-bar-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-progress-bar-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.progressBarWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.progressBarWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '280px',\n previewHeight: '180px',\n embedTitlePanel: true\n };\n};\n\n\nself.onDestroy = function() {\n};\n",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-progress-bar-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-progress-bar-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 7;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 0;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"layout\":\"default\",\"autoScale\":true,\"showValue\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":24,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"32px\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"tickMin\":0,\"tickMax\":100,\"showTicks\":true,\"ticksFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"ticksColor\":\"rgba(0,0,0,0.54)\",\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Progress bar\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"%\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"mdi:water-percent\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"18px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
},
"externalId": null,
"tags": [
"progress"
]
}

12
ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts

@ -67,6 +67,9 @@ import {
import {
ValueChartCardBasicConfigComponent
} from '@home/components/widget/config/basic/cards/value-chart-card-basic-config.component';
import {
ProgressBarBasicConfigComponent
} from '@home/components/widget/config/basic/cards/progress-bar-basic-config.component';
@NgModule({
declarations: [
@ -87,7 +90,8 @@ import {
BatteryLevelBasicConfigComponent,
WindSpeedDirectionBasicConfigComponent,
SignalStrengthBasicConfigComponent,
ValueChartCardBasicConfigComponent
ValueChartCardBasicConfigComponent,
ProgressBarBasicConfigComponent
],
imports: [
CommonModule,
@ -112,7 +116,8 @@ import {
BatteryLevelBasicConfigComponent,
WindSpeedDirectionBasicConfigComponent,
SignalStrengthBasicConfigComponent,
ValueChartCardBasicConfigComponent
ValueChartCardBasicConfigComponent,
ProgressBarBasicConfigComponent
]
})
export class BasicWidgetConfigModule {
@ -131,5 +136,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
'tb-battery-level-basic-config': BatteryLevelBasicConfigComponent,
'tb-wind-speed-direction-basic-config': WindSpeedDirectionBasicConfigComponent,
'tb-signal-strength-basic-config': SignalStrengthBasicConfigComponent,
'tb-value-chart-card-basic-config': ValueChartCardBasicConfigComponent
'tb-value-chart-card-basic-config': ValueChartCardBasicConfigComponent,
'tb-progress-bar-basic-config': ProgressBarBasicConfigComponent
};

171
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html

@ -0,0 +1,171 @@
<!--
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]="progressBarWidgetConfigForm">
<tb-timewindow-config-panel *ngIf="displayTimewindowConfig"
[onlyHistoryTimewindow]="onlyHistoryTimewindow()"
formControlName="timewindowConfig">
</tb-timewindow-config-panel>
<tb-datasources
[configMode]="basicMode"
hideDataKeyLabel
hideDataKeyColor
hideDataKeyUnits
hideDataKeyDecimals
formControlName="datasources">
</tb-datasources>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
<tb-image-cards-select rowHeight="2:1"
cols="2"
label="{{ 'widgets.progress-bar.layout' | translate }}" formControlName="layout">
<tb-image-cards-select-option *ngFor="let layout of progressBarLayouts"
[value]="layout"
[image]="progressBarLayoutImageMap.get(layout)">
{{ progressBarLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.progress-bar.auto-scale' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widget-config.title' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="titleFont"
clearButton
[previewText]="progressBarWidgetConfigForm.get('title').value"
[initialPreviewStyle]="widgetConfig.config.titleStyle">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="titleColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon">
{{ 'widgets.progress-bar.icon' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-css-unit-select fxFlex formControlName="iconSizeUnit"></tb-css-unit-select>
<tb-material-icon-select asBoxInput
iconClearButton
[color]="progressBarWidgetConfigForm.get('iconColor').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput
colorClearButton
formControlName="iconColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValue">
{{ 'widgets.progress-bar.value' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-unit-input class="flex" formControlName="units"></tb-unit-input>
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix fxHide.lt-md translate>widget-config.decimals-suffix</div>
</mat-form-field>
<tb-font-settings formControlName="valueFont"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-settings formControlName="valueColor"
settingsKey="{{'widgets.progress-bar.value' | translate }}">
</tb-color-settings>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.progress-bar.range' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.progress-bar.min</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tickMin" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.progress-bar.max</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tickMax" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<div *ngIf="progressBarWidgetConfigForm.get('layout').value === progressBarLayout.default" class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="showTicks">
{{ 'widgets.progress-bar.range-ticks' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="ticksFont"
previewText="100">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="ticksColor">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.progress-bar.bar</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.progress-bar.bar-color' | translate }}</div>
<tb-color-settings formControlName="barColor"
settingsKey="{{'widgets.progress-bar.bar-color' | translate }}">
</tb-color-settings>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.progress-bar.bar-background' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="barBackground">
</tb-color-input>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
<div class="tb-form-row space-between column-lt-md">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-border-radius' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">
</tb-widget-actions-panel>
</ng-container>

262
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.ts

@ -0,0 +1,262 @@
///
/// 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, Validators } 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 {
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 {
getTimewindowConfig,
setTimewindowConfig
} from '@home/components/widget/config/timewindow-config-panel.component';
import { formatValue, isUndefined } from '@core/utils';
import { cssSizeToStrSize, resolveCssSize } from '@shared/models/widget-settings.models';
import {
progressBarDefaultSettings,
ProgressBarLayout,
progressBarLayoutImages,
progressBarLayouts,
progressBarLayoutTranslations,
ProgressBarWidgetSettings
} from '@home/components/widget/lib/cards/progress-bar-widget.models';
@Component({
selector: 'tb-progress-bar-basic-config',
templateUrl: './progress-bar-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class ProgressBarBasicConfigComponent extends BasicWidgetConfigComponent {
public get displayTimewindowConfig(): boolean {
const datasources = this.progressBarWidgetConfigForm.get('datasources').value;
return datasourcesHasAggregation(datasources);
}
public onlyHistoryTimewindow(): boolean {
const datasources = this.progressBarWidgetConfigForm.get('datasources').value;
return datasourcesHasOnlyComparisonAggregation(datasources);
}
progressBarLayout = ProgressBarLayout;
progressBarLayouts = progressBarLayouts;
progressBarLayoutTranslationMap = progressBarLayoutTranslations;
progressBarLayoutImageMap = progressBarLayoutImages;
progressBarWidgetConfigForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.progressBarWidgetConfigForm;
}
protected setupDefaults(configData: WidgetConfigComponentData) {
this.setupDefaultDatasource(configData, [{ name: 'humidity', label: 'humidity', type: DataKeyType.timeseries }]);
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: ProgressBarWidgetSettings = {...progressBarDefaultSettings, ...(configData.config.settings || {})};
const iconSize = resolveCssSize(configData.config.iconSize);
this.progressBarWidgetConfigForm = this.fb.group({
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
layout: [settings.layout, []],
autoScale: [settings.autoScale, []],
showTitle: [configData.config.showTitle, []],
title: [configData.config.title, []],
titleFont: [configData.config.titleFont, []],
titleColor: [configData.config.titleColor, []],
showIcon: [configData.config.showTitleIcon, []],
iconSize: [iconSize[0], [Validators.min(0)]],
iconSizeUnit: [iconSize[1], []],
icon: [configData.config.titleIcon, []],
iconColor: [configData.config.iconColor, []],
showValue: [settings.showValue, []],
units: [configData.config.units, []],
decimals: [configData.config.decimals, []],
valueFont: [settings.valueFont, []],
valueColor: [settings.valueColor, []],
tickMin: [settings.tickMin, []],
tickMax: [settings.tickMax, []],
showTicks: [settings.showTicks, []],
ticksFont: [settings.ticksFont, []],
ticksColor: [settings.ticksColor, []],
barColor: [settings.barColor, []],
barBackground: [settings.barBackground, []],
background: [settings.background, []],
cardButtons: [this.getCardButtons(configData.config), []],
borderRadius: [configData.config.borderRadius, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig);
this.widgetConfig.config.datasources = config.datasources;
this.widgetConfig.config.showTitle = config.showTitle;
this.widgetConfig.config.title = config.title;
this.widgetConfig.config.titleFont = config.titleFont;
this.widgetConfig.config.titleColor = config.titleColor;
this.widgetConfig.config.showTitleIcon = config.showIcon;
this.widgetConfig.config.iconSize = cssSizeToStrSize(config.iconSize, config.iconSizeUnit);
this.widgetConfig.config.titleIcon = config.icon;
this.widgetConfig.config.iconColor = config.iconColor;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.layout = config.layout;
this.widgetConfig.config.settings.autoScale = config.autoScale;
this.widgetConfig.config.settings.showValue = config.showValue;
this.widgetConfig.config.units = config.units;
this.widgetConfig.config.decimals = config.decimals;
this.widgetConfig.config.settings.valueFont = config.valueFont;
this.widgetConfig.config.settings.valueColor = config.valueColor;
this.widgetConfig.config.settings.tickMin = config.tickMin;
this.widgetConfig.config.settings.tickMax = config.tickMax;
this.widgetConfig.config.settings.showTicks = config.showTicks;
this.widgetConfig.config.settings.ticksFont = config.ticksFont;
this.widgetConfig.config.settings.ticksColor = config.ticksColor;
this.widgetConfig.config.settings.barColor = config.barColor;
this.widgetConfig.config.settings.barBackground = config.barBackground;
this.widgetConfig.config.settings.background = config.background;
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.borderRadius = config.borderRadius;
this.widgetConfig.config.actions = config.actions;
return this.widgetConfig;
}
protected validatorTriggers(): string[] {
return ['showTitle', 'showIcon', 'showValue', 'showTicks', 'layout'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.progressBarWidgetConfigForm.get('showTitle').value;
const showIcon: boolean = this.progressBarWidgetConfigForm.get('showIcon').value;
const showValue: boolean = this.progressBarWidgetConfigForm.get('showValue').value;
const showTicks: boolean = this.progressBarWidgetConfigForm.get('showTicks').value;
const layout: ProgressBarLayout = this.progressBarWidgetConfigForm.get('layout').value;
const ticksEnabled = layout === ProgressBarLayout.default;
if (showTitle) {
this.progressBarWidgetConfigForm.get('title').enable();
this.progressBarWidgetConfigForm.get('titleFont').enable();
this.progressBarWidgetConfigForm.get('titleColor').enable();
this.progressBarWidgetConfigForm.get('showIcon').enable({emitEvent: false});
if (showIcon) {
this.progressBarWidgetConfigForm.get('iconSize').enable();
this.progressBarWidgetConfigForm.get('iconSizeUnit').enable();
this.progressBarWidgetConfigForm.get('icon').enable();
this.progressBarWidgetConfigForm.get('iconColor').enable();
} else {
this.progressBarWidgetConfigForm.get('iconSize').disable();
this.progressBarWidgetConfigForm.get('iconSizeUnit').disable();
this.progressBarWidgetConfigForm.get('icon').disable();
this.progressBarWidgetConfigForm.get('iconColor').disable();
}
} else {
this.progressBarWidgetConfigForm.get('title').disable();
this.progressBarWidgetConfigForm.get('titleFont').disable();
this.progressBarWidgetConfigForm.get('titleColor').disable();
this.progressBarWidgetConfigForm.get('showIcon').disable({emitEvent: false});
this.progressBarWidgetConfigForm.get('iconSize').disable();
this.progressBarWidgetConfigForm.get('iconSizeUnit').disable();
this.progressBarWidgetConfigForm.get('icon').disable();
this.progressBarWidgetConfigForm.get('iconColor').disable();
}
if (showValue) {
this.progressBarWidgetConfigForm.get('units').enable();
this.progressBarWidgetConfigForm.get('decimals').enable();
this.progressBarWidgetConfigForm.get('valueFont').enable();
this.progressBarWidgetConfigForm.get('valueColor').enable();
} else {
this.progressBarWidgetConfigForm.get('units').disable();
this.progressBarWidgetConfigForm.get('decimals').disable();
this.progressBarWidgetConfigForm.get('valueFont').disable();
this.progressBarWidgetConfigForm.get('valueColor').disable();
}
if (ticksEnabled) {
this.progressBarWidgetConfigForm.get('showTicks').enable({emitEvent: false});
if (showTicks) {
this.progressBarWidgetConfigForm.get('ticksFont').enable();
this.progressBarWidgetConfigForm.get('ticksColor').enable();
} else {
this.progressBarWidgetConfigForm.get('ticksFont').disable();
this.progressBarWidgetConfigForm.get('ticksColor').disable();
}
} else {
this.progressBarWidgetConfigForm.get('showTicks').disable({emitEvent: false});
this.progressBarWidgetConfigForm.get('ticksFont').disable();
this.progressBarWidgetConfigForm.get('ticksColor').disable();
}
}
private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = [];
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen');
}
return buttons;
}
private setCardButtons(buttons: string[], config: WidgetConfig) {
config.enableFullscreen = buttons.includes('fullscreen');
}
private _valuePreviewFn(): string {
const units: string = this.widgetConfig.config.units;
const decimals: number = this.widgetConfig.config.decimals;
return formatValue(78, decimals, units, true);
}
}

42
ui-ngx/src/app/modules/home/components/widget/lib/cards/progress-bar-widget.component.html

@ -0,0 +1,42 @@
<!--
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.
-->
<div #progressBarPanel class="tb-progress-bar-panel" [style]="backgroundStyle">
<div class="tb-progress-bar-overlay" [style]="overlayStyle"></div>
<div #progressBarContainer class="tb-progress-bar-container" [class]="layoutClass">
<ng-container *ngIf="layout === progressBarLayout.default">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
</ng-container>
<div class="tb-progress-bar-content">
<div *ngIf="showTitleValueRow" class="tb-progress-bar-title-value-row" [class]="titleValueRowClass">
<ng-container *ngIf="layout === progressBarLayout.simplified">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
</ng-container>
<div *ngIf="showValue" class="tb-progress-bar-value" [style]="valueStyle" [style.color]="valueColor.color">{{ valueText }}</div>
</div>
<div class="tb-progress-bar">
<div class="tb-progress-bar-background" [style.background-color]="settings.barBackground">
<div class="tb-progress-bar-progress" [style.background-color]="barColor.color" [style.width]="barWidth"></div>
</div>
<div *ngIf="showTicks" class="tb-progress-bar-ticks" [style]="ticksStyle">
<div>{{ settings.tickMin }}</div>
<div>{{ settings.tickMax }}</div>
</div>
</div>
</div>
</div>
</div>

84
ui-ngx/src/app/modules/home/components/widget/lib/cards/progress-bar-widget.component.scss

@ -0,0 +1,84 @@
/**
* 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.
*/
.tb-progress-bar-panel {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px 24px 24px 24px;
> div:not(.tb-progress-bar-overlay) {
z-index: 1;
}
.tb-progress-bar-overlay {
position: absolute;
top: 12px;
left: 12px;
bottom: 12px;
right: 12px;
}
div.tb-widget-title {
padding: 0;
}
.tb-progress-bar-container {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
&.simplified {
justify-content: center;
}
}
.tb-progress-bar-content {
display: flex;
flex-direction: column;
align-self: stretch;
gap: 4px;
.tb-progress-bar-title-value-row {
display: flex;
flex-direction: row;
justify-content: space-between;
&.flex-end {
justify-content: flex-end;
}
.tb-progress-bar-value {
white-space: nowrap;
}
}
.tb-progress-bar {
display: flex;
flex-direction: column;
.tb-progress-bar-background {
border-radius: 12px;
.tb-progress-bar-progress {
height: 5px;
border-radius: 8px;
transition: background-color .2s, width .2s;
}
}
.tb-progress-bar-ticks {
display: flex;
flex-direction: row;
justify-content: space-between;
}
}
}
}

221
ui-ngx/src/app/modules/home/components/widget/lib/cards/progress-bar-widget.component.ts

@ -0,0 +1,221 @@
///
/// 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 {
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
Input,
OnDestroy,
OnInit,
Renderer2,
TemplateRef,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { WidgetContext } from '@home/models/widget-component.models';
import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils';
import { DatePipe } from '@angular/common';
import {
backgroundStyle,
ColorProcessor,
ComponentStyle,
getDataKey,
getSingleTsValue,
overlayStyle,
resolveCssSize,
textStyle
} from '@shared/models/widget-settings.models';
import { WidgetComponent } from '@home/components/widget/widget.component';
import { progressBarDefaultSettings, ProgressBarLayout, ProgressBarWidgetSettings } from './progress-bar-widget.models';
import { ResizeObserver } from '@juggle/resize-observer';
const defaultLayoutHeight = 80;
const simplifiedLayoutHeight = 75;
const defaultAspect = defaultLayoutHeight / 150;
const simplifiedAspect = simplifiedLayoutHeight / 150;
@Component({
selector: 'tb-progress-bar-widget',
templateUrl: './progress-bar-widget.component.html',
styleUrls: ['./progress-bar-widget.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ProgressBarWidgetComponent implements OnInit, OnDestroy, AfterViewInit {
@ViewChild('progressBarPanel', {static: true})
progressBarPanel: ElementRef<HTMLElement>;
@ViewChild('progressBarContainer', {static: true})
progressBarContainer: ElementRef<HTMLElement>;
progressBarLayout = ProgressBarLayout;
settings: ProgressBarWidgetSettings;
@Input()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
layout: ProgressBarLayout;
layoutClass = '';
showTitleValueRow = true;
titleValueRowClass = '';
showValue = true;
valueText = 'N/A';
valueStyle: ComponentStyle = {};
valueColor: ColorProcessor;
barWidth = '0%';
barColor: ColorProcessor;
showTicks = true;
ticksStyle: ComponentStyle = {};
value: number;
backgroundStyle: ComponentStyle = {};
overlayStyle: ComponentStyle = {};
progressBarPanelResize$: ResizeObserver;
private decimals = 0;
private units = '';
constructor(private date: DatePipe,
private widgetComponent: WidgetComponent,
private renderer: Renderer2,
private cd: ChangeDetectorRef) {
}
ngOnInit(): void {
this.ctx.$scope.progressBarWidget = this;
this.settings = {...progressBarDefaultSettings, ...this.ctx.settings};
this.decimals = this.ctx.decimals;
this.units = this.ctx.units;
const dataKey = getDataKey(this.ctx.datasources);
if (isDefinedAndNotNull(dataKey?.decimals)) {
this.decimals = dataKey.decimals;
}
if (dataKey?.units) {
this.units = dataKey.units;
}
this.layout = this.settings.layout;
this.showValue = this.settings.showValue;
this.valueStyle = textStyle(this.settings.valueFont, '0.1px');
this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor);
this.showTitleValueRow = this.showValue ||
(this.layout === ProgressBarLayout.simplified && this.widgetComponent.dashboardWidget.showWidgetTitlePanel);
this.layoutClass = (this.layout === ProgressBarLayout.simplified || !this.widgetComponent.dashboardWidget.showWidgetTitlePanel)
? 'simplified' : '';
this.titleValueRowClass = (this.layout === ProgressBarLayout.simplified &&
!this.widgetComponent.dashboardWidget.showWidgetTitlePanel) ? 'flex-end' : '';
this.barColor = ColorProcessor.fromSettings(this.settings.barColor);
this.showTicks = this.settings.showTicks && this.layout === ProgressBarLayout.default;
if (this.showTicks) {
this.ticksStyle = textStyle(this.settings.ticksFont, '0.1px');
this.ticksStyle.color = this.settings.ticksColor;
}
this.backgroundStyle = backgroundStyle(this.settings.background);
this.overlayStyle = overlayStyle(this.settings.background.overlay);
}
ngAfterViewInit() {
if (this.settings.autoScale) {
this.renderer.setStyle(this.progressBarContainer.nativeElement, 'overflow', 'visible');
this.renderer.setStyle(this.progressBarContainer.nativeElement, 'position', 'absolute');
this.progressBarPanelResize$ = new ResizeObserver(() => {
this.onResize();
});
this.progressBarPanelResize$.observe(this.progressBarPanel.nativeElement);
this.onResize();
}
}
ngOnDestroy() {
}
public onInit() {
const borderRadius = this.ctx.$widgetElement.css('borderRadius');
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
this.cd.detectChanges();
}
public onDataUpdated() {
const tsValue = getSingleTsValue(this.ctx.data);
this.value = 0;
if (tsValue && isDefinedAndNotNull(tsValue[1]) && isNumeric(tsValue[1])) {
this.value = tsValue[1];
this.valueText = formatValue(this.value, this.decimals, this.units, true);
} else {
this.valueText = 'N/A';
}
this.valueColor.update(this.value);
this.barColor.update(this.value);
const range = this.settings.tickMax - this.settings.tickMin;
this.barWidth = `${(this.value / range) * 100}%`;
this.cd.detectChanges();
}
private onResize() {
const paddingLeft = getComputedStyle(this.progressBarPanel.nativeElement).paddingLeft;
const paddingRight = getComputedStyle(this.progressBarPanel.nativeElement).paddingRight;
const paddingTop = getComputedStyle(this.progressBarPanel.nativeElement).paddingTop;
const paddingBottom = getComputedStyle(this.progressBarPanel.nativeElement).paddingBottom;
const pLeft = resolveCssSize(paddingLeft)[0];
const pRight = resolveCssSize(paddingRight)[0];
const pTop = resolveCssSize(paddingTop)[0];
const pBottom = resolveCssSize(paddingBottom)[0];
const panelWidth = this.progressBarPanel.nativeElement.getBoundingClientRect().width - (pLeft + pRight);
const panelHeight = this.progressBarPanel.nativeElement.getBoundingClientRect().height - (pTop + pBottom);
const defaultLayout = this.layout === ProgressBarLayout.default && this.widgetComponent.dashboardWidget.showWidgetTitlePanel;
let minAspect = defaultLayout ? defaultAspect : simplifiedAspect;
let layoutHeight = defaultLayout ? defaultLayoutHeight : simplifiedLayoutHeight;
if (!this.showTitleValueRow) {
minAspect -= (40 / 150);
layoutHeight -= 40;
}
if (!this.showTicks) {
minAspect -= (10 / 150);
layoutHeight -= 10;
}
const aspect = Math.min(panelHeight / panelWidth, minAspect);
const targetWidth = panelWidth;
const targetHeight = targetWidth * aspect;
const scale = targetHeight / layoutHeight;
const width = targetWidth / scale;
const height = panelHeight / scale;
this.renderer.setStyle(this.progressBarContainer.nativeElement, 'width', width + 'px');
this.renderer.setStyle(this.progressBarContainer.nativeElement, 'height', height + 'px');
this.renderer.setStyle(this.progressBarContainer.nativeElement, 'transform', `scale(${scale})`);
}
}

98
ui-ngx/src/app/modules/home/components/widget/lib/cards/progress-bar-widget.models.ts

@ -0,0 +1,98 @@
///
/// 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 {
BackgroundSettings,
BackgroundType,
ColorSettings,
constantColor,
Font
} from '@shared/models/widget-settings.models';
export enum ProgressBarLayout {
default = 'default',
simplified = 'simplified'
}
export const progressBarLayouts = Object.keys(ProgressBarLayout) as ProgressBarLayout[];
export const progressBarLayoutTranslations = new Map<ProgressBarLayout, string>(
[
[ProgressBarLayout.default, 'widgets.progress-bar.layout-default'],
[ProgressBarLayout.simplified, 'widgets.progress-bar.layout-simplified']
]
);
export const progressBarLayoutImages = new Map<ProgressBarLayout, string>(
[
[ProgressBarLayout.default, 'assets/widget/progress-bar/default-layout.svg'],
[ProgressBarLayout.simplified, 'assets/widget/progress-bar/simplified-layout.svg']
]
);
export interface ProgressBarWidgetSettings {
layout: ProgressBarLayout;
autoScale: boolean;
showValue: boolean;
valueFont: Font;
valueColor: ColorSettings;
showTicks: boolean;
tickMin: number;
tickMax: number;
ticksFont: Font;
ticksColor: string;
barColor: ColorSettings;
barBackground: string;
background: BackgroundSettings;
}
export const progressBarDefaultSettings: ProgressBarWidgetSettings = {
layout: ProgressBarLayout.default,
autoScale: true,
showValue: true,
valueFont: {
family: 'Roboto',
size: 24,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '32px'
},
valueColor: constantColor('rgba(0, 0, 0, 0.87)'),
showTicks: true,
tickMin: 0,
tickMax: 100,
ticksFont: {
family: 'Roboto',
size: 11,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '16px'
},
ticksColor: 'rgba(0,0,0,0.54)',
barColor: constantColor('rgba(63, 82, 221, 1)'),
barBackground: 'rgba(0, 0, 0, 0.04)',
background: {
type: BackgroundType.color,
color: '#fff',
overlay: {
enabled: false,
color: 'rgba(255,255,255,0.72)',
blur: 3
}
}
};

5
ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss

@ -20,7 +20,7 @@
display: flex;
flex-direction: column;
gap: 16px;
padding: 24px;
padding: 20px 24px 24px 24px;
&.tb-battery-level-pointer {
cursor: pointer;
}
@ -34,6 +34,9 @@
bottom: 12px;
right: 12px;
}
div.tb-widget-title {
padding: 0;
}
.tb-battery-level-content {
min-height: 0;
flex: 1;

1
ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts

@ -263,6 +263,7 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView
if (this.autoScaleValueSize) {
const valueFontSize = ratios.valueFontSizeRatio * boxSize;
const valueLineHeight = ratios.valueLineHeightRaio * boxSize;
this.renderer.setStyle(this.batteryLevelValue.nativeElement, 'minWidth', '0');
this.setValueFontSize(valueFontSize, valueLineHeight, boxWidth);
}
const fontSize = parseInt(window.getComputedStyle(this.batteryLevelValue.nativeElement).fontSize, 10) || 10;

94
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html

@ -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]="progressBarWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.progress-bar.progress-bar-card-style</div>
<tb-image-cards-select rowHeight="2:1"
cols="2"
label="{{ 'widgets.progress-bar.layout' | translate }}" formControlName="layout">
<tb-image-cards-select-option *ngFor="let layout of progressBarLayouts"
[value]="layout"
[image]="progressBarLayoutImageMap.get(layout)">
{{ progressBarLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.progress-bar.auto-scale' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide" formControlName="showValue">
{{ 'widgets.progress-bar.value' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="valueFont"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-settings formControlName="valueColor"
settingsKey="{{'widgets.progress-bar.value' | translate }}">
</tb-color-settings>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.progress-bar.range' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.progress-bar.min</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tickMin" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.progress-bar.max</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tickMax" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<div *ngIf="progressBarWidgetSettingsForm.get('layout').value === progressBarLayout.default" class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="showTicks">
{{ 'widgets.progress-bar.range-ticks' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="ticksFont"
previewText="100">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="ticksColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.progress-bar.bar-color' | translate }}</div>
<tb-color-settings formControlName="barColor"
settingsKey="{{'widgets.progress-bar.bar-color' | translate }}">
</tb-color-settings>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.progress-bar.bar-background' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="barBackground">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
</div>
</ng-container>

133
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.ts

@ -0,0 +1,133 @@
///
/// 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, Injector } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { formatValue } from '@core/utils';
import {
progressBarDefaultSettings,
ProgressBarLayout,
progressBarLayoutImages,
progressBarLayouts,
progressBarLayoutTranslations
} from '@home/components/widget/lib/cards/progress-bar-widget.models';
@Component({
selector: 'tb-progress-bar-widget-settings',
templateUrl: './progress-bar-widget-settings.component.html',
styleUrls: []
})
export class ProgressBarWidgetSettingsComponent extends WidgetSettingsComponent {
progressBarLayout = ProgressBarLayout;
progressBarLayouts = progressBarLayouts;
progressBarLayoutTranslationMap = progressBarLayoutTranslations;
progressBarLayoutImageMap = progressBarLayoutImages;
progressBarWidgetSettingsForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
private $injector: Injector,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.progressBarWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {...progressBarDefaultSettings};
}
protected onSettingsSet(settings: WidgetSettings) {
this.progressBarWidgetSettingsForm = this.fb.group({
layout: [settings.layout, []],
autoScale: [settings.autoScale, []],
showValue: [settings.showValue, []],
valueFont: [settings.valueFont, []],
valueColor: [settings.valueColor, []],
tickMin: [settings.tickMin, []],
tickMax: [settings.tickMax, []],
showTicks: [settings.showTicks, []],
ticksFont: [settings.ticksFont, []],
ticksColor: [settings.ticksColor, []],
barColor: [settings.barColor, []],
barBackground: [settings.barBackground, []],
background: [settings.background, []]
});
}
protected validatorTriggers(): string[] {
return ['showValue', 'showTicks', 'layout'];
}
protected updateValidators(emitEvent: boolean) {
const showValue: boolean = this.progressBarWidgetSettingsForm.get('showValue').value;
const showTicks: boolean = this.progressBarWidgetSettingsForm.get('showTicks').value;
const layout: ProgressBarLayout = this.progressBarWidgetSettingsForm.get('layout').value;
const ticksEnabled = layout === ProgressBarLayout.default;
if (showValue) {
this.progressBarWidgetSettingsForm.get('valueFont').enable();
this.progressBarWidgetSettingsForm.get('valueColor').enable();
} else {
this.progressBarWidgetSettingsForm.get('valueFont').disable();
this.progressBarWidgetSettingsForm.get('valueColor').disable();
}
if (ticksEnabled) {
this.progressBarWidgetSettingsForm.get('showTicks').enable({emitEvent: false});
if (showTicks) {
this.progressBarWidgetSettingsForm.get('ticksFont').enable();
this.progressBarWidgetSettingsForm.get('ticksColor').enable();
} else {
this.progressBarWidgetSettingsForm.get('ticksFont').disable();
this.progressBarWidgetSettingsForm.get('ticksColor').disable();
}
} else {
this.progressBarWidgetSettingsForm.get('showTicks').disable({emitEvent: false});
this.progressBarWidgetSettingsForm.get('ticksFont').disable();
this.progressBarWidgetSettingsForm.get('ticksColor').disable();
}
this.progressBarWidgetSettingsForm.get('valueFont').updateValueAndValidity({emitEvent});
this.progressBarWidgetSettingsForm.get('valueColor').updateValueAndValidity({emitEvent});
this.progressBarWidgetSettingsForm.get('showTicks').updateValueAndValidity({emitEvent: false});
this.progressBarWidgetSettingsForm.get('ticksFont').updateValueAndValidity({emitEvent});
this.progressBarWidgetSettingsForm.get('ticksColor').updateValueAndValidity({emitEvent});
}
private _valuePreviewFn(): string {
const units: string = this.widgetConfig.config.units;
const decimals: number = this.widgetConfig.config.decimals;
return formatValue(78, decimals, units, true);
}
}

12
ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts

@ -297,6 +297,9 @@ import {
import {
ValueChartCardWidgetSettingsComponent
} from '@home/components/widget/lib/settings/cards/value-chart-card-widget-settings.component';
import {
ProgressBarWidgetSettingsComponent
} from '@home/components/widget/lib/settings/cards/progress-bar-widget-settings.component';
@NgModule({
declarations: [
@ -406,7 +409,8 @@ import {
BatteryLevelWidgetSettingsComponent,
WindSpeedDirectionWidgetSettingsComponent,
SignalStrengthWidgetSettingsComponent,
ValueChartCardWidgetSettingsComponent
ValueChartCardWidgetSettingsComponent,
ProgressBarWidgetSettingsComponent
],
imports: [
CommonModule,
@ -521,7 +525,8 @@ import {
BatteryLevelWidgetSettingsComponent,
WindSpeedDirectionWidgetSettingsComponent,
SignalStrengthWidgetSettingsComponent,
ValueChartCardWidgetSettingsComponent
ValueChartCardWidgetSettingsComponent,
ProgressBarWidgetSettingsComponent
]
})
export class WidgetSettingsModule {
@ -601,5 +606,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-battery-level-widget-settings': BatteryLevelWidgetSettingsComponent,
'tb-wind-speed-direction-widget-settings': WindSpeedDirectionWidgetSettingsComponent,
'tb-signal-strength-widget-settings': SignalStrengthWidgetSettingsComponent,
'tb-value-chart-card-widget-settings': ValueChartCardWidgetSettingsComponent
'tb-value-chart-card-widget-settings': ValueChartCardWidgetSettingsComponent,
'tb-progress-bar-widget-settings': ProgressBarWidgetSettingsComponent
};

5
ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.component.scss

@ -20,7 +20,7 @@
display: flex;
flex-direction: column;
gap: 16px;
padding: 24px;
padding: 20px 24px 24px 24px;
&.tb-wind-speed-direction-pointer {
cursor: pointer;
}
@ -34,6 +34,9 @@
bottom: 12px;
right: 12px;
}
div.tb-widget-title {
padding: 0;
}
.tb-wind-speed-direction-content {
flex: 1;
min-width: 0;

7
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -63,6 +63,7 @@ import {
} from '@home/components/widget/lib/weather/wind-speed-direction-widget.component';
import { SignalStrengthWidgetComponent } from '@home/components/widget/lib/indicator/signal-strength-widget.component';
import { ValueChartCardWidgetComponent } from '@home/components/widget/lib/cards/value-chart-card-widget.component';
import { ProgressBarWidgetComponent } from '@home/components/widget/lib/cards/progress-bar-widget.component';
@NgModule({
declarations:
@ -100,7 +101,8 @@ import { ValueChartCardWidgetComponent } from '@home/components/widget/lib/cards
BatteryLevelWidgetComponent,
WindSpeedDirectionWidgetComponent,
SignalStrengthWidgetComponent,
ValueChartCardWidgetComponent
ValueChartCardWidgetComponent,
ProgressBarWidgetComponent
],
imports: [
CommonModule,
@ -142,7 +144,8 @@ import { ValueChartCardWidgetComponent } from '@home/components/widget/lib/cards
BatteryLevelWidgetComponent,
WindSpeedDirectionWidgetComponent,
SignalStrengthWidgetComponent,
ValueChartCardWidgetComponent
ValueChartCardWidgetComponent,
ProgressBarWidgetComponent
],
providers: [
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }

16
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -6096,6 +6096,22 @@
"chart": "Chart",
"value-chart-card-style": "Value chart card style"
},
"progress-bar": {
"layout": "Layout",
"layout-default": "Default",
"layout-simplified": "Simplified",
"auto-scale": "Auto scale",
"icon": "Icon",
"value": "Value",
"range": "Range",
"min": "min",
"max": "max",
"range-ticks": "Range ticks",
"bar": "Bar",
"bar-color": "Bar color",
"bar-background": "Bar background",
"progress-bar-card-style": "Progress bar card style"
},
"alarm-count": {
"alarm-count-card-style": "Alarm count card style"
},

38
ui-ngx/src/assets/widget/progress-bar/default-layout.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

36
ui-ngx/src/assets/widget/progress-bar/simplified-layout.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

7
ui-ngx/src/form.scss

@ -225,6 +225,13 @@
margin-left: 12px;
}
}
.tb-small-label {
height: 40px;
font-size: 14px;
line-height: 40px;
letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.38);
}
}
.tb-form-row:not(.tb-standard-fields) .mat-mdc-form-field:not(.tb-not-inline-field), .mat-mdc-form-field.tb-inline-field {

Loading…
Cancel
Save