From be850f506aa99bf7ebae4d06abd4a4833d572faa Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 19 Sep 2023 17:29:36 +0300 Subject: [PATCH] UI: Implement battery level widget --- .../json/system/widget_bundles/cards.json | 1 + .../system/widget_types/battery_level.json | 23 ++ ui-ngx/src/app/core/api/widget-api.models.ts | 1 + .../dashboard-page.component.ts | 4 +- .../basic/basic-widget-config.module.ts | 12 +- .../basic/common/data-key-row.component.ts | 1 + .../battery-level-basic-config.component.html | 129 ++++++++ .../battery-level-basic-config.component.ts | 221 +++++++++++++ .../lib/count/count-widget.component.html | 2 +- .../lib/count/count-widget.component.ts | 11 +- .../battery-level-widget.component.html | 44 +++ .../battery-level-widget.component.scss | 158 ++++++++++ .../battery-level-widget.component.ts | 293 ++++++++++++++++++ .../indicator/battery-level-widget.models.ts | 108 +++++++ .../common/font-settings-panel.component.html | 6 +- .../common/font-settings-panel.component.scss | 4 + .../common/font-settings-panel.component.ts | 10 +- .../common/font-settings.component.ts | 7 +- ...ttery-level-widget-settings.component.html | 63 ++++ ...battery-level-widget-settings.component.ts | 104 +++++++ .../lib/settings/widget-settings.module.ts | 12 +- .../widget/widget-components.module.ts | 7 +- .../components/widget/widget.component.ts | 14 + .../assets/locale/locale.constant-en_US.json | 16 +- .../battery-shape-horizontal.svg | 4 + .../battery-level/battery-shape-vertical.svg | 4 + .../horizontal-divided-layout.svg | 42 +++ .../battery-level/horizontal-solid-layout.svg | 44 +++ .../battery-level/vertical-divided-layout.svg | 33 ++ .../battery-level/vertical-solid-layout.svg | 44 +++ 30 files changed, 1399 insertions(+), 23 deletions(-) create mode 100644 application/src/main/data/json/system/widget_types/battery_level.json create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.ts create mode 100644 ui-ngx/src/assets/widget/battery-level/battery-shape-horizontal.svg create mode 100644 ui-ngx/src/assets/widget/battery-level/battery-shape-vertical.svg create mode 100644 ui-ngx/src/assets/widget/battery-level/horizontal-divided-layout.svg create mode 100644 ui-ngx/src/assets/widget/battery-level/horizontal-solid-layout.svg create mode 100644 ui-ngx/src/assets/widget/battery-level/vertical-divided-layout.svg create mode 100644 ui-ngx/src/assets/widget/battery-level/vertical-solid-layout.svg diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index 8c92636cb2..b1c7778ddc 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -11,6 +11,7 @@ "cards.value_card", "cards.horizontal_value_card", "cards.aggregated_value_card", + "battery_level", "cards.label_widget", "cards.dashboard_state_widget", "cards.qr_code", diff --git a/application/src/main/data/json/system/widget_types/battery_level.json b/application/src/main/data/json/system/widget_types/battery_level.json new file mode 100644 index 0000000000..27cb5a8ddd --- /dev/null +++ b/application/src/main/data/json/system/widget_types/battery_level.json @@ -0,0 +1,23 @@ +{ + "fqn": "battery_level", + "name": "Battery level", + "deprecated": false, + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAilBMVEXg4ODf39/g4OAAAADg4ODf39////9c35Dg4ODL9dz5/vzX9+QhISHl+u3f+enB89XC89XHx8c9PT2QkJCu8MiF56x0dHRw453j4+Px8fFYWFisrKz0/fcvLy/S9uDq+/GCgoK6uro8PDzV1dVKSkpw457O9t6enp7i+etmZmarq6tm4ZeZ67p65aQon2vGAAAABnRSTlPvIL8Ar7DvmsykAAAEcklEQVR42u3dbVebMBjG8Tq3Sym3zARlSSA8ax+2ff+vtyCds666oOsSuvxf5CCnL/o7gUIRThfnZx8Wl3Pv49n54mwhMfukYXw8AYeRmM0KJ9HiVCCXAeJZAeJbAeJbAeJbAeJbAeJb/wOkzky5wDx6BcJJKU1cPqHxDmh4Aw97DcLNkFEO06jJiAH5uAaPQD++Yf4JsqKVGTRRJqA06bo2gwJYb1ZJMF5z6ngNs5TCQXYQIRjXAoz6JqUNGkUpYxllDQS1eU01GJHKoLVETQwOsoLQUAp0uRhYTzetjMwqpQ0kA5Ca9byFg+xnZEMNwLKe0x6kJ6WUJsEGqOHUwogcZL+PCNogp75h+zPCKRuSIwRKr9xsWfYQZiAZdc83rc2wChI7SEq6hYMsITpNa05bpFQ3NRlITTVDQ4oZAG8apX5CJDnasux3drUF5IZI9SQhWsog28G0bYn6boSYFHVwl/25lhQYE3I3mFHgV6rFm/LspLGraYuJeQlRpJyfp/wViOjgvP/h+8i8ChDfChDfChDfChDfChDfChDfChDfejekiKOnxUvY5RtkGT3r3lLiGySJnreGVZ5BzIR4MiXvgxRl9HtlAYs8gBTruBy7jw53X47Fycsm55BlYt5obJeBJhYbmhPIsoziwn7ykqi0kLiAlNEVplRZSFxArgZH/O3WsmsjsZA7gJQlcHNh341hH/48dgspogrVxZTM66MKB3ILqaIC8STINZaHj/VuIVdvgiQ4kGvIMkCeFSABEiABEiABEiAm95BqEiT2F4JvExy38BiCm+9fLLspvIZMKUD+BSS5sa3yGnI95eKDx5Di7mJChb+QkzkgBsh+ARIgARIgM4KINMdQrnifwiRTzlU+M4hcKaL0wUE6aykzSxnpTUv5vCAp6X6EcBKQLTEwamEGjbEmFXOAMIb0AdKRMmNNq+HvnWuo7jMuwbyHmMY3zh4gOdW7508VMQz1DJyJVhwZkkyCxBaQ7BlEpVJ3fIsjQ3A7wXGHN0C6Vqd5hqNDim/WjttqKmSX6MUsvrM/7uy75ZpW+5AsTTXfzgUiSZtxQ83DrACaJMbyVpJo+LEhxWfLoupVCBSlXUOthNSUi9podnEhNNixIcnXC9u+Xr8KES0R6cYsbbVZevzAlQxos74+MuTO2mEkxUGIYAIPNatcjmuaFdt/SdPM4IA4dBJnv6YACZAACZAACZAAMQVIgLzv4kPkL2TaxQeP/xmK6vuEiw8+Qwwltqvy/IaBk7nzAUlsmd+Q6s5+X098hky6+HAydz54fBw5mSN7gOwXIAESIAESIAEyZ8iJPBlaRdXkiw+FxXPgDp6eXk87jb8rkJhZPJBTyPhw+tpe8nWNZVniUI4hRRQvDeezXcNcJBaPszuA4GqQ2LeMLZ5mdwIxkigpiqVV1fr+xY8s5xBUZWRfWeGF3EOAorqyqyrwYj5AvCxAfCtAfCtAfCtAfCtAfOtyscBJdLn44PyHKv5G8tPp/Ij2+dnHy7m3+HB2/gO3i0/vBj05fgAAAABJRU5ErkJggg==", + "description": "Displays current battery level of device.", + "descriptor": { + "type": "latest", + "sizeX": 2.5, + "sizeY": 2.5, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.batteryLevelWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.batteryLevelWidget.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-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)\"}" + }, + "externalId": null +} \ No newline at end of file diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index d0de3481ed..5cfc83d8d9 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -89,6 +89,7 @@ export interface WidgetActionsApi { handleWidgetAction: ($event: Event, descriptor: WidgetActionDescriptor, entityId?: EntityId, entityName?: string, additionalParams?: any, entityLabel?: string) => void; elementClick: ($event: Event) => void; + cardClick: ($event: Event) => void; getActiveEntityInfo: () => SubscriptionEntityInfo; openDashboardStateInSeparateDialog: (targetDashboardStateId: string, params?: StateParams, dialogTitle?: string, hideDashboardToolbar?: boolean, dialogWidth?: number, dialogHeight?: number) => void; diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 7b84dbd6d0..a9ce196203 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -351,7 +351,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC private viewContainerRef: ViewContainerRef, private cd: ChangeDetectorRef, private sanitizer: DomSanitizer, - public elRef: ElementRef) { + public elRef: ElementRef, + private injector: Injector) { super(store); if (isDefinedAndNotNull(embeddedValue)) { this.embedded = embeddedValue; @@ -1177,6 +1178,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], maxWidth: '95vw', + injector: this.injector, data: { dashboard: this.dashboard, aliasController: this.dashboardCtx.aliasController, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts index 69500d1c85..41115904b3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts @@ -55,6 +55,9 @@ import { import { EntityCountBasicConfigComponent } from '@home/components/widget/config/basic/entity/entity-count-basic-config.component'; +import { + BatteryLevelBasicConfigComponent +} from '@home/components/widget/config/basic/indicator/battery-level-basic-config.component'; @NgModule({ declarations: [ @@ -71,7 +74,8 @@ import { DataKeyRowComponent, DataKeysPanelComponent, AlarmCountBasicConfigComponent, - EntityCountBasicConfigComponent + EntityCountBasicConfigComponent, + BatteryLevelBasicConfigComponent ], imports: [ CommonModule, @@ -92,7 +96,8 @@ import { DataKeyRowComponent, DataKeysPanelComponent, AlarmCountBasicConfigComponent, - EntityCountBasicConfigComponent + EntityCountBasicConfigComponent, + BatteryLevelBasicConfigComponent ] }) export class BasicWidgetConfigModule { @@ -107,5 +112,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type + + + + + +
+
widget-config.appearance
+ + + {{ batteryLevelLayoutTranslationMap.get(layout) | translate }} + + +
+ + {{ 'widget-config.title' | translate }} + +
+ + + + + + + +
+
+
+ + {{ 'widgets.battery-level.icon' | translate }} + +
+ + + + + + + + +
+
+
+ + {{ 'widgets.battery-level.value' | translate }} + +
+ + {{ 'widgets.battery-level.auto-scale' | translate }} + + + + + +
+
+
+
{{ 'widgets.battery-level.battery-level-color' | translate }}
+ + +
+
+
{{ 'widgets.battery-level.battery-shape-color' | translate }}
+ + +
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
widget-config.show-card-buttons
+ + {{ 'fullscreen.fullscreen' | translate }} + +
+
+
{{ 'widget-config.card-border-radius' | translate }}
+ + + +
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.ts new file mode 100644 index 0000000000..482a8dd2c3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.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 { 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, resolveCssSize } from '@shared/models/widget-settings.models'; +import { + batteryLevelDefaultSettings, + batteryLevelLayoutImages, + batteryLevelLayouts, + batteryLevelLayoutTranslations, + BatteryLevelWidgetSettings +} from '@home/components/widget/lib/indicator/battery-level-widget.models'; + +@Component({ + selector: 'tb-battery-level-basic-config', + templateUrl: './battery-level-basic-config.component.html', + styleUrls: ['../basic-config.scss'] +}) +export class BatteryLevelBasicConfigComponent extends BasicWidgetConfigComponent { + + public get displayTimewindowConfig(): boolean { + const datasources = this.batteryLevelWidgetConfigForm.get('datasources').value; + return datasourcesHasAggregation(datasources); + } + + public onlyHistoryTimewindow(): boolean { + const datasources = this.batteryLevelWidgetConfigForm.get('datasources').value; + return datasourcesHasOnlyComparisonAggregation(datasources); + } + + batteryLevelLayouts = batteryLevelLayouts; + + batteryLevelLayoutTranslationMap = batteryLevelLayoutTranslations; + batteryLevelLayoutImageMap = batteryLevelLayoutImages; + + batteryLevelWidgetConfigForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + constructor(protected store: Store, + protected widgetConfigComponent: WidgetConfigComponent, + private $injector: Injector, + private fb: UntypedFormBuilder) { + super(store, widgetConfigComponent); + } + + protected configForm(): UntypedFormGroup { + return this.batteryLevelWidgetConfigForm; + } + + protected setupDefaults(configData: WidgetConfigComponentData) { + this.setupDefaultDatasource(configData, [{ name: 'batteryLevel', label: 'batteryLevel', type: DataKeyType.timeseries }]); + } + + protected onConfigSet(configData: WidgetConfigComponentData) { + const settings: BatteryLevelWidgetSettings = {...batteryLevelDefaultSettings, ...(configData.config.settings || {})}; + const iconSize = resolveCssSize(configData.config.iconSize); + this.batteryLevelWidgetConfigForm = 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, []], + + showValue: [settings.showValue, []], + autoScaleValueSize: [settings.autoScaleValueSize, []], + valueFont: [settings.valueFont, []], + valueColor: [settings.valueColor, []], + + batteryLevelColor: [settings.batteryLevelColor, []], + batteryShapeColor: [settings.batteryShapeColor, []], + + 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.showValue = config.showValue; + this.widgetConfig.config.settings.autoScaleValueSize = config.autoScaleValueSize === true; + this.widgetConfig.config.settings.valueFont = config.valueFont; + this.widgetConfig.config.settings.valueColor = config.valueColor; + + this.widgetConfig.config.settings.batteryLevelColor = config.batteryLevelColor; + this.widgetConfig.config.settings.batteryShapeColor = config.batteryShapeColor; + + 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']; + } + + protected updateValidators(emitEvent: boolean, trigger?: string) { + const showTitle: boolean = this.batteryLevelWidgetConfigForm.get('showTitle').value; + const showIcon: boolean = this.batteryLevelWidgetConfigForm.get('showIcon').value; + const showValue: boolean = this.batteryLevelWidgetConfigForm.get('showValue').value; + + if (showTitle) { + this.batteryLevelWidgetConfigForm.get('title').enable(); + this.batteryLevelWidgetConfigForm.get('titleFont').enable(); + this.batteryLevelWidgetConfigForm.get('titleColor').enable(); + this.batteryLevelWidgetConfigForm.get('showIcon').enable({emitEvent: false}); + if (showIcon) { + this.batteryLevelWidgetConfigForm.get('iconSize').enable(); + this.batteryLevelWidgetConfigForm.get('iconSizeUnit').enable(); + this.batteryLevelWidgetConfigForm.get('icon').enable(); + this.batteryLevelWidgetConfigForm.get('iconColor').enable(); + } else { + this.batteryLevelWidgetConfigForm.get('iconSize').disable(); + this.batteryLevelWidgetConfigForm.get('iconSizeUnit').disable(); + this.batteryLevelWidgetConfigForm.get('icon').disable(); + this.batteryLevelWidgetConfigForm.get('iconColor').disable(); + } + } else { + this.batteryLevelWidgetConfigForm.get('title').disable(); + this.batteryLevelWidgetConfigForm.get('titleFont').disable(); + this.batteryLevelWidgetConfigForm.get('titleColor').disable(); + this.batteryLevelWidgetConfigForm.get('showIcon').disable({emitEvent: false}); + this.batteryLevelWidgetConfigForm.get('iconSize').disable(); + this.batteryLevelWidgetConfigForm.get('iconSizeUnit').disable(); + this.batteryLevelWidgetConfigForm.get('icon').disable(); + this.batteryLevelWidgetConfigForm.get('iconColor').disable(); + } + + if (showValue) { + this.batteryLevelWidgetConfigForm.get('autoScaleValueSize').enable(); + this.batteryLevelWidgetConfigForm.get('valueFont').enable(); + this.batteryLevelWidgetConfigForm.get('valueColor').enable(); + } else { + this.batteryLevelWidgetConfigForm.get('autoScaleValueSize').disable(); + this.batteryLevelWidgetConfigForm.get('valueFont').disable(); + this.batteryLevelWidgetConfigForm.get('valueColor').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(55, decimals, units, true); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.html index e99863e1a9..a3d70ed08b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.ts index 8a8974c8bd..1f617958e8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.ts @@ -77,6 +77,8 @@ export class CountWidgetComponent implements OnInit { showChevron = false; chevronStyle: ComponentStyle = {}; + hasCardClickAction = false; + constructor(private widgetComponent: WidgetComponent, private cd: ChangeDetectorRef) { } @@ -116,6 +118,8 @@ export class CountWidgetComponent implements OnInit { this.showChevron = this.settings.showChevron; this.chevronStyle = iconStyle(this.settings.chevronSize, this.settings.chevronSizeUnit); this.chevronStyle.color = this.settings.chevronColor; + + this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0; } public onInit() { @@ -138,11 +142,6 @@ export class CountWidgetComponent implements OnInit { } public cardClick($event: Event) { - const descriptors = this.ctx.actionsApi.getActionDescriptors('cardClick'); - if (descriptors.length) { - $event.stopPropagation(); - const descriptor = descriptors[0]; - this.ctx.actionsApi.handleWidgetAction($event, descriptor); - } + this.ctx.actionsApi.cardClick($event); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.html new file mode 100644 index 0000000000..89ca327b71 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.html @@ -0,0 +1,44 @@ + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
{{ valueText }}
+
+
+
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss new file mode 100644 index 0000000000..5dba13ec07 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss @@ -0,0 +1,158 @@ +/** + * 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. + */ +:host { + .tb-battery-level-panel { + width: 100%; + height: 100%; + position: relative; + display: flex; + flex-direction: column; + gap: 16px; + padding: 20px 24px 24px 24px; + &.tb-battery-level-pointer { + cursor: pointer; + } + > div:not(.tb-battery-level-overlay) { + z-index: 1; + } + .tb-battery-level-overlay { + position: absolute; + top: 12px; + left: 12px; + bottom: 12px; + right: 12px; + } + .tb-battery-level-content { + min-height: 0; + flex: 1; + display: flex; + justify-content: center; + &.vertical { + flex-direction: row; + gap: 16px; + .tb-battery-level-value-box { + align-items: center; + .tb-battery-level-value { + padding: 8px 12px; + } + } + } + &.horizontal { + flex-direction: column-reverse; + gap: 8px; + align-items: center; + .tb-battery-level-value-box { + .tb-battery-level-value { + padding: 4px 6px; + } + } + } + .tb-battery-level-box { + display: flex; + align-items: center; + .tb-battery-level-rectangle { + width: 100%; + height: 100%; + position: relative; + .tb-battery-level-shape { + position: absolute; + inset: 0; + mask-repeat: no-repeat; + mask-size: cover; + mask-position: center; + } + .tb-battery-level-container { + position: absolute; + display: flex; + gap: 3%; + } + .tb-battery-level-indicator-box { + width: 100%; + height: 100%; + &.solid { + background-repeat: no-repeat; + transition: background 0.2s ease-out; + } + &.divided { + transition: opacity 0.2s ease-out; + } + } + &.vertical { + .tb-battery-level-shape { + mask-image: url(/assets/widget/battery-level/battery-shape-vertical.svg); + } + .tb-battery-level-container { + flex-direction: column-reverse; + } + &.solid { + .tb-battery-level-container { + inset: 8.85% 6.25% 3.54% 6.25%; + } + } + &.divided { + .tb-battery-level-container { + inset: 9.73% 7.81% 4.42% 7.81%; + } + } + .tb-battery-level-indicator-box { + &.solid { + border-radius: 10.7% / 6%; + background-position: 0 101%; + } + &.divided { + border-radius: 7.14% / 17.8%; + } + } + } + &.horizontal { + .tb-battery-level-shape { + mask-image: url(/assets/widget/battery-level/battery-shape-horizontal.svg); + } + .tb-battery-level-container { + inset: 6.25% 8.85% 6.25% 3.54%; + flex-direction: row; + } + &.solid { + .tb-battery-level-container { + inset: 6.25% 8.85% 6.25% 3.54%; + } + } + &.divided { + .tb-battery-level-container { + inset: 7.81% 9.73% 7.81% 4.42%; + } + } + .tb-battery-level-indicator-box { + &.solid { + border-radius: 6% / 10.7%; + background-position: -1% 0%; + } + &.divided { + border-radius: 17.8% / 7.14%; + } + } + } + } + } + .tb-battery-level-value-box { + display: flex; + .tb-battery-level-value { + white-space: nowrap; + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts new file mode 100644 index 0000000000..442d207a5e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts @@ -0,0 +1,293 @@ +/// +/// 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 +} 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, + textStyle +} from '@shared/models/widget-settings.models'; +import { WidgetComponent } from '@home/components/widget/widget.component'; +import { + batteryLevelDefaultSettings, + BatteryLevelLayout, + BatteryLevelWidgetSettings +} from '@home/components/widget/lib/indicator/battery-level-widget.models'; +import { ResizeObserver } from '@juggle/resize-observer'; + +const verticalBatteryDimensions = { + shapeAspectRatio: 64 / 113, + widthRatio: { + valueTopBottomPaddingRatio: 8 / 64, + valueLeftRightPaddingRatio: 12 / 64, + valueFontSizeRatio: 20 / 64, + valueLineHeightRaio: 24 / 64 + }, + heightRatio: { + valueTopBottomPaddingRatio: 8 / 113, + valueLeftRightPaddingRatio: 12 / 113, + valueFontSizeRatio: 20 / 113, + valueLineHeightRaio: 24 / 113 + } +}; + +const horizontalBatteryDimensions = { + shapeAspectRatio: 113 / 64, + heightRatio: { + valueTopBottomPaddingRatio: 4 / 64, + valueFontSizeRatio: 20 / 64, + valueLineHeightRatio: 24 / 64 + } +}; + +@Component({ + selector: 'tb-battery-level-widget', + templateUrl: './battery-level-widget.component.html', + styleUrls: ['./battery-level-widget.component.scss'] +}) +export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterViewInit { + + @ViewChild('batteryLevelContent', {static: true}) + batteryLevelContent: ElementRef; + + @ViewChild('batteryLevelBox', {static: true}) + batteryLevelBox: ElementRef; + + @ViewChild('batteryLevelRectangle', {static: true}) + batteryLevelRectangle: ElementRef; + + @ViewChild('batteryLevelValueBox', {static: false}) + batteryLevelValueBox: ElementRef; + + @ViewChild('batteryLevelValue', {static: false}) + batteryLevelValue: ElementRef; + + settings: BatteryLevelWidgetSettings; + + @Input() + ctx: WidgetContext; + + @Input() + widgetTitlePanel: TemplateRef; + + layout: BatteryLevelLayout; + layoutClass = 'vertical'; + + vertical = true; + solid = true; + + showValue = true; + autoScaleValueSize = true; + valueText = 'N/A'; + valueStyle: ComponentStyle = {}; + valueColor: ColorProcessor; + + value: number; + + batterySections: boolean[] = [false, false, false, false]; + + batteryLevelColor: ColorProcessor; + + batteryShapeColor: ColorProcessor; + + backgroundStyle: ComponentStyle = {}; + overlayStyle: ComponentStyle = {}; + + batteryBoxResize$: ResizeObserver; + + hasCardClickAction = false; + + private decimals = 0; + private units = ''; + + constructor(private date: DatePipe, + private widgetComponent: WidgetComponent, + private renderer: Renderer2, + private cd: ChangeDetectorRef) { + } + + ngOnInit(): void { + this.ctx.$scope.batteryLevelWidget = this; + this.settings = {...batteryLevelDefaultSettings, ...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.vertical = [BatteryLevelLayout.vertical_solid, BatteryLevelLayout.vertical_divided].includes(this.layout); + this.layoutClass = this.vertical ? 'vertical' : 'horizontal'; + this.solid = [BatteryLevelLayout.vertical_solid, BatteryLevelLayout.horizontal_solid].includes(this.layout); + + this.showValue = this.settings.showValue; + this.autoScaleValueSize = this.showValue && this.settings.autoScaleValueSize; + this.valueStyle = textStyle(this.settings.valueFont, '0.1px'); + this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor); + + this.batteryLevelColor = ColorProcessor.fromSettings(this.settings.batteryLevelColor); + + this.batteryShapeColor = ColorProcessor.fromSettings(this.settings.batteryShapeColor); + + this.backgroundStyle = backgroundStyle(this.settings.background); + this.overlayStyle = overlayStyle(this.settings.background.overlay); + + this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0; + } + + ngAfterViewInit() { + this.batteryBoxResize$ = new ResizeObserver(() => { + this.onResize(); + }); + this.batteryBoxResize$.observe(this.batteryLevelContent.nativeElement); + if (this.showValue) { + this.batteryBoxResize$.observe(this.batteryLevelValueBox.nativeElement); + } + this.onResize(); + } + + ngOnDestroy() { + if (this.batteryBoxResize$) { + this.batteryBoxResize$.disconnect(); + } + } + + 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'; + } + if (!this.solid) { + const sectionSize = 100 / this.batterySections.length; + for (let i=0; i sectionSize * i; + } + } + this.valueColor.update(this.value); + this.batteryLevelColor.update(this.value); + this.batteryShapeColor.update(this.value); + this.cd.detectChanges(); + } + + public trackBySection(index: number): number { + return index; + } + + public cardClick($event: Event) { + this.ctx.actionsApi.cardClick($event); + } + + private onResize() { + if (this.vertical) { + if (this.batteryLevelValue) { + const contentWidth = this.batteryLevelContent.nativeElement.getBoundingClientRect().width; + const boxWidth = (contentWidth - 16) / 2; + const boxHeight = this.batteryLevelContent.nativeElement.getBoundingClientRect().height; + const ratios = contentWidth > boxHeight ? verticalBatteryDimensions.heightRatio : verticalBatteryDimensions.widthRatio; + const boxSize = contentWidth > boxHeight ? boxHeight : boxWidth; + const topBottomValuePadding = ratios.valueTopBottomPaddingRatio * boxSize; + const leftRightValuePadding = ratios.valueLeftRightPaddingRatio * boxSize; + const valuePadding = `${topBottomValuePadding}px ${leftRightValuePadding}px`; + this.renderer.setStyle(this.batteryLevelValue.nativeElement, 'padding', valuePadding); + if (this.autoScaleValueSize) { + const valueFontSize = ratios.valueFontSizeRatio * boxSize; + const valueLineHeight = ratios.valueLineHeightRaio * boxSize; + this.setValueFontSize(valueFontSize, valueLineHeight, boxWidth); + } + } + let height = this.batteryLevelContent.nativeElement.getBoundingClientRect().height; + const width = height * verticalBatteryDimensions.shapeAspectRatio; + this.renderer.setStyle(this.batteryLevelBox.nativeElement, 'width', width + 'px'); + const realWidth = this.batteryLevelBox.nativeElement.getBoundingClientRect().width; + if (realWidth < width) { + height = realWidth / verticalBatteryDimensions.shapeAspectRatio; + this.renderer.setStyle(this.batteryLevelRectangle.nativeElement, 'height', height + 'px'); + } else { + this.renderer.setStyle(this.batteryLevelRectangle.nativeElement, 'height', null); + } + } else { + const width = this.batteryLevelContent.nativeElement.getBoundingClientRect().width; + let height = width / horizontalBatteryDimensions.shapeAspectRatio; + this.renderer.setStyle(this.batteryLevelBox.nativeElement, 'height', height + 'px'); + const realHeight = this.batteryLevelBox.nativeElement.getBoundingClientRect().height; + if (realHeight < height) { + height = realHeight; + const newWidth = height * horizontalBatteryDimensions.shapeAspectRatio; + this.renderer.setStyle(this.batteryLevelRectangle.nativeElement, 'width', newWidth + 'px'); + } else { + this.renderer.setStyle(this.batteryLevelRectangle.nativeElement, 'width', null); + } + if (this.batteryLevelValue) { + const ratios = horizontalBatteryDimensions.heightRatio; + const valuePadding = `${(ratios.valueTopBottomPaddingRatio * height)}px 6px`; + this.renderer.setStyle(this.batteryLevelValue.nativeElement, 'padding', valuePadding); + if (this.autoScaleValueSize) { + const valueFontSize = ratios.valueFontSizeRatio * height; + const valueLineHeight = ratios.valueLineHeightRatio * height; + const boxWidth = this.batteryLevelContent.nativeElement.getBoundingClientRect().width; + this.setValueFontSize(valueFontSize, valueLineHeight, boxWidth); + } + } + } + } + + private setValueFontSize(valueFontSize: number, valueLineHeight: number, maxWidth: number) { + this.renderer.setStyle(this.batteryLevelValue.nativeElement, 'fontSize', valueFontSize + 'px'); + this.renderer.setStyle(this.batteryLevelValue.nativeElement, 'lineHeight', valueLineHeight + 'px'); + let valueWidth = this.batteryLevelValue.nativeElement.getBoundingClientRect().width; + while (valueWidth > maxWidth && valueFontSize > 6) { + valueFontSize--; + valueLineHeight--; + this.renderer.setStyle(this.batteryLevelValue.nativeElement, 'fontSize', valueFontSize + 'px'); + this.renderer.setStyle(this.batteryLevelValue.nativeElement, 'lineHeight', valueLineHeight + 'px'); + valueWidth = this.batteryLevelValue.nativeElement.getBoundingClientRect().width; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.models.ts new file mode 100644 index 0000000000..8d8724f901 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.models.ts @@ -0,0 +1,108 @@ +/// +/// 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, + constantColor, + defaultColorFunction, + Font +} from '@shared/models/widget-settings.models'; + +export enum BatteryLevelLayout { + vertical_solid = 'vertical_solid', + horizontal_solid = 'horizontal_solid', + vertical_divided = 'vertical_divided', + horizontal_divided = 'horizontal_divided' +} + +export const batteryLevelLayouts = Object.keys(BatteryLevelLayout) as BatteryLevelLayout[]; + +export const batteryLevelLayoutTranslations = new Map( + [ + [BatteryLevelLayout.vertical_solid, 'widgets.battery-level.layout-vertical-solid'], + [BatteryLevelLayout.horizontal_solid, 'widgets.battery-level.layout-horizontal-solid'], + [BatteryLevelLayout.vertical_divided, 'widgets.battery-level.layout-vertical-divided'], + [BatteryLevelLayout.horizontal_divided, 'widgets.battery-level.layout-horizontal-divided'] + ] +); + +export const batteryLevelLayoutImages = new Map( + [ + [BatteryLevelLayout.vertical_solid, 'assets/widget/battery-level/vertical-solid-layout.svg'], + [BatteryLevelLayout.horizontal_solid, 'assets/widget/battery-level/horizontal-solid-layout.svg'], + [BatteryLevelLayout.vertical_divided, 'assets/widget/battery-level/vertical-divided-layout.svg'], + [BatteryLevelLayout.horizontal_divided, 'assets/widget/battery-level/horizontal-divided-layout.svg'] + ] +); + +export interface BatteryLevelWidgetSettings { + layout: BatteryLevelLayout; + showValue: boolean; + autoScaleValueSize: boolean; + valueFont: Font; + valueColor: ColorSettings; + batteryLevelColor: ColorSettings; + batteryShapeColor: ColorSettings; + background: BackgroundSettings; +} + +export const batteryLevelDefaultSettings: BatteryLevelWidgetSettings = { + layout: BatteryLevelLayout.vertical_solid, + showValue: true, + autoScaleValueSize: true, + valueFont: { + family: 'Roboto', + size: 20, + sizeUnit: 'px', + style: 'normal', + weight: '500', + lineHeight: '24px' + }, + valueColor: constantColor('rgba(0, 0, 0, 0.87)'), + batteryLevelColor: { + color: 'rgba(92, 223, 144, 1)', + type: ColorType.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: defaultColorFunction + }, + batteryShapeColor: { + color: 'rgba(92, 223, 144, 0.32)', + type: ColorType.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: defaultColorFunction + }, + background: { + type: BackgroundType.color, + color: '#fff', + overlay: { + enabled: false, + color: 'rgba(255,255,255,0.72)', + blur: 3 + } + } +}; + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html index 9fe3273f74..ac103fe395 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html @@ -19,12 +19,13 @@
widgets.widget-font.font-settings
widgets.widget-font.size
-
+
+
widgets.widget-font.auto
widgets.widget-font.font-family
@@ -74,9 +75,10 @@
widgets.widget-font.line-height
- + +
widgets.widget-font.auto
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.scss index 3475a14e8c..03b0ee511b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.scss @@ -25,6 +25,10 @@ letter-spacing: 0.25px; color: rgba(0, 0, 0, 0.87); } + .tb-font-settings-auto { + padding: 8px 12px; + color: rgba(0, 0, 0, 0.38); + } .tb-form-row { .fixed-title-width { min-width: 120px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts index a0e77ef380..c99efa8931 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts @@ -65,6 +65,10 @@ export class FontSettingsPanelComponent extends PageComponent implements OnInit @coerceBoolean() clearButton = false; + @Input() + @coerceBoolean() + autoScale = false; + @Input() popover: TbPopoverComponent; @@ -97,12 +101,12 @@ export class FontSettingsPanelComponent extends PageComponent implements OnInit ngOnInit(): void { this.fontFormGroup = this.fb.group( { - size: [this.font?.size, [Validators.min(0)]], - sizeUnit: [(this.font?.sizeUnit || 'px'), []], + size: [{value: this.font?.size, disabled: this.autoScale}, [Validators.min(0)]], + sizeUnit: [{ value: (this.font?.sizeUnit || 'px'), disabled: this.autoScale}, []], family: [this.font?.family, []], weight: [this.font?.weight, []], style: [this.font?.style, []], - lineHeight: [this.font?.lineHeight, []] + lineHeight: [{ value: this.font?.lineHeight, disabled: this.autoScale }, []] } ); this.updatePreviewStyle(this.font); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts index f23862cf34..2c3780f1fe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts @@ -50,6 +50,10 @@ export class FontSettingsComponent implements OnInit, ControlValueAccessor { @coerceBoolean() clearButton = false; + @Input() + @coerceBoolean() + autoScale = false; + private modelValue: Font; private propagateChange = null; @@ -87,7 +91,8 @@ export class FontSettingsComponent implements OnInit, ControlValueAccessor { const ctx: any = { font: this.modelValue, initialPreviewStyle: this.initialPreviewStyle, - clearButton: this.clearButton + clearButton: this.clearButton, + autoScale: this.autoScale }; if (isDefinedAndNotNull(this.previewText)) { const previewText = typeof this.previewText === 'string' ? this.previewText : this.previewText(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html new file mode 100644 index 0000000000..1221e5bfc2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html @@ -0,0 +1,63 @@ + + +
+
widgets.battery-level.battery-level-card-style
+ + + {{ batteryLevelLayoutTranslationMap.get(layout) | translate }} + + +
+ + {{ 'widgets.battery-level.value' | translate }} + +
+ + {{ 'widgets.battery-level.auto-scale' | translate }} + + + + + +
+
+
+
{{ 'widgets.battery-level.battery-level-color' | translate }}
+ + +
+
+
{{ 'widgets.battery-level.battery-shape-color' | translate }}
+ + +
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.ts new file mode 100644 index 0000000000..627bf00cd2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.ts @@ -0,0 +1,104 @@ +/// +/// 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 { + batteryLevelDefaultSettings, + batteryLevelLayoutImages, + batteryLevelLayouts, + batteryLevelLayoutTranslations +} from '@home/components/widget/lib/indicator/battery-level-widget.models'; + +@Component({ + selector: 'tb-battery-level-widget-settings', + templateUrl: './battery-level-widget-settings.component.html', + styleUrls: [] +}) +export class BatteryLevelWidgetSettingsComponent extends WidgetSettingsComponent { + + batteryLevelLayouts = batteryLevelLayouts; + + batteryLevelLayoutTranslationMap = batteryLevelLayoutTranslations; + batteryLevelLayoutImageMap = batteryLevelLayoutImages; + + batteryLevelWidgetSettingsForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + constructor(protected store: Store, + private $injector: Injector, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.batteryLevelWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return {...batteryLevelDefaultSettings}; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.batteryLevelWidgetSettingsForm = this.fb.group({ + layout: [settings.layout, []], + + showValue: [settings.showValue, []], + autoScaleValueSize: [settings.autoScaleValueSize, []], + valueFont: [settings.valueFont, []], + valueColor: [settings.valueColor, []], + + batteryLevelColor: [settings.batteryLevelColor, []], + batteryShapeColor: [settings.batteryShapeColor, []], + + background: [settings.background, []] + }); + } + + protected validatorTriggers(): string[] { + return ['showValue']; + } + + protected updateValidators(emitEvent: boolean) { + const showValue: boolean = this.batteryLevelWidgetSettingsForm.get('showValue').value; + + if (showValue) { + this.batteryLevelWidgetSettingsForm.get('autoScaleValueSize').enable(); + this.batteryLevelWidgetSettingsForm.get('valueFont').enable(); + this.batteryLevelWidgetSettingsForm.get('valueColor').enable(); + } else { + this.batteryLevelWidgetSettingsForm.get('autoScaleValueSize').disable(); + this.batteryLevelWidgetSettingsForm.get('valueFont').disable(); + this.batteryLevelWidgetSettingsForm.get('valueColor').disable(); + } + + this.batteryLevelWidgetSettingsForm.get('autoScaleValueSize').updateValueAndValidity({emitEvent}); + this.batteryLevelWidgetSettingsForm.get('valueFont').updateValueAndValidity({emitEvent}); + this.batteryLevelWidgetSettingsForm.get('valueColor').updateValueAndValidity({emitEvent}); + } + + private _valuePreviewFn(): string { + const units: string = this.widgetConfig.config.units; + const decimals: number = this.widgetConfig.config.decimals; + return formatValue(22, decimals, units, true); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index 622f5a0c2a..6593b82747 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -285,6 +285,9 @@ import { import { EntityCountWidgetSettingsComponent } from '@home/components/widget/lib/settings/entity/entity-count-widget-settings.component'; +import { + BatteryLevelWidgetSettingsComponent +} from '@home/components/widget/lib/settings/indicator/battery-level-widget-settings.component'; @NgModule({ declarations: [ @@ -390,7 +393,8 @@ import { AggregatedValueCardKeySettingsComponent, AggregatedValueCardWidgetSettingsComponent, AlarmCountWidgetSettingsComponent, - EntityCountWidgetSettingsComponent + EntityCountWidgetSettingsComponent, + BatteryLevelWidgetSettingsComponent ], imports: [ CommonModule, @@ -501,7 +505,8 @@ import { AggregatedValueCardKeySettingsComponent, AggregatedValueCardWidgetSettingsComponent, AlarmCountWidgetSettingsComponent, - EntityCountWidgetSettingsComponent + EntityCountWidgetSettingsComponent, + BatteryLevelWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -577,5 +582,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type, actionDescriptor: WidgetActionDescriptor): Observable { const resourceTasks: Observable[] = []; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 76c30d46ae..88eed92793 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5036,6 +5036,19 @@ "blur": "Blur", "preview": "Preview" }, + "battery-level": { + "layout": "Layout", + "layout-vertical-solid": "Vertical. Solid", + "layout-horizontal-solid": "Horizontal. Solid", + "layout-vertical-divided": "Vertical. Divided", + "layout-horizontal-divided": "Horizontal. Divided", + "icon": "Icon", + "value": "Value", + "auto-scale": "Auto scale", + "battery-level-color": "Battery level color", + "battery-shape-color": "Battery shape color", + "battery-level-card-style": "Battery level card style" + }, "chart": { "common-settings": "Common settings", "enable-stacking-mode": "Enable stacking mode", @@ -6146,7 +6159,8 @@ "color": "Color", "shadow-color": "Shadow color", "preview": "Preview", - "line-height": "Line height" + "line-height": "Line height", + "auto": "Auto" }, "home": { "no-data-available": "No data available" diff --git a/ui-ngx/src/assets/widget/battery-level/battery-shape-horizontal.svg b/ui-ngx/src/assets/widget/battery-level/battery-shape-horizontal.svg new file mode 100644 index 0000000000..49c319b51e --- /dev/null +++ b/ui-ngx/src/assets/widget/battery-level/battery-shape-horizontal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui-ngx/src/assets/widget/battery-level/battery-shape-vertical.svg b/ui-ngx/src/assets/widget/battery-level/battery-shape-vertical.svg new file mode 100644 index 0000000000..4e77b5d293 --- /dev/null +++ b/ui-ngx/src/assets/widget/battery-level/battery-shape-vertical.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui-ngx/src/assets/widget/battery-level/horizontal-divided-layout.svg b/ui-ngx/src/assets/widget/battery-level/horizontal-divided-layout.svg new file mode 100644 index 0000000000..90ded667ed --- /dev/null +++ b/ui-ngx/src/assets/widget/battery-level/horizontal-divided-layout.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/battery-level/horizontal-solid-layout.svg b/ui-ngx/src/assets/widget/battery-level/horizontal-solid-layout.svg new file mode 100644 index 0000000000..8788f29c63 --- /dev/null +++ b/ui-ngx/src/assets/widget/battery-level/horizontal-solid-layout.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/battery-level/vertical-divided-layout.svg b/ui-ngx/src/assets/widget/battery-level/vertical-divided-layout.svg new file mode 100644 index 0000000000..0205ae922a --- /dev/null +++ b/ui-ngx/src/assets/widget/battery-level/vertical-divided-layout.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/battery-level/vertical-solid-layout.svg b/ui-ngx/src/assets/widget/battery-level/vertical-solid-layout.svg new file mode 100644 index 0000000000..7965ba085e --- /dev/null +++ b/ui-ngx/src/assets/widget/battery-level/vertical-solid-layout.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +