From e050cf275642d707f9c0d744d75242432f2197b5 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 12 Apr 2024 13:54:55 +0300 Subject: [PATCH 1/5] UI: Simple gauge settings with advanced color settings --- .../system/widget_types/simple_gauge.json | 4 +- .../basic/basic-widget-config.module.ts | 12 +- ...gated-value-card-basic-config.component.ts | 12 +- ...l-simple-gauge-basic-config.component.html | 173 +++++ ...tal-simple-gauge-basic-config.component.ts | 266 ++++++++ .../battery-level-basic-config.component.html | 22 +- .../widget/lib/count/count-widget.models.ts | 10 +- .../widget/lib/digital-gauge.models.ts | 41 +- .../components/widget/lib/digital-gauge.ts | 27 +- .../battery-level-widget.component.ts | 9 +- .../indicator/battery-level-widget.models.ts | 24 +- .../signal-strength-widget.models.ts | 14 +- .../chart/flot-key-settings.component.html | 1 - .../chart/flot-threshold.component.html | 48 +- .../chart/flot-threshold.component.scss | 78 --- .../chart/flot-threshold.component.ts | 28 +- .../common/advanced-range.component.html | 56 ++ .../advanced-range.component.scss} | 40 +- .../common/advanced-range.component.ts | 101 +++ .../common/color-range-list.component.html | 109 +++- .../color-range-list.component.scss} | 36 +- .../common/color-range-list.component.ts | 110 +++- .../common/color-range-panel.component.html | 2 +- .../color-settings-panel.component.html | 17 +- .../color-settings-panel.component.scss | 21 +- .../common/color-settings-panel.component.ts | 23 +- .../common/color-settings.component.ts | 67 +- .../settings/common/gradient.component.html | 208 ++++++ .../settings/common/gradient.component.scss | 145 +++++ .../lib/settings/common/gradient.component.ts | 255 ++++++++ .../common/value-source.component.html | 114 ++-- .../settings/common/value-source.component.ts | 11 +- .../common/widget-settings-common.module.ts | 13 +- ...gital-gauge-widget-settings.component.html | 594 ++++++++---------- ...digital-gauge-widget-settings.component.ts | 239 ++++--- .../gauge/fixed-color-level.component.html | 60 -- .../gauge/fixed-color-level.component.ts | 147 ----- .../settings/gauge/tick-value.component.html | 39 +- .../settings/gauge/tick-value.component.ts | 26 +- ...ttery-level-widget-settings.component.html | 22 +- .../lib/settings/widget-settings.module.ts | 3 - .../components/color-input.component.html | 1 + .../components/color-input.component.scss | 4 + .../components/color-input.component.ts | 4 + .../shared/models/widget-settings.models.ts | 365 ++++++++++- .../assets/locale/locale.constant-en_US.json | 45 +- .../assets/widget/simple-gauge/arc-layout.svg | 31 + .../widget/simple-gauge/donut-layout.svg | 29 + .../simple-gauge/horizontal-bar-layout.svg | 31 + .../simple-gauge/vertical-bar-layout.svg | 31 + 50 files changed, 2672 insertions(+), 1096 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.ts delete mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html rename ui-ngx/src/app/modules/home/components/widget/lib/settings/{gauge/fixed-color-level.component.scss => common/advanced-range.component.scss} (57%) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.ts rename ui-ngx/src/app/modules/home/components/widget/lib/settings/{gauge/tick-value.component.scss => common/color-range-list.component.scss} (61%) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts delete mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.html delete mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.ts create mode 100644 ui-ngx/src/assets/widget/simple-gauge/arc-layout.svg create mode 100644 ui-ngx/src/assets/widget/simple-gauge/donut-layout.svg create mode 100644 ui-ngx/src/assets/widget/simple-gauge/horizontal-bar-layout.svg create mode 100644 ui-ngx/src/assets/widget/simple-gauge/vertical-bar-layout.svg diff --git a/application/src/main/data/json/system/widget_types/simple_gauge.json b/application/src/main/data/json/system/widget_types/simple_gauge.json index 1683efee14..c9e451039c 100644 --- a/application/src/main/data/json/system/widget_types/simple_gauge.json +++ b/application/src/main/data/json/system/widget_types/simple_gauge.json @@ -15,7 +15,9 @@ "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "settingsDirective": "tb-digital-gauge-widget-settings", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#ef6c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"gaugeColor\":\"#eeeeee\",\"gaugeType\":\"donut\"},\"title\":\"Simple gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" + "hasBasicMode": true, + "basicModeDirective": "tb-digital-simple-gauge-basic-config", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#ef6c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"gaugeColor\":\"#eeeeee\",\"gaugeType\":\"donut\"},\"title\":\"Simple gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}" }, "externalId": null } \ No newline at end of file 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 2ff5142b9c..8952ddef28 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 @@ -110,6 +110,9 @@ import { import { TimeSeriesChartBasicConfigComponent } from '@home/components/widget/config/basic/chart/time-series-chart-basic-config.component'; +import { + DigitalSimpleGaugeBasicConfigComponent +} from '@home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component'; @NgModule({ declarations: [ @@ -145,7 +148,8 @@ import { PowerButtonBasicConfigComponent, SliderBasicConfigComponent, ToggleButtonBasicConfigComponent, - TimeSeriesChartBasicConfigComponent + TimeSeriesChartBasicConfigComponent, + DigitalSimpleGaugeBasicConfigComponent ], imports: [ CommonModule, @@ -185,7 +189,8 @@ import { PowerButtonBasicConfigComponent, SliderBasicConfigComponent, ToggleButtonBasicConfigComponent, - TimeSeriesChartBasicConfigComponent + TimeSeriesChartBasicConfigComponent, + DigitalSimpleGaugeBasicConfigComponent ] }) export class BasicWidgetConfigModule { @@ -219,5 +224,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type + + + + + +
+
widgets.gauge.gauge-appearance
+ + + {{ digitalGaugeLayoutTranslationMap.get(layout) | translate }} + + + +
+
{{ 'widgets.gauge.donut-start-angle' | translate }}
+ + + +
+ + +
+ + {{ 'widgets.gauge.min-and-max-value' | translate }} + +
+
widgets.gauge.min-value-short
+ + + +
widgets.gauge.max-value-short
+ + + + + + + +
+
+
+ +
+ + {{ 'widgets.gauge.value' | translate }} + +
+ + + + +
widget-config.decimals-suffix
+
+ + + + +
+
+ +
+ + {{ 'widgets.gauge.label' | translate }} + +
+ + + + + + + +
+
+ +
+
widgets.gauge.default-color
+ + +
+ +
+
widgets.gauge.gauge-bar-background
+ + +
+ +
+
{{ 'widgets.gauge.bar-color' | translate }}
+ + +
+ +
+
+
widget-config.card-appearance
+
+
{{ 'widget-config.background-color' | 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/gauge/digital-simple-gauge-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.ts new file mode 100644 index 0000000000..6db5660765 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.ts @@ -0,0 +1,266 @@ +/// +/// Copyright © 2016-2024 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 { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { + datasourcesHasAggregation, + datasourcesHasOnlyComparisonAggregation, + WidgetConfig, +} from '@shared/models/widget.models'; +import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { + getTimewindowConfig, + setTimewindowConfig +} from '@home/components/widget/config/timewindow-config-panel.component'; +import { deepClone, formatValue, isUndefined } from '@core/utils'; +import { Component } from '@angular/core'; +import { + colorLevel, + defaultDigitalSimpleGaugeOptions, + digitalGaugeLayoutImages, + digitalGaugeLayouts, + digitalGaugeLayoutTranslations, + DigitalGaugeSettings, + DigitalGaugeType, FixedLevelColors +} from '@home/components/widget/lib/digital-gauge.models'; +import { AdvancedColorRange, ColorSettings, ColorType, constantColor } from '@shared/models/widget-settings.models'; + +@Component({ + selector: 'tb-digital-simple-gauge-basic-config', + templateUrl: './digital-simple-gauge-basic-config.component.html', + styleUrls: ['../basic-config.scss'] +}) + +export class DigitalSimpleGaugeBasicConfigComponent extends BasicWidgetConfigComponent { + + public get displayTimewindowConfig(): boolean { + const datasources = this.simpleGaugeWidgetConfigForm.get('datasources').value; + return datasourcesHasAggregation(datasources); + } + + public onlyHistoryTimewindow(): boolean { + const datasources = this.simpleGaugeWidgetConfigForm.get('datasources').value; + return datasourcesHasOnlyComparisonAggregation(datasources); + } + + digitalGaugeType = DigitalGaugeType; + digitalGaugeLayouts = digitalGaugeLayouts; + + digitalGaugeLayoutTranslationMap = digitalGaugeLayoutTranslations; + digitalGaugeLayoutImageMap = digitalGaugeLayoutImages; + + simpleGaugeWidgetConfigForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this, true); + previewFn = this._valuePreviewFn.bind(this, false); + + constructor(protected store: Store, + protected widgetConfigComponent: WidgetConfigComponent, + protected fb: UntypedFormBuilder) { + super(store, widgetConfigComponent); + } + + protected configForm(): UntypedFormGroup { + return this.simpleGaugeWidgetConfigForm; + } + + protected setupDefaults(configData: WidgetConfigComponentData) { + super.setupDefaults(configData); + } + + protected onConfigSet(configData: WidgetConfigComponentData) { + const settings: DigitalGaugeSettings = {...defaultDigitalSimpleGaugeOptions, ...(configData.config.settings || {})}; + + if (!settings.barColor) { + settings.barColor = constantColor(null); + if (settings.fixedLevelColors?.length) { + settings.barColor.rangeList = { + advancedMode: settings.useFixedLevelColor, + range: null, + rangeAdvanced: deepClone(settings.fixedLevelColors) as AdvancedColorRange[] + }; + } + if (settings.levelColors?.length) { + settings.barColor.gradient = { + advancedMode: false, + gradient: settings.levelColors as string[], + gradientAdvanced: null + }; + } + if (settings.useFixedLevelColor) { + settings.barColor.type = ColorType.range; + } else if (settings.levelColors.length) { + settings.barColor.type = ColorType.gradient; + } + } + + this.simpleGaugeWidgetConfigForm = this.fb.group({ + timewindowConfig: [getTimewindowConfig(configData.config), []], + datasources: [configData.config.datasources, []], + + gaugeType: [settings.gaugeType, []], + donutStartAngle: [settings.donutStartAngle, []], + + showMinMax: [settings.showMinMax, []], + minValue: [settings.minValue, []], + maxValue: [settings.maxValue, []], + minMaxFont: [settings.minMaxFont, []], + minMaxColor: [settings.minMaxFont?.color, []], + + showValue: [settings.showValue, []], + decimals: [configData.config.decimals, []], + units: [configData.config.units, []], + valueFont: [settings.valueFont, []], + valueColor: [settings.valueFont?.color, []], + + showTitle: [settings.showTitle, []], + title: [settings.title, []], + titleFont: [settings.titleFont, []], + titleColor: [settings.titleFont?.color, []], + + defaultColor: [settings.defaultColor, []], + gaugeColor: [settings.gaugeColor, []], + barColor: [settings.barColor, []], + + cardButtons: [this.getCardButtons(configData.config), []], + borderRadius: [configData.config.borderRadius, []], + backgroundColor: [configData.config.backgroundColor, []], + actions: [configData.config.actions || {}, []] + }); + } + + protected prepareOutputConfig(config: any): WidgetConfigComponentData { + setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig); + this.widgetConfig.config.datasources = config.datasources; + this.widgetConfig.config.actions = config.actions; + + this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; + this.setCardButtons(config.cardButtons, this.widgetConfig.config); + this.widgetConfig.config.borderRadius = config.borderRadius; + this.widgetConfig.config.backgroundColor = config.backgroundColor; + + this.widgetConfig.config.settings.gaugeType = config.gaugeType; + this.widgetConfig.config.settings.donutStartAngle = config.donutStartAngle; + + this.widgetConfig.config.settings.showMinMax = config.showMinMax; + this.widgetConfig.config.settings.minValue = config.minValue; + this.widgetConfig.config.settings.maxValue = config.maxValue; + this.widgetConfig.config.settings.minMaxFont = config.minMaxFont; + this.widgetConfig.config.settings.minMaxFont.color = config.minMaxColor; + + this.widgetConfig.config.settings.showValue = config.showValue; + this.widgetConfig.config.units = config.units; + this.widgetConfig.config.decimals = config.decimals; + this.widgetConfig.config.settings.valueFont = config.valueFont; + this.widgetConfig.config.settings.valueFont.color = config.valueColor; + + this.widgetConfig.config.settings.showTitle = config.showTitle; + this.widgetConfig.config.settings.title = config.title; + this.widgetConfig.config.settings.titleFont = config.titleFont; + this.widgetConfig.config.settings.titleFont.color = config.titleColor; + + this.widgetConfig.config.settings.defaultColor = config.defaultColor; + this.widgetConfig.config.settings.gaugeColor = config.gaugeColor; + this.widgetConfig.config.settings.barColor = config.barColor; + const barColor: ColorSettings = config.barColor; + + if (barColor.type === ColorType.range) { + this.widgetConfig.config.settings.useFixedLevelColor = true; + this.widgetConfig.config.settings.fixedLevelColors = + barColor.rangeList.advancedMode ? barColor.rangeList.rangeAdvanced : barColor.rangeList.range; + } else { + this.widgetConfig.config.settings.useFixedLevelColor = false; + } + if (barColor.gradient?.gradient?.length) { + this.widgetConfig.config.settings.levelColors = barColor.gradient.gradient; + } + + return this.widgetConfig; + } + + protected validatorTriggers(): string[] { + return ['gaugeType', 'showValue', 'showTitle', 'showMinMax']; + } + + protected updateValidators(emitEvent: boolean, trigger?: string) { + const isDonut = this.simpleGaugeWidgetConfigForm.get('gaugeType').value === this.digitalGaugeType.donut; + + if (isDonut) { + this.simpleGaugeWidgetConfigForm.get('donutStartAngle').enable(); + this.simpleGaugeWidgetConfigForm.get('showMinMax').disable({emitEvent: false}); + this.simpleGaugeWidgetConfigForm.get('minValue').disable(); + this.simpleGaugeWidgetConfigForm.get('maxValue').disable(); + this.simpleGaugeWidgetConfigForm.get('minMaxFont').disable(); + this.simpleGaugeWidgetConfigForm.get('minMaxColor').disable(); + } else { + this.simpleGaugeWidgetConfigForm.get('donutStartAngle').disable(); + this.simpleGaugeWidgetConfigForm.get('showMinMax').enable({emitEvent: false}); + if (this.simpleGaugeWidgetConfigForm.get('showMinMax').value) { + this.simpleGaugeWidgetConfigForm.get('minValue').enable(); + this.simpleGaugeWidgetConfigForm.get('maxValue').enable(); + this.simpleGaugeWidgetConfigForm.get('minMaxFont').enable(); + this.simpleGaugeWidgetConfigForm.get('minMaxColor').enable(); + } else { + this.simpleGaugeWidgetConfigForm.get('minValue').disable(); + this.simpleGaugeWidgetConfigForm.get('maxValue').disable(); + this.simpleGaugeWidgetConfigForm.get('minMaxFont').disable(); + this.simpleGaugeWidgetConfigForm.get('minMaxColor').disable(); + } + } + + if (this.simpleGaugeWidgetConfigForm.get('showValue').value) { + this.simpleGaugeWidgetConfigForm.get('decimals').enable(); + this.simpleGaugeWidgetConfigForm.get('units').enable(); + this.simpleGaugeWidgetConfigForm.get('valueFont').enable(); + this.simpleGaugeWidgetConfigForm.get('valueColor').enable(); + } else { + this.simpleGaugeWidgetConfigForm.get('decimals').disable(); + this.simpleGaugeWidgetConfigForm.get('units').disable(); + this.simpleGaugeWidgetConfigForm.get('valueFont').disable(); + this.simpleGaugeWidgetConfigForm.get('valueColor').disable(); + } + + if (this.simpleGaugeWidgetConfigForm.get('showTitle').value) { + this.simpleGaugeWidgetConfigForm.get('title').enable(); + this.simpleGaugeWidgetConfigForm.get('titleFont').enable(); + this.simpleGaugeWidgetConfigForm.get('titleColor').enable(); + } else { + this.simpleGaugeWidgetConfigForm.get('title').disable(); + this.simpleGaugeWidgetConfigForm.get('titleFont').disable(); + this.simpleGaugeWidgetConfigForm.get('titleColor').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(units: boolean): string { + return formatValue(22, 0, units ? this.simpleGaugeWidgetConfigForm.get('units').value : null, true); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html index 3cbc1d4a6c..71cf9faaf8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html @@ -99,18 +99,34 @@ [autoScale]="batteryLevelWidgetConfigForm.get('autoScaleValueSize').value" [previewText]="valuePreviewFn"> - +
{{ 'widgets.battery-level.battery-level-color' | translate }}
- +
{{ 'widgets.battery-level.battery-shape-color' | translate }}
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts index 79976f45cf..ceea08384a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts @@ -101,10 +101,12 @@ export const countDefaultSettings = (alarmElseEntity: boolean): CountWidgetSetti ? { color: 'rgba(0, 105, 92, 1)', type: ColorType.range, - rangeList: [ - {from: 0, to: 0, color: 'rgba(0, 105, 92, 1)'}, - {from: 1, color: 'rgba(209, 39, 48, 1)'} - ], + rangeList: { + range: [ + {from: 0, to: 0, color: 'rgba(0, 105, 92, 1)'}, + {from: 1, color: 'rgba(209, 39, 48, 1)'} + ] + }, colorFunction: defaultColorFunction } : constantColor('rgba(241, 141, 23, 1)'), diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts index 26a3373cfa..cdd17e3739 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts @@ -14,10 +14,10 @@ /// limitations under the License. /// -import { JsonSettingsSchema } from '@shared/models/widget.models'; import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge'; import { AnimationRule } from '@home/components/widget/lib/analogue-gauge.models'; import { FontSettings } from '@home/components/widget/lib/settings.models'; +import { ColorSettings } from '@shared/models/widget-settings.models'; export interface AttributeSourceProperty { valueSource: string; @@ -27,8 +27,8 @@ export interface AttributeSourceProperty { } export interface FixedLevelColors { - from?: AttributeSourceProperty; - to?: AttributeSourceProperty; + from?: AttributeSourceProperty | number; + to?: AttributeSourceProperty | number; color: string; } @@ -41,6 +41,33 @@ export type colorLevel = Array; export type attributesGaugeType = 'levelColors' | 'ticks'; +export enum DigitalGaugeType { + arc = 'arc', + donut = 'donut', + horizontalBar = 'horizontalBar', + verticalBar = 'verticalBar' +} + +export const digitalGaugeLayouts = Object.keys(DigitalGaugeType) as DigitalGaugeType[]; + +export const digitalGaugeLayoutTranslations = new Map( + [ + [DigitalGaugeType.arc, 'widgets.gauge.gauge-type-arc'], + [DigitalGaugeType.donut, 'widgets.gauge.gauge-type-donut'], + [DigitalGaugeType.horizontalBar, 'widgets.gauge.gauge-type-horizontal-bar'], + [DigitalGaugeType.verticalBar, 'widgets.gauge.gauge-type-vertical-bar'] + ] +); + +export const digitalGaugeLayoutImages = new Map( + [ + [DigitalGaugeType.arc, 'assets/widget/simple-gauge/arc-layout.svg'], + [DigitalGaugeType.donut, 'assets/widget/simple-gauge/donut-layout.svg'], + [DigitalGaugeType.horizontalBar, 'assets/widget/simple-gauge/horizontal-bar-layout.svg'], + [DigitalGaugeType.verticalBar, 'assets/widget/simple-gauge/vertical-bar-layout.svg'] + ] +); + export interface DigitalGaugeSettings { minValue?: number; maxValue?: number; @@ -60,6 +87,9 @@ export interface DigitalGaugeSettings { gaugeWidthScale?: number; defaultColor?: string; gaugeColor?: string; + + barColor?: ColorSettings; + useFixedLevelColor?: boolean; levelColors?: colorLevel; fixedLevelColors?: FixedLevelColors[]; @@ -80,3 +110,8 @@ export interface DigitalGaugeSettings { colorTicks?: string; tickWidth?: number; } + +export const defaultDigitalSimpleGaugeOptions: DigitalGaugeSettings = { + gaugeType: DigitalGaugeType.donut, + timestampFormat: 'yyyy-MM-dd HH:mm:ss', +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts index fce1b27697..ddd195a92d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts @@ -24,21 +24,15 @@ import { FixedLevelColors } from '@home/components/widget/lib/digital-gauge.models'; import tinycolor from 'tinycolor2'; -import { isDefined, isDefinedAndNotNull } from '@core/utils'; +import { isDefined, isDefinedAndNotNull, parseFunction, safeExecute } from '@core/utils'; import { prepareFontSettings } from '@home/components/widget/lib/settings.models'; import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge'; import { DatePipe } from '@angular/common'; -import { - DataKey, - Datasource, - DatasourceData, - DatasourceType, - JsonSettingsSchema, - widgetType -} from '@shared/models/widget.models'; +import { DataKey, Datasource, DatasourceData, DatasourceType, widgetType } from '@shared/models/widget.models'; import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { EMPTY, Observable } from 'rxjs'; +import { ColorType } from '@shared/models/widget-settings.models'; import GenericOptions = CanvasGauges.GenericOptions; // @dynamic @@ -70,6 +64,7 @@ export class TbCanvasDigitalGauge { this.localSettings.gaugeWidthScale = settings.gaugeWidthScale || 0.75; this.localSettings.gaugeColor = settings.gaugeColor || tinycolor(keyColor).setAlpha(0.2).toRgbString(); + this.localSettings.barColor = settings.barColor; this.localSettings.useFixedLevelColor = settings.useFixedLevelColor || false; if (!settings.useFixedLevelColor) { @@ -282,7 +277,7 @@ export class TbCanvasDigitalGauge { color: this.localSettings.gaugeColor }); - function setLevelColor(levelSetting: AttributeSourceProperty, color: string) { + function setLevelColor(levelSetting, color: string) { if (levelSetting.valueSource === 'predefinedValue' && isFinite(levelSetting.value)) { predefineLevelColors.push({ value: levelSetting.value, @@ -296,6 +291,11 @@ export class TbCanvasDigitalGauge { return; } predefineLevelColors.push(null); + } else if (isFinite(levelSetting)) { + predefineLevelColors.push({ + value: levelSetting, + color + }); } } @@ -409,6 +409,13 @@ export class TbCanvasDigitalGauge { this.gauge._value = value; } this.gauge.value = value; + if (this.localSettings.barColor?.type === ColorType.function && isDefined(this.localSettings.barColor?.colorFunction)) { + this.localSettings.levelColors = [safeExecute(parseFunction(this.localSettings.barColor.colorFunction, ['value']), [value])]; + this.updateSetting(); + } else if (this.localSettings.barColor?.type === ColorType.constant && isDefinedAndNotNull(this.localSettings.barColor?.color)) { + this.localSettings.levelColors = [this.localSettings.barColor.color]; + this.updateSetting(); + } } else if (this.localSettings.showTimestamp && this.gauge.timestamp !== timestamp) { this.gauge.timestamp = timestamp; } 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 index 0cc6d266b0..9f0d69093a 100644 --- 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 @@ -191,11 +191,11 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView this.showValue = this.settings.showValue; this.autoScaleValueSize = this.showValue && this.settings.autoScaleValueSize; this.valueStyle = textStyle(this.settings.valueFont); - this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor); + this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor,this.ctx, this.cd); - this.batteryLevelColor = ColorProcessor.fromSettings(this.settings.batteryLevelColor); + this.batteryLevelColor = ColorProcessor.fromSettings(this.settings.batteryLevelColor, this.ctx, this.cd); - this.batteryShapeColor = ColorProcessor.fromSettings(this.settings.batteryShapeColor); + this.batteryShapeColor = ColorProcessor.fromSettings(this.settings.batteryShapeColor, this.ctx, this.cd); this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.settings.background.overlay); @@ -218,6 +218,9 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView if (this.batteryBoxResize$) { this.batteryBoxResize$.disconnect(); } + this.batteryLevelColor.destroy(); + this.valueColor.destroy(); + this.batteryShapeColor.destroy(); } public onInit() { 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 index 081aae6558..281b8fe40f 100644 --- 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 @@ -80,21 +80,25 @@ export const batteryLevelDefaultSettings: BatteryLevelWidgetSettings = { batteryLevelColor: { color: 'rgba(224, 224, 224, 1)', type: ColorType.range, - rangeList: [ - {from: null, to: 25, color: 'rgba(227, 71, 71, 1)'}, - {from: 25, to: 50, color: 'rgba(246, 206, 67, 1)'}, - {from: 50, to: null, color: 'rgba(92, 223, 144, 1)'} - ], + rangeList: { + range: [ + {from: null, to: 25, color: 'rgba(227, 71, 71, 1)'}, + {from: 25, to: 50, color: 'rgba(246, 206, 67, 1)'}, + {from: 50, to: null, color: 'rgba(92, 223, 144, 1)'} + ] + }, colorFunction: defaultColorFunction }, batteryShapeColor: { color: 'rgba(224, 224, 224, 0.32)', type: ColorType.range, - rangeList: [ - {from: null, to: 25, color: 'rgba(227, 71, 71, 0.32)'}, - {from: 25, to: 50, color: 'rgba(246, 206, 67, 0.32)'}, - {from: 50, to: null, color: 'rgba(92, 223, 144, 0.32)'} - ], + rangeList: { + range: [ + {from: null, to: 25, color: 'rgba(227, 71, 71, 0.32)'}, + {from: 25, to: 50, color: 'rgba(246, 206, 67, 0.32)'}, + {from: 50, to: null, color: 'rgba(92, 223, 144, 0.32)'} + ] + }, colorFunction: defaultColorFunction }, background: { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts index 6b426197d0..cb626a70dc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts @@ -84,12 +84,14 @@ export const signalStrengthDefaultSettings: SignalStrengthWidgetSettings = { 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)'} - ], + rangeList: { + range: [ + {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 }, noSignalRssiValue: -100, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html index cbde25d5e8..4767f393a8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html @@ -209,7 +209,6 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html index aa497b0d3b..ef7ad17878 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html @@ -15,34 +15,22 @@ limitations under the License. --> -
- - -
-
{{ thresholdText() }}
- - - - -
-
- - -
-
widgets.chart.line-width
- - - px - -
-
-
- +
+ +
+ + + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss deleted file mode 100644 index 2ed7a91eb4..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright © 2016-2024 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-flot-threshold { - display: flex; - flex-direction: row; - align-items: start; - gap: 4px; - .mat-expansion-panel.tb-settings.tb-flot-threshold-settings { - box-shadow: none; - border-radius: 6px; - border: 1px solid rgba(0, 0, 0, 0.12); - .mat-expansion-panel-header { - height: 56px; - border-radius: 0; - display: flex; - flex-direction: row; - align-items: stretch; - .mat-content { - overflow: hidden; - } - .tb-threshold-header { - flex: 1; - display: flex; - flex-direction: row; - gap: 16px; - align-items: center; - padding-left: 16px; - overflow: hidden; - .mat-divider-vertical { - height: 100%; - } - } - .tb-threshold-text { - flex: 1; - font-size: 16px; - font-style: normal; - font-weight: 400; - line-height: 16px; - letter-spacing: 0.15px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .mat-expansion-indicator { - margin-right: 22px; - margin-left: 22px; - margin-top: 12px; - } - } - > .mat-expansion-panel-content { - > .mat-expansion-panel-body { - padding: 16px !important; - } - } - &.mat-expanded { - .mat-expansion-panel-header { - border-bottom: 1px solid rgba(0, 0, 0, 0.12); - } - } - } - > .mdc-icon-button { - margin-top: 4px; - color: rgba(0, 0, 0, 0.54); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.ts index 1dea0cc0c6..b6b4c88962 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.ts @@ -26,15 +26,13 @@ import { import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { TranslateService } from '@ngx-translate/core'; -import { isNumber } from '@core/utils'; import { IAliasController } from '@core/api/widget-api.models'; import { TbFlotKeyThreshold } from '@home/components/widget/lib/flot-widget.models'; @Component({ selector: 'tb-flot-threshold', templateUrl: './flot-threshold.component.html', - styleUrls: ['./flot-threshold.component.scss'], + styleUrls: [], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -49,9 +47,6 @@ export class FlotThresholdComponent extends PageComponent implements OnInit, Con @Input() disabled: boolean; - @Input() - expanded = false; - @Input() aliasController: IAliasController; @@ -65,7 +60,6 @@ export class FlotThresholdComponent extends PageComponent implements OnInit, Con public thresholdFormGroup: UntypedFormGroup; constructor(protected store: Store, - private translate: TranslateService, private fb: UntypedFormBuilder) { super(store); } @@ -110,26 +104,8 @@ export class FlotThresholdComponent extends PageComponent implements OnInit, Con ); } - thresholdText(): string { - const value: ValueSourceProperty = this.thresholdFormGroup.get('valueSource').value; - return this.valueSourcePropertyText(value); - } - - private valueSourcePropertyText(source?: ValueSourceProperty): string { - if (source) { - if (source.valueSource === 'predefinedValue') { - return `${isNumber(source.value) ? source.value : 0}`; - } else if (source.valueSource === 'entityAttribute') { - const alias = source.entityAlias || 'Undefined'; - const key = source.attribute || 'Undefined'; - return `${alias}.${key}`; - } - } - return 'Undefined'; - } - private updateModel() { - const value: {valueSource: ValueSourceProperty, lineWidth: number, color: string} = this.thresholdFormGroup.value; + const value: {valueSource: ValueSourceProperty; lineWidth: number; color: string} = this.thresholdFormGroup.value; this.modelValue = { thresholdValueSource: value?.valueSource?.valueSource, thresholdEntityAlias: value?.valueSource?.entityAlias, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html new file mode 100644 index 0000000000..34ac29be1d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html @@ -0,0 +1,56 @@ + +
+
+
+
+
widgets.color.from
+ + +
+
+
widgets.color.to
+ + +
+
+ + +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.scss similarity index 57% rename from ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.scss index c5711899ea..cf4059b0c7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.scss @@ -14,27 +14,29 @@ * limitations under the License. */ :host { - display: block; - .mat-expansion-panel { - box-shadow: none; - &.fixed-color-level { - border: 1px groove rgba(0, 0, 0, .25); - .mat-expansion-panel-header { - padding: 0 24px 0 8px; - &.mat-expanded { - height: 48px; - } - } + .range { + &-container { + display: flex; + flex: 1; + flex-direction: row; + align-items: center; + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 6px; } - } -} - -:host ::ng-deep { - .mat-expansion-panel { - &.fixed-color-level { - .mat-expansion-panel-body { - padding: 0 8px 8px; + &-input { + padding: 7px; + &-fields { + display: flex; + flex-direction: column; + flex: 1; + gap: 12px; + border-right: 1px solid rgba(0, 0, 0, 0.12);; } } + &-text { + min-width: 50px; + font-size: 14px; + color: rgba(0, 0, 0, 0.38); + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.ts new file mode 100644 index 0000000000..3a6454cf0b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.ts @@ -0,0 +1,101 @@ +/// +/// Copyright © 2016-2024 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, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { IAliasController } from '@core/api/widget-api.models'; +import { AdvancedColorRange } from '@shared/models/widget-settings.models'; + +@Component({ + selector: 'tb-advanced-range', + templateUrl: './advanced-range.component.html', + styleUrls: ['./advanced-range.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AdvancedRangeComponent), + multi: true + } + ] +}) +export class AdvancedRangeComponent extends PageComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + @Input() + aliasController: IAliasController; + + @Output() + removeAdvancedRange = new EventEmitter(); + + private modelValue: AdvancedColorRange; + + private propagateChange = (v: any) => { }; + + public advancedRangeLevelFormGroup: UntypedFormGroup; + + constructor(protected store: Store, + private fb: UntypedFormBuilder) { + super(store); + } + + ngOnInit(): void { + this.advancedRangeLevelFormGroup = this.fb.group({ + from: [null, []], + to: [null, []], + color: [null, [Validators.required]] + }); + this.advancedRangeLevelFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.advancedRangeLevelFormGroup.disable({emitEvent: false}); + } else { + this.advancedRangeLevelFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: AdvancedColorRange): void { + this.modelValue = value; + this.advancedRangeLevelFormGroup.patchValue(value, {emitEvent: false}); + } + + private updateModel() { + this.modelValue = this.advancedRangeLevelFormGroup.value; + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html index 68030f89bf..778b07bfd5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html @@ -15,36 +15,95 @@ limitations under the License. --> -
{{ panelTitle }}
-
-
-
-
-
widgets.color.from
- - - -
widgets.color.to
- - - - - + +
+
widgets.color.settings-type
+ + + {{ 'widgets.color.basic-mode' | translate }} + + + {{ 'widgets.color.advanced-mode' | translate}} + + +
+ + +
+
{{ panelTitle }}
+
+
+ +
+ +
+
+
+
+
widgets.color.from
+ + + +
widgets.color.to
+ + + + + +
+ + +
-
-
+ + + +
+
+ + + +
+
+ +
+
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.scss similarity index 61% rename from ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.scss index cafbf3b17b..63265f6dbc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.scss @@ -13,28 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + :host { - display: block; - .mat-expansion-panel { - box-shadow: none; - &.tick-value { - border: 1px groove rgba(0, 0, 0, .25); - .mat-expansion-panel-header { - padding: 0 24px 0 8px; - &.mat-expanded { - height: 48px; - } - } - } - } -} + .range { + display: flex; + flex: 1; + align-items: center; + flex-direction: row; -:host ::ng-deep { - .mat-expansion-panel { - &.tick-value { - .mat-expansion-panel-body { - padding: 0 8px 8px; - } + &-container { + display: flex; + flex: 1; + flex-direction: row; + align-items: center; + gap: 12px; + padding: 8px; + margin-right: 12px; + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 6px; } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts index 287ab13ce1..db5ac7c91b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts @@ -18,21 +18,37 @@ import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, ControlValueAccessor, + FormControl, FormGroup, NG_VALUE_ACCESSOR, UntypedFormArray, UntypedFormBuilder, - UntypedFormGroup + UntypedFormGroup, + ValidationErrors } from '@angular/forms'; -import { ColorRange } from '@shared/models/widget-settings.models'; +import { AdvancedColorRange, ColorRange, ColorRangeSettings } from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { deepClone, isDefinedAndNotNull, isUndefined } from '@core/utils'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { IAliasController } from '@core/api/widget-api.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; + +export function advancedRangeValidator(control: AbstractControl): ValidationErrors | null { + const range: AdvancedColorRange = control.value; + if (!range || !range.color) { + return { + advancedRange: true + }; + } + return null; +} @Component({ selector: 'tb-color-range-list', templateUrl: './color-range-list.component.html', - styleUrls: ['color-settings-panel.component.scss'], + styleUrls: ['color-settings-panel.component.scss', 'color-range-list.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -52,19 +68,28 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On @Input() panelTitle: string; + @Input() + aliasController: IAliasController; + + @Input() + @coerceBoolean() + advancedMode = false; + modelValue: any; colorRangeListFormGroup: UntypedFormGroup; private destroy$ = new Subject(); - private propagateChange = null; + private propagateChange = (v: any) => { }; constructor(private fb: UntypedFormBuilder) {} ngOnInit(): void { this.colorRangeListFormGroup = this.fb.group({ - rangeList: this.fb.array([]) + advancedMode: [false], + range: this.fb.array([]), + rangeAdvanced: this.fb.array([]) }); this.colorRangeListFormGroup.valueChanges.pipe( @@ -89,8 +114,21 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On } writeValue(value: any): void { - if (value && value?.length) { - value.forEach((r) => this.rangeListFormArray.push(this.colorRangeControl(r), {emitEvent: false})); + if (value) { + let rangeList: ColorRangeSettings = {}; + if (isUndefined(value?.advancedMode) && value?.length) { + rangeList.advancedMode = false; + rangeList.range = value; + } else { + rangeList = deepClone(value); + } + this.colorRangeListFormGroup.get('advancedMode').patchValue(rangeList.advancedMode, {emitEvent: false}); + if (isDefinedAndNotNull(rangeList?.range)) { + rangeList.range.forEach((r) => this.rangeListFormArray.push(this.colorRangeControl(r), {emitEvent: false})); + } + if (isDefinedAndNotNull(rangeList?.rangeAdvanced)) { + rangeList.rangeAdvanced.forEach((r) => this.advancedRangeFormArray.push(this.fb.control(r), {emitEvent: false})); + } } } @@ -103,7 +141,7 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On } get rangeListFormArray(): UntypedFormArray { - return this.colorRangeListFormGroup.get('rangeList') as UntypedFormArray; + return this.colorRangeListFormGroup.get('range') as UntypedFormArray; } get rangeListFormGroups(): FormGroup[] { @@ -114,23 +152,65 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On return rangeControl; } + public trackByAdvancedRange(index: number, advancedRangeControl: AbstractControl): any { + return advancedRangeControl; + } + + public removeAdvancedRange(index: number) { + (this.colorRangeListFormGroup.get('rangeAdvanced') as UntypedFormArray).removeAt(index); + } + + get advancedRangeFormArray(): UntypedFormArray { + return this.colorRangeListFormGroup.get('rangeAdvanced') as UntypedFormArray; + } + + get advancedRangeControls(): FormControl[] { + return this.advancedRangeFormArray.controls as FormControl[]; + } + removeRange(index: number) { this.rangeListFormArray.removeAt(index); this.colorRangeListFormGroup.markAsDirty(); setTimeout(() => {this.popover?.updatePosition();}, 0); } - addRange() { - const newRange: ColorRange = { - color: 'rgba(0,0,0,0.87)' + rangeDrop(event: CdkDragDrop, range: string) { + const rangeColorsArray = this.colorRangeListFormGroup.get(range) as UntypedFormArray; + const rangeColor = rangeColorsArray.at(event.previousIndex); + rangeColorsArray.removeAt(event.previousIndex); + rangeColorsArray.insert(event.currentIndex, rangeColor); + } + + public addAdvancedRange() { + const advancedRange: AdvancedColorRange = { + from: { + valueSource: 'predefinedValue' + }, + to: { + valueSource: 'predefinedValue' + }, + color: null }; - this.rangeListFormArray.push(this.colorRangeControl(newRange)); - this.colorRangeListFormGroup.markAsDirty(); - setTimeout(() => {this.popover?.updatePosition();}, 0); + const advancedRangeColorsArray = this.colorRangeListFormGroup.get('rangeAdvanced') as UntypedFormArray; + const advancedRangeColorControl = this.fb.control(advancedRange, [advancedRangeValidator]); + advancedRangeColorsArray.push(advancedRangeColorControl); + } + + addRange() { + if (this.colorRangeListFormGroup.get('advancedMode').value) { + this.addAdvancedRange(); + } else { + const newRange: ColorRange = { + color: 'rgba(0,0,0,0.87)' + }; + this.rangeListFormArray.push(this.colorRangeControl(newRange)); + this.colorRangeListFormGroup.markAsDirty(); + setTimeout(() => {this.popover?.updatePosition();}, 0); + } } updateModel() { - this.propagateChange(this.colorRangeListFormGroup.get('rangeList').value); + this.propagateChange(this.colorRangeListFormGroup.value); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.html index 1b9a44286b..347db57baa 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.html @@ -18,7 +18,7 @@
widgets.color.color-settings
- diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html index b85a96325e..2b1db278bf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html @@ -33,10 +33,22 @@
+
+ + +
-
@@ -50,7 +62,8 @@ color="primary" type="button" [matMenuTriggerFor]="settingsSourcesMenu" [matMenuTriggerData]="{menuWidth: copySettingsButton._elementRef.nativeElement.clientWidth}"> - {{ 'widgets.color.copy-color-settings-from' | translate }} + {{ 'widgets.color.copy-color-settings-from' | translate }} + {{ 'widgets.color.copy-from' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss index 610832007f..1b0fda35b3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss @@ -16,8 +16,10 @@ @import '../../../../../../../../scss/constants'; .tb-color-settings-panel { - width: 500px; - height: 470px; + width: 700px; + max-width: 90vw; + min-height: 300px; + max-height: 90vh; display: flex; flex-direction: column; gap: 16px; @@ -31,6 +33,12 @@ letter-spacing: 0.25px; color: rgba(0, 0, 0, 0.87); } + .tb-gradient-panel { + flex: 1; + gap: 16px; + display: flex; + flex-direction: column; + } .tb-color-ranges-panel { flex: 1; min-height: 0; @@ -82,3 +90,12 @@ align-items: flex-end; } } + +.tb-advanced-range-drag { + display: flex; + flex-direction: row; + align-items: center; + tb-advanced-range { + flex: 1; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts index e96d906b45..d873b4ba7b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts @@ -24,6 +24,8 @@ import { AppState } from '@core/core.state'; import { deepClone } from '@core/utils'; import { WidgetService } from '@core/http/widget.service'; import { ColorSettingsComponent } from '@home/components/widget/lib/settings/common/color-settings.component'; +import { IAliasController } from '@core/api/widget-api.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-color-settings-panel', @@ -46,6 +48,23 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit @Output() colorSettingsApplied = new EventEmitter(); + @Input() + aliasController: IAliasController; + + @Input() + @coerceBoolean() + rangeAdvancedMode = false; + + @Input() + @coerceBoolean() + gradientAdvancedMode = false; + + @Input() + minValue: string; + + @Input() + maxValue: string; + colorType = ColorType; colorTypes = Object.keys(ColorType) as ColorType[]; @@ -67,6 +86,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit { type: [this.colorSettings?.type || ColorType.constant, []], color: [this.colorSettings?.color, []], + gradient: [this.colorSettings?.gradient, []], rangeList: [this.colorSettings?.rangeList, []], colorFunction: [this.colorSettings?.colorFunction, []] } @@ -82,8 +102,9 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit this.colorSettingsFormGroup.patchValue({ type: this.colorSettings.type, color: this.colorSettings.color, + gradient: this.colorSettings.gradient || null, colorFunction: this.colorSettings.colorFunction, - rangeList: this.colorSettings.rangeList || [] + rangeList: this.colorSettings.rangeList || null }, {emitEvent: false}); this.colorSettingsFormGroup.markAsDirty(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts index 912c1e30ac..1e45481389 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts @@ -25,12 +25,15 @@ import { ViewContainerRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ColorSettings, ColorType, ComponentStyle } from '@shared/models/widget-settings.models'; +import { ColorRange, ColorSettings, ColorType, ComponentStyle } from '@shared/models/widget-settings.models'; import { MatButton } from '@angular/material/button'; import { TbPopoverService } from '@shared/components/popover.service'; import { ColorSettingsPanelComponent } from '@home/components/widget/lib/settings/common/color-settings-panel.component'; +import { IAliasController } from '@core/api/widget-api.models'; +import { deepClone, isDefinedAndNotNull } from '@core/utils'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Injectable() export class ColorSettingsComponentService { @@ -79,13 +82,30 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD @Input() settingsKey: string; + @Input() + aliasController: IAliasController; + + @Input() + @coerceBoolean() + rangeAdvancedMode = false; + + @Input() + @coerceBoolean() + gradientAdvancedMode = false; + + @Input() + minValue: string; + + @Input() + maxValue: string; + colorType = ColorType; modelValue: ColorSettings; colorStyle: ComponentStyle = {}; - private propagateChange = null; + private propagateChange = (v: any) => { }; constructor(private popoverService: TbPopoverService, private renderer: Renderer2, @@ -113,8 +133,14 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD } writeValue(value: ColorSettings): void { - this.modelValue = value; - this.updateColorStyle(); + if (value) { + this.modelValue = value; + if (isDefinedAndNotNull(this.modelValue.rangeList) && !isDefinedAndNotNull(this.modelValue.rangeList?.advancedMode)) { + const range = deepClone(this.modelValue.rangeList) as ColorRange[]; + this.modelValue.rangeList = deepClone({advancedMode: false, range}); + } + this.updateColorStyle(); + } } openColorSettingsPopup($event: Event, matButton: MatButton) { @@ -127,7 +153,12 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD } else { const ctx: any = { colorSettings: this.modelValue, - settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this) + settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this), + aliasController: this.aliasController, + rangeAdvancedMode: this.rangeAdvancedMode, + gradientAdvancedMode: this.gradientAdvancedMode, + minValue: this.minValue, + maxValue: this.maxValue }; const colorSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, this.viewContainerRef, ColorSettingsPanelComponent, 'left', true, null, @@ -147,18 +178,32 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD private updateColorStyle() { if (!this.disabled && this.modelValue) { let colors: string[] = [this.modelValue.color]; - if (this.modelValue.type === ColorType.range && this.modelValue.rangeList?.length) { - const rangeColors = this.modelValue.rangeList.slice(0, Math.min(2, this.modelValue.rangeList.length)).map(r => r.color); + const rangeList = this.modelValue.rangeList; + if (this.modelValue.type === ColorType.range && (rangeList?.range?.length || rangeList?.rangeAdvanced?.length)) { + let rangeColors: Array; + if (rangeList?.advancedMode) { + rangeColors = rangeList.rangeAdvanced.slice(0, Math.min(2, rangeList.rangeAdvanced.length)).map(r => r.color); + } else { + rangeColors = rangeList.range.slice(0, Math.min(2, rangeList.range.length)).map(r => r.color); + } colors = colors.concat(rangeColors); + } else if (this.modelValue.type === ColorType.gradient) { + colors = this.modelValue.gradient?.advancedMode ? + this.modelValue.gradient.gradientAdvanced.map(color => color.color) : + this.modelValue.gradient.gradient; } if (colors.length === 1) { this.colorStyle = {backgroundColor: colors[0]}; } else { const gradientValues: string[] = []; - const step = 100 / colors.length; - for (let i = 0; i < colors.length; i++) { - gradientValues.push(`${colors[i]} ${step*i}%`); - gradientValues.push(`${colors[i]} ${step*(i+1)}%`); + if (this.modelValue.type === ColorType.gradient) { + gradientValues.push(...colors); + } else { + const step = 100 / colors.length; + for (let i = 0; i < colors.length; i++) { + gradientValues.push(`${colors[i]} ${step*i}%`); + gradientValues.push(`${colors[i]} ${step*(i+1)}%`); + } } this.colorStyle = {background: `linear-gradient(90deg, ${gradientValues.join(', ')})`}; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html new file mode 100644 index 0000000000..5fe3d9c025 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html @@ -0,0 +1,208 @@ + + + +
+
widgets.color.gradient-type
+ + + {{ 'widgets.color.basic-mode' | translate }} + + + {{ 'widgets.color.advanced-mode' | translate}} + + +
+ +
+
+
+
+ +
+ +
+
+
widgets.color.gradient-start
+
+
+
widgets.color.start-value
+ + + +
+ + +
+
+
+ +
+
+
+
+
+
widgets.color.gradient-color
+ + +
+ + +
+
+
+
+ + +
+
+
widgets.color.gradient-end
+
+
+
widgets.color.end-value
+ + + +
+ + +
+
+
+
+ +
+
+
+ widgets.color.gradient-start + widgets.color.gradient-start-min +
+
+ + + + +
+
+
+ +
+
+
+
+
+
+ widgets.color.gradient-color + widgets.color.gradient-color-min +
+
+ + + + +
+
+ + +
+
+
+
+ + +
+
+
+ widgets.color.gradient-end + widgets.color.gradient-end-min +
+
+ + + + +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.scss new file mode 100644 index 0000000000..38f320b940 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.scss @@ -0,0 +1,145 @@ +/** + * Copyright © 2016-2024 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 "../../../../../../../../scss/constants"; + + +:host { + overflow: auto; + height: 100%; + max-height: 420px; + .gradient { + display: flex; + flex: 1; + align-items: center; + flex-direction: row; + + &-container { + display: flex; + flex: 1; + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 12px; + padding: 8px; + margin-right: 12px; + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 6px; + &-start, &-end { + margin-right: 92px; + @media #{$mat-lt-lg} { + margin-right: 52px; + } + } + + .tb-gradient-text { + width: 140px; + @media #{$mat-xs} { + width: 50px; + } + } + } + .start-gradient-container, .end-gradient-container, .list-gradient-container { + display: flex; + gap: 8px; + .gradient-text { + font-size: 14px; + color: rgba(0, 0, 0, 0.38); + } + &-advanced { + flex: 1; + @media #{$mat-xs} { + align-items: center; + } + } + } + } + + .gradient-preview { + width: 100%; + padding: 40px 12px 0; + .gradient-background { + position: relative; + height: 56px; + border-radius: 8px; + } + } + + .gradient-settings { + flex: 1; + gap: 16px; + display: flex; + flex-direction: column; + } + + .tb-add-gradient { + margin-right: 92px; + @media #{$mat-lt-lg} { + margin-right: 52px; + } + } +} +::ng-deep { + .gradient-background { + .pointer { + position: absolute; + width: 8px; + height: 60px; + border-radius: 6px; + top: -2px; + border: 2px solid white; + -webkit-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.75); + -moz-box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.75); + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.75); + + &.start { + left: 1px; + } + + &.end { + right: 1px; + } + + &-value { + position: absolute; + top: -40px; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: center; + justify-content: center; + width: 33px; + height: 24px; + border-radius: 4px; + background-color: rgba(0, 0, 0, 0.06); + &-text { + font-size: 14px; + font-weight: 500; + } + &:after { + content: ""; + bottom: -8px; + position: absolute; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid rgba(0, 0, 0, 0.06); + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts new file mode 100644 index 0000000000..ea17f19637 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts @@ -0,0 +1,255 @@ +/// +/// Copyright © 2016-2024 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, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormGroup, + NG_VALUE_ACCESSOR, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormGroup +} from '@angular/forms'; +import { AdvancedGradient, ColorGradientSettings } from '@shared/models/widget-settings.models'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { IAliasController } from '@core/api/widget-api.models'; +import { DomSanitizer } from '@angular/platform-browser'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { isDefinedAndNotNull } from '@core/utils'; + +@Component({ + selector: 'tb-gradient', + templateUrl: './gradient.component.html', + styleUrls: ['color-settings-panel.component.scss', 'gradient.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => GradientComponent), + multi: true + } + ] +}) +export class GradientComponent implements OnInit, ControlValueAccessor, OnDestroy { + + @ViewChild('gradient') gradient: ElementRef; + + @Input() + disabled: boolean; + + @Input() + popover: TbPopoverComponent; + + @Input() + panelTitle: string; + + @Input() + aliasController: IAliasController; + + @Input() + minValue: string; + + @Input() + maxValue: string; + + @Input() + @coerceBoolean() + advancedMode = true; + + modelValue: any; + + gradientFormGroup: UntypedFormGroup; + + private destroy$ = new Subject(); + + private propagateChange = (v: any) => { }; + + constructor(private fb: UntypedFormBuilder, + private sanitizer: DomSanitizer) {} + + ngOnInit(): void { + this.gradientFormGroup = this.fb.group({ + advancedMode: [false], + gradient: this.fb.group({ + start: ['rgba(0, 255, 0, 1)'], + gradientList: this.fb.array([]), + end: ['rgba(255, 0, 0, 1)'] + }), + gradientAdvanced: this.fb.group({ + start: this.fb.group({ + source: [], + color: ['rgba(0, 255, 0, 1)'] + }), + gradientList: this.fb.array([]), + end: this.fb.group({ + source: [], + color: ['rgba(255, 0, 0, 1)'] + }) + }) + }); + + this.gradientFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => this.updateModel()); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: ColorGradientSettings): void { + if (isDefinedAndNotNull(value)) { + this.gradientFormGroup.get('advancedMode').patchValue(value.advancedMode, {emitEvent: false}); + if (value?.gradient?.length) { + this.gradientFormGroup.get('gradient').get('start').patchValue(value.gradient[0], {emitEvent: false}); + this.gradientFormGroup.get('gradient').get('end').patchValue(value.gradient[value.gradient.length - 1], {emitEvent: false}); + value.gradient.slice(1, -1).forEach(r => this.gradientListFormArray.push(this.colorGradientControl(r), {emitEvent: false})); + } + if (value?.gradientAdvanced?.length) { + this.gradientFormGroup.get('gradientAdvanced').get('start').patchValue(value.gradientAdvanced[0], {emitEvent: false}); + this.gradientFormGroup.get('gradientAdvanced').get('end').patchValue( + value.gradientAdvanced[value.gradientAdvanced.length - 1], {emitEvent: false} + ); + value.gradientAdvanced.slice(1, -1).forEach( + r => this.advancedGradientListFormArray.push(this.advancedGradientControl(r), {emitEvent: false}) + ); + } + } + } + + get generatePointers() { + if (this.gradientFormGroup.get('advancedMode').value) { + const point = 100 / (this.advancedGradientListFormArray.value.length + 1); + return `
` + + this.advancedGradientListFormArray.value.map((v, i) => this.pointer(point * (i + 1), true)).join('') + + `
`; + } else { + const point = 100 / (this.gradientListFormArray.value.length + 1); + const min = isDefinedAndNotNull(this.minValue) ? this.minValue : 0; + const max = isDefinedAndNotNull(this.maxValue) ? this.maxValue : 100; + return `
${min}
` + + this.gradientListFormArray.value.map((v, i) => this.pointer(point * (i + 1))).join('') + + `
${max}
`; + } + } + + pointer(value?: number, advanced = false) { + if (advanced) { + return `
`; + } else { + return `
` + + `
${Math.floor((+this.maxValue*value)/100)}
`; + } + } + + get linearGradient() { + const gradient = this.gradientFormGroup.get('advancedMode').value ? + [this.gradientFormGroup.value.gradientAdvanced.start.color, + ...this.gradientFormGroup.value.gradientAdvanced.gradientList.map(item => item.color), + this.gradientFormGroup.value.gradientAdvanced.end.color].join(', ') : + [this.gradientFormGroup.value.gradient.start, + ...this.gradientFormGroup.value.gradient.gradientList.map(item => item.color), + this.gradientFormGroup.value.gradient.end].join(', '); + return this.sanitizer.bypassSecurityTrustStyle(`background-image: linear-gradient(90deg, ${gradient})`); + } + + private colorGradientControl(gradient: string): UntypedFormGroup { + return this.fb.group({ + color: [gradient, []] + }); + } + + get gradientListFormArray(): UntypedFormArray { + return this.gradientFormGroup.get('gradient.gradientList') as UntypedFormArray; + } + get gradientListFormGroups(): FormGroup[] { + return this.gradientListFormArray.controls as FormGroup[]; + } + + private advancedGradientControl(gradient: AdvancedGradient): UntypedFormGroup { + return this.fb.group({ + source: [gradient.source, []], + color: [gradient.color, []] + }); + } + + get advancedGradientListFormArray(): UntypedFormArray { + return this.gradientFormGroup.get('gradientAdvanced.gradientList') as UntypedFormArray; + } + get advancedGradientListFormGroups(): FormGroup[] { + return this.advancedGradientListFormArray.controls as FormGroup[]; + } + + trackByGradient(index: number, gradientControl: AbstractControl): any { + return gradientControl; + } + + removeGradient(index: number, advanced = false) { + if (advanced) { + this.advancedGradientListFormArray.removeAt(index); + } else { + this.gradientListFormArray.removeAt(index); + } + this.gradientFormGroup.markAsDirty(); + setTimeout(() => {this.popover?.updatePosition();}, 0); + } + + gradientDrop(event: CdkDragDrop, advanced = false) { + const gradientColorsArray = advanced ? this.advancedGradientListFormArray : this.gradientListFormArray; + const gradientColor = gradientColorsArray.at(event.previousIndex); + gradientColorsArray.removeAt(event.previousIndex); + gradientColorsArray.insert(event.currentIndex, gradientColor); + } + + addGradient(advanced = false) { + if (advanced) { + this.advancedGradientListFormArray.push(this.advancedGradientControl({source: null, color: 'rgba(0,0,0,0.87)'})); + } else { + this.gradientListFormArray.push(this.colorGradientControl('rgba(0,0,0,0.87)')); + } + this.gradientFormGroup.markAsDirty(); + setTimeout(() => {this.popover?.updatePosition();}, 0); + } + + updateModel() { + this.propagateChange( + { + advancedMode: this.gradientFormGroup.value.advancedMode, + gradient: [this.gradientFormGroup.value.gradient.start, + ...this.gradientFormGroup.value.gradient.gradientList.map(item => item.color), + this.gradientFormGroup.value.gradient.end], + gradientAdvanced: [this.gradientFormGroup.value.gradientAdvanced.start, + ...this.gradientFormGroup.value.gradientAdvanced.gradientList, + this.gradientFormGroup.value.gradientAdvanced.end] + } + ); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html index cb5d62040d..24c8564ffb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html @@ -15,65 +15,59 @@ limitations under the License. --> - -
- - + +
+ + + {{ 'widgets.value-source.predefined-value' | translate }} - - + + {{ 'widgets.value-source.entity-attribute' | translate }} - - -
-
-
widgets.value-source.value
- - - -
-
-
widgets.value-source.source-entity-alias
- - - - - - - - - -
-
-
widgets.value-source.source-entity-attribute
- - - - - - - - - -
- + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.ts index 9df42f0eb8..f290a05083 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.ts @@ -19,7 +19,6 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFor import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { TranslateService } from '@ngx-translate/core'; import { IAliasController } from '@core/api/widget-api.models'; import { Observable, of } from 'rxjs'; import { catchError, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators'; @@ -60,9 +59,15 @@ export class ValueSourceComponent extends PageComponent implements OnInit, Contr @Input() aliasController: IAliasController; + @Input() + entityAliasPlaceholder = 'widgets.value-source.source-entity-alias'; + + @Input() + entityAttributePlaceholder = 'widgets.value-source.source-entity-attribute'; + private modelValue: ValueSourceProperty; - private propagateChange = null; + private propagateChange = (v: any) => { }; public valueSourceFormGroup: UntypedFormGroup; @@ -78,7 +83,6 @@ export class ValueSourceComponent extends PageComponent implements OnInit, Contr private entityAliasList: Array = []; constructor(protected store: Store, - private translate: TranslateService, private entityService: EntityService, private fb: UntypedFormBuilder) { super(store); @@ -247,5 +251,4 @@ export class ValueSourceComponent extends PageComponent implements OnInit, Contr this.valueSourceFormGroup.get('attribute').updateValueAndValidity({emitEvent: false}); this.valueSourceFormGroup.get('value').updateValueAndValidity({emitEvent: false}); } - } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index e25282bb4c..ee47f1edc4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -49,7 +49,8 @@ import { import { ColorRangeListComponent } from '@home/components/widget/lib/settings/common/color-range-list.component'; import { ColorRangePanelComponent } from '@home/components/widget/lib/settings/common/color-range-panel.component'; import { - ColorRangeSettingsComponent, ColorRangeSettingsComponentService + ColorRangeSettingsComponent, + ColorRangeSettingsComponentService } from '@home/components/widget/lib/settings/common/color-range-settings.component'; import { GetValueActionSettingsComponent @@ -139,6 +140,8 @@ import { import { TimeSeriesChartStatesPanelComponent } from '@home/components/widget/lib/settings/common/chart/time-series-chart-states-panel.component'; +import { AdvancedRangeComponent } from '@home/components/widget/lib/settings/common/advanced-range.component'; +import { GradientComponent } from '@home/components/widget/lib/settings/common/gradient.component'; @NgModule({ declarations: [ @@ -191,7 +194,9 @@ import { TimeSeriesChartStatesPanelComponent, TimeSeriesChartStateRowComponent, DataKeyInputComponent, - EntityAliasInputComponent + EntityAliasInputComponent, + AdvancedRangeComponent, + GradientComponent ], imports: [ CommonModule, @@ -248,7 +253,9 @@ import { TimeSeriesChartStatesPanelComponent, TimeSeriesChartStateRowComponent, DataKeyInputComponent, - EntityAliasInputComponent + EntityAliasInputComponent, + AdvancedRangeComponent, + GradientComponent ], providers: [ ColorSettingsComponentService, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html index e3592e73ad..6c264d7a18 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html @@ -15,368 +15,296 @@ limitations under the License. --> -
-
- widgets.gauge.common-settings -
- - widgets.gauge.min-value - + +
+
widgets.gauge.gauge-appearance
+ + + {{ digitalGaugeLayoutTranslationMap.get(layout) | translate }} + + + +
+
{{ 'widgets.gauge.donut-start-angle' | translate }}
+ + - - widgets.gauge.max-value - +
+ + +
+ + {{ 'widgets.gauge.min-and-max-value' | translate }} + +
+
widgets.gauge.min-value-short
+ + + +
widgets.gauge.max-value-short
+ + + + + + + +
+
+
+ +
+ + {{ 'widgets.gauge.value' | translate }} + +
+ + + + +
+
+ +
+ + {{ 'widgets.gauge.label' | translate }} + +
+ + + + + + + +
+
+ +
+
widgets.gauge.unit-title-and-timestamp-settings
+
+ + {{ 'widgets.gauge.show-unit-title' | translate }} + + + + +
+ +
+ + {{ 'widgets.gauge.show-timestamp' | translate }} + + +
+ +
+
{{ 'widgets.gauge.font' | translate }}
+
+ + + + +
+
+
+
+ +
+
widgets.gauge.bar-settings
+
+
{{ 'widgets.gauge.relative-bar-width' | translate }}
+ + -
- - widgets.gauge.gauge-type - - - {{ 'widgets.gauge.gauge-type-arc' | translate }} - - - {{ 'widgets.gauge.gauge-type-donut' | translate }} - - - {{ 'widgets.gauge.gauge-type-horizontal-bar' | translate }} - - - {{ 'widgets.gauge.gauge-type-vertical-bar' | translate }} - - - - - widgets.gauge.donut-start-angle - - - - -
-
- widgets.gauge.bar-settings - - widgets.gauge.relative-bar-width - - - - widgets.gauge.neon-glow-brightness - - -
- - widgets.gauge.stripes-thickness - +
+ +
+
{{ 'widgets.gauge.neon-glow-brightness' | translate }}
+ + - +
+ +
+
{{ 'widgets.gauge.stripes-thickness' | translate }}
+ + + +
+ +
+ {{ 'widgets.gauge.rounded-line-cap' | translate }} - - -
- widgets.gauge.bar-color-settings - - - - {{ 'widgets.gauge.use-precise-level-color-values' | translate }} -
- widgets.gauge.bar-colors -
-
-
-
- drag_handle -
- - - -
-
-
- widgets.gauge.no-bar-colors -
-
- +
+ +
+
{{ 'widgets.gauge.default-color' | translate }}
+ + +
+ +
+
{{ 'widgets.gauge.gauge-bar-background' | translate }}
+ + +
+ +
+
{{ 'widgets.gauge.bar-color' | translate }}
+ + +
+ +
+ +
+ + + + + {{ 'widgets.gauge.ticks' | translate }} + + + + widget-config.advanced-settings + + + +
+
{{ 'widgets.gauge.tick-width-and-color' | translate }}
+
+ + + + +
-
-
- widgets.gauge.fixed-level-colors -
+
+
widgets.gauge.tick-values
-
- - + (cdkDropListDropped)="tickValueDrop($event)"> +
+ +
-
- widgets.gauge.no-bar-colors +
+ widgets.gauge.no-tick-values
-
-
-
- -
- widgets.gauge.gauge-title-settings - - - - - {{ 'widgets.gauge.show-gauge-title' | translate }} - - - - widget-config.advanced-settings - - - -
- - widgets.gauge.gauge-title - - -
- widgets.gauge.gauge-title-font - -
-
-
-
-
-
- widgets.gauge.unit-title-and-timestamp-settings -
- - {{ 'widgets.gauge.show-unit-title' | translate }} - - - widgets.gauge.unit-title - - -
-
- - {{ 'widgets.gauge.show-timestamp' | translate }} - - - widgets.gauge.timestamp-format - - -
- - - - widget-config.advanced-settings - - - -
- widgets.gauge.label-font - -
-
-
- widgets.gauge.value-settings - - - - - {{ 'widgets.gauge.show-value' | translate }} - - - - widget-config.advanced-settings - - - -
- widgets.gauge.value-font - -
-
-
-
-
- widgets.gauge.min-max-settings - - - - - {{ 'widgets.gauge.show-min-max' | translate }} - - - - widget-config.advanced-settings - - - -
- widgets.gauge.min-max-font - -
-
-
-
-
- widgets.gauge.ticks-settings - - - - - {{ 'widgets.gauge.show-ticks' | translate }} - - - - widget-config.advanced-settings - - - -
- - widgets.gauge.tick-width - - - - -
- widgets.gauge.tick-values -
-
-
+
- - -
-
-
- widgets.gauge.no-tick-values -
-
- -
-
- - - - - -
- widgets.gauge.animation-settings +
- {{ 'widgets.gauge.enable-animation' | translate }} - + widget-config.advanced-settings -
- - widgets.gauge.animation-duration - - - - widgets.gauge.animation-rule - - - {{ 'widgets.gauge.animation-linear' | translate }} - - - {{ 'widgets.gauge.animation-quad' | translate }} - - - {{ 'widgets.gauge.animation-quint' | translate }} - - - {{ 'widgets.gauge.animation-cycle' | translate }} - - - {{ 'widgets.gauge.animation-bounce' | translate }} - - - {{ 'widgets.gauge.animation-elastic' | translate }} - - - {{ 'widgets.gauge.animation-dequad' | translate }} - - - {{ 'widgets.gauge.animation-dequint' | translate }} - - - {{ 'widgets.gauge.animation-decycle' | translate }} - - - {{ 'widgets.gauge.animation-debounce' | translate }} - - - {{ 'widgets.gauge.animation-delastic' | translate }} - - - -
+
+
widgets.gauge.animation-duration-rule
+
+ + + + + + + {{ 'widgets.gauge.animation-linear' | translate }} + + + {{ 'widgets.gauge.animation-quad' | translate }} + + + {{ 'widgets.gauge.animation-quint' | translate }} + + + {{ 'widgets.gauge.animation-cycle' | translate }} + + + {{ 'widgets.gauge.animation-bounce' | translate }} + + + {{ 'widgets.gauge.animation-elastic' | translate }} + + + {{ 'widgets.gauge.animation-dequad' | translate }} + + + {{ 'widgets.gauge.animation-dequint' | translate }} + + + {{ 'widgets.gauge.animation-decycle' | translate }} + + + {{ 'widgets.gauge.animation-debounce' | translate }} + + + {{ 'widgets.gauge.animation-delastic' | translate }} + + + +
+
-
- +
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts index 77cdf367bb..ac299cee5c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts @@ -21,11 +21,15 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge'; import { CdkDragDrop } from '@angular/cdk/drag-drop'; -import { - FixedColorLevel, - fixedColorLevelValidator -} from '@home/components/widget/lib/settings/gauge/fixed-color-level.component'; import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component'; +import { + digitalGaugeLayoutImages, + digitalGaugeLayouts, + digitalGaugeLayoutTranslations, + DigitalGaugeType +} from '@home/components/widget/lib/digital-gauge.models'; +import { deepClone, formatValue } from '@core/utils'; +import { ColorSettings, ColorType, constantColor, simpleDateFormat } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-digital-gauge-widget-settings', @@ -34,8 +38,17 @@ import { ValueSourceProperty } from '@home/components/widget/lib/settings/common }) export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent { + digitalGaugeType = DigitalGaugeType; + digitalGaugeLayouts = digitalGaugeLayouts; + + digitalGaugeLayoutTranslationMap = digitalGaugeLayoutTranslations; + digitalGaugeLayoutImageMap = digitalGaugeLayoutImages; + digitalGaugeWidgetSettingsForm: UntypedFormGroup; + valuePreviewFn = this._valuePreviewFn.bind(this, true); + previewFn = this._valuePreviewFn.bind(this, false); + constructor(protected store: Store, protected fb: UntypedFormBuilder) { super(store); @@ -107,63 +120,103 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent } protected onSettingsSet(settings: WidgetSettings) { + + + if (!settings.barColor) { + settings.barColor = constantColor(null); + + if (settings.fixedLevelColors.length) { + settings.barColor.rangeList = { + advancedMode: settings.useFixedLevelColor, + range: null, + rangeAdvanced: deepClone(settings.fixedLevelColors) + }; + } + if (settings.levelColors.length) { + settings.barColor.gradient = { + advancedMode: false, + gradient: settings.levelColors, + gradientAdvanced: null + }; + } + if (settings.useFixedLevelColor) { + settings.barColor.type = ColorType.range; + } else if (settings.levelColors.length) { + settings.barColor.type = ColorType.gradient; + } + } + this.digitalGaugeWidgetSettingsForm = this.fb.group({ - // Common gauge settings - minValue: [settings.minValue, []], - maxValue: [settings.maxValue, []], gaugeType: [settings.gaugeType, []], donutStartAngle: [settings.donutStartAngle, []], - defaultColor: [settings.defaultColor, []], - - // Gauge bar settings - gaugeWidthScale: [settings.gaugeWidthScale, [Validators.min(0)]], - neonGlowBrightness: [settings.neonGlowBrightness, [Validators.min(0), Validators.max(100)]], - dashThickness: [settings.dashThickness, [Validators.min(0)]], - roundedLineCap: [settings.roundedLineCap, []], + showMinMax: [settings.showMinMax, []], + minValue: [settings.minValue, []], + maxValue: [settings.maxValue, []], + minMaxFont: [settings.minMaxFont, []], + minMaxColor: [settings.minMaxFont.color, []], - // Gauge bar colors settings - gaugeColor: [settings.gaugeColor, []], - useFixedLevelColor: [settings.useFixedLevelColor, []], - levelColors: this.prepareLevelColorFormArray(settings.levelColors), - fixedLevelColors: this.prepareFixedLevelColorFormArray(settings.fixedLevelColors), + showValue: [settings.showValue, []], + valueFont: [settings.valueFont, []], + valueColor: [settings.valueFont.color, []], - // Title settings showTitle: [settings.showTitle, []], title: [settings.title, []], titleFont: [settings.titleFont, []], + titleColor: [settings.titleFont.color, []], - // Unit title/timestamp settings showUnitTitle: [settings.showUnitTitle, []], unitTitle: [settings.unitTitle, []], showTimestamp: [settings.showTimestamp, []], - timestampFormat: [settings.timestampFormat, []], + timestampFormat: [simpleDateFormat(settings.timestampFormat), []], labelFont: [settings.labelFont, []], + labelColor: [settings.labelFont.color, []], - // Value settings - showValue: [settings.showValue, []], - valueFont: [settings.valueFont, []], + gaugeWidthScale: [settings.gaugeWidthScale, [Validators.min(0)]], + neonGlowBrightness: [settings.neonGlowBrightness, [Validators.min(0), Validators.max(100)]], + dashThickness: [settings.dashThickness, [Validators.min(0)]], + roundedLineCap: [settings.roundedLineCap, []], - // Min/max labels settings - showMinMax: [settings.showMinMax, []], - minMaxFont: [settings.minMaxFont, []], + defaultColor: [settings.defaultColor, []], + gaugeColor: [settings.gaugeColor, []], + barColor: [settings.barColor], - // Ticks settings showTicks: [settings.showTicks, []], tickWidth: [settings.tickWidth, [Validators.min(0)]], colorTicks: [settings.colorTicks, []], ticksValue: this.prepareTicksValueFormArray(settings.ticksValue), - // Animation settings animation: [settings.animation, []], animationDuration: [settings.animationDuration, [Validators.min(0)]], animationRule: [settings.animationRule, []] - }); } + protected prepareOutputSettings(settings) { + + const barColor: ColorSettings = this.digitalGaugeWidgetSettingsForm.get('barColor').value; + + if (barColor.type === ColorType.range) { + settings.useFixedLevelColor = true; + settings.fixedLevelColors = barColor.rangeList.advancedMode ? barColor.rangeList.rangeAdvanced : barColor.rangeList.range; + } else { + settings.useFixedLevelColor = false; + } + if (barColor.gradient?.gradient?.length) { + settings.levelColors = barColor.gradient.gradient; + } + settings.barColor = this.digitalGaugeWidgetSettingsForm.get('barColor').value; + settings.timestampFormat = this.digitalGaugeWidgetSettingsForm.get('timestampFormat').value.format; + settings.minMaxFont.color = this.digitalGaugeWidgetSettingsForm.get('minMaxColor').value; + settings.valueFont.color = this.digitalGaugeWidgetSettingsForm.get('valueColor').value; + settings.titleFont.color = this.digitalGaugeWidgetSettingsForm.get('titleColor').value; + settings.labelFont.color = this.digitalGaugeWidgetSettingsForm.get('labelColor').value; + + return settings; + } + protected validatorTriggers(): string[] { - return ['gaugeType', 'showTitle', 'showUnitTitle', 'showValue', 'showMinMax', 'showTimestamp', 'useFixedLevelColor', 'showTicks', 'animation']; + return ['gaugeType', 'showTitle', 'showUnitTitle', 'showValue', 'showMinMax', 'showTimestamp', 'showTicks', 'animation']; } protected updateValidators(emitEvent: boolean) { @@ -173,7 +226,6 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent const showValue: boolean = this.digitalGaugeWidgetSettingsForm.get('showValue').value; const showMinMax: boolean = this.digitalGaugeWidgetSettingsForm.get('showMinMax').value; const showTimestamp: boolean = this.digitalGaugeWidgetSettingsForm.get('showTimestamp').value; - const useFixedLevelColor: boolean = this.digitalGaugeWidgetSettingsForm.get('useFixedLevelColor').value; const showTicks: boolean = this.digitalGaugeWidgetSettingsForm.get('showTicks').value; const animation: boolean = this.digitalGaugeWidgetSettingsForm.get('animation').value; @@ -185,9 +237,11 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent if (showTitle) { this.digitalGaugeWidgetSettingsForm.get('title').enable(); this.digitalGaugeWidgetSettingsForm.get('titleFont').enable(); + this.digitalGaugeWidgetSettingsForm.get('titleColor').enable(); } else { this.digitalGaugeWidgetSettingsForm.get('title').disable(); this.digitalGaugeWidgetSettingsForm.get('titleFont').disable(); + this.digitalGaugeWidgetSettingsForm.get('titleColor').disable(); } if (showUnitTitle) { this.digitalGaugeWidgetSettingsForm.get('unitTitle').enable(); @@ -201,25 +255,28 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent } if (showUnitTitle || showTimestamp) { this.digitalGaugeWidgetSettingsForm.get('labelFont').enable(); + this.digitalGaugeWidgetSettingsForm.get('labelColor').enable(); } else { this.digitalGaugeWidgetSettingsForm.get('labelFont').disable(); + this.digitalGaugeWidgetSettingsForm.get('labelColor').disable(); } if (showValue) { this.digitalGaugeWidgetSettingsForm.get('valueFont').enable(); + this.digitalGaugeWidgetSettingsForm.get('valueColor').enable(); } else { this.digitalGaugeWidgetSettingsForm.get('valueFont').disable(); + this.digitalGaugeWidgetSettingsForm.get('valueColor').disable(); } if (showMinMax) { + this.digitalGaugeWidgetSettingsForm.get('minValue').enable(); + this.digitalGaugeWidgetSettingsForm.get('maxValue').enable(); this.digitalGaugeWidgetSettingsForm.get('minMaxFont').enable(); + this.digitalGaugeWidgetSettingsForm.get('minMaxColor').enable(); } else { + this.digitalGaugeWidgetSettingsForm.get('minValue').disable(); + this.digitalGaugeWidgetSettingsForm.get('maxValue').disable(); this.digitalGaugeWidgetSettingsForm.get('minMaxFont').disable(); - } - if (useFixedLevelColor) { - this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors').enable(); - this.digitalGaugeWidgetSettingsForm.get('levelColors').disable(); - } else { - this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors').disable(); - this.digitalGaugeWidgetSettingsForm.get('levelColors').enable(); + this.digitalGaugeWidgetSettingsForm.get('minMaxColor').disable(); } if (showTicks) { this.digitalGaugeWidgetSettingsForm.get('tickWidth').enable(); @@ -240,13 +297,21 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent this.digitalGaugeWidgetSettingsForm.get('donutStartAngle').updateValueAndValidity({emitEvent}); this.digitalGaugeWidgetSettingsForm.get('title').updateValueAndValidity({emitEvent}); this.digitalGaugeWidgetSettingsForm.get('titleFont').updateValueAndValidity({emitEvent}); + this.digitalGaugeWidgetSettingsForm.get('titleColor').updateValueAndValidity({emitEvent}); + this.digitalGaugeWidgetSettingsForm.get('unitTitle').updateValueAndValidity({emitEvent}); this.digitalGaugeWidgetSettingsForm.get('timestampFormat').updateValueAndValidity({emitEvent}); this.digitalGaugeWidgetSettingsForm.get('labelFont').updateValueAndValidity({emitEvent}); + this.digitalGaugeWidgetSettingsForm.get('labelColor').updateValueAndValidity({emitEvent}); + this.digitalGaugeWidgetSettingsForm.get('valueFont').updateValueAndValidity({emitEvent}); + this.digitalGaugeWidgetSettingsForm.get('valueColor').updateValueAndValidity({emitEvent}); + + this.digitalGaugeWidgetSettingsForm.get('minValue').updateValueAndValidity({emitEvent}); + this.digitalGaugeWidgetSettingsForm.get('maxValue').updateValueAndValidity({emitEvent}); this.digitalGaugeWidgetSettingsForm.get('minMaxFont').updateValueAndValidity({emitEvent}); - this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors').updateValueAndValidity({emitEvent}); - this.digitalGaugeWidgetSettingsForm.get('levelColors').updateValueAndValidity({emitEvent}); + this.digitalGaugeWidgetSettingsForm.get('minMaxColor').updateValueAndValidity({emitEvent}); + this.digitalGaugeWidgetSettingsForm.get('tickWidth').updateValueAndValidity({emitEvent}); this.digitalGaugeWidgetSettingsForm.get('colorTicks').updateValueAndValidity({emitEvent}); this.digitalGaugeWidgetSettingsForm.get('ticksValue').updateValueAndValidity({emitEvent}); @@ -255,31 +320,9 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent } protected doUpdateSettings(settingsForm: UntypedFormGroup, settings: WidgetSettings) { - settingsForm.setControl('levelColors', this.prepareLevelColorFormArray(settings.levelColors), {emitEvent: false}); - settingsForm.setControl('fixedLevelColors', this.prepareFixedLevelColorFormArray(settings.fixedLevelColors), {emitEvent: false}); settingsForm.setControl('ticksValue', this.prepareTicksValueFormArray(settings.ticksValue), {emitEvent: false}); } - private prepareLevelColorFormArray(levelColors: string[] | undefined): UntypedFormArray { - const levelColorsControls: Array = []; - if (levelColors) { - levelColors.forEach((levelColor) => { - levelColorsControls.push(this.fb.control(levelColor, [Validators.required])); - }); - } - return this.fb.array(levelColorsControls); - } - - private prepareFixedLevelColorFormArray(fixedLevelColors: FixedColorLevel[] | undefined): UntypedFormArray { - const fixedLevelColorsControls: Array = []; - if (fixedLevelColors) { - fixedLevelColors.forEach((fixedLevelColor) => { - fixedLevelColorsControls.push(this.fb.control(fixedLevelColor, [fixedColorLevelValidator])); - }); - } - return this.fb.array(fixedLevelColorsControls); - } - private prepareTicksValueFormArray(ticksValue: ValueSourceProperty[] | undefined): UntypedFormArray { const ticksValueControls: Array = []; if (ticksValue) { @@ -290,71 +333,6 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent return this.fb.array(ticksValueControls); } - levelColorsFormArray(): UntypedFormArray { - return this.digitalGaugeWidgetSettingsForm.get('levelColors') as UntypedFormArray; - } - - public trackByLevelColor(index: number, levelColorControl: AbstractControl): any { - return levelColorControl; - } - - public removeLevelColor(index: number) { - (this.digitalGaugeWidgetSettingsForm.get('levelColors') as UntypedFormArray).removeAt(index); - } - - public addLevelColor() { - const levelColorsArray = this.digitalGaugeWidgetSettingsForm.get('levelColors') as UntypedFormArray; - const levelColorControl = this.fb.control(null, []); - levelColorsArray.push(levelColorControl); - this.digitalGaugeWidgetSettingsForm.updateValueAndValidity(); - } - - levelColorDrop(event: CdkDragDrop) { - const levelColorsArray = this.digitalGaugeWidgetSettingsForm.get('levelColors') as UntypedFormArray; - const levelColor = levelColorsArray.at(event.previousIndex); - levelColorsArray.removeAt(event.previousIndex); - levelColorsArray.insert(event.currentIndex, levelColor); - } - - fixedLevelColorFormArray(): UntypedFormArray { - return this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors') as UntypedFormArray; - } - - public trackByFixedLevelColor(index: number, fixedLevelColorControl: AbstractControl): any { - return fixedLevelColorControl; - } - - public removeFixedLevelColor(index: number) { - (this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors') as UntypedFormArray).removeAt(index); - } - - public addFixedLevelColor() { - const fixedLevelColor: FixedColorLevel = { - from: { - valueSource: 'predefinedValue' - }, - to: { - valueSource: 'predefinedValue' - }, - color: null - }; - const fixedLevelColorsArray = this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors') as UntypedFormArray; - const fixedLevelColorControl = this.fb.control(fixedLevelColor, [fixedColorLevelValidator]); - (fixedLevelColorControl as any).new = true; - fixedLevelColorsArray.push(fixedLevelColorControl); - this.digitalGaugeWidgetSettingsForm.updateValueAndValidity(); - if (!this.digitalGaugeWidgetSettingsForm.valid) { - this.onSettingsChanged(this.digitalGaugeWidgetSettingsForm.value); - } - } - - fixedLevelColorDrop(event: CdkDragDrop) { - const fixedLevelColorsArray = this.digitalGaugeWidgetSettingsForm.get('fixedLevelColors') as UntypedFormArray; - const fixedLevelColor = fixedLevelColorsArray.at(event.previousIndex); - fixedLevelColorsArray.removeAt(event.previousIndex); - fixedLevelColorsArray.insert(event.currentIndex, fixedLevelColor); - } - tickValuesFormArray(): UntypedFormArray { return this.digitalGaugeWidgetSettingsForm.get('ticksValue') as UntypedFormArray; } @@ -385,4 +363,7 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent tickValuesArray.insert(event.currentIndex, tickValue); } + private _valuePreviewFn(units: boolean): string { + return formatValue(22, 0, units ? this.widget.config.units : null, true); + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.html deleted file mode 100644 index 30008112ba..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.html +++ /dev/null @@ -1,60 +0,0 @@ - - - -
- -
-
{{ fixedColorLevelRangeText() }}
-
-
-
-
-
- - -
-
- -
- -
-
- widgets.gauge.from - -
-
- widgets.gauge.to - -
- - -
-
-
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.ts deleted file mode 100644 index b1464e7e8a..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.ts +++ /dev/null @@ -1,147 +0,0 @@ -/// -/// Copyright © 2016-2024 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 { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component'; -import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; -import { - AbstractControl, - ControlValueAccessor, - UntypedFormBuilder, - UntypedFormGroup, - NG_VALUE_ACCESSOR, ValidationErrors, - Validators -} from '@angular/forms'; -import { PageComponent } from '@shared/components/page.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { TranslateService } from '@ngx-translate/core'; -import { isNumber } from '@core/utils'; -import { IAliasController } from '@core/api/widget-api.models'; - -export interface FixedColorLevel { - from?: ValueSourceProperty; - to?: ValueSourceProperty; - color: string; -} - -export function fixedColorLevelValidator(control: AbstractControl): ValidationErrors | null { - const fixedColorLevel: FixedColorLevel = control.value; - if (!fixedColorLevel || !fixedColorLevel.color) { - return { - fixedColorLevel: true - }; - } - return null; -} - -@Component({ - selector: 'tb-fixed-color-level', - templateUrl: './fixed-color-level.component.html', - styleUrls: ['./fixed-color-level.component.scss'], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => FixedColorLevelComponent), - multi: true - } - ] -}) -export class FixedColorLevelComponent extends PageComponent implements OnInit, ControlValueAccessor { - - @Input() - disabled: boolean; - - @Input() - expanded = false; - - @Input() - aliasController: IAliasController; - - @Output() - removeFixedColorLevel = new EventEmitter(); - - private modelValue: FixedColorLevel; - - private propagateChange = null; - - public fixedColorLevelFormGroup: UntypedFormGroup; - - constructor(protected store: Store, - private translate: TranslateService, - private fb: UntypedFormBuilder) { - super(store); - } - - ngOnInit(): void { - this.fixedColorLevelFormGroup = this.fb.group({ - from: [null, []], - to: [null, []], - color: [null, [Validators.required]] - }); - this.fixedColorLevelFormGroup.valueChanges.subscribe(() => { - this.updateModel(); - }); - } - - registerOnChange(fn: any): void { - this.propagateChange = fn; - } - - registerOnTouched(fn: any): void { - } - - setDisabledState(isDisabled: boolean): void { - this.disabled = isDisabled; - if (isDisabled) { - this.fixedColorLevelFormGroup.disable({emitEvent: false}); - } else { - this.fixedColorLevelFormGroup.enable({emitEvent: false}); - } - } - - writeValue(value: FixedColorLevel): void { - this.modelValue = value; - this.fixedColorLevelFormGroup.patchValue( - value, {emitEvent: false} - ); - } - - fixedColorLevelRangeText(): string { - const value: FixedColorLevel = this.fixedColorLevelFormGroup.value; - const from = this.valueSourcePropertyText(value?.from); - const to = this.valueSourcePropertyText(value?.to); - return `${from} - ${to}`; - } - - private valueSourcePropertyText(source?: ValueSourceProperty): string { - if (source) { - if (source.valueSource === 'predefinedValue') { - return `${isNumber(source.value) ? source.value : 0}`; - } else if (source.valueSource === 'entityAttribute') { - const alias = source.entityAlias || 'Undefined'; - const key = source.attribute || 'Undefined'; - return `${alias}.${key}`; - } - } - return 'Undefined'; - } - - private updateModel() { - const value: FixedColorLevel = this.fixedColorLevelFormGroup.value; - this.modelValue = value; - this.propagateChange(this.modelValue); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html index c91880c432..1947ba701e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html @@ -15,30 +15,15 @@ limitations under the License. --> - - -
- -
-
{{ tickValueText() }}
-
-
- - -
-
- -
- -
- -
-
-
-
+ +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts index 9a7bb98c1c..0b8983786d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts @@ -16,18 +16,17 @@ import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component'; import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; -import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { TranslateService } from '@ngx-translate/core'; -import { isNumber } from '@core/utils'; import { IAliasController } from '@core/api/widget-api.models'; @Component({ selector: 'tb-tick-value', templateUrl: './tick-value.component.html', - styleUrls: ['./tick-value.component.scss'], + styleUrls: [], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -41,9 +40,6 @@ export class TickValueComponent extends PageComponent implements OnInit, Control @Input() disabled: boolean; - @Input() - expanded = false; - @Input() aliasController: IAliasController; @@ -94,24 +90,6 @@ export class TickValueComponent extends PageComponent implements OnInit, Control ); } - tickValueText(): string { - const value: ValueSourceProperty = this.tickValueFormGroup.get('tickValue').value; - return this.valueSourcePropertyText(value); - } - - private valueSourcePropertyText(source?: ValueSourceProperty): string { - if (source) { - if (source.valueSource === 'predefinedValue') { - return `${isNumber(source.value) ? source.value : 0}`; - } else if (source.valueSource === 'entityAttribute') { - const alias = source.entityAlias || 'Undefined'; - const key = source.attribute || 'Undefined'; - return `${alias}.${key}`; - } - } - return 'Undefined'; - } - private updateModel() { const value: ValueSourceProperty = this.tickValueFormGroup.get('tickValue').value; this.modelValue = value; 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 index ed79ec1820..6c7a49bce5 100644 --- 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 @@ -48,18 +48,34 @@ [autoScale]="batteryLevelWidgetSettingsForm.get('autoScaleValueSize').value" [previewText]="valuePreviewFn"> - +
{{ 'widgets.battery-level.battery-level-color' | translate }}
- +
{{ 'widgets.battery-level.battery-shape-color' | translate }}
- +
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 5b83f859e1..3afd0bd2bd 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 @@ -74,7 +74,6 @@ import { import { DigitalGaugeWidgetSettingsComponent } from '@home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component'; -import { FixedColorLevelComponent } from '@home/components/widget/lib/settings/gauge/fixed-color-level.component'; import { TickValueComponent } from '@home/components/widget/lib/settings/gauge/tick-value.component'; import { FlotWidgetSettingsComponent } from '@home/components/widget/lib/settings/chart/flot-widget-settings.component'; import { @@ -364,7 +363,6 @@ import { AnalogueLinearGaugeWidgetSettingsComponent, AnalogueCompassWidgetSettingsComponent, DigitalGaugeWidgetSettingsComponent, - FixedColorLevelComponent, TickValueComponent, FlotWidgetSettingsComponent, LabelDataKeyComponent, @@ -492,7 +490,6 @@ import { AnalogueLinearGaugeWidgetSettingsComponent, AnalogueCompassWidgetSettingsComponent, DigitalGaugeWidgetSettingsComponent, - FixedColorLevelComponent, TickValueComponent, FlotWidgetSettingsComponent, LabelDataKeyComponent, diff --git a/ui-ngx/src/app/shared/components/color-input.component.html b/ui-ngx/src/app/shared/components/color-input.component.html index 29df75d8d6..d57824b443 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.html +++ b/ui-ngx/src/app/shared/components/color-input.component.html @@ -38,6 +38,7 @@
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 index 8244fc85db..c9a5bf176a 100644 --- 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 @@ -22,6 +22,7 @@ import { BasicWidgetConfigComponent } from '@home/components/widget/config/widge import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { DataKey, + Datasource, datasourcesHasAggregation, datasourcesHasOnlyComparisonAggregation, WidgetConfig, @@ -74,6 +75,15 @@ export class BatteryLevelBasicConfigComponent extends BasicWidgetConfigComponent return [BatteryLevelLayout.vertical_divided, BatteryLevelLayout.horizontal_divided].includes(layout); } + public get datasource(): Datasource { + const datasources: Datasource[] = this.widgetConfig.config.datasources; + if (datasources && datasources.length) { + return datasources[0]; + } else { + return null; + } + } + constructor(protected store: Store, protected widgetConfigComponent: WidgetConfigComponent, private fb: UntypedFormBuilder) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts index cdd17e3739..c2c59046fe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts @@ -17,7 +17,14 @@ import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge'; import { AnimationRule } from '@home/components/widget/lib/analogue-gauge.models'; import { FontSettings } from '@home/components/widget/lib/settings.models'; -import { ColorSettings } from '@shared/models/widget-settings.models'; +import { + AdvancedColorRange, + ColorSettings, + ValueSourceDataKeyType, + ValueSourceWithDataKey +} from '@shared/models/widget-settings.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { isDefinedAndNotNull } from '@core/utils'; export interface AttributeSourceProperty { valueSource: string; @@ -27,8 +34,8 @@ export interface AttributeSourceProperty { } export interface FixedLevelColors { - from?: AttributeSourceProperty | number; - to?: AttributeSourceProperty | number; + from?: ValueSourceWithDataKey | number; + to?: ValueSourceWithDataKey | number; color: string; } @@ -105,7 +112,7 @@ export interface DigitalGaugeSettings { hideValue?: boolean; hideMinMax?: boolean; showTicks?: boolean; - ticksValue?: AttributeSourceProperty[]; + ticksValue?: ValueSourceWithDataKey[]; ticks?: number[]; colorTicks?: string; tickWidth?: number; @@ -115,3 +122,41 @@ export const defaultDigitalSimpleGaugeOptions: DigitalGaugeSettings = { gaugeType: DigitalGaugeType.donut, timestampFormat: 'yyyy-MM-dd HH:mm:ss', }; + +export const backwardCompatibilityFixedLevelColors = (fixedLevelColors) => { + const valueSourceWithDataKey: AdvancedColorRange[] = []; + fixedLevelColors.forEach(fixedLevelColor => valueSourceWithDataKey.push({ + from: { + type: fixedLevelColor?.from?.valueSource === 'predefinedValue' ? ValueSourceDataKeyType.constant : ValueSourceDataKeyType.entity, + value: fixedLevelColor?.from?.value || null, + entityAlias: fixedLevelColor?.from?.entityAlias || '', + entityKey: fixedLevelColor?.from?.attribute || '', + entityKeyType: DataKeyType.attribute + }, + to: { + type: fixedLevelColor?.to?.valueSource === 'predefinedValue' ? ValueSourceDataKeyType.constant : ValueSourceDataKeyType.entity, + value: fixedLevelColor?.to?.value || null, + entityAlias: fixedLevelColor?.to?.entityAlias || '', + entityKey: fixedLevelColor?.to?.attribute || '', + entityKeyType: DataKeyType.attribute + }, + color: fixedLevelColor.color + }) ); + return valueSourceWithDataKey; +}; + +export const backwardCompatibilityTicks = (ticksValue) => { + const ticks: ValueSourceWithDataKey[] = []; + if (ticksValue?.length && isDefinedAndNotNull(ticksValue[0]?.valueSource)) { + ticksValue.forEach(tick => ticks.push({ + type: tick?.valueSource === 'predefinedValue' ? ValueSourceDataKeyType.constant : ValueSourceDataKeyType.entity, + value: tick?.value || null, + entityAlias: tick?.entityAlias || '', + entityKey: tick?.attribute || '', + entityKeyType: DataKeyType.attribute + }) ); + } else { + return ticksValue; + } + return ticks; +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts index ddd195a92d..1958a22cc4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts @@ -18,7 +18,6 @@ import * as CanvasGauges from 'canvas-gauges'; import { WidgetContext } from '@home/models/widget-component.models'; import { attributesGaugeType, - AttributeSourceProperty, ColorLevelSetting, DigitalGaugeSettings, FixedLevelColors @@ -32,7 +31,12 @@ import { DataKey, Datasource, DatasourceData, DatasourceType, widgetType } from import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { EMPTY, Observable } from 'rxjs'; -import { ColorType } from '@shared/models/widget-settings.models'; +import { + ColorProcessor, + ColorType, + ValueSourceDataKeyType, + ValueSourceWithDataKey +} from '@shared/models/widget-settings.models'; import GenericOptions = CanvasGauges.GenericOptions; // @dynamic @@ -212,9 +216,9 @@ export class TbCanvasDigitalGauge { throw new Error('Not valid entity aliase name ' + entityAlias); } - const datasource = datasources.find((datasourceIteration) => { - return datasourceIteration.entityAliasId === entityAliasId; - }); + const datasource = datasources.find((datasourceIteration) => + datasourceIteration.entityAliasId === entityAliasId + ); const dataKey: DataKey = { type: DataKeyType.attribute, @@ -225,9 +229,9 @@ export class TbCanvasDigitalGauge { }; if (datasource) { - const findDataKey = datasource.dataKeys.find((dataKeyIteration) => { - return dataKeyIteration.name === attribute; - }); + const findDataKey = datasource.dataKeys.find((dataKeyIteration) => + dataKeyIteration.name === attribute + ); if (findDataKey) { findDataKey.settings.push(settings); @@ -278,15 +282,16 @@ export class TbCanvasDigitalGauge { }); function setLevelColor(levelSetting, color: string) { - if (levelSetting.valueSource === 'predefinedValue' && isFinite(levelSetting.value)) { + if (levelSetting.type === ValueSourceDataKeyType.constant && isFinite(levelSetting.value)) { predefineLevelColors.push({ value: levelSetting.value, color }); - } else if (levelSetting.entityAlias && levelSetting.attribute) { + } else if (levelSetting.type === ValueSourceDataKeyType.latestKey || levelSetting.type === ValueSourceDataKeyType.entity) { try { - levelColorsDatasource = TbCanvasDigitalGauge.generateDatasource(this.ctx, levelColorsDatasource, - levelSetting.entityAlias, levelSetting.attribute, {color, index: predefineLevelColors.length}); + levelColorsDatasource = ColorProcessor.generateDatasource( + this.ctx, levelColorsDatasource, levelSetting, {color, index: predefineLevelColors.length} + ); } catch (e) { return; } @@ -315,17 +320,18 @@ export class TbCanvasDigitalGauge { return predefineLevelColors; } - settingTicksSubscribe(options: AttributeSourceProperty[]): number[] { + settingTicksSubscribe(options: ValueSourceWithDataKey[]): number[] { let ticksDatasource: Datasource[] = []; const predefineTicks: number[] = []; for (const tick of options) { - if (tick.valueSource === 'predefinedValue' && isFinite(tick.value)) { + if (tick.type === ValueSourceDataKeyType.constant && isFinite(tick.value)) { predefineTicks.push(tick.value); - } else if (tick.entityAlias && tick.attribute) { + } else { try { - ticksDatasource = TbCanvasDigitalGauge - .generateDatasource(this.ctx, ticksDatasource, tick.entityAlias, tick.attribute, predefineTicks.length); + ticksDatasource = ColorProcessor.generateDatasource( + this.ctx, ticksDatasource, tick, {index: predefineTicks.length} + ); } catch (e) { continue; } @@ -364,7 +370,7 @@ export class TbCanvasDigitalGauge { if (keyData && keyData.data && keyData.data[0]) { const attrValue = keyData.data[0][1]; if (isFinite(attrValue)) { - for (const setting of keyData.dataKey.settings) { + for (const setting of keyData.dataKey.settings.dataKeySettings) { switch (typeAttributes) { case 'levelColors': this.localSettings.levelColors[setting.index] = { @@ -373,7 +379,7 @@ export class TbCanvasDigitalGauge { }; break; case 'ticks': - this.localSettings.ticks[setting] = attrValue; + this.localSettings.ticks[setting.index] = attrValue; break; } } 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 index 9f0d69093a..429b59774b 100644 --- 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 @@ -28,7 +28,7 @@ import { ViewEncapsulation } from '@angular/core'; import { WidgetContext } from '@home/models/widget-component.models'; -import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; +import { formattedDataFormDatasourceData, formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; import { DatePipe } from '@angular/common'; import { backgroundStyle, @@ -46,9 +46,15 @@ import { BatteryLevelWidgetSettings } from '@home/components/widget/lib/indicator/battery-level-widget.models'; import { ResizeObserver } from '@juggle/resize-observer'; -import { Observable } from 'rxjs'; +import { forkJoin, Observable } from 'rxjs'; import { ImagePipe } from '@shared/pipe/image.pipe'; import { DomSanitizer } from '@angular/platform-browser'; +import { FormattedData } from '@shared/models/widget.models'; +import { + parseThresholdData, + TimeSeriesChartThresholdType +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { map } from 'rxjs/operators'; const verticalBatteryDimensions = { shapeAspectRatio: 64 / 113, @@ -201,6 +207,10 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView this.overlayStyle = overlayStyle(this.settings.background.overlay); this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0; + + this.valueColor.colorUpdated.subscribe(() => this.cd.markForCheck()); + this.batteryLevelColor.colorUpdated.subscribe(() => this.cd.markForCheck()); + this.batteryShapeColor.colorUpdated.subscribe(() => this.cd.markForCheck()); } ngAfterViewInit() { @@ -218,6 +228,11 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView if (this.batteryBoxResize$) { this.batteryBoxResize$.disconnect(); } + + this.valueColor.colorUpdated.unsubscribe(); + this.batteryLevelColor.colorUpdated.unsubscribe(); + this.batteryShapeColor.colorUpdated.unsubscribe(); + this.batteryLevelColor.destroy(); this.valueColor.destroy(); this.batteryShapeColor.destroy(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html index 34ac29be1d..4bd50a81c6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html @@ -20,21 +20,21 @@
widgets.color.from
- - + +
widgets.color.to
- - + +
-
- -
-
-
-
-
widgets.color.from
- - - -
widgets.color.to
- - - - - +
+ +
+
+
+
+
widgets.color.from
+ + + +
widgets.color.to
+ + + + + +
+ +
- +
+
+
+ + +
+
+ +
-
- - - -
-
- - - -
-
-
+ -
- +
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts index db5ac7c91b..e37186748d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts @@ -26,7 +26,12 @@ import { UntypedFormGroup, ValidationErrors } from '@angular/forms'; -import { AdvancedColorRange, ColorRange, ColorRangeSettings } from '@shared/models/widget-settings.models'; +import { + AdvancedColorRange, + ColorRange, + ColorRangeSettings, + ValueSourceDataKeyType +} from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -34,6 +39,8 @@ import { deepClone, isDefinedAndNotNull, isUndefined } from '@core/utils'; import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { IAliasController } from '@core/api/widget-api.models'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { Datasource } from '@shared/models/widget.models'; export function advancedRangeValidator(control: AbstractControl): ValidationErrors | null { const range: AdvancedColorRange = control.value; @@ -71,6 +78,12 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On @Input() aliasController: IAliasController; + @Input() + dataKeyCallbacks: DataKeysCallbacks; + + @Input() + datasource: Datasource; + @Input() @coerceBoolean() advancedMode = false; @@ -184,10 +197,10 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On public addAdvancedRange() { const advancedRange: AdvancedColorRange = { from: { - valueSource: 'predefinedValue' + type: ValueSourceDataKeyType.constant }, to: { - valueSource: 'predefinedValue' + type: ValueSourceDataKeyType.constant }, color: null }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html index 2b1db278bf..f7df367657 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html @@ -40,6 +40,8 @@ [maxValue]="maxValue" [advancedMode]="gradientAdvancedMode" [aliasController]="aliasController" + [dataKeyCallbacks]="dataKeyCallbacks" + [datasource]="datasource" [popover]="popover">
@@ -49,6 +51,8 @@ panelTitle="{{ 'widgets.color.value-range' | translate }}" [advancedMode]="rangeAdvancedMode" [aliasController]="aliasController" + [dataKeyCallbacks]="dataKeyCallbacks" + [datasource]="datasource" [popover]="popover">
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts index d873b4ba7b..73b6cacf2e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts @@ -16,7 +16,12 @@ import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; -import { ColorSettings, ColorType, colorTypeTranslations } from '@shared/models/widget-settings.models'; +import { + ColorSettings, + ColorType, + colorTypeTranslations, + defaultGradient +} from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { Store } from '@ngrx/store'; @@ -26,6 +31,8 @@ import { WidgetService } from '@core/http/widget.service'; import { ColorSettingsComponent } from '@home/components/widget/lib/settings/common/color-settings.component'; import { IAliasController } from '@core/api/widget-api.models'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { Datasource } from '@shared/models/widget.models'; @Component({ selector: 'tb-color-settings-panel', @@ -51,6 +58,12 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit @Input() aliasController: IAliasController; + @Input() + dataKeyCallbacks: DataKeysCallbacks; + + @Input() + datasource: Datasource; + @Input() @coerceBoolean() rangeAdvancedMode = false; @@ -86,7 +99,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit { type: [this.colorSettings?.type || ColorType.constant, []], color: [this.colorSettings?.color, []], - gradient: [this.colorSettings?.gradient, []], + gradient: [this.colorSettings?.gradient || defaultGradient(), []], rangeList: [this.colorSettings?.rangeList, []], colorFunction: [this.colorSettings?.colorFunction, []] } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts index 1e45481389..7594719cff 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts @@ -34,6 +34,8 @@ import { import { IAliasController } from '@core/api/widget-api.models'; import { deepClone, isDefinedAndNotNull } from '@core/utils'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { Datasource } from '@shared/models/widget.models'; @Injectable() export class ColorSettingsComponentService { @@ -85,6 +87,12 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD @Input() aliasController: IAliasController; + @Input() + dataKeyCallbacks: DataKeysCallbacks; + + @Input() + datasource: Datasource; + @Input() @coerceBoolean() rangeAdvancedMode = false; @@ -155,6 +163,8 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD colorSettings: this.modelValue, settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this), aliasController: this.aliasController, + dataKeyCallbacks: this.dataKeyCallbacks, + datasource: this.datasource, rangeAdvancedMode: this.rangeAdvancedMode, gradientAdvancedMode: this.gradientAdvancedMode, minValue: this.minValue, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss index f8564a46ac..43dbc042cd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss @@ -28,8 +28,8 @@ padding-right: 0; .mat-mdc-form-field-infix { - padding-top: 0; - padding-bottom: 6px; + padding-top: 0 !important; + padding-bottom: 6px !important; .mdc-evolution-chip-set .mdc-evolution-chip { margin: 0; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html index 5fe3d9c025..2296aaf4e4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html @@ -40,7 +40,7 @@
widgets.color.gradient-start
-
+
widgets.color.start-value
@@ -98,7 +98,7 @@
widgets.color.gradient-end
-
+
widgets.color.end-value
@@ -118,13 +118,13 @@ widgets.color.gradient-start widgets.color.gradient-start-min
-
- - +
+ + @@ -146,12 +146,12 @@ widgets.color.gradient-color-min
- - + + @@ -190,13 +190,13 @@ widgets.color.gradient-end widgets.color.gradient-end-min
-
- - +
+ + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts index ea17f19637..35e51f6c98 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts @@ -24,7 +24,7 @@ import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; -import { AdvancedGradient, ColorGradientSettings } from '@shared/models/widget-settings.models'; +import { AdvancedGradient, ColorGradientSettings, ValueSourceDataKeyType } from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -33,6 +33,8 @@ import { IAliasController } from '@core/api/widget-api.models'; import { DomSanitizer } from '@angular/platform-browser'; import { coerceBoolean } from '@shared/decorators/coercion'; import { isDefinedAndNotNull } from '@core/utils'; +import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { Datasource } from '@shared/models/widget.models'; @Component({ selector: 'tb-gradient', @@ -62,6 +64,12 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro @Input() aliasController: IAliasController; + @Input() + dataKeyCallbacks: DataKeysCallbacks; + + @Input() + datasource: Datasource; + @Input() minValue: string; @@ -84,6 +92,7 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro private sanitizer: DomSanitizer) {} ngOnInit(): void { + console.log(this.minValue, this.maxValue); this.gradientFormGroup = this.fb.group({ advancedMode: [false], gradient: this.fb.group({ @@ -93,12 +102,12 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro }), gradientAdvanced: this.fb.group({ start: this.fb.group({ - source: [], + source: [{type: ValueSourceDataKeyType.constant}], color: ['rgba(0, 255, 0, 1)'] }), gradientList: this.fb.array([]), end: this.fb.group({ - source: [], + source: [{type: ValueSourceDataKeyType.constant}], color: ['rgba(255, 0, 0, 1)'] }) }) @@ -147,26 +156,27 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro get generatePointers() { if (this.gradientFormGroup.get('advancedMode').value) { - const point = 100 / (this.advancedGradientListFormArray.value.length + 1); + const shift = 100 / (this.advancedGradientListFormArray.value.length + 1); return `
` + - this.advancedGradientListFormArray.value.map((v, i) => this.pointer(point * (i + 1), true)).join('') + + this.advancedGradientListFormArray.value.map((v, i) => this.pointer(shift * (i + 1), i+1, null, true)).join('') + `
`; } else { - const point = 100 / (this.gradientListFormArray.value.length + 1); + const point = (+this.maxValue - +this.minValue) / (this.gradientListFormArray.value.length + 1); + const shift = 100 / (this.gradientListFormArray.value.length + 1); const min = isDefinedAndNotNull(this.minValue) ? this.minValue : 0; const max = isDefinedAndNotNull(this.maxValue) ? this.maxValue : 100; return `
${min}
` + - this.gradientListFormArray.value.map((v, i) => this.pointer(point * (i + 1))).join('') + + this.gradientListFormArray.value.map((v, i) => this.pointer(shift * (i + 1), i+1, point)).join('') + `
${max}
`; } } - pointer(value?: number, advanced = false) { + pointer(shift: number, index?: number, value?: number, advanced = false) { if (advanced) { - return `
`; + return `
`; } else { - return `
` + - `
${Math.floor((+this.maxValue*value)/100)}
`; + return `
` + + `
${Math.floor(+this.minValue + (value * index))}
`; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.html new file mode 100644 index 0000000000..be6faaefaf --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.html @@ -0,0 +1,77 @@ + +
+
+ + + + {{ valueSourceDataKeyTypeTranslation.get(type) | translate }} + + + + + +
+
+ + + + warning + + + + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.scss new file mode 100644 index 0000000000..5d769d867a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.scss @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2024 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-value-source { + display: flex; + gap: 12px; + } + .tb-source-field { + display: flex; + flex: 1; + gap: 12px; + .tb-type-field, .tb-entity-alias-field { + flex: 1; + } + } + .tb-key-value-field, .tb-constant-field { + display: flex; + flex: 1; + } + ::ng-deep { + .tb-data-key-input { + flex: 1; + } + } +} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.ts new file mode 100644 index 0000000000..ed5f8eadf5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.ts @@ -0,0 +1,191 @@ +/// +/// Copyright © 2016-2024 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 { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { IAliasController } from '@core/api/widget-api.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models'; +import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { + ValueSourceDataKeyType, + ValueSourceDataKeyTypes, + ValueSourceDataKeyTypeTranslation +} from '@shared/models/widget-settings.models'; + +@Component({ + selector: 'tb-value-source-data-key', + templateUrl: './value-source-data-key.component.html', + styleUrls: ['value-source-data-key.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ValueSourceDataKeyComponent), + multi: true + } + ] +}) +export class ValueSourceDataKeyComponent extends PageComponent implements OnInit, ControlValueAccessor { + + dataKeyType = DataKeyType; + datasourceType = DatasourceType; + + valueSourceDataKeyType = ValueSourceDataKeyType; + valueSourceDataKeyTypes = ValueSourceDataKeyTypes; + valueSourceDataKeyTypeTranslation = ValueSourceDataKeyTypeTranslation; + + @Input() + disabled: boolean; + + @Input() + aliasController: IAliasController; + + @Input() + dataKeyCallbacks: DataKeysCallbacks; + + @Input() + datasource: Datasource; + + valueSourceFormGroup: UntypedFormGroup; + + modelValue; + + latestKeyFormControl: UntypedFormControl; + entityKeyFormControl: UntypedFormControl; + + private propagateChange = (_val: any) => {}; + + constructor(protected store: Store, + private fb: UntypedFormBuilder, + private cd: ChangeDetectorRef) { + super(store); + } + + ngOnInit(): void { + this.valueSourceFormGroup = this.fb.group({ + type: [ValueSourceDataKeyType.constant, []], + value: [null, [Validators.required]], + entityAlias: [null, [Validators.required]] + }); + this.latestKeyFormControl = this.fb.control(null, [Validators.required]); + this.entityKeyFormControl = this.fb.control(null, [Validators.required]); + this.valueSourceFormGroup.valueChanges.subscribe( + () => this.updateModel() + ); + this.latestKeyFormControl.valueChanges.subscribe( + () => this.updateModel() + ); + this.entityKeyFormControl.valueChanges.subscribe( + () => this.updateModel() + ); + this.valueSourceFormGroup.get('type').valueChanges.subscribe(() => { + this.updateValidators(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.valueSourceFormGroup.disable({emitEvent: false}); + this.latestKeyFormControl.disable({emitEvent: false}); + this.entityKeyFormControl.disable({emitEvent: false}); + } else { + this.valueSourceFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(value): void { + this.modelValue = value; + this.valueSourceFormGroup.patchValue( + { + type: value.type, + value: value.value, + entityAlias: value.entityAlias + }, {emitEvent: false} + ); + if (value.type === ValueSourceDataKeyType.latestKey) { + this.latestKeyFormControl.patchValue({ + type: value.latestKeyType, + name: value.latestKey + }, {emitEvent: false}); + } else if (value.type === ValueSourceDataKeyType.entity) { + this.entityKeyFormControl.patchValue({ + type: value.entityKeyType, + name: value.entityKey + }, {emitEvent: false}); + } + + this.updateValidators(); + this.cd.markForCheck(); + } + + private updateModel() { + const value = this.valueSourceFormGroup.value; + this.modelValue.type = value.type; + this.modelValue.value = value.value; + this.modelValue.entityAlias = value.entityAlias; + + if (value.type === ValueSourceDataKeyType.latestKey) { + const latestKey: DataKey = this.latestKeyFormControl.value; + this.modelValue.latestKey = latestKey?.name; + this.modelValue.latestKeyType = (latestKey?.type as any); + } else if (value.type === ValueSourceDataKeyType.entity) { + const entityKey: DataKey = this.entityKeyFormControl.value; + this.modelValue.entityKey = entityKey?.name; + this.modelValue.entityKeyType = (entityKey?.type as any); + } + this.propagateChange(this.modelValue); + } + + private updateValidators(): void { + const type: ValueSourceDataKeyType = this.valueSourceFormGroup.get('type').value; + if (type === ValueSourceDataKeyType.constant) { + this.valueSourceFormGroup.get('value').enable({emitEvent: false}); + this.valueSourceFormGroup.get('entityAlias').disable({emitEvent: false}); + this.latestKeyFormControl.disable({emitEvent: false}); + this.entityKeyFormControl.disable({emitEvent: false}); + } else if (type === ValueSourceDataKeyType.latestKey) { + this.valueSourceFormGroup.get('value').disable({emitEvent: false}); + this.valueSourceFormGroup.get('entityAlias').disable({emitEvent: false}); + this.latestKeyFormControl.enable({emitEvent: false}); + this.entityKeyFormControl.disable({emitEvent: false}); + } else if (type === ValueSourceDataKeyType.entity) { + this.valueSourceFormGroup.get('value').disable({emitEvent: false}); + this.valueSourceFormGroup.get('entityAlias').enable({emitEvent: false}); + this.latestKeyFormControl.disable({emitEvent: false}); + this.entityKeyFormControl.enable({emitEvent: false}); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index 75e4874b23..781042a4cf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -152,6 +152,9 @@ import { import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/common/chart/chart-bar-settings.component'; import { AdvancedRangeComponent } from '@home/components/widget/lib/settings/common/advanced-range.component'; import { GradientComponent } from '@home/components/widget/lib/settings/common/gradient.component'; +import { + ValueSourceDataKeyComponent +} from '@home/components/widget/lib/settings/common/value-source-data-key.component'; @NgModule({ declarations: [ @@ -170,6 +173,7 @@ import { GradientComponent } from '@home/components/widget/lib/settings/common/g BackgroundSettingsComponent, BackgroundSettingsPanelComponent, ValueSourceComponent, + ValueSourceDataKeyComponent, LegendConfigComponent, WidgetFontComponent, CountWidgetSettingsComponent, @@ -233,6 +237,7 @@ import { GradientComponent } from '@home/components/widget/lib/settings/common/g BackgroundSettingsComponent, BackgroundSettingsPanelComponent, ValueSourceComponent, + ValueSourceDataKeyComponent, LegendConfigComponent, WidgetFontComponent, CountWidgetSettingsComponent, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html index 6c264d7a18..86cd0ff825 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html @@ -32,38 +32,36 @@ -
+
{{ 'widgets.gauge.donut-start-angle' | translate }}
- -
- - {{ 'widgets.gauge.min-and-max-value' | translate }} - -
-
widgets.gauge.min-value-short
- - - -
widgets.gauge.max-value-short
- - - - - - - -
+
+ + {{ 'widgets.gauge.min-and-max-value' | translate }} + +
+
widgets.gauge.min-value-short
+ + + +
widgets.gauge.max-value-short
+ + + + + + +
- +
@@ -185,6 +183,8 @@ [minValue]="digitalGaugeWidgetSettingsForm.get('minValue').value" [maxValue]="digitalGaugeWidgetSettingsForm.get('maxValue').value" [aliasController]="aliasController" + [dataKeyCallbacks]="dataKeyCallbacks" + [datasource]="datasource" settingsKey="{{'widgets.battery-level.value' | translate }}">
@@ -225,6 +225,8 @@ let $index = index; last as isLast;" fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts index ac299cee5c..579e01b830 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts @@ -14,22 +14,30 @@ /// limitations under the License. /// -import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { Datasource, WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; import { Component } from '@angular/core'; import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge'; import { CdkDragDrop } from '@angular/cdk/drag-drop'; -import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component'; import { + backwardCompatibilityFixedLevelColors, + backwardCompatibilityTicks, digitalGaugeLayoutImages, digitalGaugeLayouts, digitalGaugeLayoutTranslations, DigitalGaugeType } from '@home/components/widget/lib/digital-gauge.models'; -import { deepClone, formatValue } from '@core/utils'; -import { ColorSettings, ColorType, constantColor, simpleDateFormat } from '@shared/models/widget-settings.models'; +import { formatValue } from '@core/utils'; +import { + ColorSettings, + ColorType, + constantColor, + simpleDateFormat, + ValueSourceDataKeyType, + ValueSourceWithDataKey +} from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-digital-gauge-widget-settings', @@ -49,6 +57,15 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent valuePreviewFn = this._valuePreviewFn.bind(this, true); previewFn = this._valuePreviewFn.bind(this, false); + public get datasource(): Datasource { + const datasources: Datasource[] = this.widgetConfig.config.datasources; + if (datasources && datasources.length) { + return datasources[0]; + } else { + return null; + } + } + constructor(protected store: Store, protected fb: UntypedFormBuilder) { super(store); @@ -120,16 +137,14 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent } protected onSettingsSet(settings: WidgetSettings) { - - if (!settings.barColor) { - settings.barColor = constantColor(null); + settings.barColor = constantColor(settings.gaugeColor); if (settings.fixedLevelColors.length) { settings.barColor.rangeList = { advancedMode: settings.useFixedLevelColor, range: null, - rangeAdvanced: deepClone(settings.fixedLevelColors) + rangeAdvanced: backwardCompatibilityFixedLevelColors(settings.fixedLevelColors) }; } if (settings.levelColors.length) { @@ -184,7 +199,7 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent showTicks: [settings.showTicks, []], tickWidth: [settings.tickWidth, [Validators.min(0)]], colorTicks: [settings.colorTicks, []], - ticksValue: this.prepareTicksValueFormArray(settings.ticksValue), + ticksValue: this.prepareTicksValueFormArray(backwardCompatibilityTicks(settings.ticksValue)), animation: [settings.animation, []], animationDuration: [settings.animationDuration, [Validators.min(0)]], @@ -229,11 +244,32 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent const showTicks: boolean = this.digitalGaugeWidgetSettingsForm.get('showTicks').value; const animation: boolean = this.digitalGaugeWidgetSettingsForm.get('animation').value; + if (gaugeType === 'donut') { this.digitalGaugeWidgetSettingsForm.get('donutStartAngle').enable(); + + this.digitalGaugeWidgetSettingsForm.get('showMinMax').disable({emitEvent: false}); + this.digitalGaugeWidgetSettingsForm.get('minValue').enable({emitEvent: false}); + this.digitalGaugeWidgetSettingsForm.get('maxValue').enable({emitEvent: false}); + this.digitalGaugeWidgetSettingsForm.get('minMaxFont').disable({emitEvent: false}); + this.digitalGaugeWidgetSettingsForm.get('minMaxColor').disable({emitEvent: false}); } else { this.digitalGaugeWidgetSettingsForm.get('donutStartAngle').disable(); + + this.digitalGaugeWidgetSettingsForm.get('showMinMax').enable({emitEvent: false}); + if (showMinMax) { + this.digitalGaugeWidgetSettingsForm.get('minValue').enable(); + this.digitalGaugeWidgetSettingsForm.get('maxValue').enable(); + this.digitalGaugeWidgetSettingsForm.get('minMaxFont').enable(); + this.digitalGaugeWidgetSettingsForm.get('minMaxColor').enable(); + } else { + this.digitalGaugeWidgetSettingsForm.get('minValue').disable(); + this.digitalGaugeWidgetSettingsForm.get('maxValue').disable(); + this.digitalGaugeWidgetSettingsForm.get('minMaxFont').disable(); + this.digitalGaugeWidgetSettingsForm.get('minMaxColor').disable(); + } } + if (showTitle) { this.digitalGaugeWidgetSettingsForm.get('title').enable(); this.digitalGaugeWidgetSettingsForm.get('titleFont').enable(); @@ -267,17 +303,6 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent this.digitalGaugeWidgetSettingsForm.get('valueFont').disable(); this.digitalGaugeWidgetSettingsForm.get('valueColor').disable(); } - if (showMinMax) { - this.digitalGaugeWidgetSettingsForm.get('minValue').enable(); - this.digitalGaugeWidgetSettingsForm.get('maxValue').enable(); - this.digitalGaugeWidgetSettingsForm.get('minMaxFont').enable(); - this.digitalGaugeWidgetSettingsForm.get('minMaxColor').enable(); - } else { - this.digitalGaugeWidgetSettingsForm.get('minValue').disable(); - this.digitalGaugeWidgetSettingsForm.get('maxValue').disable(); - this.digitalGaugeWidgetSettingsForm.get('minMaxFont').disable(); - this.digitalGaugeWidgetSettingsForm.get('minMaxColor').disable(); - } if (showTicks) { this.digitalGaugeWidgetSettingsForm.get('tickWidth').enable(); this.digitalGaugeWidgetSettingsForm.get('colorTicks').enable(); @@ -323,7 +348,7 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent settingsForm.setControl('ticksValue', this.prepareTicksValueFormArray(settings.ticksValue), {emitEvent: false}); } - private prepareTicksValueFormArray(ticksValue: ValueSourceProperty[] | undefined): UntypedFormArray { + private prepareTicksValueFormArray(ticksValue: ValueSourceWithDataKey[] | undefined): UntypedFormArray { const ticksValueControls: Array = []; if (ticksValue) { ticksValue.forEach((tickValue) => { @@ -346,8 +371,8 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent } public addTickValue() { - const tickValue: ValueSourceProperty = { - valueSource: 'predefinedValue' + const tickValue: ValueSourceWithDataKey = { + type: ValueSourceDataKeyType.constant }; const tickValuesArray = this.digitalGaugeWidgetSettingsForm.get('ticksValue') as UntypedFormArray; const tickValueControl = this.fb.control(tickValue, []); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html index 1947ba701e..7155ad8d5e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html @@ -17,7 +17,12 @@ -->
- + +
@@ -64,6 +66,8 @@ minValue="0" maxValue="100" [aliasController]="aliasController" + [dataKeyCallbacks]="dataKeyCallbacks" + [datasource]="datasource" settingsKey="{{'widgets.battery-level.battery-level-color' | translate }}">
@@ -75,6 +79,8 @@ minValue="0" maxValue="100" [aliasController]="aliasController" + [dataKeyCallbacks]="dataKeyCallbacks" + [datasource]="datasource" settingsKey="{{'widgets.battery-level.battery-shape-color' | 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 index fb0e2b540c..2b27a40eff 100644 --- 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 @@ -15,7 +15,7 @@ /// import { Component, Injector } from '@angular/core'; -import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { Datasource, WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -48,6 +48,15 @@ export class BatteryLevelWidgetSettingsComponent extends WidgetSettingsComponent return [BatteryLevelLayout.vertical_divided, BatteryLevelLayout.horizontal_divided].includes(layout); } + public get datasource(): Datasource { + const datasources: Datasource[] = this.widgetConfig.config.datasources; + if (datasources && datasources.length) { + return datasources[0]; + } else { + return null; + } + } + constructor(protected store: Store, private $injector: Injector, private fb: UntypedFormBuilder) { diff --git a/ui-ngx/src/app/shared/models/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts index 94c60465d4..917ed24feb 100644 --- a/ui-ngx/src/app/shared/models/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -14,15 +14,7 @@ /// limitations under the License. /// -import { - isDefinedAndNotNull, - isNumber, - isNumeric, - isString, - isUndefinedOrNull, - mergeDeep, - parseFunction -} from '@core/utils'; +import { isDefinedAndNotNull, isNumber, isNumeric, isUndefinedOrNull, mergeDeep, parseFunction } from '@core/utils'; import { DataEntry, DataKey, @@ -30,9 +22,10 @@ import { DatasourceData, DatasourceType, TargetDevice, - TargetDeviceType, widgetType + TargetDeviceType, + widgetType } from '@shared/models/widget.models'; -import { ChangeDetectorRef, Injector } from '@angular/core'; +import { ChangeDetectorRef, EventEmitter, Injector } from '@angular/core'; import { DatePipe } from '@angular/common'; import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; import { TranslateService } from '@ngx-translate/core'; @@ -44,17 +37,10 @@ import { map } from 'rxjs/operators'; import { DomSanitizer } from '@angular/platform-browser'; import { AVG_MONTH, DAY, HOUR, Interval, IntervalMath, MINUTE, SECOND, YEAR } from '@shared/models/time/time.models'; import moment from 'moment'; -import { - attributesGaugeType, - ColorLevelSetting, - FixedLevelColors -} from '@home/components/widget/lib/digital-gauge.models'; import tinycolor from 'tinycolor2'; -import { DigitalGaugeColorRange } from '@home/components/widget/lib/canvas-digital-gauge'; import { WidgetContext } from '@home/models/widget-component.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; -import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component'; export type ComponentStyle = {[klass: string]: any}; @@ -120,9 +106,35 @@ export interface ColorRange { color: string; } +export enum ValueSourceDataKeyType { + constant = 'constant', + latestKey = 'latestKey', + entity = 'entity' +} + +export const ValueSourceDataKeyTypes = Object.keys(ValueSourceDataKeyType) as ValueSourceDataKeyType[]; + +export const ValueSourceDataKeyTypeTranslation = new Map( + [ + [ValueSourceDataKeyType.constant, 'widgets.value-source.type-constant'], + [ValueSourceDataKeyType.latestKey, 'widgets.value-source.type-latest-key'], + [ValueSourceDataKeyType.entity, 'widgets.value-source.type-entity'] + ] +); + +export interface ValueSourceWithDataKey { + type: ValueSourceDataKeyType; + value?: number; + latestKeyType?: DataKeyType.attribute | DataKeyType.timeseries; + latestKey?: string; + entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries; + entityAlias?: string; + entityKey?: string; +} + export interface AdvancedColorRange { - from?: ValueSourceProperty; - to?: ValueSourceProperty; + from?: ValueSourceWithDataKey; + to?: ValueSourceWithDataKey; color: string; } @@ -214,7 +226,7 @@ export interface ColorRangeSettings { } export interface AdvancedGradient { - source: ValueSourceProperty; + source: ValueSourceWithDataKey; color: string; } @@ -224,6 +236,11 @@ export interface ColorGradientSettings { gradientAdvanced?: Array; } +export interface DataKeySettings { + color?: string; + index: number; +} + export interface TimewindowStyle { showIcon: boolean; icon: string; @@ -248,6 +265,21 @@ export const constantColor = (color: string): ColorSettings => ({ colorFunction: defaultColorFunction }); +export const defaultGradient = (): ColorGradientSettings => ({ + advancedMode: false, + gradient: ['rgba(0, 255, 0, 1)', 'rgba(255, 0, 0, 1)'], + gradientAdvanced: [ + { + source: {type: ValueSourceDataKeyType.constant}, + color: 'rgba(0, 255, 0, 1)' + }, + { + source: {type: ValueSourceDataKeyType.constant}, + color: 'rgba(255, 0, 0, 1)' + } + ] +}); + export const defaultColorFunction = 'var temperature = value;\n' + 'if (typeof temperature !== undefined) {\n' + ' var percent = (temperature + 60)/120 * 100;\n' + @@ -293,7 +325,7 @@ type ValueColorFunction = (value: any) => string; export abstract class ColorProcessor { - static fromSettings(color: ColorSettings, ctx?: WidgetContext, cd?: ChangeDetectorRef): ColorProcessor { + static fromSettings(color: ColorSettings, ctx?: WidgetContext, cd?: ChangeDetectorRef, maxValue = 100): ColorProcessor { const settings = color || constantColor(null); switch (settings.type) { case ColorType.constant: @@ -303,7 +335,7 @@ export abstract class ColorProcessor { case ColorType.function: return new FunctionColorProcessor(settings); case ColorType.gradient: - return new GradientColorProcessor(settings, ctx, cd); + return new GradientColorProcessor(settings, ctx, cd, maxValue); default: return new ConstantColorProcessor(settings); } @@ -311,6 +343,8 @@ export abstract class ColorProcessor { color: string; + colorUpdated: EventEmitter = new EventEmitter(); + protected constructor(protected settings: ColorSettings) { this.color = settings.color; } @@ -318,42 +352,70 @@ export abstract class ColorProcessor { abstract update(value: any): void; abstract destroy(): void; - generateDatasource(ctx: WidgetContext, datasources: Datasource[], entityAlias: string, - attribute: string, settings: any): Datasource[]{ - const entityAliasId = ctx.aliasController.getEntityAliasId(entityAlias); - if (!entityAliasId) { - throw new Error('Not valid entity aliase name ' + entityAlias); + static generateDatasource( + ctx: WidgetContext, + datasources: Datasource[], + valueSource: ValueSourceWithDataKey, + settings: DataKeySettings): Datasource[]{ + if (valueSource.type === ValueSourceDataKeyType.latestKey) { + if (ctx.datasources.length) { + if (isDefinedAndNotNull(ctx.datasources[0].aliasName)) { + datasources = this.parseDatasource(ctx, valueSource, ctx.datasources[0].aliasName, settings, datasources, true); + } else { + datasources = this.parseDatasource(ctx, valueSource, '', settings, datasources, true); + } + } + } else if (valueSource.type === ValueSourceDataKeyType.entity) { + datasources = this.parseDatasource(ctx, valueSource, valueSource.entityAlias, settings, datasources, false); } + return datasources; + } - const datasource = datasources.find((datasourceIteration) => datasourceIteration.entityAliasId === entityAliasId); - - const dataKey: DataKey = { - type: DataKeyType.attribute, - name: attribute, - label: attribute, - settings: [settings], - _hash: Math.random() + static parseDatasource(ctx: WidgetContext, + valueSource: ValueSourceWithDataKey, + entityAlias: string, settings: DataKeySettings, + datasources: Datasource[], + isLatest: boolean) { + let dataKey: DataKey = null; + const dataKeySettings = [settings]; + const entityAliasId = ctx.aliasController.getEntityAliasId(entityAlias); + let datasource = datasources.find(d => + entityAlias ? (d.entityAliasId === entityAliasId) : (d.deviceId === ctx.datasources[0].deviceId) + ); + dataKey = { + type: isLatest ? valueSource.latestKeyType : valueSource.entityKeyType, + name: isLatest ? valueSource.latestKey : valueSource.entityKey, + label: isLatest ? valueSource.latestKey : valueSource.entityKey, + settings: {dataKeySettings} }; - if (datasource) { - const findDataKey = datasource.dataKeys.find((dataKeyIteration) => dataKeyIteration.name === attribute); - + const findDataKey = datasource.dataKeys.find(dataKeyIteration => + dataKeyIteration.name === (isLatest ? valueSource.latestKey : valueSource.entityKey)); if (findDataKey) { - findDataKey.settings.push(settings); + findDataKey.settings.dataKeySettings.push(settings); } else { datasource.dataKeys.push(dataKey); } } else { - const datasourceAttribute: Datasource = { - type: DatasourceType.entity, - name: entityAlias, - aliasName: entityAlias, - entityAliasId, - dataKeys: [dataKey] - }; - datasources.push(datasourceAttribute); + if (entityAlias) { + datasource = { + type: DatasourceType.entity, + name: entityAlias, + aliasName: entityAlias, + entityAliasId, + dataKeys: [ dataKey ] + }; + } else { + datasource = { + type: DatasourceType.device, + name: ctx.datasources[0].name, + deviceName: ctx.datasources[0].name, + deviceId: ctx.datasources[0].deviceId, + dataKeys: [ dataKey ] + }; + } + datasources.push(datasource); } - return datasources; } @@ -384,13 +446,13 @@ class RangeColorProcessor extends ColorProcessor { let advancedRangeDatasource: Datasource[] = []; let index = 0; - function setRangeColor(rangeSetting, color: string) { - if (rangeSetting.valueSource === 'predefinedValue' && isFinite(rangeSetting.value)) { + function setRangeColor(rangeSetting: ValueSourceWithDataKey, color: string) { + if (rangeSetting.type === ValueSourceDataKeyType.constant && isFinite(rangeSetting.value)) { index++; - } else if (rangeSetting.entityAlias && rangeSetting.attribute) { + } else { try { advancedRangeDatasource = this.generateDatasource(this.ctx, advancedRangeDatasource, - rangeSetting.entityAlias, rangeSetting.attribute, {color, index}); + rangeSetting, {color, index}); } catch (e) { return; } @@ -413,7 +475,7 @@ class RangeColorProcessor extends ColorProcessor { }); } else { this.color = this.computeFromRange(this.progress); - this.cd.markForCheck(); + this.colorUpdated.emit(); } } @@ -430,7 +492,7 @@ class RangeColorProcessor extends ColorProcessor { onDataUpdated: (subscription) => { this.updateAttribute(subscription.data); this.color = this.computeFromRange(this.progress); - this.cd.markForCheck(); + this.colorUpdated.emit(); } } }; @@ -443,7 +505,7 @@ class RangeColorProcessor extends ColorProcessor { if (keyData && keyData.data && keyData.data[0]) { const attrValue = keyData.data[0][1]; if (isFinite(attrValue)) { - for (const setting of keyData.dataKey.settings) { + for (const setting of keyData.dataKey.settings.dataKeySettings) { const index = Math.floor(setting.index/2); if (index === setting.index/2) { this.settings.rangeList.rangeAdvanced[index].from.value = attrValue; @@ -535,12 +597,13 @@ class GradientColorProcessor extends ColorProcessor { constructor(protected settings: ColorSettings, protected ctx: WidgetContext, - protected cd: ChangeDetectorRef) { + protected cd: ChangeDetectorRef, + protected maxValue: number) { super(settings); } update(value: any): void { - this.progress = value / 100; + this.progress = value / this.maxValue; if(isDefinedAndNotNull(this.settings.gradient.advancedMode) && this.settings.gradient.advancedMode) { this.advancedRangeSubscribe(this.settings.gradient.gradientAdvanced); } else { @@ -563,7 +626,7 @@ class GradientColorProcessor extends ColorProcessor { if (levelColor !== null) { const tColor = tinycolor(levelColor); colorsRange.push({ - pct: this.settings.gradient.advancedMode ? levelColors[i].source.value / 100 : inc * i, + pct: this.settings.gradient.advancedMode ? levelColors[i].source.value / this.maxValue : inc * i, color: tColor.toRgb(), alpha: tColor.getAlpha(), rgbString: tColor.toRgbString() @@ -599,13 +662,13 @@ class GradientColorProcessor extends ColorProcessor { let advancedGradientDatasource: Datasource[] = []; let index = 0; - function setGradientColor(gradientSetting: ValueSourceProperty) { - if (gradientSetting.valueSource === 'predefinedValue' && isFinite(gradientSetting.value)) { + function setGradientColor(gradientSetting: ValueSourceWithDataKey) { + if (gradientSetting.type === ValueSourceDataKeyType.constant && isFinite(gradientSetting.value)) { index++; - } else if (gradientSetting.entityAlias && gradientSetting.attribute) { + } else { try { advancedGradientDatasource = this.generateDatasource(this.ctx, advancedGradientDatasource, - gradientSetting.entityAlias, gradientSetting.attribute, {index}); + gradientSetting, {index}); } catch (e) { return; } @@ -623,7 +686,7 @@ class GradientColorProcessor extends ColorProcessor { }); } else { this.color = this.getProgressColor(this.progress, this.settings.gradient.gradientAdvanced); - this.cd.markForCheck(); + this.colorUpdated.emit(); } } @@ -640,7 +703,7 @@ class GradientColorProcessor extends ColorProcessor { onDataUpdated: (subscription) => { this.updateAttribute(subscription.data); this.color = this.getProgressColor(this.progress, this.settings.gradient.gradientAdvanced); - this.cd.markForCheck(); + this.colorUpdated.emit(); } } }; @@ -653,7 +716,7 @@ class GradientColorProcessor extends ColorProcessor { if (keyData && keyData.data && keyData.data[0]) { const attrValue = keyData.data[0][1]; if (isFinite(attrValue)) { - for (const setting of keyData.dataKey.settings) { + for (const setting of keyData.dataKey.settings.dataKeySettings) { this.settings.gradient.gradientAdvanced[setting.index].source.value = attrValue; } } 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 431a3de86c..1816c0ef7c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -7019,8 +7019,14 @@ "predefined-value": "Constant", "entity-attribute": "Entity attribute", "value": "Value", + "value-required": "Value is required.", + "key-required": "Key is required.", + "entity-key-required": "Entity key is required.", "source-entity-alias": "Source entity alias", - "source-entity-attribute": "Source entity attribute" + "source-entity-attribute": "Source entity attribute", + "type-constant": "Constant", + "type-latest-key": "Key", + "type-entity": "Entity" }, "rpc-state": { "initial-state": "Initial state", From 486a010af0c68d3959c67ba86737d3cf9efe8b72 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 20 May 2024 17:29:59 +0300 Subject: [PATCH 4/5] UI: Fixed add advanced gradient --- .../widget/lib/settings/common/gradient.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts index 35e51f6c98..b5dbea5cab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts @@ -241,7 +241,9 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro addGradient(advanced = false) { if (advanced) { - this.advancedGradientListFormArray.push(this.advancedGradientControl({source: null, color: 'rgba(0,0,0,0.87)'})); + this.advancedGradientListFormArray.push( + this.advancedGradientControl({source: {type: ValueSourceDataKeyType.constant}, color: 'rgba(0,0,0,0.87)'}) + ); } else { this.gradientListFormArray.push(this.colorGradientControl('rgba(0,0,0,0.87)')); } From 9e55fd391b44eaec7f561694e4f3b5a6bab6a27c Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 21 May 2024 14:43:28 +0300 Subject: [PATCH 5/5] UI: refactoring --- .../battery-level-widget.component.ts | 19 ++--- .../common/color-settings-panel.component.ts | 2 +- .../lib/settings/common/gradient.component.ts | 1 - .../shared/models/widget-settings.models.ts | 69 +++++++++++++------ 4 files changed, 55 insertions(+), 36 deletions(-) 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 index 429b59774b..c74c357739 100644 --- 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 @@ -28,7 +28,7 @@ import { ViewEncapsulation } from '@angular/core'; import { WidgetContext } from '@home/models/widget-component.models'; -import { formattedDataFormDatasourceData, formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; +import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; import { DatePipe } from '@angular/common'; import { backgroundStyle, @@ -37,7 +37,8 @@ import { getDataKey, getSingleTsValue, overlayStyle, - textStyle + textStyle, + updateGradientMinMaxValues } from '@shared/models/widget-settings.models'; import { WidgetComponent } from '@home/components/widget/widget.component'; import { @@ -46,15 +47,9 @@ import { BatteryLevelWidgetSettings } from '@home/components/widget/lib/indicator/battery-level-widget.models'; import { ResizeObserver } from '@juggle/resize-observer'; -import { forkJoin, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { ImagePipe } from '@shared/pipe/image.pipe'; import { DomSanitizer } from '@angular/platform-browser'; -import { FormattedData } from '@shared/models/widget.models'; -import { - parseThresholdData, - TimeSeriesChartThresholdType -} from '@home/components/widget/lib/chart/time-series-chart.models'; -import { map } from 'rxjs/operators'; const verticalBatteryDimensions = { shapeAspectRatio: 64 / 113, @@ -197,11 +192,11 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView this.showValue = this.settings.showValue; this.autoScaleValueSize = this.showValue && this.settings.autoScaleValueSize; this.valueStyle = textStyle(this.settings.valueFont); - this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor,this.ctx, this.cd); + this.valueColor = ColorProcessor.fromSettings(updateGradientMinMaxValues(this.settings.valueColor, 0, 100), this.ctx); - this.batteryLevelColor = ColorProcessor.fromSettings(this.settings.batteryLevelColor, this.ctx, this.cd); + this.batteryLevelColor = ColorProcessor.fromSettings(updateGradientMinMaxValues(this.settings.batteryLevelColor, 0, 100), this.ctx); - this.batteryShapeColor = ColorProcessor.fromSettings(this.settings.batteryShapeColor, this.ctx, this.cd); + this.batteryShapeColor = ColorProcessor.fromSettings(updateGradientMinMaxValues(this.settings.batteryShapeColor, 0, 100), this.ctx); this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.settings.background.overlay); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts index 73b6cacf2e..84d668565f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts @@ -99,7 +99,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit { type: [this.colorSettings?.type || ColorType.constant, []], color: [this.colorSettings?.color, []], - gradient: [this.colorSettings?.gradient || defaultGradient(), []], + gradient: [this.colorSettings?.gradient || defaultGradient(+this.minValue, +this.maxValue), []], rangeList: [this.colorSettings?.rangeList, []], colorFunction: [this.colorSettings?.colorFunction, []] } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts index b5dbea5cab..b66e5ed1e9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts @@ -92,7 +92,6 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro private sanitizer: DomSanitizer) {} ngOnInit(): void { - console.log(this.minValue, this.maxValue); this.gradientFormGroup = this.fb.group({ advancedMode: [false], gradient: this.fb.group({ diff --git a/ui-ngx/src/app/shared/models/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts index 917ed24feb..f4bb216c3a 100644 --- a/ui-ngx/src/app/shared/models/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -37,7 +37,7 @@ import { map } from 'rxjs/operators'; import { DomSanitizer } from '@angular/platform-browser'; import { AVG_MONTH, DAY, HOUR, Interval, IntervalMath, MINUTE, SECOND, YEAR } from '@shared/models/time/time.models'; import moment from 'moment'; -import tinycolor from 'tinycolor2'; +import tinycolor, { ColorInput } from 'tinycolor2'; import { WidgetContext } from '@home/models/widget-component.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; @@ -234,6 +234,8 @@ export interface ColorGradientSettings { advancedMode?: boolean; gradient?: Array; gradientAdvanced?: Array; + minValue?: number; + maxValue?: number; } export interface DataKeySettings { @@ -265,7 +267,7 @@ export const constantColor = (color: string): ColorSettings => ({ colorFunction: defaultColorFunction }); -export const defaultGradient = (): ColorGradientSettings => ({ +export const defaultGradient = (minValue?: number, maxValue?: number): ColorGradientSettings => ({ advancedMode: false, gradient: ['rgba(0, 255, 0, 1)', 'rgba(255, 0, 0, 1)'], gradientAdvanced: [ @@ -277,9 +279,21 @@ export const defaultGradient = (): ColorGradientSettings => ({ source: {type: ValueSourceDataKeyType.constant}, color: 'rgba(255, 0, 0, 1)' } - ] + ], + minValue: isDefinedAndNotNull(minValue) ? minValue : 0, + maxValue: isDefinedAndNotNull(maxValue) ? maxValue : 100 }); +export const updateGradientMinMaxValues = (colorSettings: ColorSettings, minValue: number, maxValue: number): ColorSettings => { + if (isDefinedAndNotNull(colorSettings.gradient)) { + colorSettings.gradient.minValue = minValue; + colorSettings.gradient.maxValue = maxValue; + } else { + colorSettings.gradient = defaultGradient(minValue, maxValue); + } + return colorSettings; +}; + export const defaultColorFunction = 'var temperature = value;\n' + 'if (typeof temperature !== undefined) {\n' + ' var percent = (temperature + 60)/120 * 100;\n' + @@ -325,17 +339,17 @@ type ValueColorFunction = (value: any) => string; export abstract class ColorProcessor { - static fromSettings(color: ColorSettings, ctx?: WidgetContext, cd?: ChangeDetectorRef, maxValue = 100): ColorProcessor { + static fromSettings(color: ColorSettings, ctx?: WidgetContext): ColorProcessor { const settings = color || constantColor(null); switch (settings.type) { case ColorType.constant: return new ConstantColorProcessor(settings); case ColorType.range: - return new RangeColorProcessor(settings, ctx, cd); + return new RangeColorProcessor(settings, ctx); case ColorType.function: return new FunctionColorProcessor(settings); case ColorType.gradient: - return new GradientColorProcessor(settings, ctx, cd, maxValue); + return new GradientColorProcessor(settings, ctx); default: return new ConstantColorProcessor(settings); } @@ -437,8 +451,7 @@ class RangeColorProcessor extends ColorProcessor { private progress; constructor(protected settings: ColorSettings, - protected ctx: WidgetContext, - protected cd: ChangeDetectorRef) { + protected ctx: WidgetContext) { super(settings); } @@ -563,10 +576,10 @@ class RangeColorProcessor extends ColorProcessor { return this.settings.color; } - public static constantRange(range): boolean { + public static constantRange(range: ColorRange): boolean { return isNumber(range.from) && isNumber(range.to) && range.from === range.to; } - public static constantAdvancedRange(range): boolean { + public static constantAdvancedRange(range: AdvancedColorRange): boolean { return isNumber(range.from.value) && isNumber(range.to.value) && range.from.value === range.to.value; } } @@ -593,17 +606,20 @@ class GradientColorProcessor extends ColorProcessor { private progress: number; + private minValue: number; + private maxValue: number; + private advancedGradientSourcesSubscription: IWidgetSubscription; constructor(protected settings: ColorSettings, - protected ctx: WidgetContext, - protected cd: ChangeDetectorRef, - protected maxValue: number) { + protected ctx: WidgetContext) { super(settings); + this.minValue = isDefinedAndNotNull(settings.gradient.minValue) ? settings.gradient.minValue : 0; + this.maxValue = isDefinedAndNotNull(settings.gradient.maxValue) ? settings.gradient.maxValue : 100; } update(value: any): void { - this.progress = value / this.maxValue; + this.progress = this.calculateProgress(+value, this.minValue, this.maxValue); if(isDefinedAndNotNull(this.settings.gradient.advancedMode) && this.settings.gradient.advancedMode) { this.advancedRangeSubscribe(this.settings.gradient.gradientAdvanced); } else { @@ -617,23 +633,32 @@ class GradientColorProcessor extends ColorProcessor { } } - getProgressColor(progress: number, levelColors): string { + calculateProgress(current: number, min: number, max: number) { + if (current < min) { + return 0; + } else if (current > max) { + return 1; + } + return (current - min) / (max - min); + } + + getProgressColor(progress: number, levelColors: Array): string { const colorsCount = levelColors.length; const inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1; const colorsRange = []; - for (let i = 0; i < levelColors.length; i++) { - const levelColor: any = this.settings.gradient.advancedMode ? levelColors[i].color : levelColors[i]; - if (levelColor !== null) { - const tColor = tinycolor(levelColor); + levelColors.forEach((levelColor, i) => { + const color = typeof levelColor === 'string' ? levelColor : levelColor.color; + if (color !== null) { + const tColor = tinycolor(color); colorsRange.push({ - pct: this.settings.gradient.advancedMode ? levelColors[i].source.value / this.maxValue : inc * i, + pct: typeof levelColor !== 'string' ? + this.calculateProgress(+levelColor.source.value, this.minValue, this.maxValue) : inc * i, color: tColor.toRgb(), alpha: tColor.getAlpha(), rgbString: tColor.toRgbString() }); } - } - + }); if (progress === 0 || colorsRange.length === 1) { return colorsRange[0].rgbString; }