Browse Source

UI: Implement 'Signal strength' widget. Add 'Status indicators' widgets bundle.

pull/9402/head
Igor Kulikov 3 years ago
parent
commit
39aa07043a
  1. 1
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 14
      application/src/main/data/json/system/widget_bundles/status_indicators.json
  3. 5
      application/src/main/data/json/system/widget_types/battery_level.json
  4. 29
      application/src/main/data/json/system/widget_types/signal_strength.json
  5. 1
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  6. 12
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  7. 197
      ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/signal-strength-basic-config.component.html
  8. 316
      ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/signal-strength-basic-config.component.ts
  9. 40
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.html
  10. 90
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.scss
  11. 387
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts
  12. 141
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts
  13. 121
      ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.html
  14. 167
      ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.ts
  15. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  16. 7
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  17. 10
      ui-ngx/src/app/shared/models/widget-settings.models.ts
  18. 16
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  19. 31
      ui-ngx/src/assets/widget/signal-strength/cellular-bar-layout.svg
  20. 36
      ui-ngx/src/assets/widget/signal-strength/wifi-layout.svg

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

@ -11,7 +11,6 @@
"cards.value_card",
"cards.horizontal_value_card",
"cards.aggregated_value_card",
"battery_level",
"cards.label_widget",
"cards.dashboard_state_widget",
"cards.qr_code",

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

@ -0,0 +1,14 @@
{
"widgetsBundle": {
"alias": "status_indicators",
"title": "Status indicators",
"image": "data:image/png;base64,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",
"description": "Contains widgets displaying battery level and signal strength.",
"externalId": null,
"name": "Status indicators"
},
"widgetTypeFqns": [
"battery_level",
"signal_strength"
]
}

5
application/src/main/data/json/system/widget_types/battery_level.json

@ -17,7 +17,8 @@
"settingsDirective": "tb-battery-level-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-battery-level-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"batteryLevel\",\"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\":{\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"layout\":\"vertical_solid\",\"showValue\":true,\"autoScaleValueSize\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":20,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"24px\"},\"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';\"},\"batteryLevelColor\":{\"color\":\"rgba(92, 223, 144, 1)\",\"type\":\"range\",\"rangeList\":[{\"from\":0,\"to\":25,\"color\":\"rgba(227, 71, 71, 1)\"},{\"from\":25,\"to\":50,\"color\":\"rgba(246, 206, 67, 1)\"},{\"from\":50,\"to\":100,\"color\":\"rgba(92, 223, 144, 1)\"}],\"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';\"},\"batteryShapeColor\":{\"color\":\"rgba(92, 223, 144, 0.32)\",\"type\":\"range\",\"rangeList\":[{\"from\":0,\"to\":25,\"color\":\"rgba(227, 71, 71, 0.32)\"},{\"from\":25,\"to\":50,\"color\":\"rgba(246, 206, 67, 0.32)\"},{\"from\":50,\"to\":100,\"color\":\"rgba(92, 223, 144, 0.32)\"}],\"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';\"}},\"title\":\"Battery level\",\"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\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"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)\"}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"batteryLevel\",\"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\":{\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"layout\":\"vertical_solid\",\"showValue\":true,\"autoScaleValueSize\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":20,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"24px\"},\"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';\"},\"batteryLevelColor\":{\"color\":\"rgba(92, 223, 144, 1)\",\"type\":\"range\",\"rangeList\":[{\"from\":0,\"to\":25,\"color\":\"rgba(227, 71, 71, 1)\"},{\"from\":25,\"to\":50,\"color\":\"rgba(246, 206, 67, 1)\"},{\"from\":50,\"to\":100,\"color\":\"rgba(92, 223, 144, 1)\"}],\"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';\"},\"batteryShapeColor\":{\"color\":\"rgba(92, 223, 144, 0.32)\",\"type\":\"range\",\"rangeList\":[{\"from\":0,\"to\":25,\"color\":\"rgba(227, 71, 71, 0.32)\"},{\"from\":25,\"to\":50,\"color\":\"rgba(246, 206, 67, 0.32)\"},{\"from\":50,\"to\":100,\"color\":\"rgba(92, 223, 144, 0.32)\"}],\"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';\"},\"sectionsCount\":4},\"title\":\"Battery level\",\"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:battery-high\",\"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
"externalId": null,
"tags": null
}

29
application/src/main/data/json/system/widget_types/signal_strength.json

@ -0,0 +1,29 @@
{
"fqn": "signal_strength",
"name": "Signal strength",
"deprecated": false,
"image": "data:image/png;base64,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",
"description": "Presents the current signal strength as WiFi or Cellular Bar.",
"descriptor": {
"type": "latest",
"sizeX": 2.5,
"sizeY": 2.5,
"resources": [],
"templateHtml": "<tb-signal-strength-widget\n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-signal-strength-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.signalStrengthWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.signalStrengthWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '200px',\n previewHeight: '200px',\n embedTitlePanel: true\n };\n};\n\nself.actionSources = function() {\n return {\n 'cardClick': {\n name: 'widget-action.card-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n};\n",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-signal-strength-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-signal-strength-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"rssi\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (!prevValue) {\\n prevValue = Math.random() * -96;\\n}\\nvar value = prevValue + (Math.random() * 60 - 30);\\nif (value > 0) {\\n\\tvalue = 0;\\n} else if (value < -96) {\\n value = -96;\\n}\\nlet rand = Math.random();\\nreturn rand < 0.2 ? (rand < 0.1 ? -101 : '') : 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\":\"wifi\",\"showDate\":false,\"dateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"dateFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"dateColor\":\"rgba(0, 0, 0, 0.38)\",\"activeBarsColor\":{\"color\":\"rgba(92, 223, 144, 1)\",\"type\":\"range\",\"rangeList\":[{\"to\":-85,\"color\":\"rgba(227, 71, 71, 1)\"},{\"from\":-85,\"to\":-70,\"color\":\"rgba(255, 122, 0, 1)\"},{\"from\":-70,\"to\":-55,\"color\":\"rgba(246, 206, 67, 1)\"},{\"from\":-55,\"color\":\"rgba(92, 223, 144, 1)\"}],\"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';\"},\"inactiveBarsColor\":\"rgba(224, 224, 224, 1)\",\"showTooltip\":true,\"showTooltipValue\":true,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0,0,0,0.76)\",\"showTooltipDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0,0,0,0.76)\",\"tooltipBackgroundColor\":\"rgba(255,255,255,0.72)\",\"tooltipBackgroundBlur\":3,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Signal strength\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"dBm\",\"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\":\"signal_cellular_alt\",\"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": [
"wifi",
"signal",
"cellular",
"rssi"
]
}

1
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -533,6 +533,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
this.deleteSystemWidgetBundle("tables");
this.deleteSystemWidgetBundle("count_widgets");
this.deleteSystemWidgetBundle("weather_widgets");
this.deleteSystemWidgetBundle("status_indicators");
installScripts.loadSystemWidgets();
}

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

@ -61,6 +61,9 @@ import {
import {
WindSpeedDirectionBasicConfigComponent
} from '@home/components/widget/config/basic/weather/wind-speed-direction-basic-config.component';
import {
SignalStrengthBasicConfigComponent
} from '@home/components/widget/config/basic/indicator/signal-strength-basic-config.component';
@NgModule({
declarations: [
@ -79,7 +82,8 @@ import {
AlarmCountBasicConfigComponent,
EntityCountBasicConfigComponent,
BatteryLevelBasicConfigComponent,
WindSpeedDirectionBasicConfigComponent
WindSpeedDirectionBasicConfigComponent,
SignalStrengthBasicConfigComponent
],
imports: [
CommonModule,
@ -102,7 +106,8 @@ import {
AlarmCountBasicConfigComponent,
EntityCountBasicConfigComponent,
BatteryLevelBasicConfigComponent,
WindSpeedDirectionBasicConfigComponent
WindSpeedDirectionBasicConfigComponent,
SignalStrengthBasicConfigComponent
]
})
export class BasicWidgetConfigModule {
@ -119,5 +124,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
'tb-alarm-count-basic-config': AlarmCountBasicConfigComponent,
'tb-entity-count-basic-config': EntityCountBasicConfigComponent,
'tb-battery-level-basic-config': BatteryLevelBasicConfigComponent,
'tb-wind-speed-direction-basic-config': WindSpeedDirectionBasicConfigComponent
'tb-wind-speed-direction-basic-config': WindSpeedDirectionBasicConfigComponent,
'tb-signal-strength-basic-config': SignalStrengthBasicConfigComponent
};

197
ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/signal-strength-basic-config.component.html

@ -0,0 +1,197 @@
<!--
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]="signalStrengthWidgetConfigForm">
<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.signal-strength.layout' | translate }}" formControlName="layout">
<tb-image-cards-select-option *ngFor="let layout of signalStrengthLayouts"
[value]="layout"
[image]="signalStrengthLayoutImageMap.get(layout)">
{{ signalStrengthLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<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]="signalStrengthWidgetConfigForm.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.signal-strength.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]="signalStrengthWidgetConfigForm.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 column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showDate">
{{ 'widgets.signal-strength.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex formControlName="dateFormat"></tb-date-format-select>
<tb-font-settings formControlName="dateFont"
[previewText]="datePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="dateColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.signal-strength.active-bars-color' | translate }}</div>
<tb-color-settings formControlName="activeBarsColor"
settingsKey="{{'widgets.signal-strength.active-bars-color' | translate }}">
</tb-color-settings>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.signal-strength.inactive-bars-color' | translate }}</div>
<tb-color-input asBoxInput
formControlName="inactiveBarsColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="signalStrengthWidgetConfigForm.get('showTooltip').value"
[disabled]="!signalStrengthWidgetConfigForm.get('showTooltip').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.signal-strength.tooltip' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipValue">
{{ 'widgets.signal-strength.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="tooltipValueFont"
[previewText]="tooltipValuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tooltipValueColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipDate">
{{ 'widgets.signal-strength.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-font-settings formControlName="tooltipDateFont"
[previewText]="tooltipDatePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tooltipDateColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.signal-strength.background-color' | translate }}</div>
<tb-color-input asBoxInput
formControlName="tooltipBackgroundColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.signal-strength.background-blur</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1"
placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
</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>

316
ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/signal-strength-basic-config.component.ts

@ -0,0 +1,316 @@
///
/// 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 { 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,
DateFormatProcessor,
DateFormatSettings,
resolveCssSize
} from '@shared/models/widget-settings.models';
import {
signalStrengthDefaultSettings,
signalStrengthLayoutImages,
signalStrengthLayouts,
signalStrengthLayoutTranslations,
SignalStrengthWidgetSettings
} from '@home/components/widget/lib/indicator/signal-strength-widget.models';
@Component({
selector: 'tb-signal-strength-basic-config',
templateUrl: './signal-strength-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class SignalStrengthBasicConfigComponent extends BasicWidgetConfigComponent {
public get displayTimewindowConfig(): boolean {
const datasources = this.signalStrengthWidgetConfigForm.get('datasources').value;
return datasourcesHasAggregation(datasources);
}
public onlyHistoryTimewindow(): boolean {
const datasources = this.signalStrengthWidgetConfigForm.get('datasources').value;
return datasourcesHasOnlyComparisonAggregation(datasources);
}
signalStrengthLayouts = signalStrengthLayouts;
signalStrengthLayoutTranslationMap = signalStrengthLayoutTranslations;
signalStrengthLayoutImageMap = signalStrengthLayoutImages;
signalStrengthWidgetConfigForm: UntypedFormGroup;
datePreviewFn = this._datePreviewFn.bind(this);
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private $injector: Injector,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.signalStrengthWidgetConfigForm;
}
protected setupDefaults(configData: WidgetConfigComponentData) {
this.setupDefaultDatasource(configData, [{ name: 'rssi', label: 'rssi', type: DataKeyType.timeseries }]);
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: SignalStrengthWidgetSettings = {...signalStrengthDefaultSettings, ...(configData.config.settings || {})};
const iconSize = resolveCssSize(configData.config.iconSize);
this.signalStrengthWidgetConfigForm = this.fb.group({
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
layout: [settings.layout, []],
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, []],
showDate: [settings.showDate, []],
dateFormat: [settings.dateFormat, []],
dateFont: [settings.dateFont, []],
dateColor: [settings.dateColor, []],
activeBarsColor: [settings.activeBarsColor, []],
inactiveBarsColor: [settings.inactiveBarsColor, []],
showTooltip: [settings.showTooltip, []],
showTooltipValue: [settings.showTooltipValue, []],
units: [configData.config.units, []],
decimals: [configData.config.decimals, []],
tooltipValueFont: [settings.tooltipValueFont, []],
tooltipValueColor: [settings.tooltipValueColor, []],
showTooltipDate: [settings.showTooltipDate, []],
tooltipDateFormat: [settings.tooltipDateFormat, []],
tooltipDateFont: [settings.tooltipDateFont, []],
tooltipDateColor: [settings.tooltipDateColor, []],
tooltipBackgroundColor: [settings.tooltipBackgroundColor, []],
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
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.showDate = config.showDate;
this.widgetConfig.config.settings.dateFormat = config.dateFormat;
this.widgetConfig.config.settings.dateFont = config.dateFont;
this.widgetConfig.config.settings.dateColor = config.dateColor;
this.widgetConfig.config.settings.activeBarsColor = config.activeBarsColor;
this.widgetConfig.config.settings.inactiveBarsColor = config.inactiveBarsColor;
this.widgetConfig.config.settings.showTooltip = config.showTooltip;
this.widgetConfig.config.settings.showTooltipValue = config.showTooltipValue;
this.widgetConfig.config.units = config.units;
this.widgetConfig.config.decimals = config.decimals;
this.widgetConfig.config.settings.tooltipValueFont = config.tooltipValueFont;
this.widgetConfig.config.settings.tooltipValueColor = config.tooltipValueColor;
this.widgetConfig.config.settings.showTooltipDate = config.showTooltipDate;
this.widgetConfig.config.settings.tooltipDateFormat = config.tooltipDateFormat;
this.widgetConfig.config.settings.tooltipDateFont = config.tooltipDateFont;
this.widgetConfig.config.settings.tooltipDateColor = config.tooltipDateColor;
this.widgetConfig.config.settings.tooltipBackgroundColor = config.tooltipBackgroundColor;
this.widgetConfig.config.settings.tooltipBackgroundBlur = config.tooltipBackgroundBlur;
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', 'showDate', 'showTooltip', 'showTooltipValue', 'showTooltipDate'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.signalStrengthWidgetConfigForm.get('showTitle').value;
const showIcon: boolean = this.signalStrengthWidgetConfigForm.get('showIcon').value;
const showDate: boolean = this.signalStrengthWidgetConfigForm.get('showDate').value;
const showTooltip: boolean = this.signalStrengthWidgetConfigForm.get('showTooltip').value;
const showTooltipValue: boolean = this.signalStrengthWidgetConfigForm.get('showTooltipValue').value;
const showTooltipDate: boolean = this.signalStrengthWidgetConfigForm.get('showTooltipDate').value;
if (showTitle) {
this.signalStrengthWidgetConfigForm.get('title').enable();
this.signalStrengthWidgetConfigForm.get('titleFont').enable();
this.signalStrengthWidgetConfigForm.get('titleColor').enable();
this.signalStrengthWidgetConfigForm.get('showIcon').enable({emitEvent: false});
if (showIcon) {
this.signalStrengthWidgetConfigForm.get('iconSize').enable();
this.signalStrengthWidgetConfigForm.get('iconSizeUnit').enable();
this.signalStrengthWidgetConfigForm.get('icon').enable();
this.signalStrengthWidgetConfigForm.get('iconColor').enable();
} else {
this.signalStrengthWidgetConfigForm.get('iconSize').disable();
this.signalStrengthWidgetConfigForm.get('iconSizeUnit').disable();
this.signalStrengthWidgetConfigForm.get('icon').disable();
this.signalStrengthWidgetConfigForm.get('iconColor').disable();
}
} else {
this.signalStrengthWidgetConfigForm.get('title').disable();
this.signalStrengthWidgetConfigForm.get('titleFont').disable();
this.signalStrengthWidgetConfigForm.get('titleColor').disable();
this.signalStrengthWidgetConfigForm.get('showIcon').disable({emitEvent: false});
this.signalStrengthWidgetConfigForm.get('iconSize').disable();
this.signalStrengthWidgetConfigForm.get('iconSizeUnit').disable();
this.signalStrengthWidgetConfigForm.get('icon').disable();
this.signalStrengthWidgetConfigForm.get('iconColor').disable();
}
if (showDate) {
this.signalStrengthWidgetConfigForm.get('dateFormat').enable();
this.signalStrengthWidgetConfigForm.get('dateFont').enable();
this.signalStrengthWidgetConfigForm.get('dateColor').enable();
} else {
this.signalStrengthWidgetConfigForm.get('dateFormat').disable();
this.signalStrengthWidgetConfigForm.get('dateFont').disable();
this.signalStrengthWidgetConfigForm.get('dateColor').disable();
}
if (showTooltip) {
this.signalStrengthWidgetConfigForm.get('showTooltipValue').enable({emitEvent: false});
this.signalStrengthWidgetConfigForm.get('showTooltipDate').enable({emitEvent: false});
this.signalStrengthWidgetConfigForm.get('tooltipBackgroundColor').enable();
this.signalStrengthWidgetConfigForm.get('tooltipBackgroundBlur').enable();
if (showTooltipValue) {
this.signalStrengthWidgetConfigForm.get('units').enable();
this.signalStrengthWidgetConfigForm.get('decimals').enable();
this.signalStrengthWidgetConfigForm.get('tooltipValueFont').enable();
this.signalStrengthWidgetConfigForm.get('tooltipValueColor').enable();
} else {
this.signalStrengthWidgetConfigForm.get('units').disable();
this.signalStrengthWidgetConfigForm.get('decimals').disable();
this.signalStrengthWidgetConfigForm.get('tooltipValueFont').disable();
this.signalStrengthWidgetConfigForm.get('tooltipValueColor').disable();
}
if (showTooltipDate) {
this.signalStrengthWidgetConfigForm.get('tooltipDateFormat').enable();
this.signalStrengthWidgetConfigForm.get('tooltipDateFont').enable();
this.signalStrengthWidgetConfigForm.get('tooltipDateColor').enable();
} else {
this.signalStrengthWidgetConfigForm.get('tooltipDateFormat').disable();
this.signalStrengthWidgetConfigForm.get('tooltipDateFont').disable();
this.signalStrengthWidgetConfigForm.get('tooltipDateColor').disable();
}
} else {
this.signalStrengthWidgetConfigForm.get('showTooltipValue').disable({emitEvent: false});
this.signalStrengthWidgetConfigForm.get('showTooltipDate').disable({emitEvent: false});
this.signalStrengthWidgetConfigForm.get('tooltipBackgroundColor').disable();
this.signalStrengthWidgetConfigForm.get('tooltipBackgroundBlur').disable();
this.signalStrengthWidgetConfigForm.get('units').disable();
this.signalStrengthWidgetConfigForm.get('decimals').disable();
this.signalStrengthWidgetConfigForm.get('tooltipValueFont').disable();
this.signalStrengthWidgetConfigForm.get('tooltipValueColor').disable();
this.signalStrengthWidgetConfigForm.get('tooltipDateFormat').disable();
this.signalStrengthWidgetConfigForm.get('tooltipDateFont').disable();
this.signalStrengthWidgetConfigForm.get('tooltipDateColor').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 _datePreviewFn(): string {
const dateFormat: DateFormatSettings = this.signalStrengthWidgetConfigForm.get('dateFormat').value;
const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat);
processor.update(Date.now());
return processor.formatted;
}
private _tooltipValuePreviewFn(): string {
const units: string = this.widgetConfig.config.units;
const decimals: number = this.widgetConfig.config.decimals;
return formatValue(-76, decimals, units, true);
}
private _tooltipDatePreviewFn(): string {
const dateFormat: DateFormatSettings = this.signalStrengthWidgetConfigForm.get('tooltipDateFormat').value;
const processor = DateFormatProcessor.fromSettings(this.$injector, {...dateFormat, ...{hideLastUpdatePrefix: true}});
processor.update(Date.now());
return processor.formatted;
}
}

40
ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.html

@ -0,0 +1,40 @@
<!--
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 class="tb-signal-strength-panel" [style]="backgroundStyle" [class.tb-signal-strength-pointer]="hasCardClickAction" (click)="cardClick($event)">
<div class="tb-signal-strength-overlay" [style]="overlayStyle"></div>
<div *ngIf="widgetComponent.dashboardWidget.showWidgetTitlePanel || showDate" class="tb-signal-strength-title-panel">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div *ngIf="showDate" [style]="dateStyle" [innerHTML]="dateFormat.formatted"></div>
</div>
<div class="tb-signal-strength-content">
<div #signalStrengthShape class="tb-signal-strength-shape">
</div>
<div *ngIf="showTooltip" class="tb-signal-strength-tooltip-container">
<div #signalStrengthTooltip [style]="tooltipStyle" class="tb-signal-strength-tooltip">
<div *ngIf="showTooltipValue" class="tb-signal-strength-tooltip-row">
<div [style]="tooltipValueLabelStyle" translate>widgets.signal-strength.value</div>
<div [style]="tooltipValueStyle">{{ tooltipValueText }}</div>
</div>
<div *ngIf="showTooltipDate" class="tb-signal-strength-tooltip-row">
<div [style]="tooltipDateLabelStyle" translate>widgets.signal-strength.last-update</div>
<div [style]="tooltipDateStyle" [innerHTML]="tooltipDateFormat.formatted"></div>
</div>
</div>
</div>
</div>
</div>

90
ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.scss

@ -0,0 +1,90 @@
/**
* 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-signal-strength-panel {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
gap: 16px;
padding: 20px 24px 24px 24px;
&.tb-signal-strength-pointer {
cursor: pointer;
}
> div:not(.tb-signal-strength-overlay) {
z-index: 1;
}
.tb-signal-strength-overlay {
position: absolute;
top: 12px;
left: 12px;
bottom: 12px;
right: 12px;
}
.tb-signal-strength-title-panel {
display: flex;
flex-direction: column;
.tb-widget-title {
padding: 0;
}
}
.tb-signal-strength-content {
flex: 1;
min-width: 0;
min-height: 0;
position: relative;
.tb-signal-strength-shape {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.tb-signal-strength-tooltip-container {
z-index: 10;
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
&:hover {
.tb-signal-strength-tooltip {
opacity: 1;
}
}
.tb-signal-strength-tooltip {
position: absolute;
z-index: 20;
pointer-events: none;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 4px;
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
opacity: 0;
transition: opacity .35s;
.tb-signal-strength-tooltip-row {
display: flex;
justify-content: space-between;
align-items: center;
align-self: stretch;
gap: 8px;
}
}
}
}
}

387
ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts

@ -0,0 +1,387 @@
///
/// 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 {
backgroundStyle,
ColorProcessor,
ComponentStyle,
DateFormatProcessor,
getDataKey,
getSingleTsValue,
overlayStyle,
textStyle
} from '@shared/models/widget-settings.models';
import { WidgetComponent } from '@home/components/widget/widget.component';
import { formatValue, isDefinedAndNotNull, isNumeric, isUndefinedOrNull } from '@core/utils';
import { ResizeObserver } from '@juggle/resize-observer';
import { Element, G, Svg, SVG } from '@svgdotjs/svg.js';
import {
signalBarActive,
signalStrengthDefaultSettings,
SignalStrengthLayout,
SignalStrengthWidgetSettings
} from '@home/components/widget/lib/indicator/signal-strength-widget.models';
import tinycolor from 'tinycolor2';
import { TranslateService } from '@ngx-translate/core';
const shapeWidth = 149;
const shapeHeight = 113;
const shapeAspect = shapeWidth / shapeHeight;
@Component({
selector: 'tb-signal-strength-widget',
templateUrl: './signal-strength-widget.component.html',
styleUrls: ['./signal-strength-widget.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterViewInit {
@ViewChild('signalStrengthShape', {static: false})
signalStrengthShape: ElementRef<HTMLElement>;
@ViewChild('signalStrengthTooltip', {static: false})
signalStrengthTooltip: ElementRef<HTMLElement>;
settings: SignalStrengthWidgetSettings;
@Input()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
layout: SignalStrengthLayout;
activeBarsColor: ColorProcessor;
showDate = false;
dateFormat: DateFormatProcessor;
dateStyle: ComponentStyle = {};
showTooltip = false;
tooltipStyle: ComponentStyle = {};
showTooltipValue = false;
tooltipValueText = 'N/A';
tooltipValueLabelStyle: ComponentStyle = {
color: 'rgba(0, 0, 0, 0.38)'
};
tooltipValueStyle: ComponentStyle = {};
showTooltipDate = false;
tooltipDateFormat: DateFormatProcessor;
tooltipDateLabelStyle: ComponentStyle = {
color: 'rgba(0, 0, 0, 0.38)'
};
tooltipDateStyle: ComponentStyle = {};
backgroundStyle: ComponentStyle = {};
overlayStyle: ComponentStyle = {};
shapeResize$: ResizeObserver;
hasCardClickAction = false;
private decimals = 0;
private units = '';
private drawSvgShapePending = false;
private svgShape: Svg;
private bars: Element[] = Array.from(Array(4), () => null);
private centerTextElement: G;
private lastCenterText: string;
private inactiveBarsColorHex: string;
private inactiveBarsOpacity: number;
private rssi = -100;
private noSignal = false;
private noData = false;
constructor(public widgetComponent: WidgetComponent,
private translate: TranslateService,
private renderer: Renderer2,
private cd: ChangeDetectorRef) {
}
ngOnInit(): void {
this.ctx.$scope.signalStrengthWidget = this;
this.settings = {...signalStrengthDefaultSettings, ...this.ctx.settings};
this.layout = this.settings.layout;
this.showDate = this.settings.showDate;
if (this.showDate) {
this.dateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.dateFormat);
this.dateStyle = textStyle(this.settings.dateFont, '0.25px');
this.dateStyle.color = this.settings.dateColor;
}
this.activeBarsColor = ColorProcessor.fromSettings(this.settings.activeBarsColor);
const inactiveBarsColor = tinycolor(this.settings.inactiveBarsColor);
this.inactiveBarsColorHex = inactiveBarsColor.toHexString();
this.inactiveBarsOpacity = inactiveBarsColor.getAlpha();
this.showTooltip = this.settings.showTooltip && (this.settings.showTooltipValue || this.settings.showTooltipDate);
if (this.showTooltip) {
this.tooltipStyle = {
background: this.settings.tooltipBackgroundColor,
backdropFilter: `blur(${this.settings.tooltipBackgroundBlur}px)`
};
}
this.showTooltipValue = this.showTooltip && this.settings.showTooltipValue;
this.showTooltipDate = this.showTooltip && this.settings.showTooltipDate;
if (this.showTooltipValue) {
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.tooltipValueStyle = textStyle(this.settings.tooltipValueFont, '0.13px');
this.tooltipValueStyle.color = this.settings.tooltipValueColor;
this.tooltipValueLabelStyle = {...this.tooltipValueStyle, ...this.tooltipValueLabelStyle};
}
if (this.showTooltipDate) {
this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector,
{...this.settings.tooltipDateFormat, ...{hideLastUpdatePrefix: true}});
this.tooltipDateStyle = textStyle(this.settings.tooltipDateFont, '0.13px');
this.tooltipDateStyle.color = this.settings.tooltipDateColor;
this.tooltipDateLabelStyle = {...this.tooltipDateStyle, ...this.tooltipDateLabelStyle};
}
this.backgroundStyle = backgroundStyle(this.settings.background);
this.overlayStyle = overlayStyle(this.settings.background.overlay);
this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0;
}
ngAfterViewInit() {
if (this.drawSvgShapePending) {
this.drawSvg();
}
}
ngOnDestroy() {
if (this.shapeResize$) {
this.shapeResize$.disconnect();
}
}
public onInit() {
const borderRadius = this.ctx.$widgetElement.css('borderRadius');
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
if (this.signalStrengthShape) {
this.drawSvg();
} else {
this.drawSvgShapePending = true;
}
this.cd.detectChanges();
}
public onDataUpdated() {
const tsValue = getSingleTsValue(this.ctx.data);
let ts;
let value;
if (tsValue) {
ts = tsValue[0];
value = tsValue[1];
} else {
if (this.showTooltipValue) {
this.tooltipValueText = 'N/A';
}
}
this.noData = isUndefinedOrNull(value) || !isNumeric(value);
if (!this.noData) {
this.rssi = Number(value);
if (this.showTooltipValue) {
this.tooltipValueText = formatValue(value, this.decimals, this.units, true);
}
} else {
this.rssi = -100;
if (this.showTooltipValue) {
this.tooltipValueText = 'N/A';
}
}
this.noSignal = this.rssi <= -100;
this.activeBarsColor.update(this.rssi);
if (this.showDate) {
this.dateFormat.update(ts);
}
if (this.showTooltipDate) {
this.tooltipDateFormat.update(ts);
}
this.cd.detectChanges();
this.renderValues();
}
public cardClick($event: Event) {
this.ctx.actionsApi.cardClick($event);
}
private drawSvg() {
this.svgShape = SVG().addTo(this.signalStrengthShape.nativeElement).size(shapeWidth, shapeHeight);
this.renderer.setStyle(this.svgShape.node, 'overflow', 'visible');
this.renderer.setStyle(this.svgShape.node, 'user-select', 'none');
// Draw bars
if (this.layout === SignalStrengthLayout.wifi) {
this.bars[0] = this.svgShape.path('m 74.5,101.5 c 4.9706,0 9,-4.0294 9,-9 0,-4.9706 -4.0294,-9 -9,-9 -4.9706,0 ' +
'-9,4.0294 -9,9 0,4.9706 4.0294,9 9,9 z').fill(this.inactiveBarsColorHex).opacity(this.inactiveBarsOpacity);
this.bars[1] = this.svgShape.path('M 100.475,69.1453 C 93.3268,62.9321 84.0946,59.5 74.5299,59.5 c -9.5647,0 -18.7969,3.4321 ' +
'-25.9455,9.6453 -0.6041,0.5111 -1.0983,1.1344 -1.4539,1.8338 -0.3556,0.6994 -0.5656,1.4609 -0.6177,2.2402 -0.0521,0.7794 ' +
'0.0547,1.5612 0.3142,2.2999 0.2595,0.7386 0.6665,1.4196 1.1974,2.0032 0.5308,0.5836 1.175,1.0582 1.8951,1.3964 0.72,0.3381 ' +
'1.5016,0.533 2.2994,0.5733 0.7977,0.0404 1.5957,-0.0747 2.3476,-0.3384 0.7519,-0.2638 1.4427,-0.671 2.0323,-1.198 ' +
'4.9407,-4.2934 11.321,-6.665 17.9311,-6.665 6.6101,0 12.9904,2.3716 17.9311,6.665 1.1957,1.0392 2.7646,1.5713 ' +
'4.3616,1.4795 1.5969,-0.0919 3.0911,-0.8003 4.1544,-1.9694 1.062,-1.1691 1.607,-2.7031 1.513,-4.2645 ' +
'-0.094,-1.5615 -0.819,-3.0225 -2.015,-4.0616 z').fill(this.inactiveBarsColorHex).opacity(this.inactiveBarsOpacity);
this.bars[2] = this.svgShape.path('m 74.5,35.5001 c -16.3337,-0.0218 -32.061,5.9702 -43.9755,16.7543 -0.6032,0.5216 ' +
'-1.0925,1.1541 -1.4391,1.8606 -0.3467,0.7065 -0.5436,1.4726 -0.5795,2.2536 -0.0358,0.7809 0.0903,1.5608 0.371,2.2941 ' +
'0.2806,0.7332 0.71,1.4051 1.2632,1.976 0.5531,0.5709 1.2188,1.0295 1.958,1.3489 0.7392,0.3194 1.5371,0.493 2.3467,0.5109 ' +
'0.8097,0.0178 1.6149,-0.1206 2.3684,-0.4071 0.7535,-0.2865 1.4402,-0.7153 2.0197,-1.2613 9.6742,-8.7299 22.4234,-13.5862 ' +
'35.6671,-13.5862 13.2437,0 25.993,4.8563 35.667,13.5862 0.58,0.546 1.266,0.9748 2.02,1.2613 0.753,0.2865 1.559,0.4249 ' +
'2.368,0.4071 0.81,-0.0179 1.608,-0.1915 2.347,-0.5109 0.739,-0.3194 1.405,-0.778 1.958,-1.3489 0.553,-0.5709 0.983,-1.2428 ' +
'1.263,-1.976 0.281,-0.7333 0.407,-1.5132 0.371,-2.2941 -0.036,-0.781 -0.233,-1.5471 -0.579,-2.2536 -0.347,-0.7065 ' +
'-0.836,-1.339 -1.44,-1.8606 ' +
'C 106.561,41.4703 90.8337,35.4783 74.5,35.5001 Z').fill(this.inactiveBarsColorHex).opacity(this.inactiveBarsOpacity);
this.bars[3] = this.svgShape.path('M 134.464,33.9289 C 117.976,19.4835 96.6509,11.5 74.5537,11.5 c -22.0972,0 -43.422,7.9835 ' +
'-59.9102,22.4289 -0.6196,0.5198 -1.1273,1.1559 -1.493,1.871 -0.3658,0.7151 -0.5823,1.4948 -0.6368,2.2933 -0.0545,0.7984 ' +
'0.054,1.5995 0.3193,2.3562 0.2652,0.7567 0.6818,1.4536 1.2253,2.05 0.5434,0.5963 1.2028,1.08 1.9393,1.4225 0.7366,0.3426 ' +
'1.5354,0.5372 2.3496,0.5723 0.8142,0.0351 1.6273,-0.0899 2.3916,-0.3677 0.7643,-0.2778 1.4643,-0.7028 2.0589,-1.25 ' +
'14.2455,-12.4752 32.6666,-19.3694 51.7545,-19.3694 19.0879,0 37.5088,6.8942 51.7548,19.3694 1.209,1.0617 2.798,1.6084 ' +
'4.418,1.5196 1.619,-0.0887 3.137,-0.8056 4.218,-1.9929 1.081,-1.1873 1.638,-2.7477 1.547,-4.338 -0.09,-1.5903 ' +
'-0.82,-3.0802 -2.029,-4.142 z').fill(this.inactiveBarsColorHex).opacity(this.inactiveBarsOpacity);
} else {
this.bars[0] = this.svgShape.rect(18, 37).radius(4).move(19, 75).fill(this.inactiveBarsColorHex).opacity(this.inactiveBarsOpacity);
this.bars[1] = this.svgShape.rect(18, 61).radius(4).move(50, 50).fill(this.inactiveBarsColorHex).opacity(this.inactiveBarsOpacity);
this.bars[2] = this.svgShape.rect(18, 86).radius(4).move(80, 26).fill(this.inactiveBarsColorHex).opacity(this.inactiveBarsOpacity);
this.bars[3] = this.svgShape.rect(18, 111).radius(4).move(111, 1).fill(this.inactiveBarsColorHex).opacity(this.inactiveBarsOpacity);
}
this.shapeResize$ = new ResizeObserver(() => {
this.onResize();
});
this.shapeResize$.observe(this.signalStrengthShape.nativeElement);
this.onResize();
this.renderValues();
}
private renderValues() {
if (this.svgShape) {
const activeBarsColor = tinycolor(this.activeBarsColor.color);
const activeBarsColorHex = activeBarsColor.toHexString();
const activeBarsOpacity = activeBarsColor.getAlpha();
for (let index = 0; index < this.bars.length; index++) {
const bar = this.bars[index];
const active = signalBarActive(this.rssi, index);
const newFill = active ? activeBarsColorHex : this.inactiveBarsColorHex;
const newOpacity = active ? activeBarsOpacity : this.inactiveBarsOpacity;
if (newFill !== bar.fill() || newOpacity !== bar.opacity()) {
bar.timeline().finish();
bar.animate(200).attr({fill: newFill, opacity: newOpacity});
}
}
const centerText = this.noData ? 'N/A' : (this.noSignal ? this.translate.instant('widgets.signal-strength.no-signal') : '');
this.renderCenterText(centerText);
}
}
private renderCenterText(text: string) {
if (this.lastCenterText !== text) {
if (this.centerTextElement) {
this.centerTextElement.remove();
this.centerTextElement = null;
}
if (text) {
this.centerTextElement = this.svgShape.group();
const textElement = this.centerTextElement.text(text).font(
{
family: 'Roboto',
weight: '500',
style: 'normal',
size: '12px',
leading: 1.333
}
).fill('#fff');
const bounds = textElement.bbox();
this.centerTextElement.rect(bounds.width + 16, bounds.height + 8)
.move(bounds.x - 8, bounds.y - 4).radius(4).fill('#848484').insertBefore(textElement);
this.centerTextElement.center(shapeWidth / 2, shapeHeight / 2);
}
this.lastCenterText = text;
}
}
private onResize() {
const shapeContainerWidth = this.signalStrengthShape.nativeElement.getBoundingClientRect().width;
const shapeContainerHeight = this.signalStrengthShape.nativeElement.getBoundingClientRect().height;
const shapeContainerAspect = shapeContainerWidth / shapeContainerHeight;
let scale: number;
if (shapeAspect >= shapeContainerAspect) {
scale = shapeContainerWidth / shapeWidth;
} else {
scale = shapeContainerHeight / shapeHeight;
}
this.renderer.setStyle(this.svgShape.node, 'transform', `scale(${scale})`);
if (this.showTooltip) {
const targetWidth = shapeContainerWidth;
const minAspect = 0.45;
const aspect = Math.min(shapeContainerHeight / targetWidth, minAspect);
const targetHeight = targetWidth * aspect;
const height = 80;
scale = targetHeight / height;
const width = targetWidth / scale;
this.renderer.setStyle(this.signalStrengthTooltip.nativeElement, 'width', width + 'px');
this.renderer.setStyle(this.signalStrengthTooltip.nativeElement, 'transform', `scale(${scale})`);
}
}
}

141
ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts

@ -0,0 +1,141 @@
///
/// 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,
ColorType,
DateFormatSettings,
defaultColorFunction,
Font,
lastUpdateAgoDateFormat
} from '@shared/models/widget-settings.models';
export enum SignalStrengthLayout {
wifi = 'wifi',
cellular_bar = 'cellular_bar'
}
export const signalStrengthLayouts = Object.keys(SignalStrengthLayout) as SignalStrengthLayout[];
export const signalStrengthLayoutTranslations = new Map<SignalStrengthLayout, string>(
[
[SignalStrengthLayout.wifi, 'widgets.signal-strength.layout-wifi'],
[SignalStrengthLayout.cellular_bar, 'widgets.signal-strength.layout-cellular-bar']
]
);
export const signalStrengthLayoutImages = new Map<SignalStrengthLayout, string>(
[
[SignalStrengthLayout.wifi, 'assets/widget/signal-strength/wifi-layout.svg'],
[SignalStrengthLayout.cellular_bar, 'assets/widget/signal-strength/cellular-bar-layout.svg']
]
);
export interface SignalStrengthWidgetSettings {
layout: SignalStrengthLayout;
showDate: boolean;
dateFormat: DateFormatSettings;
dateFont: Font;
dateColor: string;
activeBarsColor: ColorSettings;
inactiveBarsColor: string;
showTooltip: boolean;
showTooltipValue: boolean;
tooltipValueFont: Font;
tooltipValueColor: string;
showTooltipDate: boolean;
tooltipDateFormat: DateFormatSettings;
tooltipDateFont: Font;
tooltipDateColor: string;
tooltipBackgroundColor: string;
tooltipBackgroundBlur: number;
background: BackgroundSettings;
}
export const signalStrengthDefaultSettings: SignalStrengthWidgetSettings = {
layout: SignalStrengthLayout.wifi,
showDate: false,
dateFormat: lastUpdateAgoDateFormat(),
dateFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '16px'
},
dateColor: 'rgba(0, 0, 0, 0.38)',
activeBarsColor: {
color: 'rgba(92, 223, 144, 1)',
type: ColorType.range,
rangeList: [
{to: -85, color: 'rgba(227, 71, 71, 1)'},
{from: -85, to: -70, color: 'rgba(255, 122, 0, 1)'},
{from: -70, to: -55, color: 'rgba(246, 206, 67, 1)'},
{from: -55, color: 'rgba(92, 223, 144, 1)'}
],
colorFunction: defaultColorFunction
},
inactiveBarsColor: 'rgba(224, 224, 224, 1)',
showTooltip: true,
showTooltipValue: true,
tooltipValueFont: {
family: 'Roboto',
size: 13,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '16px'
},
tooltipValueColor: 'rgba(0,0,0,0.76)',
showTooltipDate: true,
tooltipDateFormat: lastUpdateAgoDateFormat(),
tooltipDateFont: {
family: 'Roboto',
size: 13,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '16px'
},
tooltipDateColor: 'rgba(0,0,0,0.76)',
tooltipBackgroundColor: 'rgba(255,255,255,0.72)',
tooltipBackgroundBlur: 3,
background: {
type: BackgroundType.color,
color: '#fff',
overlay: {
enabled: false,
color: 'rgba(255,255,255,0.72)',
blur: 3
}
}
};
export const signalBarActive = (rssi: number, index: number): boolean => {
switch (index) {
case 0:
return rssi > -100;
case 1:
return rssi >= -85;
case 2:
return rssi >= -70;
case 3:
return rssi >= -55;
}
};

121
ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.html

@ -0,0 +1,121 @@
<!--
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]="signalStrengthWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.signal-strength.signal-strength-card-style</div>
<tb-image-cards-select rowHeight="2:1"
cols="2"
label="{{ 'widgets.signal-strength.layout' | translate }}" formControlName="layout">
<tb-image-cards-select-option *ngFor="let layout of signalStrengthLayouts"
[value]="layout"
[image]="signalStrengthLayoutImageMap.get(layout)">
{{ signalStrengthLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showDate">
{{ 'widgets.signal-strength.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex formControlName="dateFormat"></tb-date-format-select>
<tb-font-settings formControlName="dateFont"
[previewText]="datePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="dateColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.signal-strength.active-bars-color' | translate }}</div>
<tb-color-settings formControlName="activeBarsColor"
settingsKey="{{'widgets.signal-strength.active-bars-color' | translate }}">
</tb-color-settings>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.signal-strength.inactive-bars-color' | translate }}</div>
<tb-color-input asBoxInput
formControlName="inactiveBarsColor">
</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>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="signalStrengthWidgetSettingsForm.get('showTooltip').value"
[disabled]="!signalStrengthWidgetSettingsForm.get('showTooltip').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.signal-strength.tooltip' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="showTooltipValue">
{{ 'widgets.signal-strength.value' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="tooltipValueFont"
[previewText]="tooltipValuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tooltipValueColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipDate">
{{ 'widgets.signal-strength.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-font-settings formControlName="tooltipDateFont"
[previewText]="tooltipDatePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tooltipDateColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.signal-strength.background-color' | translate }}</div>
<tb-color-input asBoxInput
formControlName="tooltipBackgroundColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.signal-strength.background-blur</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1"
placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
</div>
</ng-container>

167
ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.ts

@ -0,0 +1,167 @@
///
/// 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 {
signalStrengthDefaultSettings,
signalStrengthLayoutImages,
signalStrengthLayouts,
signalStrengthLayoutTranslations
} from '@home/components/widget/lib/indicator/signal-strength-widget.models';
import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models';
@Component({
selector: 'tb-signal-strength-widget-settings',
templateUrl: './signal-strength-widget-settings.component.html',
styleUrls: ['./../widget-settings.scss'],
})
export class SignalStrengthWidgetSettingsComponent extends WidgetSettingsComponent {
signalStrengthLayouts = signalStrengthLayouts;
signalStrengthLayoutTranslationMap = signalStrengthLayoutTranslations;
signalStrengthLayoutImageMap = signalStrengthLayoutImages;
signalStrengthWidgetSettingsForm: UntypedFormGroup;
datePreviewFn = this._datePreviewFn.bind(this);
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
private $injector: Injector,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.signalStrengthWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {...signalStrengthDefaultSettings};
}
protected onSettingsSet(settings: WidgetSettings) {
this.signalStrengthWidgetSettingsForm = this.fb.group({
layout: [settings.layout, []],
showDate: [settings.showDate, []],
dateFormat: [settings.dateFormat, []],
dateFont: [settings.dateFont, []],
dateColor: [settings.dateColor, []],
activeBarsColor: [settings.activeBarsColor, []],
inactiveBarsColor: [settings.inactiveBarsColor, []],
showTooltip: [settings.showTooltip, []],
showTooltipValue: [settings.showTooltipValue, []],
tooltipValueFont: [settings.tooltipValueFont, []],
tooltipValueColor: [settings.tooltipValueColor, []],
showTooltipDate: [settings.showTooltipDate, []],
tooltipDateFormat: [settings.tooltipDateFormat, []],
tooltipDateFont: [settings.tooltipDateFont, []],
tooltipDateColor: [settings.tooltipDateColor, []],
tooltipBackgroundColor: [settings.tooltipBackgroundColor, []],
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
background: [settings.background, []]
});
}
protected validatorTriggers(): string[] {
return ['showDate', 'showTooltip', 'showTooltipValue', 'showTooltipDate'];
}
protected updateValidators(emitEvent: boolean) {
const showDate: boolean = this.signalStrengthWidgetSettingsForm.get('showDate').value;
const showTooltip: boolean = this.signalStrengthWidgetSettingsForm.get('showTooltip').value;
const showTooltipValue: boolean = this.signalStrengthWidgetSettingsForm.get('showTooltipValue').value;
const showTooltipDate: boolean = this.signalStrengthWidgetSettingsForm.get('showTooltipDate').value;
if (showDate) {
this.signalStrengthWidgetSettingsForm.get('dateFormat').enable();
this.signalStrengthWidgetSettingsForm.get('dateFont').enable();
this.signalStrengthWidgetSettingsForm.get('dateColor').enable();
} else {
this.signalStrengthWidgetSettingsForm.get('dateFormat').disable();
this.signalStrengthWidgetSettingsForm.get('dateFont').disable();
this.signalStrengthWidgetSettingsForm.get('dateColor').disable();
}
if (showTooltip) {
this.signalStrengthWidgetSettingsForm.get('showTooltipValue').enable({emitEvent: false});
this.signalStrengthWidgetSettingsForm.get('showTooltipDate').enable({emitEvent: false});
this.signalStrengthWidgetSettingsForm.get('tooltipBackgroundColor').enable();
this.signalStrengthWidgetSettingsForm.get('tooltipBackgroundBlur').enable();
if (showTooltipValue) {
this.signalStrengthWidgetSettingsForm.get('tooltipValueFont').enable();
this.signalStrengthWidgetSettingsForm.get('tooltipValueColor').enable();
} else {
this.signalStrengthWidgetSettingsForm.get('tooltipValueFont').disable();
this.signalStrengthWidgetSettingsForm.get('tooltipValueColor').disable();
}
if (showTooltipDate) {
this.signalStrengthWidgetSettingsForm.get('tooltipDateFormat').enable();
this.signalStrengthWidgetSettingsForm.get('tooltipDateFont').enable();
this.signalStrengthWidgetSettingsForm.get('tooltipDateColor').enable();
} else {
this.signalStrengthWidgetSettingsForm.get('tooltipDateFormat').disable();
this.signalStrengthWidgetSettingsForm.get('tooltipDateFont').disable();
this.signalStrengthWidgetSettingsForm.get('tooltipDateColor').disable();
}
} else {
this.signalStrengthWidgetSettingsForm.get('showTooltipValue').disable({emitEvent: false});
this.signalStrengthWidgetSettingsForm.get('showTooltipDate').disable({emitEvent: false});
this.signalStrengthWidgetSettingsForm.get('tooltipBackgroundColor').disable();
this.signalStrengthWidgetSettingsForm.get('tooltipBackgroundBlur').disable();
this.signalStrengthWidgetSettingsForm.get('tooltipValueFont').disable();
this.signalStrengthWidgetSettingsForm.get('tooltipValueColor').disable();
this.signalStrengthWidgetSettingsForm.get('tooltipDateFormat').disable();
this.signalStrengthWidgetSettingsForm.get('tooltipDateFont').disable();
this.signalStrengthWidgetSettingsForm.get('tooltipDateColor').disable();
}
}
private _datePreviewFn(): string {
const dateFormat: DateFormatSettings = this.signalStrengthWidgetSettingsForm.get('dateFormat').value;
const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat);
processor.update(Date.now());
return processor.formatted;
}
private _tooltipValuePreviewFn(): string {
const units: string = this.widgetConfig.config.units;
const decimals: number = this.widgetConfig.config.decimals;
return formatValue(-76, decimals, units, true);
}
private _tooltipDatePreviewFn(): string {
const dateFormat: DateFormatSettings = this.signalStrengthWidgetSettingsForm.get('tooltipDateFormat').value;
const processor = DateFormatProcessor.fromSettings(this.$injector, {...dateFormat, ...{hideLastUpdatePrefix: true}});
processor.update(Date.now());
return processor.formatted;
}
}

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

@ -291,6 +291,9 @@ import {
import {
WindSpeedDirectionWidgetSettingsComponent
} from '@home/components/widget/lib/settings/weather/wind-speed-direction-widget-settings.component';
import {
SignalStrengthWidgetSettingsComponent
} from '@home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component';
@NgModule({
declarations: [
@ -398,7 +401,8 @@ import {
AlarmCountWidgetSettingsComponent,
EntityCountWidgetSettingsComponent,
BatteryLevelWidgetSettingsComponent,
WindSpeedDirectionWidgetSettingsComponent
WindSpeedDirectionWidgetSettingsComponent,
SignalStrengthWidgetSettingsComponent
],
imports: [
CommonModule,
@ -511,7 +515,8 @@ import {
AlarmCountWidgetSettingsComponent,
EntityCountWidgetSettingsComponent,
BatteryLevelWidgetSettingsComponent,
WindSpeedDirectionWidgetSettingsComponent
WindSpeedDirectionWidgetSettingsComponent,
SignalStrengthWidgetSettingsComponent
]
})
export class WidgetSettingsModule {
@ -589,5 +594,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-alarm-count-widget-settings': AlarmCountWidgetSettingsComponent,
'tb-entity-count-widget-settings': EntityCountWidgetSettingsComponent,
'tb-battery-level-widget-settings': BatteryLevelWidgetSettingsComponent,
'tb-wind-speed-direction-widget-settings': WindSpeedDirectionWidgetSettingsComponent
'tb-wind-speed-direction-widget-settings': WindSpeedDirectionWidgetSettingsComponent,
'tb-signal-strength-widget-settings': SignalStrengthWidgetSettingsComponent
};

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

@ -61,6 +61,7 @@ import { BatteryLevelWidgetComponent } from '@home/components/widget/lib/indicat
import {
WindSpeedDirectionWidgetComponent
} from '@home/components/widget/lib/weather/wind-speed-direction-widget.component';
import { SignalStrengthWidgetComponent } from '@home/components/widget/lib/indicator/signal-strength-widget.component';
@NgModule({
declarations:
@ -96,7 +97,8 @@ import {
AggregatedValueCardWidgetComponent,
CountWidgetComponent,
BatteryLevelWidgetComponent,
WindSpeedDirectionWidgetComponent
WindSpeedDirectionWidgetComponent,
SignalStrengthWidgetComponent
],
imports: [
CommonModule,
@ -136,7 +138,8 @@ import {
AggregatedValueCardWidgetComponent,
CountWidgetComponent,
BatteryLevelWidgetComponent,
WindSpeedDirectionWidgetComponent
WindSpeedDirectionWidgetComponent,
SignalStrengthWidgetComponent
],
providers: [
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }

10
ui-ngx/src/app/shared/models/widget-settings.models.ts

@ -231,6 +231,7 @@ export interface DateFormatSettings {
format?: string;
lastUpdateAgo?: boolean;
custom?: boolean;
hideLastUpdatePrefix?: boolean;
}
export const simpleDateFormat = (format: string): DateFormatSettings => ({
@ -319,8 +320,13 @@ export class LastUpdateAgoDateFormatProcessor extends DateFormatProcessor {
}
update(ts: string| number | Date): void {
this.formatted = this.translate.instant('date.last-update-n-ago-text',
{agoText: this.dateAgoPipe.transform(ts, {applyAgo: true, short: true, textPart: true})});
const agoText = this.dateAgoPipe.transform(ts, {applyAgo: true, short: true, textPart: true});
if (this.settings.hideLastUpdatePrefix) {
this.formatted = agoText;
} else {
this.formatted = this.translate.instant('date.last-update-n-ago-text',
{agoText});
}
}
}

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

@ -5060,6 +5060,22 @@
"battery-level-card-style": "Battery level card style",
"sections-count": "Sections count"
},
"signal-strength": {
"value": "Value",
"last-update": "Last update",
"no-signal": "No signal",
"layout": "Layout",
"layout-wifi": "Wi-Fi",
"layout-cellular-bar": "Cellular bar",
"icon": "Icon",
"date": "Date",
"active-bars-color": "Active signal bars color",
"inactive-bars-color": "Inactive signal bars color",
"tooltip": "Tooltip",
"background-color": "Background color",
"background-blur": "Background blur",
"signal-strength-card-style": "Signal strength card style"
},
"chart": {
"common-settings": "Common settings",
"enable-stacking-mode": "Enable stacking mode",

31
ui-ngx/src/assets/widget/signal-strength/cellular-bar-layout.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

36
ui-ngx/src/assets/widget/signal-strength/wifi-layout.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

Loading…
Cancel
Save