Browse Source

UI: Value stepper widget

pull/12627/head
Artem Dzhereleiko 2 years ago
parent
commit
cc6f0b6c7b
  1. 1
      application/src/main/data/json/system/widget_bundles/buttons.json
  2. 1
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  3. 51
      application/src/main/data/json/system/widget_types/value_stepper.json
  4. 5
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  5. 32
      ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html
  6. 38
      ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.ts
  7. 297
      ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.html
  8. 225
      ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.ts
  9. 7
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts
  10. 373
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts
  11. 43
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.html
  12. 90
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.scss
  13. 388
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.ts
  14. 239
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.models.ts
  15. 266
      ui-ngx/src/app/modules/home/components/widget/lib/settings/control/value-stepper-widget-settings.component.html
  16. 191
      ui-ngx/src/app/modules/home/components/widget/lib/settings/control/value-stepper-widget-settings.component.ts
  17. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  18. 3
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  19. 37
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  20. 1
      ui-ngx/src/assets/widget/value-stepper/filled.svg
  21. 1
      ui-ngx/src/assets/widget/value-stepper/simplified.svg
  22. 1
      ui-ngx/src/assets/widget/value-stepper/volume.svg

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

@ -12,6 +12,7 @@
"command_button", "command_button",
"toggle_button", "toggle_button",
"two_segment_button", "two_segment_button",
"value_stepper",
"power_button" "power_button"
] ]
} }

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

@ -12,6 +12,7 @@
"command_button", "command_button",
"toggle_button", "toggle_button",
"two_segment_button", "two_segment_button",
"value_stepper",
"power_button", "power_button",
"slider", "slider",
"control_widgets.switch_control", "control_widgets.switch_control",

51
application/src/main/data/json/system/widget_types/value_stepper.json

@ -0,0 +1,51 @@
{
"fqn": "slider",
"name": "Value stepper",
"deprecated": false,
"image": "tb-image;/api/images/system/value-stepper-widget.svg",
"description": "Allows users to click the buttons to send commands to devices or update attributes/time series data. Configurable settings let users define how to retrieve the initial state and specify actions for each button.",
"descriptor": {
"type": "rpc",
"sizeX": 3.5,
"sizeY": 2,
"resources": [],
"templateHtml": "<tb-value-stepper-widget\n [ctx]='ctx'\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-value-stepper-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '230px',\n previewHeight: '110px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n",
"dataKeySettingsForm": [],
"settingsDirective": "tb-value-stepper-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-value-stepper-basic-config",
"defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"initialState\":{\"action\":\"EXECUTE_RPC\",\"defaultValue\":0,\"executeRpc\":{\"method\":\"getState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"getAttribute\":{\"key\":\"state\",\"scope\":null},\"getTimeSeries\":{\"key\":\"state\"},\"getAlarmStatus\":{\"severityList\":null,\"typeList\":null},\"dataToValue\":{\"type\":\"NONE\",\"compareToValue\":true,\"dataToValueFunction\":\"/* Should return integer value */\\nreturn data;\"}},\"disabledState\":{\"action\":\"DO_NOTHING\",\"defaultValue\":false,\"getAttribute\":{\"key\":\"state\",\"scope\":null},\"getTimeSeries\":{\"key\":\"state\"},\"getAlarmStatus\":{\"severityList\":null,\"typeList\":null},\"dataToValue\":{\"type\":\"NONE\",\"compareToValue\":true,\"dataToValueFunction\":\"/* Should return boolean value */\\nreturn data;\"}},\"leftButtonClick\":{\"action\":\"EXECUTE_RPC\",\"executeRpc\":{\"method\":\"setState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"setAttribute\":{\"key\":\"state\",\"scope\":\"SERVER_SCOPE\"},\"putTimeSeries\":{\"key\":\"state\"},\"valueToData\":{\"type\":\"VALUE\",\"constantValue\":0,\"valueToDataFunction\":\"/* Convert input integer value to RPC parameters or attribute/time-series value */\\nreturn value;\"}},\"rightButtonClick\":{\"action\":\"EXECUTE_RPC\",\"executeRpc\":{\"method\":\"setState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"setAttribute\":{\"key\":\"state\",\"scope\":\"SERVER_SCOPE\"},\"putTimeSeries\":{\"key\":\"state\"},\"valueToData\":{\"type\":\"VALUE\",\"constantValue\":0,\"valueToDataFunction\":\"/* Convert input integer value to RPC parameters or attribute/time-series value */\\nreturn value;\"}},\"appearance\":{\"type\":\"simplified\",\"autoScale\":true,\"minValueRange\":-100,\"maxValueRange\":100,\"valueStep\":0.5,\"showValueBox\":true,\"valueUnits\":\"\",\"valueDecimals\":1,\"valueFont\":{\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"size\":16,\"sizeUnit\":\"px\",\"lineHeight\":\"24px\"},\"valueColor\":\"#000\",\"valueBoxBackground\":\"rgba(0, 0, 0, 0.04)\",\"showBorder\":true,\"borderWidth\":1,\"borderColor\":\"#305680\"},\"buttonAppearance\":{\"leftButton\":{\"showButton\":true,\"icon\":\"arrow_back_ios_new\",\"iconSize\":24,\"iconSizeUnit\":\"px\",\"mainColorOn\":\"#3F52DD\",\"backgroundColorOn\":\"#FFFFFF\",\"mainColorDisabled\":\"rgba(0,0,0,0.12)\",\"backgroundColorDisabled\":\"#FFFFFF\",\"customStyle\":{\"enabled\":null,\"hovered\":null,\"pressed\":null,\"activated\":null,\"disabled\":null}},\"rightButton\":{\"showButton\":true,\"icon\":\"arrow_forward_ios\",\"iconSize\":24,\"iconSizeUnit\":\"px\",\"mainColorOn\":\"#3F52DD\",\"backgroundColorOn\":\"#FFFFFF\",\"mainColorDisabled\":\"rgba(0,0,0,0.12)\",\"backgroundColorDisabled\":\"#FFFFFF\",\"customStyle\":{\"enabled\":null,\"hovered\":null,\"pressed\":null,\"activated\":null,\"disabled\":null}}},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"padding\":\"12px\"},\"title\":\"Value stepper\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"actions\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":null,\"weight\":\"500\",\"style\":null,\"lineHeight\":\"24px\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"configMode\":\"basic\",\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"datasources\":null,\"borderRadius\":null}"
},
"resources": [
{
"link": "/api/images/system/value-stepper-widget.svg",
"title": "\"Value stepper\" system widget image",
"type": "IMAGE",
"subType": "IMAGE",
"fileName": "value-stepper-widget.svg",
"publicResourceKey": "s0UKoqbiMCcKVn0pD55XZzPUR89XlXAO",
"mediaType": "image/svg+xml",
"data": "PHN2ZyB3aWR0aD0iMjE0IiBoZWlnaHQ9Ijc2IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnIGZpbHRlcj0idXJsKCNhKSI+PHJlY3QgeD0iOC41IiB5PSI0LjUiIHdpZHRoPSIxOTciIGhlaWdodD0iNTkiIHJ4PSI0IiBmaWxsPSIjZmZmIiBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiLz48cmVjdCB4PSI5IiB5PSI1IiB3aWR0aD0iMTk2IiBoZWlnaHQ9IjU4IiByeD0iMy41IiBzdHJva2U9IiMzMDU2ODAiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxyZWN0IHg9IjIwLjUiIHk9IjE4IiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHJ4PSIxNiIgZmlsbD0iIzMwNTY4MCIvPjxwYXRoIGQ9Im0zOC41IDQwIDEuNDEtMS40MUwzNS4zMyAzNGw0LjU4LTQuNTlMMzguNSAyOGwtNiA2IDYgNloiIGZpbGw9IiNmZmYiLz48cmVjdCB4PSI2NSIgeT0iMTguNSIgd2lkdGg9Ijg0IiBoZWlnaHQ9IjMxIiByeD0iMy41IiBmaWxsPSIjMzA1NjgwIiBmaWxsLW9wYWNpdHk9Ii4wNCIvPjxyZWN0IHg9IjY1IiB5PSIxOC41IiB3aWR0aD0iODQiIGhlaWdodD0iMzEiIHJ4PSIzLjUiIHN0cm9rZT0iIzMwNTY4MCIvPjxwYXRoIGQ9Ik04OC41OSAzNy41VjM5aC03LjYzdi0xLjI5bDMuNy00LjA0Yy40MS0uNDYuNzMtLjg1Ljk3LTEuMTkuMjMtLjMzLjQtLjYzLjQ5LS45YTIuMyAyLjMgMCAwIDAtLjA2LTEuNzNjLS4xMy0uMjctLjMyLS41LS41OC0uNjVhMS43IDEuNyAwIDAgMC0uOTMtLjI0Yy0uNDIgMC0uNzcuMS0xLjA2LjI3LS4yOC4xOS0uNS40NC0uNjUuNzZhMi42IDIuNiAwIDAgMC0uMjIgMS4xaC0xLjg4YzAtLjY3LjE1LTEuMjcuNDYtMS44Mi4zLS41NS43My0uOTkgMS4zLTEuM2E0LjEgNC4xIDAgMCAxIDIuMDgtLjVjLjc2IDAgMS40LjEzIDEuOTQuMzguNTMuMjYuOTMuNjIgMS4yIDEuMDlhMy4zNiAzLjM2IDAgMCAxIC4yNSAyLjcyIDUgNSAwIDAgMS0uNDkgMS4wNCA5IDkgMCAwIDEtLjc0IDEuMDRjLS4yOC4zNS0uNi43LS45NCAxLjA1bC0yLjQ2IDIuNzFoNS4yNVptOS4yNy05Ljg4djEuMDRMOTMuMyAzOWgtMS45OGw0LjU0LTkuODhoLTUuOXYtMS41aDcuODlabTEuOTggMTAuNDRjMC0uMjkuMS0uNTMuMy0uNzMuMi0uMi40Ni0uMy44LS4zcy42LjEuOC4zYy4yLjIuMy40NC4zLjczYTEgMSAwIDAgMS0uMy43NGMtLjIuMi0uNDYuMy0uOC4zcy0uNi0uMS0uOC0uM2ExIDEgMCAwIDEtLjMtLjc0Wm02LjQ5LTQuMzUtMS41LS4zNy42MS01LjcyaDYuMTR2MS42SDEwN2wtLjMyIDIuNzlhMy42NyAzLjY3IDAgMCAxIDEuODEtLjQ2Yy41NCAwIDEuMDIuMDkgMS40NC4yNi40Mi4xNy43OS40MyAxLjA4Ljc2LjMuMzMuNTMuNzMuNjggMS4yYTUgNSAwIDAgMSAwIDMuMDcgMy4xNyAzLjE3IDAgMCAxLTEuODUgMi4wM2MtLjQ2LjE5LTEuMDEuMjktMS42NS4yOWE0LjYgNC42IDAgMCAxLTEuMzYtLjIgMy43MyAzLjczIDAgMCAxLTEuMTctLjYyIDMuMTQgMy4xNCAwIDAgMS0xLjE5LTIuNDJoMS44NWMuMDUuMzcuMTUuNjkuMy45NS4xNi4yNS4zOC40NS42NC41OGEyIDIgMCAwIDAgLjkyLjJjLjMyIDAgLjYtLjA1LjgzLS4xNi4yMy0uMTEuNDItLjI3LjU3LS40OC4xNS0uMjIuMjctLjQ3LjM0LS43NWEzLjYyIDMuNjIgMCAwIDAtLjAyLTEuODcgMS45OCAxLjk4IDAgMCAwLS4zOC0uNzJjLS4xNy0uMi0uMzgtLjM2LS42My0uNDdhMi4xMyAyLjEzIDAgMCAwLS44OC0uMTdjLS40NSAwLS44LjA3LTEuMDQuMi0uMjMuMTMtLjQ1LjI5LS42NS40OFptMTEuNzUtNC4xNmMwLS4zOC4xLS43Mi4yOC0xLjA0LjE5LS4zMi40NC0uNTcuNzUtLjc2YTEuOTYgMS45NiAwIDAgMSAyLjA1IDBjLjMxLjE5LjU2LjQ0Ljc0Ljc2LjE5LjMyLjI4LjY2LjI4IDEuMDRzLS4xLjczLS4yOCAxLjA1Yy0uMTguMzEtLjQzLjU2LS43NC43NGEyLjA0IDIuMDQgMCAwIDEtMi44LS43NCAyLjAzIDIuMDMgMCAwIDEtLjI4LTEuMDVabTEuMDUgMGExIDEgMCAwIDAgMSAxIC45Ny45NyAwIDAgMCAuOTgtMSAxIDEgMCAwIDAtLjI3LS43Mi45My45MyAwIDAgMC0uNy0uM2MtLjI4IDAtLjUxLjEtLjcxLjMtLjIuMi0uMy40My0uMy43MlptMTIuMTkgNS43NWgxLjk1YTQuNSA0LjUgMCAwIDEtLjYyIDEuOTkgMy43MiAzLjcyIDAgMCAxLTEuNSAxLjM3IDUgNSAwIDAgMS0yLjMzLjUgNC4xNSA0LjE1IDAgMCAxLTMuMzQtMS40NWMtLjQtLjQ4LS43MS0xLjA0LS45Mi0xLjctLjIxLS42Ni0uMzItMS40LS4zMi0yLjIydi0uOTVjMC0uODEuMS0xLjU1LjMyLTIuMjIuMjItLjY2LjUzLTEuMjIuOTQtMS42OS40LS40Ny45LS44NCAxLjQ2LTEuMDlhNC43OCA0Ljc4IDAgMCAxIDEuOTMtLjM3Yy45IDAgMS42Ny4xNyAyLjMuNS42Mi4zMyAxLjEuOCAxLjQ1IDEuMzguMzUuNTkuNTYgMS4yNi42NCAyLjAyaC0xLjk1Yy0uMDUtLjQ4LS4xNy0uOS0uMzUtMS4yNWExLjc3IDEuNzcgMCAwIDAtLjc2LS44IDIuNzMgMi43MyAwIDAgMC0xLjMzLS4yOGMtLjQ1IDAtLjg0LjA4LTEuMTcuMjVhMi4yIDIuMiAwIDAgMC0uODQuNzNjLS4yMi4zMy0uMzkuNzItLjUgMS4yLS4xMS40Ny0uMTcgMS0uMTcgMS42di45N2MwIC41Ny4wNSAxLjEuMTUgMS41Ni4xLjQ3LjI2Ljg2LjQ3IDEuMi4yMS4zMy40OC41OS44MS43Ny4zMy4xOC43Mi4yNyAxLjE4LjI3LjU2IDAgMS0uMDggMS4zNS0uMjYuMzUtLjE4LjYxLS40NC44LS43OC4xNy0uMzQuMy0uNzYuMzUtMS4yNVoiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iLjg3Ii8+PHJlY3QgeD0iMTYxLjUiIHk9IjE4IiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHJ4PSIxNiIgZmlsbD0iIzMwNTY4MCIvPjxwYXRoIGQ9Im0xNzUuNSAyOC0xLjQxIDEuNDEgNC41OCA0LjU5LTQuNTggNC41OUwxNzUuNSA0MGw2LTYtNi02WiIgZmlsbD0iI2ZmZiIvPjwvZz48ZGVmcz48ZmlsdGVyIGlkPSJhIiB4PSIuNSIgeT0iLjUiIHdpZHRoPSIyMTMiIGhlaWdodD0iNzUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAxMjcgMCIgcmVzdWx0PSJoYXJkQWxwaGEiLz48ZmVPZmZzZXQgZHk9IjQiLz48ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSI0Ii8+PGZlQ29tcG9zaXRlIGluMj0iaGFyZEFscGhhIiBvcGVyYXRvcj0ib3V0Ii8+PGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjA0IDAiLz48ZmVCbGVuZCBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfNTY2OV8xNjA3MDUiLz48ZmVCbGVuZCBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3dfNTY2OV8xNjA3MDUiIHJlc3VsdD0ic2hhcGUiLz48L2ZpbHRlcj48L2RlZnM+PC9zdmc+",
"public": true
}
],
"scada": false,
"tags": [
"command",
"downlink",
"device configuration",
"device control",
"invocation",
"remote method",
"remote function",
"interface",
"subroutine call",
"inter-process communication",
"server request",
"update attribute",
"set attribute",
"add time-series"
]
}

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

@ -146,6 +146,9 @@ import { ScadaSymbolBasicConfigComponent } from '@home/components/widget/config/
import { import {
SegmentedButtonBasicConfigComponent SegmentedButtonBasicConfigComponent
} from '@home/components/widget/config/basic/button/segmented-button-basic-config.component'; } from '@home/components/widget/config/basic/button/segmented-button-basic-config.component';
import {
ValueStepperBasicConfigComponent
} from '@home/components/widget/config/basic/rpc/value-stepper-basic-config.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -182,6 +185,7 @@ import {
PowerButtonBasicConfigComponent, PowerButtonBasicConfigComponent,
SliderBasicConfigComponent, SliderBasicConfigComponent,
ToggleButtonBasicConfigComponent, ToggleButtonBasicConfigComponent,
ValueStepperBasicConfigComponent,
TimeSeriesChartBasicConfigComponent, TimeSeriesChartBasicConfigComponent,
ComparisonKeyRowComponent, ComparisonKeyRowComponent,
ComparisonKeysTableComponent, ComparisonKeysTableComponent,
@ -236,6 +240,7 @@ import {
PowerButtonBasicConfigComponent, PowerButtonBasicConfigComponent,
SliderBasicConfigComponent, SliderBasicConfigComponent,
ToggleButtonBasicConfigComponent, ToggleButtonBasicConfigComponent,
ValueStepperBasicConfigComponent,
TimeSeriesChartBasicConfigComponent, TimeSeriesChartBasicConfigComponent,
StatusWidgetBasicConfigComponent, StatusWidgetBasicConfigComponent,
PieChartBasicConfigComponent, PieChartBasicConfigComponent,

32
ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html

@ -116,6 +116,38 @@
</tb-color-input> </tb-color-input>
</div> </div>
</div> </div>
<div class="tb-form-row column-xs" formGroupName="onButtonIcon">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon">
{{ 'widgets.power-button.button-icon-on' | translate }}
</mat-slide-toggle>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-css-unit-select class="flex-1" formControlName="iconSizeUnit"></tb-css-unit-select>
<tb-material-icon-select asBoxInput
iconClearButton
[color]="powerButtonWidgetConfigForm.get('mainColorOn').value"
formControlName="icon">
</tb-material-icon-select>
</div>
</div>
<div class="tb-form-row column-xs" formGroupName="offButtonIcon">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon">
{{ 'widgets.power-button.button-icon-off' | translate }}
</mat-slide-toggle>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-css-unit-select class="flex-1" formControlName="iconSizeUnit"></tb-css-unit-select>
<tb-material-icon-select asBoxInput
iconClearButton
[color]="powerButtonWidgetConfigForm.get('mainColorOff').value"
formControlName="icon">
</tb-material-icon-select>
</div>
</div>
<div class="tb-form-row space-between column-xs"> <div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.power-button.power-on-colors' | translate }}</div> <div>{{ 'widgets.power-button.power-on-colors' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-3"> <div class="flex flex-row items-center justify-start gap-3">

38
ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.ts

@ -87,6 +87,19 @@ export class PowerButtonBasicConfigComponent extends BasicWidgetConfigComponent
icon: [configData.config.titleIcon, []], icon: [configData.config.titleIcon, []],
iconColor: [configData.config.iconColor, []], iconColor: [configData.config.iconColor, []],
onButtonIcon: this.fb.group({
showIcon: [settings.onButtonIcon.showIcon, []],
iconSize: [settings.onButtonIcon.iconSize, [Validators.min(0)]],
iconSizeUnit: [settings.onButtonIcon.iconSizeUnit, []],
icon: [settings.onButtonIcon.icon, []],
}),
offButtonIcon: this.fb.group({
showIcon: [settings.offButtonIcon.showIcon, []],
iconSize: [settings.offButtonIcon.iconSize, [Validators.min(0)]],
iconSizeUnit: [settings.offButtonIcon.iconSizeUnit, []],
icon: [settings.offButtonIcon.icon, []],
}),
mainColorOn: [settings.mainColorOn, []], mainColorOn: [settings.mainColorOn, []],
backgroundColorOn: [settings.backgroundColorOn, []], backgroundColorOn: [settings.backgroundColorOn, []],
@ -128,6 +141,9 @@ export class PowerButtonBasicConfigComponent extends BasicWidgetConfigComponent
this.widgetConfig.config.settings.layout = config.layout; this.widgetConfig.config.settings.layout = config.layout;
this.widgetConfig.config.settings.onButtonIcon = config.onButtonIcon;
this.widgetConfig.config.settings.offButtonIcon = config.offButtonIcon;
this.widgetConfig.config.settings.mainColorOn = config.mainColorOn; this.widgetConfig.config.settings.mainColorOn = config.mainColorOn;
this.widgetConfig.config.settings.backgroundColorOn = config.backgroundColorOn; this.widgetConfig.config.settings.backgroundColorOn = config.backgroundColorOn;
@ -148,12 +164,14 @@ export class PowerButtonBasicConfigComponent extends BasicWidgetConfigComponent
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {
return ['showTitle', 'showIcon']; return ['showTitle', 'showIcon', 'onButtonIcon.showIcon', 'offButtonIcon.showIcon'];
} }
protected updateValidators(emitEvent: boolean, trigger?: string) { protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.powerButtonWidgetConfigForm.get('showTitle').value; const showTitle: boolean = this.powerButtonWidgetConfigForm.get('showTitle').value;
const showIcon: boolean = this.powerButtonWidgetConfigForm.get('showIcon').value; const showIcon: boolean = this.powerButtonWidgetConfigForm.get('showIcon').value;
const onButtonIcon: boolean = this.powerButtonWidgetConfigForm.get('onButtonIcon').get('showIcon').value;
const offButtonIcon: boolean = this.powerButtonWidgetConfigForm.get('offButtonIcon').get('showIcon').value;
if (showTitle) { if (showTitle) {
this.powerButtonWidgetConfigForm.get('title').enable(); this.powerButtonWidgetConfigForm.get('title').enable();
this.powerButtonWidgetConfigForm.get('titleFont').enable(); this.powerButtonWidgetConfigForm.get('titleFont').enable();
@ -180,6 +198,24 @@ export class PowerButtonBasicConfigComponent extends BasicWidgetConfigComponent
this.powerButtonWidgetConfigForm.get('icon').disable(); this.powerButtonWidgetConfigForm.get('icon').disable();
this.powerButtonWidgetConfigForm.get('iconColor').disable(); this.powerButtonWidgetConfigForm.get('iconColor').disable();
} }
if (onButtonIcon) {
this.powerButtonWidgetConfigForm.get('onButtonIcon').get('iconSize').enable();
this.powerButtonWidgetConfigForm.get('onButtonIcon').get('iconSizeUnit').enable();
this.powerButtonWidgetConfigForm.get('onButtonIcon').get('icon').enable();
} else {
this.powerButtonWidgetConfigForm.get('onButtonIcon').get('iconSize').disable();
this.powerButtonWidgetConfigForm.get('onButtonIcon').get('iconSizeUnit').disable();
this.powerButtonWidgetConfigForm.get('onButtonIcon').get('icon').disable();
}
if (offButtonIcon) {
this.powerButtonWidgetConfigForm.get('offButtonIcon').get('iconSize').enable();
this.powerButtonWidgetConfigForm.get('offButtonIcon').get('iconSizeUnit').enable();
this.powerButtonWidgetConfigForm.get('offButtonIcon').get('icon').enable();
} else {
this.powerButtonWidgetConfigForm.get('offButtonIcon').get('iconSize').disable();
this.powerButtonWidgetConfigForm.get('offButtonIcon').get('iconSizeUnit').disable();
this.powerButtonWidgetConfigForm.get('offButtonIcon').get('icon').disable();
}
} }
private getCardButtons(config: WidgetConfig): string[] { private getCardButtons(config: WidgetConfig): string[] {

297
ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.html

@ -0,0 +1,297 @@
<!--
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.
-->
<ng-container [formGroup]="valueStepperWidgetConfigForm">
<tb-target-device formControlName="targetDevice"></tb-target-device>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.value-stepper.behavior</div>
<div class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-stepper.initial-state-hint' | translate}}" translate>widgets.value-stepper.initial-state</div>
<tb-get-value-action-settings class="flex-1"
panelTitle="{{ 'widgets.value-stepper.initial-state' | translate }}"
[valueType]="valueType.DOUBLE"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="initialState"></tb-get-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-stepper.left-button-click-hint' | translate}}" translate>widgets.value-stepper.left-button-click</div>
<tb-set-value-action-settings class="flex-1"
panelTitle="{{ 'widgets.value-stepper.left-button-click' | translate }}"
[valueType]="valueType.DOUBLE"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="leftButtonClick"></tb-set-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-stepper.right-button-click-hint' | translate}}" translate>widgets.value-stepper.right-button-click</div>
<tb-set-value-action-settings class="flex-1"
panelTitle="{{ 'widgets.value-stepper.right-button-click' | translate }}"
[valueType]="valueType.DOUBLE"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="rightButtonClick"></tb-set-value-action-settings>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.button-state.disabled-state-hint' | translate}}" translate>widgets.button-state.disabled-state</div>
<tb-get-value-action-settings class="flex-1"
panelTitle="{{ 'widgets.button-state.disabled-state' | translate }}"
[valueType]="valueType.BOOLEAN"
stateLabel="{{ 'widgets.button-state.disabled' | translate }}"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="disabledState"></tb-get-value-action-settings>
</div>
</div>
<div class="tb-form-panel" formGroupName="appearance">
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
<tb-image-cards-select rowHeight="2:1"
[cols]="{columns: 3,
breakpoints: {
'lt-sm': 1,
'lt-md': 2
}}"
label="{{ 'widgets.button.layout' | translate }}" formControlName="type">
<tb-image-cards-select-option *ngFor="let type of valueStepperTypes"
[value]="type"
[image]="valueStepperTypeImageMap.get(type)">
{{ valueStepperTypeTranslationMap.get(type) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.value-stepper.auto-scale' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.value-stepper.value-range' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widgets.value-stepper.min-range</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="minValueRange" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.value-stepper.max-range</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="maxValueRange" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.value-stepper.value-increment-decrement-step' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="valueStep" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValueBox">
{{ 'widgets.value-stepper.value' | translate }}
</mat-slide-toggle>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<tb-unit-input class="flex" formControlName="valueUnits"></tb-unit-input>
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput formControlName="valueDecimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix class="lt-md:!hidden" translate>widget-config.decimals-suffix</div>
</mat-form-field>
<tb-font-settings formControlName="valueFont"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="valueColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.value-stepper.value-box-background' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="valueBoxBackground">
</tb-color-input>
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBorder">
{{ 'widgets.value-stepper.border' | translate }}
</mat-slide-toggle>
<div class="flex flex-1 flex-row items-center justify-end gap-2">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="borderWidth" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix class="lt-md:!hidden">px</div>
</mat-form-field>
<tb-color-input asBoxInput
colorClearButton
formControlName="borderColor">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-panel" formGroupName="buttonAppearance">
<div class="flex flex-row items-center justify-between">
<div class="tb-form-panel-title" translate>widgets.value-stepper.button-appearance</div>
<tb-toggle-select [(ngModel)]="buttonAppearanceType" [ngModelOptions]="{standalone: true}">
<tb-toggle-option value="left">{{ 'widgets.value-stepper.left' | translate }}</tb-toggle-option>
<tb-toggle-option value="right">{{ 'widgets.value-stepper.right' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<div class="tb-form-panel no-border no-padding" formGroupName="leftButton" [class.!hidden]="buttonAppearanceType !== 'left'">
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showButton">
{{ 'widgets.value-stepper.left-button' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row">
<div>{{ 'widgets.value-stepper.icon' | translate }}</div>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-css-unit-select class="flex-1" formControlName="iconSizeUnit"></tb-css-unit-select>
<tb-material-icon-select asBoxInput
iconClearButton
formControlName="icon">
</tb-material-icon-select>
</div>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.value-stepper.button-on-colors' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-3">
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.value-stepper.main</div>
<tb-color-input asBoxInput
formControlName="mainColorOn">
</tb-color-input>
</div>
<mat-divider vertical></mat-divider>
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.value-stepper.background</div>
<tb-color-input asBoxInput
formControlName="backgroundColorOn">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.value-stepper.disabled-colors' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-3">
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.value-stepper.main</div>
<tb-color-input asBoxInput
formControlName="mainColorDisabled">
</tb-color-input>
</div>
<mat-divider vertical></mat-divider>
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.value-stepper.background</div>
<tb-color-input asBoxInput
formControlName="backgroundColorDisabled">
</tb-color-input>
</div>
</div>
</div>
</div>
<div class="tb-form-panel no-border no-padding" formGroupName="rightButton" [class.!hidden]="buttonAppearanceType !== 'right'">
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showButton">
{{ 'widgets.value-stepper.right-button' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row">
<div>{{ 'widgets.value-stepper.icon' | translate }}</div>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-css-unit-select class="flex-1" formControlName="iconSizeUnit"></tb-css-unit-select>
<tb-material-icon-select asBoxInput
iconClearButton
formControlName="icon">
</tb-material-icon-select>
</div>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.value-stepper.button-on-colors' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-3">
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.value-stepper.main</div>
<tb-color-input asBoxInput
formControlName="mainColorOn">
</tb-color-input>
</div>
<mat-divider vertical></mat-divider>
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.value-stepper.background</div>
<tb-color-input asBoxInput
formControlName="backgroundColorOn">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.value-stepper.disabled-colors' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-3">
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.value-stepper.main</div>
<tb-color-input asBoxInput
formControlName="mainColorDisabled">
</tb-color-input>
</div>
<mat-divider vertical></mat-divider>
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.value-stepper.background</div>
<tb-color-input asBoxInput
formControlName="backgroundColorDisabled">
</tb-color-input>
</div>
</div>
</div>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
<div class="tb-form-row space-between column-lt-md">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-border-radius' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-padding' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="padding" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">
</tb-widget-actions-panel>
</ng-container>

225
ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.ts

@ -0,0 +1,225 @@
///
/// 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 } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { TargetDevice, WidgetConfig, } from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { formatValue, isUndefined } from '@core/utils';
import { ValueType } from '@shared/models/constants';
import {
valueStepperDefaultSettings,
valueStepperTypeImages,
valueStepperTypes,
valueStepperTypeTranslations,
ValueStepperWidgetSettings
} from '@home/components/widget/lib/rpc/value-stepper-widget.models';
type ButtonAppearanceType = 'left' | 'right';
@Component({
selector: 'tb-value-stepper-basic-config',
templateUrl: './value-stepper-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class ValueStepperBasicConfigComponent extends BasicWidgetConfigComponent {
get targetDevice(): TargetDevice {
return this.valueStepperWidgetConfigForm.get('targetDevice').value;
}
valueStepperTypeTranslationMap = valueStepperTypeTranslations;
valueStepperTypes = valueStepperTypes;
valueStepperTypeImageMap = valueStepperTypeImages;
buttonAppearanceType: ButtonAppearanceType = 'left';
valueType = ValueType;
valueStepperWidgetConfigForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.valueStepperWidgetConfigForm;
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: ValueStepperWidgetSettings = {...valueStepperDefaultSettings, ...(configData.config.settings || {})};
this.valueStepperWidgetConfigForm = this.fb.group({
targetDevice: [configData.config.targetDevice, []],
initialState: [settings.initialState, []],
leftButtonClick: [settings.leftButtonClick, []],
rightButtonClick: [settings.rightButtonClick, []],
disabledState: [settings.disabledState, []],
appearance: this.fb.group({
type: [settings.appearance.type, []],
autoScale: [settings.appearance.autoScale, []],
minValueRange: [settings.appearance.minValueRange, []],
maxValueRange: [settings.appearance.maxValueRange, []],
valueStep: [settings.appearance.valueStep, [Validators.min(0)]],
showValueBox: [settings.appearance.showValueBox, []],
valueUnits: [settings.appearance.valueUnits, []],
valueDecimals: [settings.appearance.valueDecimals, []],
valueFont: [settings.appearance.valueFont, []],
valueColor: [settings.appearance.valueColor],
valueBoxBackground: [settings.appearance.valueBoxBackground, []],
showBorder: [settings.appearance.showBorder, []],
borderWidth: [settings.appearance.borderWidth, []],
borderColor: [settings.appearance.borderColor, []]
}),
buttonAppearance: this.fb.group({
leftButton: this.fb.group({
showButton: [settings.buttonAppearance.leftButton.showButton],
icon: [settings.buttonAppearance.leftButton.icon],
iconSize: [settings.buttonAppearance.leftButton.iconSize],
iconSizeUnit: [settings.buttonAppearance.leftButton.iconSizeUnit],
mainColorOn: [settings.buttonAppearance.leftButton.mainColorOn, []],
backgroundColorOn: [settings.buttonAppearance.leftButton.backgroundColorOn, []],
mainColorDisabled: [settings.buttonAppearance.leftButton.mainColorDisabled, []],
backgroundColorDisabled: [settings.buttonAppearance.leftButton.backgroundColorDisabled, []]
}),
rightButton: this.fb.group({
showButton: [settings.buttonAppearance.rightButton.showButton],
icon: [settings.buttonAppearance.rightButton.icon],
iconSize: [settings.buttonAppearance.rightButton.iconSize],
iconSizeUnit: [settings.buttonAppearance.rightButton.iconSizeUnit],
mainColorOn: [settings.buttonAppearance.rightButton.mainColorOn, []],
backgroundColorOn: [settings.buttonAppearance.rightButton.backgroundColorOn, []],
mainColorDisabled: [settings.buttonAppearance.rightButton.mainColorDisabled, []],
backgroundColorDisabled: [settings.buttonAppearance.rightButton.backgroundColorDisabled, []]
})
}),
background: [settings.background, []],
cardButtons: [this.getCardButtons(configData.config), []],
borderRadius: [configData.config.borderRadius, []],
padding: [settings.padding, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
this.widgetConfig.config.targetDevice = config.targetDevice;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.initialState = config.initialState;
this.widgetConfig.config.settings.disabledState = config.disabledState;
this.widgetConfig.config.settings.leftButtonClick = config.leftButtonClick;
this.widgetConfig.config.settings.rightButtonClick = config.rightButtonClick;
this.widgetConfig.config.settings.appearance = config.appearance;
this.widgetConfig.config.settings.buttonAppearance = config.buttonAppearance;
this.widgetConfig.config.settings.background = config.background;
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.borderRadius = config.borderRadius;
this.widgetConfig.config.settings.padding = config.padding;
this.widgetConfig.config.actions = config.actions;
return this.widgetConfig;
}
protected validatorTriggers(): string[] {
return ['appearance.showValueBox', 'appearance.showBorder',
'buttonAppearance.leftButton.showButton', 'buttonAppearance.rightButton.showButton'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showValueBox: boolean = this.valueStepperWidgetConfigForm.get('appearance').get('showValueBox').value;
const showBorder: boolean = this.valueStepperWidgetConfigForm.get('appearance').get('showBorder').value;
const showLeftButton: boolean = this.valueStepperWidgetConfigForm.get('buttonAppearance').get('leftButton').get('showButton').value;
const showRightButton: boolean = this.valueStepperWidgetConfigForm.get('buttonAppearance').get('rightButton').get('showButton').value;
if (showValueBox) {
this.valueStepperWidgetConfigForm.get('appearance').get('valueUnits').enable();
this.valueStepperWidgetConfigForm.get('appearance').get('valueDecimals').enable();
this.valueStepperWidgetConfigForm.get('appearance').get('valueFont').enable();
this.valueStepperWidgetConfigForm.get('appearance').get('valueColor').enable();
this.valueStepperWidgetConfigForm.get('appearance').get('valueBoxBackground').enable();
this.valueStepperWidgetConfigForm.get('appearance').get('showBorder').enable({emitEvent: false});
if (showBorder) {
this.valueStepperWidgetConfigForm.get('appearance').get('borderWidth').enable();
this.valueStepperWidgetConfigForm.get('appearance').get('borderColor').enable();
} else {
this.valueStepperWidgetConfigForm.get('appearance').get('borderWidth').disable();
this.valueStepperWidgetConfigForm.get('appearance').get('borderColor').disable();
}
} else {
this.valueStepperWidgetConfigForm.get('appearance').get('valueUnits').disable();
this.valueStepperWidgetConfigForm.get('appearance').get('valueDecimals').disable();
this.valueStepperWidgetConfigForm.get('appearance').get('valueFont').disable();
this.valueStepperWidgetConfigForm.get('appearance').get('valueColor').disable();
this.valueStepperWidgetConfigForm.get('appearance').get('valueBoxBackground').disable();
this.valueStepperWidgetConfigForm.get('appearance').get('showBorder').disable({emitEvent: false});
this.valueStepperWidgetConfigForm.get('appearance').get('borderWidth').disable();
this.valueStepperWidgetConfigForm.get('appearance').get('borderColor').disable();
}
this.buttonValidators(showLeftButton, 'leftButton');
this.buttonValidators(showRightButton, 'rightButton');
}
private buttonValidators(showButtonValue: boolean, button: string) {
if (showButtonValue) {
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('icon').enable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('iconSize').enable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('iconSizeUnit').enable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('mainColorOn').enable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('backgroundColorOn').enable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('mainColorDisabled').enable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('backgroundColorDisabled').enable()
} else {
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('icon').disable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('iconSize').disable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('iconSizeUnit').disable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('mainColorOn').disable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('backgroundColorOn').disable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('mainColorDisabled').disable()
this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('backgroundColorDisabled').disable()
}
}
private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = [];
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen');
}
return buttons;
}
private setCardButtons(buttons: string[], config: WidgetConfig) {
config.enableFullscreen = buttons.includes('fullscreen');
}
private _valuePreviewFn(): string {
const units: string = this.valueStepperWidgetConfigForm.get('appearance').get('valueUnits').value;
const decimals: number = this.valueStepperWidgetConfigForm.get('appearance').get('valueDecimals').value;
return formatValue(48, decimals, units, false);
}
}

7
ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts

@ -33,11 +33,11 @@ import { DomSanitizer } from '@angular/platform-browser';
import { ValueType } from '@shared/models/constants'; import { ValueType } from '@shared/models/constants';
import { import {
powerButtonDefaultSettings, powerButtonDefaultSettings,
PowerButtonShape, PowerButtonShape, powerButtonShapeSize,
powerButtonShapeSize,
PowerButtonWidgetSettings PowerButtonWidgetSettings
} from '@home/components/widget/lib/rpc/power-button-widget.models'; } from '@home/components/widget/lib/rpc/power-button-widget.models';
import { SVG, Svg } from '@svgdotjs/svg.js'; import { SVG, Svg } from '@svgdotjs/svg.js';
import { MatIconRegistry } from '@angular/material/icon';
@Component({ @Component({
selector: 'tb-power-button-widget', selector: 'tb-power-button-widget',
@ -72,6 +72,7 @@ export class PowerButtonWidgetComponent extends
constructor(protected imagePipe: ImagePipe, constructor(protected imagePipe: ImagePipe,
protected sanitizer: DomSanitizer, protected sanitizer: DomSanitizer,
private renderer: Renderer2, private renderer: Renderer2,
private iconRegistry: MatIconRegistry,
protected cd: ChangeDetectorRef, protected cd: ChangeDetectorRef,
protected zone: NgZone) { protected zone: NgZone) {
super(cd); super(cd);
@ -180,7 +181,7 @@ export class PowerButtonWidgetComponent extends
this.renderer.setStyle(this.svgShape.node, 'user-select', 'none'); this.renderer.setStyle(this.svgShape.node, 'user-select', 'none');
this.zone.run(() => { this.zone.run(() => {
this.powerButtonSvgShape = PowerButtonShape.fromSettings(this.ctx, this.svgShape, this.powerButtonSvgShape = PowerButtonShape.fromSettings(this.ctx, this.svgShape, this.iconRegistry,
this.settings, this.value, this.disabledState, () => this.onClick()); this.settings, this.value, this.disabledState, () => this.onClick());
}); });

373
ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts

@ -28,6 +28,11 @@ import { Circle, Effect, Element, G, Gradient, Path, Runner, Svg, Text, Timeline
import '@svgdotjs/svg.filter.js'; import '@svgdotjs/svg.filter.js';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { Observable, of } from 'rxjs';
import { isSvgIcon, splitIconName } from '@shared/models/icon.models';
import { catchError, map, take } from 'rxjs/operators';
import { MatIconRegistry } from '@angular/material/icon';
import { isDefinedAndNotNull } from '@core/utils';
export enum PowerButtonLayout { export enum PowerButtonLayout {
default = 'default', default = 'default',
@ -71,20 +76,29 @@ export const powerButtonLayoutImages = new Map<PowerButtonLayout, string>(
] ]
); );
export interface ButtonIconSettings {
showIcon: boolean;
iconSize: number;
iconSizeUnit: string;
icon: string;
}
export interface PowerButtonWidgetSettings { export interface PowerButtonWidgetSettings {
initialState: GetValueSettings<boolean>; initialState?: GetValueSettings<boolean>;
disabledState: GetValueSettings<boolean>; disabledState?: GetValueSettings<boolean>;
onUpdateState: SetValueSettings; onUpdateState?: SetValueSettings;
offUpdateState: SetValueSettings; offUpdateState?: SetValueSettings;
layout: PowerButtonLayout; layout: PowerButtonLayout;
onButtonIcon: ButtonIconSettings,
offButtonIcon: ButtonIconSettings,
mainColorOn: string; mainColorOn: string;
backgroundColorOn: string; backgroundColorOn: string;
mainColorOff: string; mainColorOff: string;
backgroundColorOff: string; backgroundColorOff: string;
mainColorDisabled: string; mainColorDisabled: string;
backgroundColorDisabled: string; backgroundColorDisabled: string;
background: BackgroundSettings; background?: BackgroundSettings;
padding: string; padding?: string;
} }
export const powerButtonDefaultSettings: PowerButtonWidgetSettings = { export const powerButtonDefaultSettings: PowerButtonWidgetSettings = {
@ -177,6 +191,18 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = {
} }
}, },
layout: PowerButtonLayout.default, layout: PowerButtonLayout.default,
onButtonIcon: {
showIcon: false,
iconSize: 32,
iconSizeUnit: 'px',
icon: 'power_settings_new'
},
offButtonIcon: {
showIcon: false,
iconSize: 32,
iconSizeUnit: 'px',
icon: 'power_settings_new'
},
mainColorOn: '#3F52DD', mainColorOn: '#3F52DD',
backgroundColorOn: '#FFFFFF', backgroundColorOn: '#FFFFFF',
mainColorOff: '#A2A2A2', mainColorOff: '#A2A2A2',
@ -207,6 +233,11 @@ interface PowerButtonColorState {
backgroundColor: PowerButtonColor; backgroundColor: PowerButtonColor;
} }
interface ButtonsIconSettings {
onButtonIcon: ButtonIconSettings;
offButtonIcon: ButtonIconSettings;
}
type PowerButtonShapeColors = Record<PowerButtonState, PowerButtonColorState>; type PowerButtonShapeColors = Record<PowerButtonState, PowerButtonColorState>;
const createPowerButtonShapeColors = (settings: PowerButtonWidgetSettings): PowerButtonShapeColors => { const createPowerButtonShapeColors = (settings: PowerButtonWidgetSettings): PowerButtonShapeColors => {
@ -257,33 +288,35 @@ export abstract class PowerButtonShape {
static fromSettings(ctx: WidgetContext, static fromSettings(ctx: WidgetContext,
svgShape: Svg, svgShape: Svg,
iconRegistry: MatIconRegistry,
settings: PowerButtonWidgetSettings, settings: PowerButtonWidgetSettings,
value: boolean, value: boolean,
disabled: boolean, disabled: boolean,
onClick: () => void): PowerButtonShape { onClick: () => void): PowerButtonShape {
switch (settings.layout) { switch (settings.layout) {
case PowerButtonLayout.default: case PowerButtonLayout.default:
return new DefaultPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); return new DefaultPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick);
case PowerButtonLayout.simplified: case PowerButtonLayout.simplified:
return new SimplifiedPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); return new SimplifiedPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick);
case PowerButtonLayout.outlined: case PowerButtonLayout.outlined:
return new OutlinedPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); return new OutlinedPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick);
case PowerButtonLayout.default_volume: case PowerButtonLayout.default_volume:
return new DefaultVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); return new DefaultVolumePowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick);
case PowerButtonLayout.simplified_volume: case PowerButtonLayout.simplified_volume:
return new SimplifiedVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); return new SimplifiedVolumePowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick);
case PowerButtonLayout.outlined_volume: case PowerButtonLayout.outlined_volume:
return new OutlinedVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); return new OutlinedVolumePowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick);
case PowerButtonLayout.default_icon: case PowerButtonLayout.default_icon:
return new DefaultIconPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); return new DefaultIconPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick);
case PowerButtonLayout.simplified_icon: case PowerButtonLayout.simplified_icon:
return new SimplifiedIconPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); return new SimplifiedIconPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick);
case PowerButtonLayout.outlined_icon: case PowerButtonLayout.outlined_icon:
return new OutlinedIconPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); return new OutlinedIconPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick);
} }
} }
protected readonly colors: PowerButtonShapeColors; protected readonly colors: PowerButtonShapeColors;
protected readonly icons: ButtonsIconSettings;
protected readonly onLabel: string; protected readonly onLabel: string;
protected readonly offLabel: string; protected readonly offLabel: string;
@ -293,18 +326,67 @@ export abstract class PowerButtonShape {
protected pressed = false; protected pressed = false;
protected forcePressed = false; protected forcePressed = false;
protected offPowerSymbolIcon: Element;
protected onPowerSymbolIcon: Element;
protected offLabelShape: Text;
protected onLabelShape: Text;
protected offPowerSymbolCircle: Path;
protected offPowerSymbolLine: Path;
protected onPowerSymbolCircle: Path;
protected onPowerSymbolLine: Path;
protected constructor(protected widgetContext: WidgetContext, protected constructor(protected widgetContext: WidgetContext,
protected svgShape: Svg, protected svgShape: Svg,
protected iconRegistry: MatIconRegistry,
protected settings: PowerButtonWidgetSettings, protected settings: PowerButtonWidgetSettings,
protected value: boolean, protected value: boolean,
protected disabled: boolean, protected disabled: boolean,
protected onClick: () => void) { protected onClick: () => void) {
this.colors = createPowerButtonShapeColors(this.settings); this.colors = createPowerButtonShapeColors(this.settings);
this.icons = {onButtonIcon: this.settings.onButtonIcon, offButtonIcon: this.settings.offButtonIcon};
this.onLabel = this.widgetContext.translate.instant('widgets.power-button.on-label').toUpperCase(); this.onLabel = this.widgetContext.translate.instant('widgets.power-button.on-label').toUpperCase();
this.offLabel = this.widgetContext.translate.instant('widgets.power-button.off-label').toUpperCase(); this.offLabel = this.widgetContext.translate.instant('widgets.power-button.off-label').toUpperCase();
this._drawShape(); this._drawShape();
} }
public createIconElement(icon: string, size: number): Observable<Element> {
const isSvg = isSvgIcon(icon);
if (isSvg) {
const [namespace, iconName] = splitIconName(icon);
return this.iconRegistry
.getNamedSvgIcon(iconName, namespace)
.pipe(
take(1),
map((svgElement) => {
const element = new Element(svgElement.firstChild);
const box = element.bbox();
const scale = size / box.height;
element.scale(scale);
return element;
}),
catchError(() => of(null)
));
} else {
const iconName = splitIconName(icon)[1];
const textElement = this.svgShape.text(iconName);
const fontSetClasses = (
this.iconRegistry.getDefaultFontSetClass()
).filter(className => className.length > 0);
fontSetClasses.forEach(className => textElement.addClass(className));
textElement.font({size: `${size}px`});
textElement.attr({
style: `font-size: ${size}px`,
'text-anchor': 'start'
});
const tspan = textElement.first();
tspan.attr({
'dominant-baseline': 'hanging'
});
return of(textElement);
}
}
public setValue(value: boolean) { public setValue(value: boolean) {
if (this.value !== value) { if (this.value !== value) {
this.value = value; this.value = value;
@ -330,6 +412,106 @@ export abstract class PowerButtonShape {
} }
} }
public drawOffShape(centerGroup: G, label: boolean, labelWeight?: string, circleStroke?: boolean) {
if (this.icons.offButtonIcon.showIcon) {
this.createIconElement(this.icons.offButtonIcon.icon, this.icons.offButtonIcon.iconSize).subscribe(icon =>
this.offPowerSymbolIcon = icon.center(cx, cy).addTo(centerGroup));
} else {
if (label) {
this.offLabelShape = this.createOffLabel(labelWeight).addTo(centerGroup);
} else {
this.offPowerSymbolCircle = this.svgShape.path(circleStroke ? powerCircle : powerCircleStroke).center(cx, cy).addTo(centerGroup);
this.offPowerSymbolLine = this.svgShape.path(circleStroke ? powerLine : powerLineStroke).center(cx, cy-12).addTo(centerGroup);
}
}
}
public drawOnShape(onCenterGroup?: G, label?: boolean, labelWeight?: string, circleStroke?: boolean, mask?: Circle) {
if (this.icons.onButtonIcon.showIcon) {
this.createIconElement(this.icons.onButtonIcon.icon, this.icons.onButtonIcon.iconSize).subscribe(icon => {
this.onPowerSymbolIcon = icon.center(cx, cy);
if (isDefinedAndNotNull(onCenterGroup)) {
this.onPowerSymbolIcon.addTo(onCenterGroup);
}
if (isDefinedAndNotNull(mask)) {
this.createMask(mask, [this.onPowerSymbolIcon]);
}
});
} else {
if (label) {
this.onLabelShape = this.createOnLabel(labelWeight);
if (isDefinedAndNotNull(onCenterGroup)) {
this.onLabelShape.addTo(onCenterGroup);
}
if (isDefinedAndNotNull(mask)) {
this.createMask(mask, [this.onLabelShape]);
}
} else {
this.onPowerSymbolCircle = this.svgShape.path(circleStroke ? powerCircle : powerCircleStroke).center(cx, cy);
this.onPowerSymbolLine = this.svgShape.path(circleStroke ? powerLine : powerLineStroke).center(cx, cy-12);
if (isDefinedAndNotNull(onCenterGroup)) {
this.onPowerSymbolCircle.addTo(onCenterGroup);
this.onPowerSymbolLine.addTo(onCenterGroup);
}
if (isDefinedAndNotNull(mask)) {
this.createMask(mask, [this.onPowerSymbolCircle, this.onPowerSymbolLine]);
}
}
}
}
public onCenterTimeLine(timeline: Timeline, label: boolean) {
if (this.icons.onButtonIcon.showIcon) {
this.onPowerSymbolIcon.timeline(timeline);
} else {
if (label) {
this.onLabelShape.timeline(timeline);
} else {
this.onPowerSymbolCircle.timeline(timeline);
this.onPowerSymbolLine.timeline(timeline);
}
}
}
public offCenterColor(mainColor: PowerButtonColor, label: boolean) {
if (this.icons.offButtonIcon.showIcon) {
this.offPowerSymbolIcon.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
} else {
if (label) {
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
} else {
this.offPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
this.offPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
}
}
}
public onCenterColor(mainColor: PowerButtonColor, label: boolean) {
if (this.icons.onButtonIcon.showIcon) {
this.onPowerSymbolIcon.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
} else {
if (label) {
this.onLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
} else {
this.onPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
this.onPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
}
}
}
public buttonAnimation(scale: number, label: boolean) {
if (this.icons.onButtonIcon.showIcon) {
powerButtonAnimation(this.onPowerSymbolIcon).transform({scale});
} else {
if (label) {
powerButtonAnimation(this.onLabelShape).transform({scale, origin: {x: cx, y: cy}});
} else {
powerButtonAnimation(this.onPowerSymbolCircle).transform({scale, origin: {x: cx, y: cy}});
powerButtonAnimation(this.onPowerSymbolLine).transform({scale, origin: {x: cx, y: cy}});
}
}
}
private _drawShape() { private _drawShape() {
this.backgroundShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) this.backgroundShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy)
@ -530,9 +712,7 @@ class DefaultPowerButtonShape extends PowerButtonShape {
private outerBorder: Circle; private outerBorder: Circle;
private outerBorderMask: Circle; private outerBorderMask: Circle;
private offLabelShape: Text;
private onCircleShape: Circle; private onCircleShape: Circle;
private onLabelShape: Text;
private pressedShadow: InnerShadowCircle; private pressedShadow: InnerShadowCircle;
private pressedTimeline: Timeline; private pressedTimeline: Timeline;
private centerGroup: G; private centerGroup: G;
@ -543,22 +723,20 @@ class DefaultPowerButtonShape extends PowerButtonShape {
this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy); this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy);
this.createMask(this.outerBorder, [this.outerBorderMask]); this.createMask(this.outerBorder, [this.outerBorderMask]);
this.centerGroup = this.svgShape.group(); this.centerGroup = this.svgShape.group();
this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); this.drawOffShape(this.centerGroup, true);
this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 20) this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy);
.center(cx, cy); this.drawOnShape(null, true, '', false, this.onCircleShape);
this.onLabelShape = this.createOnLabel();
this.createMask(this.onCircleShape, [this.onLabelShape]);
this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 20, cx, cy, 0, 0); this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 20, cx, cy, 0, 0);
this.pressedTimeline = new Timeline(); this.pressedTimeline = new Timeline();
this.centerGroup.timeline(this.pressedTimeline); this.centerGroup.timeline(this.pressedTimeline);
this.onLabelShape.timeline(this.pressedTimeline); this.onCenterTimeLine(this.pressedTimeline, true);
this.pressedShadow.timeline(this.pressedTimeline); this.pressedShadow.timeline(this.pressedTimeline);
} }
protected drawColorState(mainColor: PowerButtonColor) { protected drawColorState(mainColor: PowerButtonColor) {
this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.offCenterColor(mainColor, true);
this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
} }
@ -578,14 +756,14 @@ class DefaultPowerButtonShape extends PowerButtonShape {
this.pressedTimeline.finish(); this.pressedTimeline.finish();
const pressedScale = 0.75; const pressedScale = 0.75;
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); powerButtonAnimation(this.centerGroup).transform({scale: pressedScale});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale, origin: {x: cx, y: cy}}); this.buttonAnimation(pressedScale, true);
this.pressedShadow.animate(6, 0.6); this.pressedShadow.animate(6, 0.6);
} }
protected onPressEnd() { protected onPressEnd() {
this.pressedTimeline.finish(); this.pressedTimeline.finish();
powerButtonAnimation(this.centerGroup).transform({scale: 1}); powerButtonAnimation(this.centerGroup).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}}); this.buttonAnimation(1, true);
this.pressedShadow.animateRestore(); this.pressedShadow.animateRestore();
} }
@ -596,8 +774,6 @@ class SimplifiedPowerButtonShape extends PowerButtonShape {
private outerBorder: Circle; private outerBorder: Circle;
private outerBorderMask: Circle; private outerBorderMask: Circle;
private onCircleShape: Circle; private onCircleShape: Circle;
private offLabelShape: Text;
private onLabelShape: Text;
private pressedShadow: InnerShadowCircle; private pressedShadow: InnerShadowCircle;
private pressedTimeline: Timeline; private pressedTimeline: Timeline;
private centerGroup: G; private centerGroup: G;
@ -608,22 +784,21 @@ class SimplifiedPowerButtonShape extends PowerButtonShape {
this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 4).center(cx, cy); this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 4).center(cx, cy);
this.createMask(this.outerBorder, [this.outerBorderMask]); this.createMask(this.outerBorder, [this.outerBorderMask]);
this.centerGroup = this.svgShape.group(); this.centerGroup = this.svgShape.group();
this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); this.drawOffShape(this.centerGroup, true);
this.onCircleShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy); this.onCircleShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy);
this.onLabelShape = this.createOnLabel(); this.drawOnShape(null, true, '', false, this.onCircleShape)
this.createMask(this.onCircleShape, [this.onLabelShape]);
this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 4, cx, cy, 0, 0); this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 4, cx, cy, 0, 0);
this.pressedTimeline = new Timeline(); this.pressedTimeline = new Timeline();
this.centerGroup.timeline(this.pressedTimeline); this.centerGroup.timeline(this.pressedTimeline);
this.onLabelShape.timeline(this.pressedTimeline); this.onCenterTimeLine(this.pressedTimeline, true);
this.pressedShadow.timeline(this.pressedTimeline); this.pressedShadow.timeline(this.pressedTimeline);
} }
protected drawColorState(mainColor: PowerButtonColor) { protected drawColorState(mainColor: PowerButtonColor) {
this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.offCenterColor(mainColor, true);
} }
protected drawOff() { protected drawOff() {
@ -642,14 +817,14 @@ class SimplifiedPowerButtonShape extends PowerButtonShape {
this.pressedTimeline.finish(); this.pressedTimeline.finish();
const pressedScale = 0.75; const pressedScale = 0.75;
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); powerButtonAnimation(this.centerGroup).transform({scale: pressedScale});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale, origin: {x: cx, y: cy}}); this.buttonAnimation(pressedScale, true);
this.pressedShadow.animate(6, 0.6); this.pressedShadow.animate(6, 0.6);
} }
protected onPressEnd() { protected onPressEnd() {
this.pressedTimeline.finish(); this.pressedTimeline.finish();
powerButtonAnimation(this.centerGroup).transform({scale: 1}); powerButtonAnimation(this.centerGroup).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}}); this.buttonAnimation(1, true);
this.pressedShadow.animateRestore(); this.pressedShadow.animateRestore();
} }
} }
@ -659,9 +834,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape {
private outerBorderMask: Circle; private outerBorderMask: Circle;
private innerBorder: Circle; private innerBorder: Circle;
private innerBorderMask: Circle; private innerBorderMask: Circle;
private offLabelShape: Text;
private onCircleShape: Circle; private onCircleShape: Circle;
private onLabelShape: Text;
private pressedShadow: InnerShadowCircle; private pressedShadow: InnerShadowCircle;
private pressedTimeline: Timeline; private pressedTimeline: Timeline;
private centerGroup: G; private centerGroup: G;
@ -677,25 +850,23 @@ class OutlinedPowerButtonShape extends PowerButtonShape {
this.innerBorderMask = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); this.innerBorderMask = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy);
this.createMask(this.innerBorder, [this.innerBorderMask]); this.createMask(this.innerBorder, [this.innerBorderMask]);
this.centerGroup = this.svgShape.group(); this.centerGroup = this.svgShape.group();
this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); this.drawOffShape(this.centerGroup, true);
this.onCenterGroup = this.svgShape.group(); this.onCenterGroup = this.svgShape.group();
this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 28).center(cx, cy) this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 28).center(cx, cy).addTo(this.onCenterGroup);
.addTo(this.onCenterGroup); this.drawOnShape(null, true, '', false, this.onCircleShape)
this.onLabelShape = this.createOnLabel();
this.createMask(this.onCircleShape, [this.onLabelShape]);
this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 24, cx, cy, 0, 0); this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 24, cx, cy, 0, 0);
this.pressedTimeline = new Timeline(); this.pressedTimeline = new Timeline();
this.centerGroup.timeline(this.pressedTimeline); this.centerGroup.timeline(this.pressedTimeline);
this.onCenterGroup.timeline(this.pressedTimeline); this.onCenterGroup.timeline(this.pressedTimeline);
this.onLabelShape.timeline(this.pressedTimeline); this.onCenterTimeLine(this.pressedTimeline, true);
this.pressedShadow.timeline(this.pressedTimeline); this.pressedShadow.timeline(this.pressedTimeline);
} }
protected drawColorState(mainColor: PowerButtonColor) { protected drawColorState(mainColor: PowerButtonColor) {
this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
this.innerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.innerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.offCenterColor(mainColor, true);
this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
} }
@ -714,7 +885,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape {
const pressedScale = 0.75; const pressedScale = 0.75;
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); powerButtonAnimation(this.centerGroup).transform({scale: pressedScale});
powerButtonAnimation(this.onCenterGroup).transform({scale: 0.98}); powerButtonAnimation(this.onCenterGroup).transform({scale: 0.98});
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98, origin: {x: cx, y: cy}}); this.buttonAnimation(pressedScale / 0.98, true);
this.pressedShadow.animate(6, 0.6); this.pressedShadow.animate(6, 0.6);
} }
@ -722,7 +893,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape {
this.pressedTimeline.finish(); this.pressedTimeline.finish();
powerButtonAnimation(this.centerGroup).transform({scale: 1}); powerButtonAnimation(this.centerGroup).transform({scale: 1});
powerButtonAnimation(this.onCenterGroup).transform({scale: 1}); powerButtonAnimation(this.onCenterGroup).transform({scale: 1});
powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}}); this.buttonAnimation(1, true);
this.pressedShadow.animateRestore(); this.pressedShadow.animateRestore();
} }
} }
@ -737,35 +908,29 @@ class DefaultVolumePowerButtonShape extends PowerButtonShape {
private innerShadow: InnerShadowCircle; private innerShadow: InnerShadowCircle;
//private innerShadowGradient: Gradient; //private innerShadowGradient: Gradient;
//private innerShadowGradientStop: Stop; //private innerShadowGradientStop: Stop;
private offLabelShape: Text; protected onCircleShape: Circle;
private onCircleShape: Circle;
private onLabelShape: Text;
private pressedTimeline: Timeline; private pressedTimeline: Timeline;
private centerGroup: G; private centerGroup: G;
protected drawOffCenter(centerGroup: G) { protected drawOffCenter(centerGroup: G) {
this.offLabelShape = this.createOffLabel('400').addTo(centerGroup); this.drawOffShape(centerGroup, true, '400');
} }
protected drawOnCenter() { protected drawOnCenter() {
this.onLabelShape = this.createOnLabel('400'); this.drawOnShape(null, true, '400', false, this.onCircleShape);
}
protected addOnCenterToMask(onCircleShape: Circle) {
this.createMask(onCircleShape,[this.onLabelShape]);
} }
protected addOnCenterTimeLine(pressedTimeline: Timeline) { protected addOnCenterTimeLine(pressedTimeline: Timeline) {
this.onLabelShape.timeline(pressedTimeline); this.onCenterTimeLine(pressedTimeline, true);
} }
protected drawOffCenterColor(mainColor: PowerButtonColor) { protected drawOffCenterColor(mainColor: PowerButtonColor) {
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.offCenterColor(mainColor, true);
} }
protected onCenterAnimation(scale: number) { protected onCenterAnimation(scale: number) {
powerButtonAnimation(this.onLabelShape).transform({scale, origin: {x: cx, y: cy}}); this.buttonAnimation(scale, true);
} }
protected drawShape() { protected drawShape() {
@ -789,7 +954,6 @@ class DefaultVolumePowerButtonShape extends PowerButtonShape {
this.drawOffCenter(this.centerGroup); this.drawOffCenter(this.centerGroup);
this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy);
this.drawOnCenter(); this.drawOnCenter();
this.addOnCenterToMask(this.onCircleShape);
this.innerShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 24, cx, cy, 3, 0.3); this.innerShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 24, cx, cy, 3, 0.3);
this.pressedTimeline = new Timeline(); this.pressedTimeline = new Timeline();
@ -857,38 +1021,25 @@ class DefaultVolumePowerButtonShape extends PowerButtonShape {
} }
class DefaultIconPowerButtonShape extends DefaultVolumePowerButtonShape { class DefaultIconPowerButtonShape extends DefaultVolumePowerButtonShape {
private offPowerSymbolCircle: Path;
private offPowerSymbolLine: Path;
private onPowerSymbolCircle: Path;
private onPowerSymbolLine: Path;
protected drawOffCenter(centerGroup: G) { protected drawOffCenter(centerGroup: G) {
this.offPowerSymbolCircle = this.svgShape.path(powerCircle).center(cx, cy).addTo(centerGroup); this.drawOffShape(centerGroup, false);
this.offPowerSymbolLine = this.svgShape.path(powerLine).center(cx, cy-12).addTo(centerGroup);
} }
protected drawOnCenter() { protected drawOnCenter() {
this.onPowerSymbolCircle = this.svgShape.path(powerCircle).center(cx, cy); this.drawOnShape(null, false, '', false, this.onCircleShape);
this.onPowerSymbolLine = this.svgShape.path(powerLine).center(cx, cy-12);
}
protected addOnCenterToMask(onCircleShape: Circle) {
this.createMask(onCircleShape, [this.onPowerSymbolCircle, this.onPowerSymbolLine]);
} }
protected addOnCenterTimeLine(pressedTimeline: Timeline) { protected addOnCenterTimeLine(pressedTimeline: Timeline) {
this.onPowerSymbolCircle.timeline(pressedTimeline); this.onCenterTimeLine(pressedTimeline, false);
this.onPowerSymbolLine.timeline(pressedTimeline);
} }
protected drawOffCenterColor(mainColor: PowerButtonColor) { protected drawOffCenterColor(mainColor: PowerButtonColor) {
this.offPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.offCenterColor(mainColor, false);
this.offPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
} }
protected onCenterAnimation(scale: number) { protected onCenterAnimation(scale: number) {
powerButtonAnimation(this.onPowerSymbolCircle).transform({scale, origin: {x: cx, y: cy}}); this.buttonAnimation(scale, false);
powerButtonAnimation(this.onPowerSymbolLine).transform({scale, origin: {x: cx, y: cy}});
} }
} }
@ -896,8 +1047,6 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape {
private outerBorder: Circle; private outerBorder: Circle;
private outerBorderMask: Circle; private outerBorderMask: Circle;
private offLabelShape: Text;
private onLabelShape: Text;
private innerShadow: InnerShadowCircle; private innerShadow: InnerShadowCircle;
private pressedShadow: InnerShadowCircle; private pressedShadow: InnerShadowCircle;
private pressedTimeline: Timeline; private pressedTimeline: Timeline;
@ -905,8 +1054,8 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape {
private onCenterGroup: G; private onCenterGroup: G;
protected drawCenterGroup(centerGroup: G, onCenterGroup: G) { protected drawCenterGroup(centerGroup: G, onCenterGroup: G) {
this.offLabelShape = this.createOffLabel().addTo(centerGroup); this.drawOffShape(centerGroup, true);
this.onLabelShape = this.createOnLabel().addTo(onCenterGroup); this.drawOnShape(onCenterGroup, true);
} }
protected drawShape() { protected drawShape() {
@ -926,8 +1075,8 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape {
} }
protected drawColorState(mainColor: PowerButtonColor){ protected drawColorState(mainColor: PowerButtonColor){
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.offCenterColor(mainColor, true);
this.onLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.onCenterColor(mainColor, true);
} }
protected drawOff() { protected drawOff() {
@ -970,23 +1119,15 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape {
} }
class SimplifiedIconPowerButtonShape extends SimplifiedVolumePowerButtonShape { class SimplifiedIconPowerButtonShape extends SimplifiedVolumePowerButtonShape {
private offPowerSymbolCircle: Path;
private offPowerSymbolLine: Path;
private onPowerSymbolCircle: Path;
private onPowerSymbolLine: Path;
protected drawCenterGroup(centerGroup: G, onCenterGroup: G) { protected drawCenterGroup(centerGroup: G, onCenterGroup: G) {
this.offPowerSymbolCircle = this.svgShape.path(powerCircle).center(cx, cy).addTo(centerGroup); this.drawOffShape(centerGroup, false);
this.offPowerSymbolLine = this.svgShape.path(powerLine).center(cx, cy-12).addTo(centerGroup); this.drawOnShape(onCenterGroup, false, '', false);
this.onPowerSymbolCircle = this.svgShape.path(powerCircle).center(cx, cy).addTo(onCenterGroup);
this.onPowerSymbolLine = this.svgShape.path(powerLine).center(cx, cy-12).addTo(onCenterGroup);
} }
protected drawColorState(mainColor: PowerButtonColor) { protected drawColorState(mainColor: PowerButtonColor) {
this.offPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.offCenterColor(mainColor, false);
this.offPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.onCenterColor(mainColor, false);
this.onPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
this.onPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
} }
} }
@ -996,36 +1137,30 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape {
private outerBorderGradient: Gradient; private outerBorderGradient: Gradient;
private innerBorder: Circle; private innerBorder: Circle;
private innerBorderMask: Circle; private innerBorderMask: Circle;
private offLabelShape: Text; protected onCircleShape: Circle;
private onCircleShape: Circle;
private onLabelShape: Text;
private pressedShadow: InnerShadowCircle; private pressedShadow: InnerShadowCircle;
private pressedTimeline: Timeline; private pressedTimeline: Timeline;
private centerGroup: G; private centerGroup: G;
private onCenterGroup: G; private onCenterGroup: G;
protected drawOffCenter(centerGroup: G) { protected drawOffCenter(centerGroup: G) {
this.offLabelShape = this.createOffLabel('800').addTo(centerGroup); this.drawOffShape(centerGroup, true, '800');
} }
protected drawOnCenter() { protected drawOnCenter() {
this.onLabelShape = this.createOnLabel('800'); this.drawOnShape(null, true, '800', false, this.onCircleShape);
}
protected addOnCenterToMask(onCircleShape: Circle) {
this.createMask(onCircleShape,[this.onLabelShape]);
} }
protected addOnCenterTimeLine(pressedTimeline: Timeline) { protected addOnCenterTimeLine(pressedTimeline: Timeline) {
this.onLabelShape.timeline(pressedTimeline); this.onCenterTimeLine(pressedTimeline, true);
} }
protected drawOffCenterColor(mainColor: PowerButtonColor) { protected drawOffCenterColor(mainColor: PowerButtonColor) {
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.offCenterColor(mainColor, true);
} }
protected onCenterAnimation(scale: number) { protected onCenterAnimation(scale: number) {
powerButtonAnimation(this.onLabelShape).transform({scale, origin: {x: cx, y: cy}}); this.buttonAnimation(scale, true);
} }
protected drawShape() { protected drawShape() {
@ -1047,7 +1182,6 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape {
this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 30).center(cx, cy) this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 30).center(cx, cy)
.addTo(this.onCenterGroup); .addTo(this.onCenterGroup);
this.drawOnCenter(); this.drawOnCenter();
this.addOnCenterToMask(this.onCircleShape);
this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 30, cx, cy, 0, 0); this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 30, cx, cy, 0, 0);
this.backgroundShape.addClass('tb-small-shadow'); this.backgroundShape.addClass('tb-small-shadow');
@ -1102,37 +1236,24 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape {
} }
class OutlinedIconPowerButtonShape extends OutlinedVolumePowerButtonShape { class OutlinedIconPowerButtonShape extends OutlinedVolumePowerButtonShape {
private offPowerSymbolCircle: Path;
private offPowerSymbolLine: Path;
private onPowerSymbolCircle: Path;
private onPowerSymbolLine: Path;
protected drawOffCenter(centerGroup: G) { protected drawOffCenter(centerGroup: G) {
this.offPowerSymbolCircle = this.svgShape.path(powerCircleStroke).center(cx, cy).addTo(centerGroup); this.drawOffShape(centerGroup, false, '', true);
this.offPowerSymbolLine = this.svgShape.path(powerLineStroke).center(cx, cy-12).addTo(centerGroup);
} }
protected drawOnCenter() { protected drawOnCenter() {
this.onPowerSymbolCircle = this.svgShape.path(powerCircleStroke).center(cx, cy); this.drawOnShape(null, false, '', true, this.onCircleShape);
this.onPowerSymbolLine = this.svgShape.path(powerLineStroke).center(cx, cy-12);
}
protected addOnCenterToMask(onCircleShape: Circle) {
this.createMask(onCircleShape, [this.onPowerSymbolCircle, this.onPowerSymbolLine]);
} }
protected addOnCenterTimeLine(pressedTimeline: Timeline) { protected addOnCenterTimeLine(pressedTimeline: Timeline) {
this.onPowerSymbolCircle.timeline(pressedTimeline); this.onCenterTimeLine(pressedTimeline, false);
this.onPowerSymbolLine.timeline(pressedTimeline);
} }
protected drawOffCenterColor(mainColor: PowerButtonColor) { protected drawOffCenterColor(mainColor: PowerButtonColor) {
this.offPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.offCenterColor(mainColor, false);
this.offPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity});
} }
protected onCenterAnimation(scale: number) { protected onCenterAnimation(scale: number) {
powerButtonAnimation(this.onPowerSymbolCircle).transform({scale, origin: {x: cx, y: cy}}); this.buttonAnimation(scale, false);
powerButtonAnimation(this.onPowerSymbolLine).transform({scale, origin: {x: cx, y: cy}});
} }
} }

43
ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.html

@ -0,0 +1,43 @@
<!--
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.
-->
<div class="tb-value-stepper-panel" [style.padding]="padding" [style]="backgroundStyle$ | async">
<div class="tb-value-stepper-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div #stepperContent class="tb-value-stepper-content">
<div [class.!hidden]="!showLeftButton"
#leftButton
class="tb-button-shape tb-button-shape-left"
[class.tb-button-pointer]="!leftDisabledState && (loading$ | async) === false">
</div>
<div #valueBoxContainer
class="tb-value-stepper-value-box"
[class.!hidden]="!showValueBox"
[class.tb-value-stepper-value-box-disabled]="(disabledState$ | async) === true">
<div #value
class="tb-value-stepper-value"
[class.tb-value-stepper-value-disabled]="(disabledState$ | async) === true"
[style]="valueStyle">{{ valueText }}</div>
</div>
<div [class.!hidden]="!showRightButton"
#rightButton
class="tb-button-shape tb-button-shape-right"
[class.tb-button-pointer]="!rightDisabledState && (loading$ | async) === false">
</div>
</div>
<mat-progress-bar class="tb-action-widget-progress" style="height: 4px;" color="accent" mode="indeterminate" *ngIf="loading$ | async"></mat-progress-bar>
</div>

90
ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.scss

@ -0,0 +1,90 @@
/**
* 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-value-stepper-panel {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
gap: 16px;
padding: 20px 24px 24px 24px;
> div:not(.tb-value-stepper-overlay) {
z-index: 1;
}
.tb-value-stepper-overlay {
position: absolute;
top: 12px;
left: 12px;
bottom: 12px;
right: 12px;
}
div.tb-widget-title {
padding: 0;
}
.tb-value-stepper-content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
height: 100%;
.tb-value-stepper-value-box {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
height: 32px;
padding: 0 12px;
border-radius: 4px;
.tb-value-stepper-value-disabled {
color: rgba(0, 0, 0, 0.38) !important;
}
&-disabled {
border-color: rgba(0, 0, 0, 0.38);
}
}
.tb-button-shape {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
&-left {
margin-right: 12px;
}
&-right {
margin-left: 12px;
}
svg {
.tb-small-shadow {
filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.2));
}
.tb-shadow {
filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.15));
}
}
&.tb-button-pointer {
svg {
.tb-hover-circle {
cursor: pointer;
}
}
}
}
}
}

388
ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.ts

@ -0,0 +1,388 @@
///
/// 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 {
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
NgZone,
OnDestroy,
OnInit,
Renderer2,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { BasicActionWidgetComponent, ValueSetter } from '@home/components/widget/lib/action/action-widget.models';
import { backgroundStyle, ComponentStyle, overlayStyle, textStyle } from '@shared/models/widget-settings.models';
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser';
import { ValueType } from '@shared/models/constants';
import {
PowerButtonLayout,
PowerButtonShape,
powerButtonShapeSize,
PowerButtonWidgetSettings
} from '@home/components/widget/lib/rpc/power-button-widget.models';
import { SVG, Svg } from '@svgdotjs/svg.js';
import { MatIconRegistry } from '@angular/material/icon';
import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils';
import {
valueStepperDefaultSettings,
ValueStepperWidgetSettings
} from '@home/components/widget/lib/rpc/value-stepper-widget.models';
import { UtilsService } from '@core/services/utils.service';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'tb-value-stepper-widget',
templateUrl: './value-stepper-widget.component.html',
styleUrls: ['../action/action-widget.scss', './value-stepper-widget.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ValueStepperWidgetComponent extends
BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('leftButton', {static: false})
leftButton: ElementRef<HTMLElement>;
@ViewChild('rightButton', {static: false})
rightButton: ElementRef<HTMLElement>;
@ViewChild('stepperContent', {static: false})
stepperContent: ElementRef<HTMLElement>;
@ViewChild('valueBoxContainer', {static: false})
valueBox: ElementRef<HTMLElement>;
@ViewChild('value', {static: false})
valueElement: ElementRef<HTMLElement>;
settings: ValueStepperWidgetSettings;
backgroundStyle$: Observable<ComponentStyle>;
overlayStyle: ComponentStyle = {};
padding: string;
valueStyle: ComponentStyle = {};
value: number = null;
autoScale = false;
showValueBox = true;
showLeftButton = true;
showRightButton = true;
valueText = 'N/A';
disabledState$ = new BehaviorSubject(false);
private prevValue: number = null;
private shapeResize$: ResizeObserver;
private drawSvgShapePending = false;
private svgShapeLeft: Svg;
private svgShapeRight: Svg;
private powerButtonSvgShapeLeft: PowerButtonShape;
private powerButtonSvgShapeRight: PowerButtonShape;
private disabledState = false;
public leftDisabledState = false;
public rightDisabledState = false;
private valueSetterLeft: ValueSetter<number>;
private valueSetterRight: ValueSetter<number>;
private leftDisabledState$ = new BehaviorSubject(false);
private rightDisabledState$ = new BehaviorSubject(false);
private readonly destroy$ = new Subject<void>();
constructor(protected imagePipe: ImagePipe,
protected sanitizer: DomSanitizer,
private renderer: Renderer2,
private iconRegistry: MatIconRegistry,
private utils: UtilsService,
private elementRef: ElementRef,
protected cd: ChangeDetectorRef,
protected zone: NgZone) {
super(cd);
}
ngOnInit(): void {
super.ngOnInit();
this.settings = {...valueStepperDefaultSettings, ...this.ctx.settings};
this.autoScale = this.settings.appearance.autoScale;
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
this.overlayStyle = overlayStyle(this.settings.background.overlay);
this.padding = this.settings.background.overlay.enabled ? undefined : this.settings.padding;
this.showValueBox = this.settings.appearance.showValueBox;
this.showLeftButton = this.settings.buttonAppearance.leftButton.showButton;
this.showRightButton = this.settings.buttonAppearance.rightButton.showButton;
this.valueStyle = textStyle(this.settings.appearance.valueFont);
this.valueStyle.color = this.settings.appearance.valueColor;
if (this.showValueBox) {
const valueBoxCss = `.tb-value-stepper-value-box {\n`+
`border: ${this.settings.appearance.showBorder ?
`${this.settings.appearance.borderWidth}px solid ${this.settings.appearance.borderColor}` :
'none'};\n`+
`background-color: ${this.settings.appearance.valueBoxBackground}` +
`}`;
this.utils.applyCssToElement(this.renderer, this.elementRef.nativeElement, 'tb-value-stepper-value-box', valueBoxCss);
}
const getInitialStateSettings =
{...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.slider.initial-value')};
this.createValueGetter(getInitialStateSettings, ValueType.INTEGER, {
next: (value) => this.onValue(value)
});
const disabledStateSettings =
{...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.disabled-state')};
this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, {
next: (value) => this.disabledState$.next(value)
});
const leftButtonClick = {...this.settings.leftButtonClick,
actionLabel: this.ctx.translate.instant('widgets.slider.on-value-change')};
this.valueSetterLeft = this.createValueSetter(leftButtonClick);
const rightButtonClick = {...this.settings.rightButtonClick,
actionLabel: this.ctx.translate.instant('widgets.slider.on-value-change')};
this.valueSetterRight = this.createValueSetter(rightButtonClick);
combineLatest([
this.loading$,
this.disabledState$.asObservable(),
this.leftDisabledState$.asObservable()
]).pipe(
takeUntil(this.destroy$)
).subscribe(value => {
const state = value.includes(true);
this.updateLeftDisabledState(state)
});
combineLatest([
this.loading$,
this.disabledState$.asObservable(),
this.rightDisabledState$.asObservable()
]).pipe(
takeUntil(this.destroy$)
).subscribe(value => {
const state = value.includes(true);
this.updateRightDisabledState(state)
});
}
ngAfterViewInit(): void {
if (this.drawSvgShapePending) {
this.drawSvg();
}
super.ngAfterViewInit();
}
ngOnDestroy() {
if (this.shapeResize$) {
this.shapeResize$.disconnect();
}
this.destroy$.next();
this.destroy$.complete();
super.ngOnDestroy();
}
public onInit() {
super.onInit();
const borderRadius = this.ctx.$widgetElement.css('borderRadius');
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
if (this.leftButton || this.rightButton) {
this.drawSvg();
} else {
this.drawSvgShapePending = true;
}
this.cd.detectChanges();
}
private onValue(value: number): void {
this.value = value;
this.prevValue = value;
if ((this.value + this.settings.appearance.valueStep) >= this.settings.appearance.maxValueRange) {
this.rightDisabledState$.next(true);
} else {
this.rightDisabledState$.next(false);
}
if ((this.value - this.settings.appearance.valueStep) <= this.settings.appearance.minValueRange) {
this.leftDisabledState$.next(true);
} else {
this.leftDisabledState$.next(false);
}
this.updateValueText();
this.cd.markForCheck();
}
private updateValueText() {
if (isDefinedAndNotNull(this.value) && isNumeric(this.value)) {
this.valueText = formatValue(this.value, this.settings.appearance.valueDecimals, this.settings.appearance.valueUnits, false);
} else {
this.valueText = 'N/A';
}
}
private onClick(rightButtonClick: boolean = false) {
this.updateValueText();
if (!this.ctx.isEdit && !this.ctx.isPreview && !this.disabledState) {
const prevValue = this.prevValue;
const targetValue = rightButtonClick ?
(this.value + this.settings.appearance.valueStep) :
(this.value - this.settings.appearance.valueStep);
this.updateValue(rightButtonClick ? this.valueSetterRight : this.valueSetterLeft, targetValue, {
next: () => this.onValue(targetValue),
error: () => this.onValue(prevValue)
});
}
}
private drawSvg() {
let leftButtonSetting: PowerButtonWidgetSettings;
let rightButtonSetting: PowerButtonWidgetSettings;
if (this.showLeftButton) {
this.svgShapeLeft = SVG().addTo(this.leftButton.nativeElement).size(powerButtonShapeSize, powerButtonShapeSize);
this.renderer.setStyle(this.svgShapeLeft.node, 'overflow', 'visible');
this.renderer.setStyle(this.svgShapeLeft.node, 'user-select', 'none');
leftButtonSetting = {
layout: PowerButtonLayout[this.settings.appearance.type],
onButtonIcon: {
showIcon: true,
icon: this.settings.buttonAppearance.leftButton.icon,
iconSize: this.settings.buttonAppearance.leftButton.iconSize * 1.7,
iconSizeUnit: this.settings.buttonAppearance.leftButton.iconSizeUnit
},
offButtonIcon: {
showIcon: true,
icon: this.settings.buttonAppearance.leftButton.icon,
iconSize: this.settings.buttonAppearance.leftButton.iconSize * 1.7,
iconSizeUnit: this.settings.buttonAppearance.leftButton.iconSizeUnit
},
mainColorOn: this.settings.buttonAppearance.leftButton.mainColorOn,
backgroundColorOn: this.settings.buttonAppearance.leftButton.backgroundColorOn,
mainColorOff: this.settings.buttonAppearance.leftButton.mainColorOff,
backgroundColorOff: this.settings.buttonAppearance.leftButton.backgroundColorOff,
mainColorDisabled: this.settings.buttonAppearance.leftButton.mainColorDisabled,
backgroundColorDisabled: this.settings.buttonAppearance.leftButton.backgroundColorDisabled
};
}
if (this.showRightButton) {
this.svgShapeRight = SVG().addTo(this.rightButton.nativeElement).size(powerButtonShapeSize, powerButtonShapeSize);
this.renderer.setStyle(this.svgShapeRight.node, 'overflow', 'visible');
this.renderer.setStyle(this.svgShapeRight.node, 'user-select', 'none');
rightButtonSetting = {
layout: PowerButtonLayout[this.settings.appearance.type],
onButtonIcon: {
showIcon: true,
icon: this.settings.buttonAppearance.rightButton.icon,
iconSize: this.settings.buttonAppearance.rightButton.iconSize * 1.7,
iconSizeUnit: this.settings.buttonAppearance.rightButton.iconSizeUnit
},
offButtonIcon: {
showIcon: true,
icon: this.settings.buttonAppearance.rightButton.icon,
iconSize: this.settings.buttonAppearance.rightButton.iconSize * 1.7,
iconSizeUnit: this.settings.buttonAppearance.rightButton.iconSizeUnit
},
mainColorOn: this.settings.buttonAppearance.rightButton.mainColorOn,
backgroundColorOn: this.settings.buttonAppearance.rightButton.backgroundColorOn,
mainColorOff: this.settings.buttonAppearance.rightButton.mainColorOff,
backgroundColorOff: this.settings.buttonAppearance.rightButton.backgroundColorOff,
mainColorDisabled: this.settings.buttonAppearance.rightButton.mainColorDisabled,
backgroundColorDisabled: this.settings.buttonAppearance.rightButton.backgroundColorDisabled
};
}
this.zone.run(() => {
if (this.showLeftButton) {
this.powerButtonSvgShapeLeft = PowerButtonShape.fromSettings(this.ctx, this.svgShapeLeft, this.iconRegistry,
leftButtonSetting , true, this.leftDisabledState, () => this.onClick());
}
if (this.showRightButton) {
this.powerButtonSvgShapeRight = PowerButtonShape.fromSettings(this.ctx, this.svgShapeRight, this.iconRegistry,
rightButtonSetting, true, this.rightDisabledState, () => this.onClick(true));
}
});
this.shapeResize$ = new ResizeObserver(() => {
this.onResize();
});
if (this.autoScale) {
this.shapeResize$.observe(this.stepperContent.nativeElement);
}
if (this.showLeftButton) {
this.shapeResize$.observe(this.leftButton.nativeElement);
}
if (this.showRightButton) {
this.shapeResize$.observe(this.rightButton.nativeElement);
}
this.onResize();
}
private updateLeftDisabledState(disabled: boolean) {
this.leftDisabledState = disabled;
this.powerButtonSvgShapeLeft?.setDisabled(this.leftDisabledState);
this.cd.markForCheck();
}
private updateRightDisabledState(disabled: boolean) {
this.rightDisabledState = disabled;
this.powerButtonSvgShapeRight?.setDisabled(this.rightDisabledState);
this.cd.markForCheck();
}
private onResize() {
const panelWidth = this.stepperContent.nativeElement.getBoundingClientRect().width;
const panelHeight = this.stepperContent.nativeElement.getBoundingClientRect().height;
const minAspect = 0.2;
const avgContentHeight = 32;
const targetHeight = panelWidth * Math.min(panelHeight / panelWidth, minAspect);
const multiplier = targetHeight / avgContentHeight;
const size = avgContentHeight * multiplier;
if (this.showValueBox) {
this.renderer.setStyle(this.valueBox?.nativeElement, 'height', `${size}px`);
this.renderer.setStyle(this.valueElement?.nativeElement, 'font-size', `${this.settings.appearance.valueFont.size * multiplier}px`);
}
if (this.showLeftButton) {
this.renderer.setStyle(this.leftButton?.nativeElement, 'width', `${size}px`);
this.renderer.setStyle(this.leftButton?.nativeElement, 'height', `${size}px`);
}
if (this.showRightButton) {
this.renderer.setStyle(this.rightButton?.nativeElement, 'width', `${size}px`);
this.renderer.setStyle(this.rightButton?.nativeElement, 'height', `${size}px`);
}
if (size) {
const scale = size / powerButtonShapeSize;
if (this.showLeftButton) {
this.renderer.setStyle(this.svgShapeLeft?.node, 'transform', `scale(${scale})`);
}
if (this.showRightButton) {
this.renderer.setStyle(this.svgShapeRight?.node, 'transform', `scale(${scale})`);
}
}
}
}

239
ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.models.ts

@ -0,0 +1,239 @@
import {
DataToValueType,
GetValueAction,
GetValueSettings, SetValueAction,
SetValueSettings, ValueToDataType
} from '@shared/models/action-widget-settings.models';
import { defaultWidgetAction, WidgetAction } from '@shared/models/widget.models';
import {
ButtonToggleAppearance, segmentedButtonDefaultAppearance,
SegmentedButtonWidgetSettings
} from '@home/components/widget/lib/button/segmented-button-widget.models';
import { WidgetButtonCustomStyles, WidgetButtonType } from '@shared/components/button/widget-button.models';
import { BackgroundSettings, BackgroundType, cssUnit, Font } from '@shared/models/widget-settings.models';
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
const defaultMainColor = '#305680';
export enum ValueStepperType {
simplified = 'simplified',
default = 'default',
default_volume = 'default_volume'
}
export const valueStepperTypes = Object.keys(ValueStepperType) as ValueStepperType[];
export const valueStepperTypeTranslations = new Map<ValueStepperType, string>(
[
[ValueStepperType.simplified, 'widgets.value-stepper.simplified'],
[ValueStepperType.default, 'widgets.value-stepper.filled'],
[ValueStepperType.default_volume, 'widgets.value-stepper.volume']
]
);
export const valueStepperTypeImages = new Map<ValueStepperType, string>(
[
[ValueStepperType.simplified, 'assets/widget/value-stepper/simplified.svg'],
[ValueStepperType.default, 'assets/widget/value-stepper/filled.svg'],
[ValueStepperType.default_volume, 'assets/widget/value-stepper/volume.svg']
]
);
export interface ValueStepperWidgetSettings {
initialState: GetValueSettings<number>;
leftButtonClick: SetValueSettings;
rightButtonClick: SetValueSettings;
disabledState: GetValueSettings<boolean>;
appearance: ValueStepperAppearance;
buttonAppearance: {
leftButton: ValueStepperButtonAppearance;
rightButton: ValueStepperButtonAppearance;
}
background: BackgroundSettings;
padding: string;
}
export interface ValueStepperAppearance {
type: ValueStepperType;
autoScale: boolean;
minValueRange: number;
maxValueRange: number;
valueStep: number;
showValueBox: boolean;
valueUnits: string;
valueDecimals: number;
valueFont: Font;
valueColor: string;
valueBoxBackground: string;
showBorder: boolean;
borderWidth: number;
borderColor: string;
}
export interface ValueStepperButtonAppearance {
showButton: boolean;
icon: string;
iconSize: number;
iconSizeUnit: cssUnit;
mainColorOn: string;
backgroundColorOn: string;
mainColorOff: string;
backgroundColorOff: string;
mainColorDisabled: string;
backgroundColorDisabled: string;
customStyle: WidgetButtonCustomStyles;
}
export const valueStepperDefaultAppearance: ValueStepperAppearance = {
type: ValueStepperType.simplified,
autoScale: true,
minValueRange: -100,
maxValueRange: 100,
valueStep: 0.5,
showValueBox: true,
valueUnits: '',
valueDecimals: 1,
valueFont: {
family: 'Roboto',
weight: '500',
style: 'normal',
size: 16,
sizeUnit: 'px',
lineHeight: '24px'
},
valueColor: '#000',
valueBoxBackground: 'rgba(0, 0, 0, 0.12)',
showBorder: true,
borderWidth: 1,
borderColor: defaultMainColor
}
export const valueStepperButtonDefaultAppearance: ValueStepperButtonAppearance = {
showButton: true,
icon: '',
iconSize: 24,
iconSizeUnit: 'px',
mainColorOn: '#3F52DD',
backgroundColorOn: '#FFFFFF',
mainColorOff: '#A2A2A2',
backgroundColorOff: '#FFFFFF',
mainColorDisabled: 'rgba(0,0,0,0.12)',
backgroundColorDisabled: '#FFFFFF',
customStyle: {
enabled: null,
hovered: null,
pressed: null,
activated: null,
disabled: null
}
}
export const valueStepperDefaultSettings: ValueStepperWidgetSettings = {
initialState: {
action: GetValueAction.EXECUTE_RPC,
defaultValue: 0,
executeRpc: {
method: 'getState',
requestTimeout: 5000,
requestPersistent: false,
persistentPollingInterval: 1000
},
getAttribute: {
key: 'state',
scope: null
},
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
dataToValueFunction: '/* Should return integer value */\nreturn data;'
}
},
disabledState: {
action: GetValueAction.DO_NOTHING,
defaultValue: false,
getAttribute: {
key: 'state',
scope: null
},
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
dataToValueFunction: '/* Should return boolean value */\nreturn data;'
}
},
leftButtonClick: {
action: SetValueAction.EXECUTE_RPC,
executeRpc: {
method: 'setState',
requestTimeout: 5000,
requestPersistent: false,
persistentPollingInterval: 1000
},
setAttribute: {
key: 'state',
scope: AttributeScope.SERVER_SCOPE
},
putTimeSeries: {
key: 'state'
},
valueToData: {
type: ValueToDataType.VALUE,
constantValue: 0,
valueToDataFunction: '/* Convert input integer value to RPC parameters or attribute/time-series value */\nreturn value;'
}
},
rightButtonClick: {
action: SetValueAction.EXECUTE_RPC,
executeRpc: {
method: 'setState',
requestTimeout: 5000,
requestPersistent: false,
persistentPollingInterval: 1000
},
setAttribute: {
key: 'state',
scope: AttributeScope.SERVER_SCOPE
},
putTimeSeries: {
key: 'state'
},
valueToData: {
type: ValueToDataType.VALUE,
constantValue: 0,
valueToDataFunction: '/* Convert input integer value to RPC parameters or attribute/time-series value */\nreturn value;'
}
},
appearance: valueStepperDefaultAppearance,
buttonAppearance: {
leftButton: {...valueStepperButtonDefaultAppearance, icon: 'arrow_back_ios_new'},
rightButton: {...valueStepperButtonDefaultAppearance, icon: 'arrow_forward_ios'}
},
background: {
type: BackgroundType.color,
color: '#fff',
overlay: {
enabled: false,
color: 'rgba(255,255,255,0.72)',
blur: 3
}
},
padding: '12px'
};

266
ui-ngx/src/app/modules/home/components/widget/lib/settings/control/value-stepper-widget-settings.component.html

@ -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.
-->
<ng-container [formGroup]="valueStepperWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.value-stepper.behavior</div>
<div class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-stepper.initial-state-hint' | translate}}" translate>widgets.value-stepper.initial-state</div>
<tb-get-value-action-settings class="flex-1"
panelTitle="{{ 'widgets.value-stepper.initial-state' | translate }}"
[valueType]="valueType.DOUBLE"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="initialState"></tb-get-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-stepper.left-button-click-hint' | translate}}" translate>widgets.value-stepper.left-button-click</div>
<tb-set-value-action-settings class="flex-1"
panelTitle="{{ 'widgets.value-stepper.left-button-click' | translate }}"
[valueType]="valueType.DOUBLE"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="leftButtonClick"></tb-set-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-stepper.right-button-click-hint' | translate}}" translate>widgets.value-stepper.right-button-click</div>
<tb-set-value-action-settings class="flex-1"
panelTitle="{{ 'widgets.value-stepper.right-button-click' | translate }}"
[valueType]="valueType.DOUBLE"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="rightButtonClick"></tb-set-value-action-settings>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.button-state.disabled-state-hint' | translate}}" translate>widgets.button-state.disabled-state</div>
<tb-get-value-action-settings class="flex-1"
panelTitle="{{ 'widgets.button-state.disabled-state' | translate }}"
[valueType]="valueType.BOOLEAN"
stateLabel="{{ 'widgets.button-state.disabled' | translate }}"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="disabledState"></tb-get-value-action-settings>
</div>
</div>
<div class="tb-form-panel" formGroupName="appearance">
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
<tb-image-cards-select rowHeight="3:1"
[cols]="{columns: 2,
breakpoints: {
'lt-sm': 1
}}"
label="{{ 'widgets.button.layout' | translate }}" formControlName="type">
<tb-image-cards-select-option *ngFor="let type of valueStepperTypes"
[value]="type"
[image]="valueStepperTypeImageMap.get(type)">
{{ valueStepperTypeTranslationMap.get(type) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.value-stepper.auto-scale' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.value-stepper.value-range' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widgets.value-stepper.min-range</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="minValueRange" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.value-stepper.max-range</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="maxValueRange" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.value-stepper.value-increment-decrement-step' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="valueStep" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValueBox">
{{ 'widgets.value-stepper.value' | translate }}
</mat-slide-toggle>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<tb-unit-input class="flex" formControlName="valueUnits"></tb-unit-input>
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput formControlName="valueDecimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix class="lt-md:!hidden" translate>widget-config.decimals-suffix</div>
</mat-form-field>
<tb-font-settings formControlName="valueFont"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="valueColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.value-stepper.value-box-background' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="valueBoxBackground">
</tb-color-input>
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBorder">
{{ 'widgets.value-stepper.border' | translate }}
</mat-slide-toggle>
<div class="flex flex-1 flex-row items-center justify-end gap-2">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="borderWidth" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix class="lt-md:!hidden">px</div>
</mat-form-field>
<tb-color-input asBoxInput
colorClearButton
formControlName="borderColor">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-panel" formGroupName="buttonAppearance">
<div class="flex flex-row items-center justify-between">
<div class="tb-form-panel-title" translate>widgets.value-stepper.button-appearance</div>
<tb-toggle-select [(ngModel)]="buttonAppearanceType" [ngModelOptions]="{standalone: true}">
<tb-toggle-option value="left">{{ 'widgets.value-stepper.left' | translate }}</tb-toggle-option>
<tb-toggle-option value="right">{{ 'widgets.value-stepper.right' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<div class="tb-form-panel no-border no-padding" formGroupName="leftButton" [class.!hidden]="buttonAppearanceType !== 'left'">
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showButton">
{{ 'widgets.value-stepper.left-button' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row">
<div>{{ 'widgets.value-stepper.icon' | translate }}</div>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-css-unit-select class="flex-1" formControlName="iconSizeUnit"></tb-css-unit-select>
<tb-material-icon-select asBoxInput
iconClearButton
formControlName="icon">
</tb-material-icon-select>
</div>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.power-button.power-on-colors' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-3">
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.power-button.main</div>
<tb-color-input asBoxInput
formControlName="mainColorOn">
</tb-color-input>
</div>
<mat-divider vertical></mat-divider>
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.power-button.background</div>
<tb-color-input asBoxInput
formControlName="backgroundColorOn">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.power-button.disabled-colors' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-3">
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.power-button.main</div>
<tb-color-input asBoxInput
formControlName="mainColorDisabled">
</tb-color-input>
</div>
<mat-divider vertical></mat-divider>
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.power-button.background</div>
<tb-color-input asBoxInput
formControlName="backgroundColorDisabled">
</tb-color-input>
</div>
</div>
</div>
</div>
<div class="tb-form-panel no-border no-padding" formGroupName="rightButton" [class.!hidden]="buttonAppearanceType !== 'right'">
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showButton">
{{ 'widgets.value-stepper.right-button' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row">
<div>{{ 'widgets.value-stepper.icon' | translate }}</div>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-css-unit-select class="flex-1" formControlName="iconSizeUnit"></tb-css-unit-select>
<tb-material-icon-select asBoxInput
iconClearButton
formControlName="icon">
</tb-material-icon-select>
</div>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.power-button.power-on-colors' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-3">
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.power-button.main</div>
<tb-color-input asBoxInput
formControlName="mainColorOn">
</tb-color-input>
</div>
<mat-divider vertical></mat-divider>
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.power-button.background</div>
<tb-color-input asBoxInput
formControlName="backgroundColorOn">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.power-button.disabled-colors' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-3">
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.power-button.main</div>
<tb-color-input asBoxInput
formControlName="mainColorDisabled">
</tb-color-input>
</div>
<mat-divider vertical></mat-divider>
<div class="flex flex-row items-center justify-start gap-2">
<div translate>widgets.power-button.background</div>
<tb-color-input asBoxInput
formControlName="backgroundColorDisabled">
</tb-color-input>
</div>
</div>
</div>
</div>
</div>
</ng-container>

191
ui-ngx/src/app/modules/home/components/widget/lib/settings/control/value-stepper-widget-settings.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 { Component } from '@angular/core';
import { TargetDevice, WidgetSettings, WidgetSettingsComponent, widgetType } from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { ValueType } from '@shared/models/constants';
import { getTargetDeviceFromDatasources } from '@shared/models/widget-settings.models';
import {
valueStepperDefaultSettings,
valueStepperTypeImages,
valueStepperTypes,
valueStepperTypeTranslations
} from '@home/components/widget/lib/rpc/value-stepper-widget.models';
import { formatValue } from '@core/utils';
type ButtonAppearanceType = 'left' | 'right';
@Component({
selector: 'tb-value-stepper-widget-settings',
templateUrl: './value-stepper-widget-settings.component.html',
styleUrls: ['../widget-settings.scss']
})
export class ValueStepperWidgetSettingsComponent extends WidgetSettingsComponent {
get targetDevice(): TargetDevice {
const datasources = this.widgetConfig?.config?.datasources;
return getTargetDeviceFromDatasources(datasources);
}
get widgetType(): widgetType {
return this.widgetConfig?.widgetType;
}
get borderRadius(): string {
return this.widgetConfig?.config?.borderRadius;
}
valueType = ValueType;
valueStepperWidgetSettingsForm: UntypedFormGroup;
valueStepperTypeTranslationMap = valueStepperTypeTranslations;
valueStepperTypes = valueStepperTypes;
valueStepperTypeImageMap = valueStepperTypeImages;
buttonAppearanceType: ButtonAppearanceType = 'left';
valuePreviewFn = this._valuePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.valueStepperWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {...valueStepperDefaultSettings};
}
protected onSettingsSet(settings: WidgetSettings) {
this.valueStepperWidgetSettingsForm = this.fb.group({
initialState: [settings.initialState, []],
leftButtonClick: [settings.leftButtonClick, []],
rightButtonClick: [settings.rightButtonClick, []],
disabledState: [settings.disabledState, []],
appearance: this.fb.group({
type: [settings.appearance.type, []],
autoScale: [settings.appearance.autoScale, []],
minValueRange: [settings.appearance.minValueRange, []],
maxValueRange: [settings.appearance.maxValueRange, []],
valueStep: [settings.appearance.valueStep, [Validators.min(0)]],
showValueBox: [settings.appearance.showValueBox, []],
valueUnits: [settings.appearance.valueUnits, []],
valueDecimals: [settings.appearance.valueDecimals, []],
valueFont: [settings.appearance.valueFont, []],
valueColor: [settings.appearance.valueColor],
valueBoxBackground: [settings.appearance.valueBoxBackground, []],
showBorder: [settings.appearance.showBorder, []],
borderWidth: [settings.appearance.borderWidth, []],
borderColor: [settings.appearance.borderColor, []]
}),
buttonAppearance: this.fb.group({
leftButton: this.fb.group({
showButton: [settings.buttonAppearance.leftButton.showButton],
icon: [settings.buttonAppearance.leftButton.icon],
iconSize: [settings.buttonAppearance.leftButton.iconSize],
iconSizeUnit: [settings.buttonAppearance.leftButton.iconSizeUnit],
mainColorOn: [settings.buttonAppearance.leftButton.mainColorOn, []],
backgroundColorOn: [settings.buttonAppearance.leftButton.backgroundColorOn, []],
mainColorDisabled: [settings.buttonAppearance.leftButton.mainColorDisabled, []],
backgroundColorDisabled: [settings.buttonAppearance.leftButton.backgroundColorDisabled, []]
}),
rightButton: this.fb.group({
showButton: [settings.buttonAppearance.rightButton.showButton],
icon: [settings.buttonAppearance.rightButton.icon],
iconSize: [settings.buttonAppearance.rightButton.iconSize],
iconSizeUnit: [settings.buttonAppearance.rightButton.iconSizeUnit],
mainColorOn: [settings.buttonAppearance.rightButton.mainColorOn, []],
backgroundColorOn: [settings.buttonAppearance.rightButton.backgroundColorOn, []],
mainColorDisabled: [settings.buttonAppearance.rightButton.mainColorDisabled, []],
backgroundColorDisabled: [settings.buttonAppearance.rightButton.backgroundColorDisabled, []]
})
})
});
}
protected validatorTriggers(): string[] {
return ['appearance.showValueBox', 'appearance.showBorder',
'buttonAppearance.leftButton.showButton', 'buttonAppearance.rightButton.showButton'];
}
protected updateValidators(_emitEvent: boolean): void {
const showValueBox: boolean = this.valueStepperWidgetSettingsForm.get('appearance').get('showValueBox').value;
const showBorder: boolean = this.valueStepperWidgetSettingsForm.get('appearance').get('showBorder').value;
const showLeftButton: boolean = this.valueStepperWidgetSettingsForm.get('buttonAppearance').get('leftButton').get('showButton').value;
const showRightButton: boolean = this.valueStepperWidgetSettingsForm.get('buttonAppearance').get('rightButton').get('showButton').value;
if (showValueBox) {
this.valueStepperWidgetSettingsForm.get('appearance').get('valueUnits').enable();
this.valueStepperWidgetSettingsForm.get('appearance').get('valueDecimals').enable();
this.valueStepperWidgetSettingsForm.get('appearance').get('valueFont').enable();
this.valueStepperWidgetSettingsForm.get('appearance').get('valueColor').enable();
this.valueStepperWidgetSettingsForm.get('appearance').get('valueBoxBackground').enable();
this.valueStepperWidgetSettingsForm.get('appearance').get('showBorder').enable({emitEvent: false});
if (showBorder) {
this.valueStepperWidgetSettingsForm.get('appearance').get('borderWidth').enable();
this.valueStepperWidgetSettingsForm.get('appearance').get('borderColor').enable();
} else {
this.valueStepperWidgetSettingsForm.get('appearance').get('borderWidth').disable();
this.valueStepperWidgetSettingsForm.get('appearance').get('borderColor').disable();
}
} else {
this.valueStepperWidgetSettingsForm.get('appearance').get('valueUnits').disable();
this.valueStepperWidgetSettingsForm.get('appearance').get('valueDecimals').disable();
this.valueStepperWidgetSettingsForm.get('appearance').get('valueFont').disable();
this.valueStepperWidgetSettingsForm.get('appearance').get('valueColor').disable();
this.valueStepperWidgetSettingsForm.get('appearance').get('valueBoxBackground').disable();
this.valueStepperWidgetSettingsForm.get('appearance').get('showBorder').disable({emitEvent: false});
this.valueStepperWidgetSettingsForm.get('appearance').get('borderWidth').disable();
this.valueStepperWidgetSettingsForm.get('appearance').get('borderColor').disable();
}
this.buttonValidators(showLeftButton, 'leftButton');
this.buttonValidators(showRightButton, 'rightButton');
}
private buttonValidators(showButtonValue: boolean, button: string) {
if (showButtonValue) {
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('icon').enable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('iconSize').enable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('iconSizeUnit').enable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('mainColorOn').enable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('backgroundColorOn').enable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('mainColorDisabled').enable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('backgroundColorDisabled').enable()
} else {
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('icon').disable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('iconSize').disable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('iconSizeUnit').disable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('mainColorOn').disable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('backgroundColorOn').disable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('mainColorDisabled').disable()
this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('backgroundColorDisabled').disable()
}
}
private _valuePreviewFn(): string {
const units: string = this.valueStepperWidgetSettingsForm.get('appearance').get('valueUnits').value;
const decimals: number = this.valueStepperWidgetSettingsForm.get('appearance').get('valueDecimals').value;
return formatValue(48, decimals, units, false);
}
}

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

@ -371,6 +371,9 @@ import {
import { import {
SegmentedButtonWidgetSettingsComponent SegmentedButtonWidgetSettingsComponent
} from '@home/components/widget/lib/settings/button/segmented-button-widget-settings.component'; } from '@home/components/widget/lib/settings/button/segmented-button-widget-settings.component';
import {
ValueStepperWidgetSettingsComponent
} from '@home/components/widget/lib/settings/control/value-stepper-widget-settings.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -487,6 +490,7 @@ import {
SingleSwitchWidgetSettingsComponent, SingleSwitchWidgetSettingsComponent,
ActionButtonWidgetSettingsComponent, ActionButtonWidgetSettingsComponent,
SegmentedButtonWidgetSettingsComponent, SegmentedButtonWidgetSettingsComponent,
ValueStepperWidgetSettingsComponent,
CommandButtonWidgetSettingsComponent, CommandButtonWidgetSettingsComponent,
PowerButtonWidgetSettingsComponent, PowerButtonWidgetSettingsComponent,
SliderWidgetSettingsComponent, SliderWidgetSettingsComponent,
@ -624,6 +628,7 @@ import {
SingleSwitchWidgetSettingsComponent, SingleSwitchWidgetSettingsComponent,
ActionButtonWidgetSettingsComponent, ActionButtonWidgetSettingsComponent,
SegmentedButtonWidgetSettingsComponent, SegmentedButtonWidgetSettingsComponent,
ValueStepperWidgetSettingsComponent,
CommandButtonWidgetSettingsComponent, CommandButtonWidgetSettingsComponent,
PowerButtonWidgetSettingsComponent, PowerButtonWidgetSettingsComponent,
SliderWidgetSettingsComponent, SliderWidgetSettingsComponent,

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

@ -88,6 +88,7 @@ import {
import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive';
import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component'; import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component';
import { TwoSegmentButtonWidgetComponent } from '@home/components/widget/lib/button/two-segment-button-widget.component'; import { TwoSegmentButtonWidgetComponent } from '@home/components/widget/lib/button/two-segment-button-widget.component';
import { ValueStepperWidgetComponent } from '@home/components/widget/lib/rpc/value-stepper-widget.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -128,6 +129,7 @@ import { TwoSegmentButtonWidgetComponent } from '@home/components/widget/lib/but
TwoSegmentButtonWidgetComponent, TwoSegmentButtonWidgetComponent,
CommandButtonWidgetComponent, CommandButtonWidgetComponent,
PowerButtonWidgetComponent, PowerButtonWidgetComponent,
ValueStepperWidgetComponent,
SliderWidgetComponent, SliderWidgetComponent,
ToggleButtonWidgetComponent, ToggleButtonWidgetComponent,
TimeSeriesChartWidgetComponent, TimeSeriesChartWidgetComponent,
@ -190,6 +192,7 @@ import { TwoSegmentButtonWidgetComponent } from '@home/components/widget/lib/but
TwoSegmentButtonWidgetComponent, TwoSegmentButtonWidgetComponent,
CommandButtonWidgetComponent, CommandButtonWidgetComponent,
PowerButtonWidgetComponent, PowerButtonWidgetComponent,
ValueStepperWidgetComponent,
SliderWidgetComponent, SliderWidgetComponent,
ToggleButtonWidgetComponent, ToggleButtonWidgetComponent,
TimeSeriesChartWidgetComponent, TimeSeriesChartWidgetComponent,

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

@ -6617,6 +6617,8 @@
"layout-outlined-icon": "Outlined.Icon", "layout-outlined-icon": "Outlined.Icon",
"main": "Main", "main": "Main",
"background": "Background", "background": "Background",
"button-icon-on": "Button icon 'On'",
"button-icon-off": "Button icon 'Off'",
"power-on-colors": "Power 'On' colors", "power-on-colors": "Power 'On' colors",
"power-off-colors": "Power 'Off' colors", "power-off-colors": "Power 'Off' colors",
"disabled-colors": "Disabled colors", "disabled-colors": "Disabled colors",
@ -6669,6 +6671,41 @@
"preview": "Preview", "preview": "Preview",
"copy-style-from": "Copy style from" "copy-style-from": "Copy style from"
}, },
"value-stepper": {
"behavior": "Behavior",
"simplified": "Simplified",
"filled": "Filled",
"outlined": "Outlined",
"volume": "Volume",
"initial-state": "Initial state",
"initial-state-hint": "Action to get the initial value.",
"disabled-state": "Disabled state",
"disabled-state-hint": "Configure condition under which the component is disabled.",
"right-button-click": "Right button click",
"right-button-click-hint": "Action while pressing on right button.",
"left-button-click": "Left button click",
"left-button-click-hint": "Action while pressing on left button.",
"auto-scale": "Auto scale",
"value-range": "Range",
"min-range": "Min",
"max-range": "Max",
"value-increment-decrement-step": "Value increment/decrement step",
"value": "Value",
"value-box-background": "Value box background",
"border": "Border",
"button-appearance": "Button appearance",
"left": "Left",
"right": "Right",
"left-button": "Left button",
"right-button": "Right button",
"icon": "Icon",
"color-palette": "Color palette",
"main": "Main",
"background": "Background",
"button-icon-on": "Button icon 'On'",
"button-on-colors": "Power 'On' colors",
"disabled-colors": "Disabled colors"
},
"button-state": { "button-state": {
"activated-state": "Activated state", "activated-state": "Activated state",
"activated-state-hint": "Configure condition under which the button is active.", "activated-state-hint": "Configure condition under which the button is active.",

1
ui-ngx/src/assets/widget/value-stepper/filled.svg

@ -0,0 +1 @@
<svg width="214" height="76" fill="none" xmlns="http://www.w3.org/2000/svg"><g filter="url(#a)"><rect x="8.5" y="4.5" width="197" height="59" rx="4" fill="#fff" shape-rendering="crispEdges"/><rect x="21" y="18.5" width="31" height="31" rx="15.5" stroke="#305680"/><rect x="24.5" y="22" width="24" height="24" rx="12" fill="#305680"/><path d="m38.5 40 1.41-1.41L35.33 34l4.58-4.59L38.5 28l-6 6 6 6Z" fill="#fff"/><path d="M65 22a3.5 3.5 0 0 1 3.5-3.5h77A3.5 3.5 0 0 1 149 22v24a3.5 3.5 0 0 1-3.5 3.5h-77A3.5 3.5 0 0 1 65 46V22Z" fill="#305680" fill-opacity=".04"/><path d="M65 22a3.5 3.5 0 0 1 3.5-3.5h77A3.5 3.5 0 0 1 149 22v24a3.5 3.5 0 0 1-3.5 3.5h-77A3.5 3.5 0 0 1 65 46V22Z" stroke="#305680"/><path d="M88.59 37.5V39h-7.63v-1.29l3.7-4.04c.41-.46.73-.85.97-1.19.23-.33.4-.63.49-.9a2.3 2.3 0 0 0-.06-1.73c-.13-.27-.32-.5-.58-.65a1.7 1.7 0 0 0-.93-.24c-.42 0-.77.1-1.06.27-.28.19-.5.44-.65.76a2.6 2.6 0 0 0-.22 1.1h-1.88c0-.67.15-1.27.46-1.82.3-.55.73-.99 1.3-1.3a4.1 4.1 0 0 1 2.08-.5c.76 0 1.4.13 1.94.38.53.26.93.62 1.2 1.09a3.36 3.36 0 0 1 .25 2.72 5 5 0 0 1-.49 1.04 9 9 0 0 1-.74 1.04c-.28.35-.6.7-.94 1.05l-2.46 2.71h5.25Zm9.27-9.88v1.04L93.3 39h-1.98l4.54-9.88h-5.9v-1.5h7.89Zm1.98 10.44c0-.29.1-.53.3-.73.2-.2.46-.3.8-.3s.6.1.8.3c.2.2.3.44.3.73a1 1 0 0 1-.3.74c-.2.2-.46.3-.8.3s-.6-.1-.8-.3a1 1 0 0 1-.3-.74Zm6.49-4.35-1.5-.37.61-5.72h6.14v1.6H107l-.32 2.79a3.67 3.67 0 0 1 1.81-.46c.54 0 1.02.09 1.44.26.42.17.79.43 1.08.76.3.33.53.73.68 1.2a5 5 0 0 1 0 3.07 3.17 3.17 0 0 1-1.85 2.03c-.46.19-1.01.29-1.65.29a4.6 4.6 0 0 1-1.36-.2 3.73 3.73 0 0 1-1.17-.62 3.14 3.14 0 0 1-1.19-2.42h1.85c.05.37.15.69.3.95.16.25.38.45.64.58a2 2 0 0 0 .92.2c.32 0 .6-.05.83-.16.23-.11.42-.27.57-.48.15-.22.27-.47.34-.75a3.62 3.62 0 0 0-.02-1.87 1.98 1.98 0 0 0-.38-.72c-.17-.2-.38-.36-.63-.47a2.13 2.13 0 0 0-.88-.17c-.45 0-.8.07-1.04.2-.23.13-.45.29-.65.48Zm11.75-4.16c0-.38.1-.72.28-1.04.19-.32.44-.57.75-.76a1.96 1.96 0 0 1 2.05 0c.31.19.56.44.74.76.19.32.28.66.28 1.04s-.1.73-.28 1.05c-.18.31-.43.56-.74.74a2.04 2.04 0 0 1-2.8-.74 2.03 2.03 0 0 1-.28-1.05Zm1.05 0a1 1 0 0 0 1 1 .97.97 0 0 0 .98-1 1 1 0 0 0-.27-.72.93.93 0 0 0-.7-.3c-.28 0-.51.1-.71.3-.2.2-.3.43-.3.72Zm12.19 5.75h1.95a4.5 4.5 0 0 1-.62 1.99 3.72 3.72 0 0 1-1.5 1.37 5 5 0 0 1-2.33.5 4.15 4.15 0 0 1-3.34-1.45c-.4-.48-.71-1.04-.92-1.7-.21-.66-.32-1.4-.32-2.22v-.95c0-.81.1-1.55.32-2.22.22-.66.53-1.22.94-1.69.4-.47.9-.84 1.46-1.09a4.78 4.78 0 0 1 1.93-.37c.9 0 1.67.17 2.3.5.62.33 1.1.8 1.45 1.38.35.59.56 1.26.64 2.02h-1.95c-.05-.48-.17-.9-.35-1.25a1.77 1.77 0 0 0-.76-.8 2.73 2.73 0 0 0-1.33-.28c-.45 0-.84.08-1.17.25a2.2 2.2 0 0 0-.84.73c-.22.33-.39.72-.5 1.2-.11.47-.17 1-.17 1.6v.97c0 .57.05 1.1.15 1.56.1.47.26.86.47 1.2.21.33.48.59.81.77.33.18.72.27 1.18.27.56 0 1-.08 1.35-.26.35-.18.61-.44.8-.78.17-.34.3-.76.35-1.25Z" fill="#000" fill-opacity=".87"/><rect x="162" y="18.5" width="31" height="31" rx="15.5" stroke="#305680"/><rect x="165.5" y="22" width="24" height="24" rx="12" fill="#305680"/><path d="m175.5 28-1.41 1.41 4.58 4.59-4.58 4.59L175.5 40l6-6-6-6Z" fill="#fff"/></g><defs><filter id="a" x=".5" y=".5" width="213" height="75" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/><feOffset dy="4"/><feGaussianBlur stdDeviation="4"/><feComposite in2="hardAlpha" operator="out"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_5669_166705"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_5669_166705" result="shape"/></filter></defs></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

1
ui-ngx/src/assets/widget/value-stepper/simplified.svg

@ -0,0 +1 @@
<svg width="214" height="76" fill="none" xmlns="http://www.w3.org/2000/svg"><g filter="url(#a)"><rect x="8.83" y="4.5" width="197" height="59" rx="4" fill="#fff" shape-rendering="crispEdges"/><rect x="20.83" y="18" width="32" height="32" rx="16" fill="#305680"/><path d="m38.83 40 1.41-1.41L35.66 34l4.58-4.59-1.4-1.41-6 6 6 6Z" fill="#fff"/><rect x="65.33" y="18.5" width="84" height="31" rx="3.5" fill="#305680" fill-opacity=".04"/><rect x="65.33" y="18.5" width="84" height="31" rx="3.5" stroke="#305680"/><path d="M88.92 37.5V39h-7.63v-1.29l3.7-4.04c.41-.46.73-.85.97-1.19.23-.33.4-.63.49-.9a2.3 2.3 0 0 0-.05-1.73c-.13-.27-.33-.5-.58-.65a1.7 1.7 0 0 0-.93-.24c-.42 0-.78.1-1.06.27-.3.19-.5.44-.65.76a2.6 2.6 0 0 0-.22 1.1h-1.88c0-.67.15-1.27.45-1.82.3-.55.74-.99 1.31-1.3a4.1 4.1 0 0 1 2.07-.5c.76 0 1.4.13 1.94.38.53.26.93.62 1.21 1.09.28.47.42 1.02.42 1.66 0 .36-.06.7-.17 1.06a5 5 0 0 1-.5 1.04c-.2.35-.45.7-.73 1.04-.3.35-.6.7-.95 1.05l-2.46 2.71h5.25Zm9.27-9.88v1.04L93.65 39h-1.99l4.54-9.88h-5.89v-1.5h7.88Zm1.98 10.44c0-.29.1-.53.3-.73.2-.2.46-.3.8-.3s.61.1.8.3c.2.2.3.44.3.73a1 1 0 0 1-.3.74c-.19.2-.46.3-.8.3s-.6-.1-.8-.3a1 1 0 0 1-.3-.74Zm6.5-4.35-1.5-.37.6-5.72h6.14v1.6h-4.57l-.31 2.79a3.67 3.67 0 0 1 1.8-.46c.54 0 1.02.09 1.44.26.43.17.79.43 1.09.76.3.33.52.73.68 1.2a5 5 0 0 1 0 3.07c-.16.45-.38.86-.7 1.2-.3.36-.69.63-1.16.83a4.6 4.6 0 0 1-3.02.09 3.73 3.73 0 0 1-1.16-.62 3.14 3.14 0 0 1-1.18-2.42h1.84c.05.37.15.69.3.95.17.25.38.45.65.58a2 2 0 0 0 .92.2c.32 0 .59-.05.82-.16.23-.11.42-.27.57-.48.16-.22.27-.47.34-.75a3.62 3.62 0 0 0-.01-1.87 1.98 1.98 0 0 0-.39-.72c-.16-.2-.37-.36-.63-.47a2.13 2.13 0 0 0-.88-.17c-.45 0-.8.07-1.03.2-.24.13-.46.29-.66.48Zm11.74-4.16c0-.38.1-.72.28-1.04.19-.32.44-.57.75-.76a1.96 1.96 0 0 1 2.06 0c.3.19.55.44.73.76.19.32.28.66.28 1.04s-.1.73-.28 1.05a1.97 1.97 0 0 1-1.76 1.02 2.04 2.04 0 0 1-1.78-1.02 2.03 2.03 0 0 1-.28-1.05Zm1.06 0a1 1 0 0 0 1 1 .97.97 0 0 0 .98-1 1 1 0 0 0-.28-.72.93.93 0 0 0-.7-.3c-.27 0-.5.1-.7.3-.2.2-.3.43-.3.72Zm12.18 5.75h1.96a4.5 4.5 0 0 1-.63 1.99 3.72 3.72 0 0 1-1.5 1.37 5 5 0 0 1-2.33.5 4.15 4.15 0 0 1-3.34-1.45c-.4-.48-.7-1.04-.92-1.7-.21-.66-.31-1.4-.31-2.22v-.95c0-.81.1-1.55.32-2.22a4.9 4.9 0 0 1 .93-1.69c.4-.47.9-.84 1.46-1.09a4.78 4.78 0 0 1 1.93-.37c.91 0 1.68.17 2.3.5.63.33 1.11.8 1.45 1.38.35.59.57 1.26.64 2.02h-1.95c-.05-.48-.17-.9-.34-1.25a1.77 1.77 0 0 0-.77-.8 2.73 2.73 0 0 0-1.33-.28c-.44 0-.84.08-1.17.25a2.2 2.2 0 0 0-.83.73c-.23.33-.4.72-.51 1.2-.11.47-.17 1-.17 1.6v.97c0 .57.05 1.1.15 1.56.1.47.26.86.47 1.2.22.33.49.59.81.77.33.18.72.27 1.18.27.56 0 1.01-.08 1.35-.26.35-.18.62-.44.8-.78.18-.34.3-.76.35-1.25Z" fill="#000" fill-opacity=".87"/><rect x="161.83" y="18" width="32" height="32" rx="16" fill="#305680"/><path d="m175.83 28-1.4 1.41L179 34l-4.58 4.59 1.41 1.41 6-6-6-6Z" fill="#fff"/></g><defs><filter id="a" x=".83" y=".5" width="213" height="75" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/><feOffset dy="4"/><feGaussianBlur stdDeviation="4"/><feComposite in2="hardAlpha" operator="out"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_5669_166701"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_5669_166701" result="shape"/></filter></defs></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

1
ui-ngx/src/assets/widget/value-stepper/volume.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

Loading…
Cancel
Save