22 changed files with 2165 additions and 130 deletions
@ -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" |
|||
] |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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})`); |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -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' |
|||
}; |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
Loading…
Reference in new issue