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