Commit 9e1fe1e1c3bf99f18c28ab5b7f45992477e46741

Authored by Igor Kulikov
1 parent d66a3feb

Implement control widgets.

Showing 22 changed files with 2469 additions and 12 deletions
... ... @@ -45,9 +45,9 @@
45 45 "sizeX": 5,
46 46 "sizeY": 4.5,
47 47 "resources": [],
48   - "templateHtml": "<tb-knob ctx='ctx'></tb-knob>",
  48 + "templateHtml": "<tb-knob [ctx]='ctx'></tb-knob>",
49 49 "templateCss": "",
50   - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n",
  50 + "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
51 51 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\"\n ]\n}",
52 52 "dataKeySettingsSchema": "{}\n",
53 53 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
... ... @@ -61,9 +61,9 @@
61 61 "sizeX": 4,
62 62 "sizeY": 2.5,
63 63 "resources": [],
64   - "templateHtml": "<tb-switch ctx='ctx'></tb-switch>",
  64 + "templateHtml": "<tb-switch [ctx]='ctx'></tb-switch>",
65 65 "templateCss": "",
66   - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n",
  66 + "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
67 67 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}",
68 68 "dataKeySettingsSchema": "{}\n",
69 69 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"showOnOffLabels\":true,\"title\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
... ... @@ -77,9 +77,9 @@
77 77 "sizeX": 2.5,
78 78 "sizeY": 2,
79 79 "resources": [],
80   - "templateHtml": "<tb-round-switch ctx='ctx'></tb-round-switch>",
  80 + "templateHtml": "<tb-round-switch [ctx]='ctx'></tb-round-switch>",
81 81 "templateCss": "",
82   - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n",
  82 + "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
83 83 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}",
84 84 "dataKeySettingsSchema": "{}\n",
85 85 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
... ... @@ -93,9 +93,9 @@
93 93 "sizeX": 2.5,
94 94 "sizeY": 2.5,
95 95 "resources": [],
96   - "templateHtml": "<tb-led-indicator ctx='ctx'></tb-led-indicator>",
  96 + "templateHtml": "<tb-led-indicator [ctx]='ctx'></tb-led-indicator>",
97 97 "templateCss": "",
98   - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n",
  98 + "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
99 99 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}",
100 100 "dataKeySettingsSchema": "{}\n",
101 101 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
... ...
... ... @@ -185,7 +185,7 @@ export interface WidgetSubscriptionCallbacks {
185 185 }
186 186
187 187 export interface WidgetSubscriptionOptions {
188   - type: widgetType;
  188 + type?: widgetType;
189 189 stateData?: boolean;
190 190 alarmSource?: Datasource;
191 191 alarmSearchStatus?: AlarmSearchStatus;
... ...
... ... @@ -395,10 +395,12 @@ export class CanvasDigitalGauge extends BaseGauge {
395 395 let color = this.contextProgressClone.currentColor;
396 396 const options = this.options as CanvasDigitalGaugeOptions;
397 397 if (!color) {
  398 + const progress = (CanvasGauges.drawings.normalizedValue(options).normal - options.minValue) /
  399 + (options.maxValue - options.minValue);
398 400 if (options.neonGlowBrightness) {
399   - color = getProgressColor(0, options.neonColorsRange);
  401 + color = getProgressColor(progress, options.neonColorsRange);
400 402 } else {
401   - color = getProgressColor(0, options.colorsRange);
  403 + color = getProgressColor(progress, options.colorsRange);
402 404 }
403 405 }
404 406 return color;
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div class="tb-knob" fxLayout="column" [ngStyle]="{'pointerEvents': ctx.isEdit ? 'none' : 'all'}">
  19 + <div #knobContainer id="knob-container" fxFlex fxLayout="column" fxLayoutAlign="center center">
  20 + <div #knob class="knob">
  21 + <div #knobValueContainer class="value-container" fxLayout="row" fxLayoutAlign="center center">
  22 + <span #knobValue class="knob-value">{{ value }}</span>
  23 + </div>
  24 + <div class="canvas-background"></div>
  25 + <canvas #canvasBar id="canvasBar"></canvas>
  26 + <div #knobTopPointerContainer class="top-pointer-container">
  27 + <div #knobTopPointer class="top-pointer"></div>
  28 + </div>
  29 + <div class="top"></div>
  30 + <div #knobErrorContainer class="error-container" [ngStyle]="{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}"
  31 + fxLayout="row" fxLayoutAlign="center center">
  32 + <span #knobError class="knob-error">{{ error }}</span>
  33 + </div>
  34 + <div #knobTitleContainer class="title-container" fxLayout="row" fxLayoutAlign="center center">
  35 + <span #knobTitle class="knob-title">{{ title }}</span>
  36 + </div>
  37 + <div #knobMinmaxContainer class="minmax-container" fxLayout="row" fxLayoutAlign="start center">
  38 + <span class="minmax-label">min</span>
  39 + <span fxFlex></span>
  40 + <span class="minmax-label">max</span>
  41 + </div>
  42 + <div #textMeasure id="text-measure"></div>
  43 + </div>
  44 + </div>
  45 +</div>
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +$knob-img: url("./svg/knob.svg") !default;
  17 +
  18 +$bars-margin-pct: percentage(.033) !default;
  19 +$background-margin-pct: percentage(.05) !default;
  20 +$value-container-margin-pct: percentage(.35) !default;
  21 +$error-height: percentage(.05) !default;
  22 +$title-height: percentage(.066) !default;
  23 +$title-container-margin-pct: percentage(.2) !default;
  24 +$title-container-margin-bottom-pct: percentage(.05) !default;
  25 +$minmax-height: percentage(.04) !default;
  26 +$minmax-container-margin-pct: percentage(.18) !default;
  27 +$minmax-container-margin-bottom-pct: percentage(.12) !default;
  28 +
  29 +$background-color: #e6e7e8 !default;
  30 +
  31 +:host {
  32 + .tb-knob {
  33 + width: 100%;
  34 + height: 100%;
  35 + background: $background-color;
  36 +
  37 + .knob {
  38 + position: relative;
  39 +
  40 + &[draggable] {
  41 + user-select: none;
  42 + }
  43 +
  44 + #canvasBar {
  45 + position: absolute;
  46 + top: 0;
  47 + right: 0;
  48 + bottom: 0;
  49 + left: 0;
  50 + z-index: 2;
  51 + }
  52 +
  53 + .canvas-background {
  54 + position: absolute;
  55 + top: $background-margin-pct;
  56 + right: $background-margin-pct;
  57 + bottom: $background-margin-pct;
  58 + left: $background-margin-pct;
  59 + z-index: 2;
  60 + background: #3f4346;
  61 + border-radius: 50%;
  62 + }
  63 +
  64 + .value-container {
  65 + position: absolute;
  66 + top: $value-container-margin-pct;
  67 + right: $value-container-margin-pct;
  68 + bottom: $value-container-margin-pct;
  69 + left: $value-container-margin-pct;
  70 + z-index: 4;
  71 +
  72 + .knob-value {
  73 + font-weight: 500;
  74 + color: #fff;
  75 + white-space: nowrap;
  76 + }
  77 + }
  78 +
  79 + .error-container {
  80 + position: absolute;
  81 + top: 1%;
  82 + right: 0;
  83 + left: 0;
  84 + z-index: 4;
  85 + height: $error-height;
  86 +
  87 + .knob-error {
  88 + color: #ff3315;
  89 + white-space: nowrap;
  90 + }
  91 + }
  92 +
  93 + .title-container {
  94 + position: absolute;
  95 + right: $title-container-margin-pct;
  96 + bottom: $title-container-margin-bottom-pct;
  97 + left: $title-container-margin-pct;
  98 + z-index: 4;
  99 + height: $title-height;
  100 +
  101 + .knob-title {
  102 + font-weight: 500;
  103 + color: #757575;
  104 + white-space: nowrap;
  105 + }
  106 + }
  107 +
  108 + .minmax-container {
  109 + position: absolute;
  110 + right: $minmax-container-margin-pct;
  111 + bottom: $minmax-container-margin-bottom-pct;
  112 + left: $minmax-container-margin-pct;
  113 + z-index: 4;
  114 + height: $minmax-height;
  115 +
  116 + .minmax-label {
  117 + font-weight: 500;
  118 + color: #757575;
  119 + white-space: nowrap;
  120 + }
  121 + }
  122 +
  123 + .top-pointer-container {
  124 + position: absolute;
  125 + top: $bars-margin-pct;
  126 + right: $bars-margin-pct;
  127 + bottom: $bars-margin-pct;
  128 + left: $bars-margin-pct;
  129 + z-index: 3;
  130 + cursor: pointer !important;
  131 +
  132 + .top-pointer {
  133 + position: absolute;
  134 + top: 50%;
  135 + left: 22%;
  136 + width: 5%;
  137 + height: 5%;
  138 + margin-top: -2.5%;
  139 + cursor: pointer !important;
  140 + content: "";
  141 + background-color: #b5b5b5;
  142 + border-radius: 50%;
  143 + box-shadow: 1px 0 2px #040404;
  144 + }
  145 + }
  146 +
  147 + .top {
  148 + position: absolute;
  149 + top: $bars-margin-pct;
  150 + right: $bars-margin-pct;
  151 + bottom: $bars-margin-pct;
  152 + left: $bars-margin-pct;
  153 + z-index: 2;
  154 + cursor: pointer !important;
  155 + background: $knob-img no-repeat;
  156 + background-size: contain;
  157 + }
  158 +
  159 + #text-measure {
  160 + position: absolute;
  161 + width: auto;
  162 + height: auto;
  163 + white-space: nowrap;
  164 + visibility: hidden;
  165 + }
  166 + }
  167 + }
  168 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
  18 +import { PageComponent } from '@shared/components/page.component';
  19 +import { WidgetContext } from '@home/models/widget-component.models';
  20 +import { UtilsService } from '@core/services/utils.service';
  21 +import { Store } from '@ngrx/store';
  22 +import { AppState } from '@core/core.state';
  23 +import { isDefined, isNumber } from '@core/utils';
  24 +import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
  25 +import GenericOptions = CanvasGauges.GenericOptions;
  26 +import * as tinycolor_ from 'tinycolor2';
  27 +
  28 +const tinycolor = tinycolor_;
  29 +
  30 +interface KnobSettings {
  31 + minValue: number;
  32 + maxValue: number;
  33 + initialValue: number;
  34 + title: string;
  35 + getValueMethod: string;
  36 + setValueMethod: string;
  37 + requestTimeout: number;
  38 +}
  39 +
  40 +@Component({
  41 + selector: 'tb-knob',
  42 + templateUrl: './knob.component.html',
  43 + styleUrls: ['./knob.component.scss']
  44 +})
  45 +export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
  46 +
  47 + @ViewChild('knob', {static: true}) knobRef: ElementRef<HTMLElement>;
  48 + @ViewChild('knobContainer', {static: true}) knobContainerRef: ElementRef<HTMLElement>;
  49 + @ViewChild('knobTopPointerContainer', {static: true}) knobTopPointerContainerRef: ElementRef<HTMLElement>;
  50 + @ViewChild('knobTopPointer', {static: true}) knobTopPointerRef: ElementRef<HTMLElement>;
  51 + @ViewChild('knobValueContainer', {static: true}) knobValueContainerRef: ElementRef<HTMLElement>;
  52 + @ViewChild('knobValue', {static: true}) knobValueRef: ElementRef<HTMLElement>;
  53 + @ViewChild('knobErrorContainer', {static: true}) knobErrorContainerRef: ElementRef<HTMLElement>;
  54 + @ViewChild('knobError', {static: true}) knobErrorRef: ElementRef<HTMLElement>;
  55 + @ViewChild('knobTitleContainer', {static: true}) knobTitleContainerRef: ElementRef<HTMLElement>;
  56 + @ViewChild('knobTitle', {static: true}) knobTitleRef: ElementRef<HTMLElement>;
  57 + @ViewChild('knobMinmaxContainer', {static: true}) knobMinmaxContainerRef: ElementRef<HTMLElement>;
  58 + @ViewChild('textMeasure', {static: true}) textMeasureRef: ElementRef<HTMLElement>;
  59 + @ViewChild('canvasBar', {static: true}) canvasBarElementRef: ElementRef<HTMLElement>;
  60 +
  61 + @Input()
  62 + ctx: WidgetContext;
  63 +
  64 + value = '0';
  65 + error = '';
  66 + title = '';
  67 + minValue: number;
  68 + maxValue: number;
  69 +
  70 + private startDeg = -1;
  71 + private currentDeg = 0;
  72 + private rotation = 0;
  73 + private lastDeg = 0;
  74 + private moving = false;
  75 +
  76 + private minDeg = -45;
  77 + private maxDeg = 225;
  78 +
  79 + private isSimulated: boolean;
  80 + private requestTimeout: number;
  81 + private getValueMethod: string;
  82 + private setValueMethod: string;
  83 +
  84 + private executingUpdateValue: boolean;
  85 + private scheduledValue: number;
  86 + private rpcValue: number;
  87 +
  88 + private knob: JQuery<HTMLElement>;
  89 + private knobContainer: JQuery<HTMLElement>;
  90 + private knobTopPointerContainer: JQuery<HTMLElement>;
  91 + private knobTopPointer: JQuery<HTMLElement>;
  92 + private knobValueContainer: JQuery<HTMLElement>;
  93 + private knobValue: JQuery<HTMLElement>;
  94 + private knobTitleContainer: JQuery<HTMLElement>;
  95 + private knobTitle: JQuery<HTMLElement>;
  96 + private knobErrorContainer: JQuery<HTMLElement>;
  97 + private knobError: JQuery<HTMLElement>;
  98 + private knobMinmaxContainer: JQuery<HTMLElement>;
  99 + private minmaxLabel: JQuery<HTMLElement>;
  100 + private textMeasure: JQuery<HTMLElement>;
  101 + private canvasBarElement: HTMLElement;
  102 +
  103 + private canvasBar: CanvasDigitalGauge;
  104 +
  105 + private knobResizeListener: any;
  106 +
  107 + constructor(private utils: UtilsService,
  108 + protected store: Store<AppState>) {
  109 + super(store);
  110 + }
  111 +
  112 + ngOnInit(): void {
  113 + this.knob = $(this.knobRef.nativeElement);
  114 + this.knobContainer = $(this.knobContainerRef.nativeElement);
  115 + this.knobTopPointerContainer = $(this.knobTopPointerContainerRef.nativeElement);
  116 + this.knobTopPointer = $(this.knobTopPointerRef.nativeElement);
  117 + this.knobValueContainer = $(this.knobValueContainerRef.nativeElement);
  118 + this.knobValue = $(this.knobValueRef.nativeElement);
  119 + this.knobTitleContainer = $(this.knobTitleContainerRef.nativeElement);
  120 + this.knobTitle = $(this.knobTitleRef.nativeElement);
  121 + this.knobErrorContainer = $(this.knobErrorContainerRef.nativeElement);
  122 + this.knobError = $(this.knobErrorRef.nativeElement);
  123 + this.knobMinmaxContainer = $(this.knobMinmaxContainerRef.nativeElement);
  124 + this.minmaxLabel = this.knobMinmaxContainer.find('.minmax-label');
  125 + this.textMeasure = $(this.textMeasureRef.nativeElement);
  126 + this.canvasBarElement = this.canvasBarElementRef.nativeElement;
  127 +
  128 + this.knobResizeListener = this.resize.bind(this);
  129 + // @ts-ignore
  130 + addResizeListener(this.knobContainerRef.nativeElement, this.knobResizeListener);
  131 + this.init();
  132 + }
  133 +
  134 + ngOnDestroy(): void {
  135 + if (this.knobResizeListener) {
  136 + // @ts-ignore
  137 + removeResizeListener(this.knobContainerRef.nativeElement, this.knobResizeListener);
  138 + }
  139 + }
  140 +
  141 + private init() {
  142 + const settings: KnobSettings = this.ctx.settings;
  143 +
  144 + this.minValue = isDefined(settings.minValue) ? settings.minValue : 0;
  145 + this.maxValue = isDefined(settings.maxValue) ? settings.maxValue : 100;
  146 + this.title = isDefined(settings.title) ? settings.title : '';
  147 +
  148 + const levelColors = ['#19ff4b', '#ffff19', '#ff3232'];
  149 +
  150 + const canvasBarData: CanvasDigitalGaugeOptions = {
  151 + renderTo: this.canvasBarElement,
  152 + hideValue: true,
  153 + neonGlowBrightness: 0,
  154 + gaugeWidthScale: 0.4,
  155 + gaugeColor: 'rgba(0, 0, 0, 0)',
  156 + levelColors,
  157 + minValue: this.minValue,
  158 + maxValue: this.maxValue,
  159 + gaugeType: 'donut',
  160 + dashThickness: 2,
  161 + donutStartAngle: 3/4*Math.PI,
  162 + donutEndAngle: 9/4*Math.PI,
  163 + animation: false
  164 + };
  165 +
  166 + this.canvasBar = new CanvasDigitalGauge(canvasBarData).draw();
  167 +
  168 + this.knob.on('click', (e) => {
  169 + if (this.moving) {
  170 + this.moving = false;
  171 + return false;
  172 + }
  173 + e.preventDefault();
  174 +
  175 + const offset = this.knob.offset();
  176 + const center = {
  177 + y : offset.top + this.knob.height()/2,
  178 + x: offset.left + this.knob.width()/2
  179 + };
  180 + const rad2deg = 180/Math.PI;
  181 + const t: Touch = ((e.originalEvent as any).touches) ? (e.originalEvent as any).touches[0] : e;
  182 +
  183 + const a = center.y - t.pageY;
  184 + const b = center.x - t.pageX;
  185 + let deg = Math.atan2(a,b)*rad2deg;
  186 + if(deg < 0){
  187 + deg = 360 + deg;
  188 + }
  189 + if (deg > this.maxDeg) {
  190 + if (deg - 360 > this.minDeg) {
  191 + deg = deg - 360;
  192 + } else {
  193 + return false;
  194 + }
  195 + }
  196 + this.currentDeg = deg;
  197 + this.lastDeg = deg;
  198 + this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)');
  199 + this.turn(this.degreeToRatio(this.currentDeg));
  200 + this.rotation = this.currentDeg;
  201 + this.startDeg = -1;
  202 + });
  203 +
  204 + this.knob.on('mousedown touchstart', (e) => {
  205 + e.preventDefault();
  206 + const offset = this.knob.offset();
  207 + const center = {
  208 + y : offset.top + this.knob.height()/2,
  209 + x: offset.left + this.knob.width()/2
  210 + };
  211 + const rad2deg = 180/Math.PI;
  212 +
  213 + this.knob.on('mousemove.rem touchmove.rem', (ev) => {
  214 + this.moving = true;
  215 + const t: Touch = ((ev.originalEvent as any).touches) ? (ev.originalEvent as any).touches[0] : ev;
  216 +
  217 + const a = center.y - t.pageY;
  218 + const b = center.x - t.pageX;
  219 + let deg = Math.atan2(a,b)*rad2deg;
  220 + if(deg < 0){
  221 + deg = 360 + deg;
  222 + }
  223 +
  224 + if(this.startDeg === -1){
  225 + this.startDeg = deg;
  226 + }
  227 +
  228 + let tmp = Math.floor((deg-this.startDeg) + this.rotation);
  229 +
  230 + if(tmp < 0){
  231 + tmp = 360 + tmp;
  232 + }
  233 + else if(tmp > 359){
  234 + tmp = tmp % 360;
  235 + }
  236 +
  237 + if (tmp > this.maxDeg) {
  238 + if (tmp - 360 > this.minDeg) {
  239 + tmp = tmp - 360;
  240 + } else {
  241 + const deltaMax = Math.abs(this.maxDeg - this.lastDeg);
  242 + const deltaMin = Math.abs(this.minDeg - this.lastDeg);
  243 + if (deltaMax < deltaMin) {
  244 + tmp = this.maxDeg;
  245 + } else {
  246 + tmp = this.minDeg;
  247 + }
  248 + }
  249 + }
  250 + if(Math.abs(tmp - this.lastDeg) > 180){
  251 + this.startDeg = deg;
  252 + this.rotation = this.currentDeg;
  253 + return false;
  254 + }
  255 +
  256 + this.currentDeg = tmp;
  257 + this.lastDeg = tmp;
  258 +
  259 + this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)');
  260 + this.turn(this.degreeToRatio(this.currentDeg));
  261 + });
  262 +
  263 + $(document).on('mouseup.rem touchend.rem',() => {
  264 + this.knob.off('.rem');
  265 + $(document).off('.rem');
  266 + this.rotation = this.currentDeg;
  267 + this.startDeg = -1;
  268 + });
  269 +
  270 + });
  271 +
  272 + const initialValue = isDefined(settings.initialValue) ? settings.initialValue : this.minValue;
  273 + this.setValue(initialValue);
  274 +
  275 + const subscription = this.ctx.defaultSubscription;
  276 + const rpcEnabled = subscription.rpcEnabled;
  277 +
  278 + this.isSimulated = this.utils.widgetEditMode;
  279 +
  280 + this.requestTimeout = 500;
  281 + if (settings.requestTimeout) {
  282 + this.requestTimeout = settings.requestTimeout;
  283 + }
  284 + this.getValueMethod = 'getValue';
  285 + if (settings.getValueMethod && settings.getValueMethod.length) {
  286 + this.getValueMethod = settings.getValueMethod;
  287 + }
  288 + this.setValueMethod = 'setValue';
  289 + if (settings.setValueMethod && settings.setValueMethod.length) {
  290 + this.setValueMethod = settings.setValueMethod;
  291 + }
  292 + if (!rpcEnabled) {
  293 + this.onError('Target device is not set!');
  294 + } else {
  295 + if (!this.isSimulated) {
  296 + this.rpcRequestValue();
  297 + }
  298 + }
  299 + }
  300 +
  301 + private degreeToRatio(degree: number): number {
  302 + return (degree-this.minDeg)/(this.maxDeg-this.minDeg);
  303 + }
  304 +
  305 + private ratioToDegree(ratio: number): number {
  306 + return this.minDeg + ratio*(this.maxDeg-this.minDeg);
  307 + }
  308 +
  309 + private turn(ratio: number) {
  310 + const value = Number((this.minValue + (this.maxValue - this.minValue)*ratio).toFixed(this.ctx.decimals));
  311 + if (this.canvasBar.value !== value) {
  312 + this.canvasBar.value = value;
  313 + }
  314 + this.updateColor(this.canvasBar.getValueColor());
  315 + this.onValue(value);
  316 + }
  317 +
  318 + private resize() {
  319 + const width = this.knobContainer.width();
  320 + const height = this.knobContainer.height();
  321 + const size = Math.min(width, height);
  322 + this.knob.css({width: size, height: size});
  323 + this.canvasBar.update({width: size, height: size} as GenericOptions);
  324 + this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width());
  325 + this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width());
  326 + const minmaxHeight = this.knobMinmaxContainer.height();
  327 + this.minmaxLabel.css({fontSize: minmaxHeight+'px', lineHeight: minmaxHeight+'px'});
  328 + this.checkValueSize();
  329 + }
  330 +
  331 + private checkValueSize() {
  332 + const fontSize = this.knobValueContainer.height()/3.3;
  333 + const containerWidth = this.knobValueContainer.width();
  334 + this.setFontSize(this.knobValue, this.value+'', fontSize, containerWidth);
  335 + }
  336 +
  337 + private setFontSize(element: JQuery<HTMLElement>, text: string, fontSize: number, maxWidth: number) {
  338 + let textWidth = this.measureTextWidth(text, fontSize);
  339 + while (textWidth > maxWidth) {
  340 + fontSize--;
  341 + textWidth = this.measureTextWidth(text, fontSize);
  342 + }
  343 + element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
  344 + }
  345 +
  346 + private measureTextWidth(text: string, fontSize: number): number {
  347 + this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
  348 + this.textMeasure.text(text);
  349 + return this.textMeasure.width();
  350 + }
  351 +
  352 + private setValue(value: number) {
  353 + const ratio = (value-this.minValue) / (this.maxValue - this.minValue);
  354 + this.rotation = this.lastDeg = this.currentDeg = this.ratioToDegree(ratio);
  355 + this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)');
  356 + if (this.canvasBar.value !== value) {
  357 + this.canvasBar.value = value;
  358 + }
  359 + this.updateColor(this.canvasBar.getValueColor());
  360 + this.value = this.formatValue(value);
  361 + this.checkValueSize();
  362 + }
  363 +
  364 + private updateColor(color: string) {
  365 + const glowColor = tinycolor(color).brighten(30).toHexString();
  366 + this.knobValue.css({color: glowColor});
  367 + const textShadow = `${color} 1px 1px 10px, ${glowColor} 1px 1px 10px`;
  368 + this.knobValue.css({textShadow});
  369 + this.knobTopPointer.css({backgroundColor: glowColor});
  370 + const boxShadow = `inset 1px 0 2px #040404, 1px 1px 8px 2px ${glowColor}`;
  371 + this.knobTopPointer.css({boxShadow});
  372 + }
  373 +
  374 + private onValue(value: number) {
  375 + this.value = this.formatValue(value);
  376 + this.checkValueSize();
  377 + this.rpcUpdateValue(value);
  378 + this.ctx.detectChanges();
  379 + }
  380 +
  381 + private formatValue(value: any): string {
  382 + return this.ctx.utils.formatValue(value, this.ctx.decimals, this.ctx.units, true);
  383 + }
  384 +
  385 + private rpcRequestValue() {
  386 + this.error = '';
  387 + this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe(
  388 + (responseBody) => {
  389 + if (isNumber(responseBody)) {
  390 + const numValue = Number(Number(responseBody).toFixed(this.ctx.decimals));
  391 + this.setValue(numValue);
  392 + } else {
  393 + const errorText = `Unable to parse response: ${responseBody}`;
  394 + this.onError(errorText);
  395 + }
  396 + },
  397 + () => {
  398 + const errorText = this.ctx.defaultSubscription.rpcErrorText;
  399 + this.onError(errorText);
  400 + }
  401 + );
  402 + }
  403 +
  404 + private rpcUpdateValue(value: number) {
  405 + if (this.executingUpdateValue) {
  406 + this.scheduledValue = value;
  407 + return;
  408 + } else {
  409 + this.scheduledValue = null;
  410 + this.rpcValue = value;
  411 + this.executingUpdateValue = true;
  412 + }
  413 + this.error = '';
  414 + this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, value, this.requestTimeout).subscribe(
  415 + () => {
  416 + this.executingUpdateValue = false;
  417 + if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
  418 + this.rpcUpdateValue(this.scheduledValue);
  419 + }
  420 + },
  421 + () => {
  422 + this.executingUpdateValue = false;
  423 + const errorText = this.ctx.defaultSubscription.rpcErrorText;
  424 + this.onError(errorText);
  425 + }
  426 + );
  427 + }
  428 +
  429 + private onError(error: string) {
  430 + this.error = error;
  431 + this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width());
  432 + this.ctx.detectChanges();
  433 + }
  434 +
  435 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div class="tb-led-indicator" fxLayout="column">
  19 + <div fxFlex="20" #ledTitleContainer class="title-container" fxLayout="row" fxLayoutAlign="center center" [fxShow]="showTitle">
  20 + <span #ledTitle class="led-title">{{title}}</span>
  21 + </div>
  22 + <div fxFlex="{{showTitle ? 80 : 100}}"
  23 + [ngStyle]="{paddingTop: showTitle ? '5px': '10px'}" #ledContainer id="led-container" fxLayout="column" fxLayoutAlign="center center">
  24 + <div #led class="led">
  25 + </div>
  26 + </div>
  27 + <div #ledErrorContainer class="error-container" [ngStyle]="{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}"
  28 + fxLayout="row" fxLayoutAlign="center center">
  29 + <span #ledError class="led-error">{{ error }}</span>
  30 + </div>
  31 + <div #textMeasure id="text-measure"></div>
  32 +</div>
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +@import "~compass-sass-mixins/lib/compass";
  17 +
  18 +$error-height: 14px !default;
  19 +
  20 +$background-color: #e6e7e8 !default;
  21 +
  22 +:host {
  23 + .tb-led-indicator {
  24 + width: 100%;
  25 + height: 100%;
  26 + background: $background-color;
  27 +
  28 + .title-container {
  29 + .led-title {
  30 + font-weight: 500;
  31 + color: #757575;
  32 + white-space: nowrap;
  33 + }
  34 + }
  35 +
  36 + .error-container {
  37 + position: absolute;
  38 + top: 1%;
  39 + right: 0;
  40 + left: 0;
  41 + z-index: 4;
  42 + height: $error-height;
  43 +
  44 + .led-error {
  45 + color: #ff3315;
  46 + white-space: nowrap;
  47 + }
  48 + }
  49 +
  50 + #text-measure {
  51 + position: absolute;
  52 + width: auto;
  53 + height: auto;
  54 + white-space: nowrap;
  55 + visibility: hidden;
  56 + }
  57 +
  58 + #led-container {
  59 + padding: 10px;
  60 +
  61 + .led {
  62 + position: relative;
  63 + cursor: pointer;
  64 + background-image: radial-gradient(circle closest-corner at 50% 50%, transparent, rgba(0, 0, 0, .25));
  65 + border-radius: 50%;
  66 + transition: background-color .5s, box-shadow .5s;
  67 +
  68 + &.disabled {
  69 + background-image: radial-gradient(circle closest-corner at 50% 50%, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
  70 + }
  71 + }
  72 + }
  73 + }
  74 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
  18 +import { PageComponent } from '@shared/components/page.component';
  19 +import { WidgetContext } from '@home/models/widget-component.models';
  20 +import { Store } from '@ngrx/store';
  21 +import { AppState } from '@core/core.state';
  22 +import { isDefined } from '@core/utils';
  23 +import * as tinycolor_ from 'tinycolor2';
  24 +import { UtilsService } from '@core/services/utils.service';
  25 +import { IWidgetSubscription, SubscriptionInfo, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
  26 +import { DatasourceType, widgetType } from '@shared/models/widget.models';
  27 +import { EntityType } from '@shared/models/entity-type.models';
  28 +import Timeout = NodeJS.Timeout;
  29 +
  30 +const tinycolor = tinycolor_;
  31 +
  32 +const checkStatusPollingInterval = 10000;
  33 +
  34 +type RetrieveValueMethod = 'attribute' | 'timeseries';
  35 +
  36 +interface LedIndicatorSettings {
  37 + initialValue: boolean;
  38 + title: string;
  39 + ledColor: string;
  40 + performCheckStatus: boolean;
  41 + checkStatusMethod: string;
  42 + retrieveValueMethod: RetrieveValueMethod;
  43 + valueAttribute: string;
  44 + parseValueFunction: string;
  45 + requestTimeout: number;
  46 +}
  47 +
  48 +@Component({
  49 + selector: 'tb-led-indicator',
  50 + templateUrl: './led-indicator.component.html',
  51 + styleUrls: ['./led-indicator.component.scss']
  52 +})
  53 +export class LedIndicatorComponent extends PageComponent implements OnInit, OnDestroy {
  54 +
  55 + @ViewChild('led', {static: true}) ledRef: ElementRef<HTMLElement>;
  56 + @ViewChild('ledContainer', {static: true}) ledContainerRef: ElementRef<HTMLElement>;
  57 + @ViewChild('textMeasure', {static: true}) textMeasureRef: ElementRef<HTMLElement>;
  58 + @ViewChild('ledTitleContainer', {static: true}) ledTitleContainerRef: ElementRef<HTMLElement>;
  59 + @ViewChild('ledTitle', {static: true}) ledTitleRef: ElementRef<HTMLElement>;
  60 + @ViewChild('ledErrorContainer', {static: true}) ledErrorContainerRef: ElementRef<HTMLElement>;
  61 + @ViewChild('ledError', {static: true}) ledErrorRef: ElementRef<HTMLElement>;
  62 +
  63 + @Input()
  64 + ctx: WidgetContext;
  65 +
  66 + showTitle = false;
  67 + value = false;
  68 + error = '';
  69 + title = '';
  70 +
  71 + private valueAttribute: string;
  72 + private ledColor: string;
  73 + private ledMiddleColor: string;
  74 + private disabledColor: string;
  75 + private disabledMiddleColor: string;
  76 +
  77 + private isSimulated: boolean;
  78 + private requestTimeout: number;
  79 + private retrieveValueMethod: RetrieveValueMethod;
  80 + private parseValueFunction: (data: any) => boolean;
  81 + private performCheckStatus: boolean;
  82 + private checkStatusMethod: string;
  83 +
  84 + private destroyed = false;
  85 + private checkStatusTimeoutHandle: Timeout;
  86 + private subscription: IWidgetSubscription;
  87 +
  88 + private led: JQuery<HTMLElement>;
  89 + private ledContainer: JQuery<HTMLElement>;
  90 + private textMeasure: JQuery<HTMLElement>;
  91 + private ledTitleContainer: JQuery<HTMLElement>;
  92 + private ledTitle: JQuery<HTMLElement>;
  93 + private ledErrorContainer: JQuery<HTMLElement>;
  94 + private ledError: JQuery<HTMLElement>;
  95 +
  96 + private ledResizeListener: any;
  97 +
  98 + private subscriptionOptions: WidgetSubscriptionOptions = {
  99 + callbacks: {
  100 + onDataUpdated: this.onDataUpdated.bind(this),
  101 + onDataUpdateError: this.onDataUpdateError.bind(this),
  102 + dataLoading: () => {}
  103 + }
  104 + };
  105 +
  106 +
  107 + constructor(private utils: UtilsService,
  108 + protected store: Store<AppState>) {
  109 + super(store);
  110 + }
  111 +
  112 + ngOnInit(): void {
  113 + this.led = $(this.ledRef.nativeElement);
  114 + this.ledContainer = $(this.ledContainerRef.nativeElement);
  115 + this.textMeasure = $(this.textMeasureRef.nativeElement);
  116 + this.ledTitleContainer = $(this.ledTitleContainerRef.nativeElement);
  117 + this.ledTitle = $(this.ledTitleRef.nativeElement);
  118 + this.ledErrorContainer = $(this.ledErrorContainerRef.nativeElement);
  119 + this.ledError = $(this.ledErrorRef.nativeElement);
  120 +
  121 + this.ledResizeListener = this.resize.bind(this);
  122 + // @ts-ignore
  123 + addResizeListener(this.ledContainerRef.nativeElement, this.ledResizeListener);
  124 + this.init();
  125 + }
  126 +
  127 + ngOnDestroy(): void {
  128 + this.destroyed = true;
  129 + if (this.checkStatusTimeoutHandle) {
  130 + clearTimeout(this.checkStatusTimeoutHandle);
  131 + }
  132 + if (this.subscription) {
  133 + this.ctx.subscriptionApi.removeSubscription(this.subscription.id);
  134 + }
  135 + if (this.ledResizeListener) {
  136 + // @ts-ignore
  137 + removeResizeListener(this.ledContainerRef.nativeElement, this.ledResizeListener);
  138 + }
  139 + }
  140 +
  141 + private init() {
  142 + const settings: LedIndicatorSettings = this.ctx.settings;
  143 + this.title = isDefined(settings.title) ? settings.title : '';
  144 + this.showTitle = !!(this.title && this.title.length);
  145 +
  146 + const origColor = isDefined(settings.ledColor) ? settings.ledColor : 'green';
  147 + this.valueAttribute = isDefined(settings.valueAttribute) ? settings.valueAttribute : 'value';
  148 +
  149 + this.ledColor = tinycolor(origColor).brighten(30).toHexString();
  150 + this.ledMiddleColor = tinycolor(origColor).toHexString();
  151 + this.disabledColor = tinycolor(origColor).darken(40).toHexString();
  152 + this.disabledMiddleColor = tinycolor(origColor).darken(60).toHexString();
  153 +
  154 + const initialValue = isDefined(settings.initialValue) ? settings.initialValue : false;
  155 + this.setValue(initialValue, true);
  156 +
  157 + const subscription = this.ctx.defaultSubscription;
  158 + const rpcEnabled = subscription.rpcEnabled;
  159 +
  160 + this.isSimulated = this.utils.widgetEditMode;
  161 +
  162 + this.requestTimeout = 500;
  163 + if (settings.requestTimeout) {
  164 + this.requestTimeout = settings.requestTimeout;
  165 + }
  166 + this.retrieveValueMethod = 'attribute';
  167 + if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
  168 + this.retrieveValueMethod = settings.retrieveValueMethod;
  169 + }
  170 + this.parseValueFunction = (data) => !!data;
  171 + if (settings.parseValueFunction && settings.parseValueFunction.length) {
  172 + try {
  173 + this.parseValueFunction = new Function('data', settings.parseValueFunction) as (data: any) => boolean;
  174 + } catch (e) {
  175 + this.parseValueFunction = (data) => !!data;
  176 + }
  177 + }
  178 + this.performCheckStatus = settings.performCheckStatus !== false;
  179 + if (this.performCheckStatus) {
  180 + this.checkStatusMethod = 'checkStatus';
  181 + if (settings.checkStatusMethod && settings.checkStatusMethod.length) {
  182 + this.checkStatusMethod = settings.checkStatusMethod;
  183 + }
  184 + }
  185 + if (!rpcEnabled) {
  186 + this.onError('Target device is not set!');
  187 + } else {
  188 + if (!this.isSimulated) {
  189 + if (this.performCheckStatus) {
  190 + this.rpcCheckStatus();
  191 + } else {
  192 + this.subscribeForValue();
  193 + }
  194 + }
  195 + }
  196 + }
  197 +
  198 + private resize() {
  199 + const width = this.ledContainer.width();
  200 + const height = this.ledContainer.height();
  201 + const size = Math.min(width, height);
  202 +
  203 + this.led.css({width: size, height: size});
  204 +
  205 + if (this.showTitle) {
  206 + this.setFontSize(this.ledTitle, this.title, this.ledTitleContainer.height() * 2 / 3, this.ledTitleContainer.width());
  207 + }
  208 + this.setFontSize(this.ledError, this.error, this.ledErrorContainer.height(), this.ledErrorContainer.width());
  209 + }
  210 +
  211 + private setFontSize(element: JQuery<HTMLElement>, text: string, fontSize: number, maxWidth: number) {
  212 + let textWidth = this.measureTextWidth(text, fontSize);
  213 + while (textWidth > maxWidth) {
  214 + fontSize--;
  215 + textWidth = this.measureTextWidth(text, fontSize);
  216 + }
  217 + element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
  218 + }
  219 +
  220 + private measureTextWidth(text: string, fontSize: number): number {
  221 + this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
  222 + this.textMeasure.text(text);
  223 + return this.textMeasure.width();
  224 + }
  225 +
  226 + private onError(error: string) {
  227 + this.error = error;
  228 + this.setFontSize(this.ledError, this.error, this.ledErrorContainer.height(), this.ledErrorContainer.width());
  229 + this.ctx.detectChanges();
  230 + }
  231 +
  232 + private setValue(value: boolean, forceUpdate?: boolean) {
  233 + if (this.value !== value || forceUpdate) {
  234 + this.value = value;
  235 + this.updateColor();
  236 + }
  237 + }
  238 +
  239 + private updateColor() {
  240 + const color = this.value ? this.ledColor : this.disabledColor;
  241 + const middleColor = this.value ? this.ledMiddleColor : this.disabledMiddleColor;
  242 + const boxShadow = `#000 0 -1px 6px 1px, inset ${middleColor} 0 -1px 8px, ${color} 0 3px 11px`;
  243 + this.led.css({backgroundColor: color});
  244 + this.led.css({boxShadow});
  245 + if (this.value) {
  246 + this.led.removeClass( 'disabled' );
  247 + } else {
  248 + this.led.addClass( 'disabled' );
  249 + }
  250 + }
  251 +
  252 + private rpcCheckStatus() {
  253 + if (this.destroyed) {
  254 + return;
  255 + }
  256 + this.error = '';
  257 + this.ctx.controlApi.sendTwoWayCommand(this.checkStatusMethod, null, this.requestTimeout).subscribe(
  258 + (responseBody) => {
  259 + const status = !!responseBody;
  260 + if (status) {
  261 + if (this.checkStatusTimeoutHandle) {
  262 + clearTimeout(this.checkStatusTimeoutHandle);
  263 + this.checkStatusTimeoutHandle = null;
  264 + }
  265 + this.subscribeForValue();
  266 + } else {
  267 + const errorText = 'Unknown device status!';
  268 + this.onError(errorText);
  269 + if (this.checkStatusTimeoutHandle) {
  270 + clearTimeout(this.checkStatusTimeoutHandle);
  271 + }
  272 + this.checkStatusTimeoutHandle = setTimeout(this.rpcCheckStatus.bind(this), checkStatusPollingInterval);
  273 + }
  274 + },
  275 + () => {
  276 + const errorText = this.ctx.defaultSubscription.rpcErrorText;
  277 + this.onError(errorText);
  278 + if (this.checkStatusTimeoutHandle) {
  279 + clearTimeout(this.checkStatusTimeoutHandle);
  280 + }
  281 + this.checkStatusTimeoutHandle = setTimeout(this.rpcCheckStatus.bind(this), checkStatusPollingInterval);
  282 + }
  283 + );
  284 + }
  285 +
  286 + private subscribeForValue() {
  287 + const subscriptionsInfo: SubscriptionInfo[] = [{
  288 + type: DatasourceType.entity,
  289 + entityType: EntityType.DEVICE,
  290 + entityId: this.ctx.defaultSubscription.targetDeviceId
  291 + }];
  292 +
  293 + if (this.retrieveValueMethod === 'attribute') {
  294 + subscriptionsInfo[0].attributes = [
  295 + {name: this.valueAttribute}
  296 + ];
  297 + } else {
  298 + subscriptionsInfo[0].timeseries = [
  299 + {name: this.valueAttribute}
  300 + ];
  301 + }
  302 +
  303 + this.ctx.subscriptionApi.createSubscriptionFromInfo (
  304 + widgetType.latest, subscriptionsInfo, this.subscriptionOptions, false, true).subscribe(
  305 + (subscription) => {
  306 + this.subscription = subscription;
  307 + }
  308 + );
  309 + }
  310 +
  311 + private onDataUpdated (subscription: IWidgetSubscription, detectChanges: boolean) {
  312 + let value = false;
  313 + const data = subscription.data;
  314 + if (data.length) {
  315 + const keyData = data[0];
  316 + if (keyData && keyData.data && keyData.data[0]) {
  317 + const attrValue = keyData.data[0][1];
  318 + if (attrValue) {
  319 + let parsed = null;
  320 + try {
  321 + parsed = this.parseValueFunction(JSON.parse(attrValue));
  322 + } catch (e){/**/}
  323 + value = !!parsed;
  324 + }
  325 + }
  326 + }
  327 + this.setValue(value);
  328 + if (detectChanges) {
  329 + this.ctx.detectChanges();
  330 + }
  331 + }
  332 +
  333 + private onDataUpdateError(subscription: IWidgetSubscription, e: any) {
  334 + const exceptionData = this.utils.parseException(e);
  335 + let errorText = exceptionData.name;
  336 + if (exceptionData.message) {
  337 + errorText += ': ' + exceptionData.message;
  338 + }
  339 + this.onError(errorText);
  340 + }
  341 +
  342 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div class="tb-round-switch" fxLayout="column" [ngStyle]="{'pointerEvents': ctx.isEdit ? 'none' : 'all'}">
  19 + <div #switchTitleContainer fxFlex="20" class="title-container" fxLayout="row" fxLayoutAlign="center center" [fxShow]="showTitle">
  20 + <span #switchTitle class="switch-title">{{title}}</span>
  21 + </div>
  22 + <div #switchContainer fxFlex="{{showTitle ? 80 : 100}}" [ngStyle]="{paddingTop: showTitle ? '5px': '10px'}" id="switch-container" fxLayout="column" fxLayoutAlign="center center">
  23 + <div #switch class="switch">
  24 + <input #onoff type="checkbox" id="{{checkboxId}}" name="onoff" />
  25 + <div class="back">
  26 + <label class="but" for="{{checkboxId}}">
  27 + <span class="on">I</span>
  28 + <span class="off">0</span>
  29 + </label>
  30 + </div>
  31 + </div>
  32 + </div>
  33 + <div #switchErrorContainer class="error-container" [ngStyle]="{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}"
  34 + fxLayout="row" fxLayoutAlign="center center">
  35 + <span #switchError class="switch-error">{{ error }}</span>
  36 + </div>
  37 + <div #textMeasure id="text-measure"></div>
  38 +</div>
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +@import "~compass-sass-mixins/lib/compass";
  17 +
  18 +$error-height: 14px !default;
  19 +
  20 +$background-color: #e6e7e8 !default;
  21 +
  22 +:host {
  23 + .tb-round-switch {
  24 + width: 100%;
  25 + height: 100%;
  26 + background: $background-color;
  27 +
  28 + .title-container {
  29 + .switch-title {
  30 + font-weight: 500;
  31 + color: #757575;
  32 + white-space: nowrap;
  33 + }
  34 + }
  35 +
  36 + .error-container {
  37 + position: absolute;
  38 + top: 1%;
  39 + right: 0;
  40 + left: 0;
  41 + z-index: 4;
  42 + height: $error-height;
  43 +
  44 + .switch-error {
  45 + color: #ff3315;
  46 + white-space: nowrap;
  47 + }
  48 + }
  49 +
  50 + #text-measure {
  51 + position: absolute;
  52 + width: auto;
  53 + height: auto;
  54 + white-space: nowrap;
  55 + visibility: hidden;
  56 + }
  57 +
  58 + #switch-container {
  59 + padding: 10px;
  60 +
  61 + .switch {
  62 + position: relative;
  63 +
  64 + box-sizing: border-box;
  65 + width: 260px;
  66 + min-width: 260px;
  67 + height: 260px;
  68 + min-height: 260px;
  69 + padding: 25px;
  70 + font-family: sans-serif;
  71 + font-size: 48px;
  72 +
  73 + color: #424242;
  74 + cursor: pointer;
  75 + background: #ddd;
  76 + background: linear-gradient(180deg, #bbb, #ddd);
  77 + border-radius: 130px;
  78 +
  79 + box-shadow: 0 0 0 8px rgba(0, 0, 0, .1),
  80 + 0 0 3px 1px rgba(0, 0, 0, .1),
  81 + inset 0 8px 3px -8px rgba(255, 255, 255, .4);
  82 +
  83 + input {
  84 + display: none;
  85 + }
  86 +
  87 + .on,
  88 + .off {
  89 + position: absolute;
  90 + width: 100%;
  91 + text-align: center;
  92 +
  93 + text-shadow: 1px 1px 4px #4a4a4a;
  94 + }
  95 +
  96 + .on {
  97 + top: 10px;
  98 + font-family: sans-serif;
  99 + color: #444;
  100 +
  101 + transition: all .1s;
  102 + }
  103 +
  104 + .off {
  105 + bottom: 5px;
  106 +
  107 + transition: all .1s;
  108 +
  109 + transform: scaleY(.85);
  110 + }
  111 +
  112 + .but {
  113 + position: relative;
  114 + display: block;
  115 + width: 200px;
  116 + height: 178px;
  117 + font-size: 48px;
  118 + cursor: pointer;
  119 + background-color: #d8d8d8;
  120 + border-bottom-width: 0;
  121 + border-radius: 400px 400px 400px 400px / 400px 400px 300px 300px;
  122 +
  123 + box-shadow: inset 8px 6px 5px -7px #a2a2a2,
  124 + inset -8px 6px 5px -7px #a2a2a2,
  125 + inset 0 -3px 2px -2px rgba(200, 200, 200, .5),
  126 + 0 3px 3px -2px #fff,
  127 + inset 0 -230px 60px -200px rgba(255, 255, 255, .2),
  128 + inset 0 220px 40px -200px rgba(0, 0, 0, .3);
  129 +
  130 + transition: all .2s;
  131 + }
  132 +
  133 + .back {
  134 +
  135 + box-sizing: border-box;
  136 + width: 210px;
  137 + height: 210px;
  138 + padding: 4px 4px;
  139 + cursor: pointer;
  140 + background-color: #888787;
  141 + background-image: linear-gradient(-90deg, transparent 30%, transparent 70%), linear-gradient(0deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
  142 + border-radius: 105px;
  143 +
  144 + box-shadow: 30px 30px 30px -20px rgba(58, 58, 58, .3),
  145 + -30px 30px 30px -20px rgba(58, 58, 58, .3),
  146 + 0 30px 30px 0 rgba(16, 16, 16, .3),
  147 + inset 0 -1px 0 0 #484848;
  148 +
  149 + transition: all .2s;
  150 + }
  151 +
  152 +
  153 + input:checked + .back .on,
  154 + input:checked + .back .off {
  155 + text-shadow: 1px 1px 4px #4a4a4a;
  156 + }
  157 +
  158 + input:checked + .back .on {
  159 + top: 10px;
  160 + color: #4c4c4c;
  161 +
  162 + transform: scaleY(.85);
  163 + }
  164 +
  165 + input:checked + .back .off {
  166 + bottom: 5px;
  167 + color: #444;
  168 +
  169 + transform: scaleY(1);
  170 + }
  171 +
  172 + input:checked + .back .but {
  173 + margin-top: 20px;
  174 + background: #dcdcdc radial-gradient(circle closest-corner at 50% 15%, rgba(0, 0, 0, .3), transparent);
  175 + border-radius: 400px 400px 400px 400px / 300px 300px 400px 400px;
  176 +
  177 + box-shadow: inset 8px -4px 5px -7px #a9a9a9,
  178 + inset -8px -4px 5px -7px #808080,
  179 + 0 -3px 8px -4px rgba(50, 50, 50, .4),
  180 + inset 0 3px 4px -2px #9c9c9c,
  181 + inset 0 280px 40px -200px rgba(0, 0, 0, .2),
  182 + inset 0 -200px 40px -200px rgba(180, 180, 180, .2);
  183 + }
  184 +
  185 + input:checked + .back {
  186 + padding: 2px 4px;
  187 +
  188 + background-image: linear-gradient(0deg, #868686 30%, transparent 70%), linear-gradient(90deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
  189 +
  190 + box-shadow: 30px 30px 30px -20px rgba(49, 49, 49, .1),
  191 + -30px 30px 30px -20px rgba(111, 111, 111, .1),
  192 + 0 30px 30px 0 rgba(0, 0, 0, .2),
  193 + inset 0 1px 2px 0 rgba(167, 167, 167, .6);
  194 + }
  195 + }
  196 + }
  197 + }
  198 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
  18 +import { PageComponent } from '@shared/components/page.component';
  19 +import { WidgetContext } from '@home/models/widget-component.models';
  20 +import { UtilsService } from '@core/services/utils.service';
  21 +import { Store } from '@ngrx/store';
  22 +import { AppState } from '@core/core.state';
  23 +import { isDefined } from '@core/utils';
  24 +import { IWidgetSubscription, SubscriptionInfo, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
  25 +import { DatasourceType, widgetType } from '@shared/models/widget.models';
  26 +import { EntityType } from '@shared/models/entity-type.models';
  27 +
  28 +type RetrieveValueMethod = 'rpc' | 'attribute' | 'timeseries';
  29 +
  30 +interface RoundSwitchSettings {
  31 + initialValue: boolean;
  32 + title: string;
  33 + retrieveValueMethod: RetrieveValueMethod;
  34 + valueKey: string;
  35 + getValueMethod: string;
  36 + setValueMethod: string;
  37 + parseValueFunction: string;
  38 + convertValueFunction: string;
  39 + requestTimeout: number;
  40 +}
  41 +
  42 +@Component({
  43 + selector: 'tb-round-switch',
  44 + templateUrl: './round-switch.component.html',
  45 + styleUrls: ['./round-switch.component.scss']
  46 +})
  47 +export class RoundSwitchComponent extends PageComponent implements OnInit, OnDestroy {
  48 +
  49 + @ViewChild('switch', {static: true}) switchElementRef: ElementRef<HTMLElement>;
  50 + @ViewChild('switchContainer', {static: true}) switchContainerRef: ElementRef<HTMLElement>;
  51 + @ViewChild('onoff', {static: true}) onoffRef: ElementRef<HTMLElement>;
  52 + @ViewChild('textMeasure', {static: true}) textMeasureRef: ElementRef<HTMLElement>;
  53 + @ViewChild('switchTitleContainer', {static: true}) switchTitleContainerRef: ElementRef<HTMLElement>;
  54 + @ViewChild('switchTitle', {static: true}) switchTitleRef: ElementRef<HTMLElement>;
  55 + @ViewChild('switchErrorContainer', {static: true}) switchErrorContainerRef: ElementRef<HTMLElement>;
  56 + @ViewChild('switchError', {static: true}) switchErrorRef: ElementRef<HTMLElement>;
  57 +
  58 + @Input()
  59 + ctx: WidgetContext;
  60 +
  61 + showTitle = false;
  62 + value = false;
  63 + error = '';
  64 + title = '';
  65 + checkboxId = 'onoff-' + this.utils.guid();
  66 +
  67 + private isSimulated: boolean;
  68 + private requestTimeout: number;
  69 + private retrieveValueMethod: RetrieveValueMethod;
  70 + private valueKey: string;
  71 + private parseValueFunction: (data: any) => boolean;
  72 + private convertValueFunction: (value: any) => any;
  73 + private getValueMethod: string;
  74 + private setValueMethod: string;
  75 +
  76 + private valueSubscription: IWidgetSubscription;
  77 +
  78 + private executingUpdateValue: boolean;
  79 + private scheduledValue: boolean;
  80 + private rpcValue: boolean;
  81 +
  82 + private switchElement: JQuery<HTMLElement>;
  83 + private switchContainer: JQuery<HTMLElement>;
  84 + private onoff: JQuery<HTMLElement>;
  85 + private textMeasure: JQuery<HTMLElement>;
  86 + private switchTitleContainer: JQuery<HTMLElement>;
  87 + private switchTitle: JQuery<HTMLElement>;
  88 + private switchErrorContainer: JQuery<HTMLElement>;
  89 + private switchError: JQuery<HTMLElement>;
  90 +
  91 + private switchResizeListener: any;
  92 +
  93 + constructor(private utils: UtilsService,
  94 + protected store: Store<AppState>) {
  95 + super(store);
  96 + }
  97 +
  98 + ngOnInit(): void {
  99 + this.switchElement = $(this.switchElementRef.nativeElement);
  100 + this.switchContainer = $(this.switchContainerRef.nativeElement);
  101 + this.onoff = $(this.onoffRef.nativeElement);
  102 + this.textMeasure = $(this.textMeasureRef.nativeElement);
  103 + this.switchTitleContainer = $(this.switchTitleContainerRef.nativeElement);
  104 + this.switchTitle = $(this.switchTitleRef.nativeElement);
  105 + this.switchErrorContainer = $(this.switchErrorContainerRef.nativeElement);
  106 + this.switchError = $(this.switchErrorRef.nativeElement);
  107 +
  108 + this.onoff.on('change', () => {
  109 + this.value = this.onoff.prop('checked') === false;
  110 + this.onValue();
  111 + });
  112 +
  113 + this.switchResizeListener = this.resize.bind(this);
  114 + // @ts-ignore
  115 + addResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener);
  116 + this.init();
  117 + // this.ctx.resize = this.resize.bind(this);
  118 + }
  119 +
  120 + ngOnDestroy(): void {
  121 + if (this.valueSubscription) {
  122 + this.ctx.subscriptionApi.removeSubscription(this.valueSubscription.id);
  123 + }
  124 + if (this.switchResizeListener) {
  125 + // @ts-ignore
  126 + removeResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener);
  127 + }
  128 + }
  129 +
  130 + private init() {
  131 + const settings: RoundSwitchSettings = this.ctx.settings;
  132 + this.title = isDefined(settings.title) ? settings.title : '';
  133 + this.showTitle = !!(this.title && this.title.length);
  134 + const initialValue = isDefined(settings.initialValue) ? settings.initialValue : false;
  135 + this.setValue(initialValue);
  136 +
  137 + const subscription = this.ctx.defaultSubscription;
  138 + const rpcEnabled = subscription.rpcEnabled;
  139 +
  140 + this.isSimulated = this.utils.widgetEditMode;
  141 +
  142 + this.requestTimeout = 500;
  143 + if (settings.requestTimeout) {
  144 + this.requestTimeout = settings.requestTimeout;
  145 + }
  146 + this.retrieveValueMethod = 'rpc';
  147 + if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
  148 + this.retrieveValueMethod = settings.retrieveValueMethod;
  149 + }
  150 + this.valueKey = 'value';
  151 + if (settings.valueKey && settings.valueKey.length) {
  152 + this.valueKey = settings.valueKey;
  153 + }
  154 + this.parseValueFunction = (data) => !!data;
  155 + if (settings.parseValueFunction && settings.parseValueFunction.length) {
  156 + try {
  157 + this.parseValueFunction = new Function('data', settings.parseValueFunction) as (data: any) => boolean;
  158 + } catch (e) {
  159 + this.parseValueFunction = (data) => !!data;
  160 + }
  161 + }
  162 + this.convertValueFunction = (value) => value;
  163 + if (settings.convertValueFunction && settings.convertValueFunction.length) {
  164 + try {
  165 + this.convertValueFunction = new Function('value', settings.convertValueFunction) as (value: any) => any;
  166 + } catch (e) {
  167 + this.convertValueFunction = (value) => value;
  168 + }
  169 + }
  170 + this.getValueMethod = 'getValue';
  171 + if (settings.getValueMethod && settings.getValueMethod.length) {
  172 + this.getValueMethod = settings.getValueMethod;
  173 + }
  174 + this.setValueMethod = 'setValue';
  175 + if (settings.setValueMethod && settings.setValueMethod.length) {
  176 + this.setValueMethod = settings.setValueMethod;
  177 + }
  178 + if (!rpcEnabled) {
  179 + this.onError('Target device is not set!');
  180 + } else {
  181 + if (!this.isSimulated) {
  182 + if (this.retrieveValueMethod === 'rpc') {
  183 + this.rpcRequestValue();
  184 + } else if (this.retrieveValueMethod === 'attribute' || this.retrieveValueMethod === 'timeseries') {
  185 + this.subscribeForValue();
  186 + }
  187 + }
  188 + }
  189 +
  190 + }
  191 +
  192 + private resize() {
  193 + const width = this.switchContainer.width();
  194 + const height = this.switchContainer.height();
  195 + const size = Math.min(width, height);
  196 + const scale = size/260;
  197 + this.switchElement.css({
  198 + '-webkit-transform': `scale(${scale})`,
  199 + '-moz-transform': `scale(${scale})`,
  200 + '-ms-transform': `scale(${scale})`,
  201 + '-o-transform': `scale(${scale})`,
  202 + transform: `scale(${scale})`
  203 + });
  204 + if (this.showTitle) {
  205 + this.setFontSize(this.switchTitle, this.title, this.switchTitleContainer.height() * 2 / 3, this.switchTitleContainer.width());
  206 + }
  207 + this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width());
  208 + }
  209 +
  210 + private setFontSize(element: JQuery<HTMLElement>, text: string, fontSize: number, maxWidth: number) {
  211 + let textWidth = this.measureTextWidth(text, fontSize);
  212 + while (textWidth > maxWidth) {
  213 + fontSize--;
  214 + textWidth = this.measureTextWidth(text, fontSize);
  215 + }
  216 + element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
  217 + }
  218 +
  219 + private measureTextWidth(text: string, fontSize: number): number {
  220 + this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
  221 + this.textMeasure.text(text);
  222 + return this.textMeasure.width();
  223 + }
  224 +
  225 + private onError(error: string) {
  226 + this.error = error;
  227 + this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width());
  228 + this.ctx.detectChanges();
  229 + }
  230 +
  231 + private setValue(value: boolean) {
  232 + this.value = value ? true : false;
  233 + this.onoff.prop('checked', !this.value);
  234 + }
  235 +
  236 + private onValue() {
  237 + this.rpcUpdateValue(this.value);
  238 + }
  239 +
  240 + private rpcRequestValue() {
  241 + this.error = '';
  242 + this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe(
  243 + (responseBody) => {
  244 + this.setValue(this.parseValueFunction(responseBody));
  245 + },
  246 + () => {
  247 + const errorText = this.ctx.defaultSubscription.rpcErrorText;
  248 + this.onError(errorText);
  249 + }
  250 + );
  251 + }
  252 +
  253 + private rpcUpdateValue(value) {
  254 + if (this.executingUpdateValue) {
  255 + this.scheduledValue = value;
  256 + return;
  257 + } else {
  258 + this.scheduledValue = null;
  259 + this.rpcValue = value;
  260 + this.executingUpdateValue = true;
  261 + }
  262 + this.error = '';
  263 + this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe(
  264 + () => {
  265 + this.executingUpdateValue = false;
  266 + if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
  267 + this.rpcUpdateValue(this.scheduledValue);
  268 + }
  269 + },
  270 + () => {
  271 + this.executingUpdateValue = false;
  272 + const errorText = this.ctx.defaultSubscription.rpcErrorText;
  273 + this.onError(errorText);
  274 + }
  275 + );
  276 + }
  277 +
  278 + private subscribeForValue() {
  279 + const valueSubscriptionInfo: SubscriptionInfo[] = [{
  280 + type: DatasourceType.entity,
  281 + entityType: EntityType.DEVICE,
  282 + entityId: this.ctx.defaultSubscription.targetDeviceId
  283 + }];
  284 + if (this.retrieveValueMethod === 'attribute') {
  285 + valueSubscriptionInfo[0].attributes = [
  286 + {name: this.valueKey}
  287 + ];
  288 + } else {
  289 + valueSubscriptionInfo[0].timeseries = [
  290 + {name: this.valueKey}
  291 + ];
  292 + }
  293 + const subscriptionOptions: WidgetSubscriptionOptions = {
  294 + callbacks: {
  295 + onDataUpdated: this.onDataUpdated.bind(this),
  296 + onDataUpdateError: this.onDataUpdateError.bind(this)
  297 + }
  298 + };
  299 + this.ctx.subscriptionApi.createSubscriptionFromInfo (
  300 + widgetType.latest, valueSubscriptionInfo, subscriptionOptions, false, true).subscribe(
  301 + (subscription) => {
  302 + this.valueSubscription = subscription;
  303 + }
  304 + );
  305 + }
  306 +
  307 + private onDataUpdated(subscription: IWidgetSubscription, detectChanges: boolean) {
  308 + let value = false;
  309 + const data = subscription.data;
  310 + if (data.length) {
  311 + const keyData = data[0];
  312 + if (keyData && keyData.data && keyData.data[0]) {
  313 + const attrValue = keyData.data[0][1];
  314 + if (attrValue) {
  315 + let parsed = null;
  316 + try {
  317 + parsed = this.parseValueFunction(JSON.parse(attrValue));
  318 + } catch (e){/**/}
  319 + value = !!parsed;
  320 + }
  321 + }
  322 + }
  323 + this.setValue(value);
  324 + if (detectChanges) {
  325 + this.ctx.detectChanges();
  326 + }
  327 + }
  328 +
  329 + private onDataUpdateError(subscription: IWidgetSubscription, e: any) {
  330 + const exceptionData = this.utils.parseException(e);
  331 + let errorText = exceptionData.name;
  332 + if (exceptionData.message) {
  333 + errorText += ': ' + exceptionData.message;
  334 + }
  335 + this.onError(errorText);
  336 + }
  337 +
  338 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { NgModule } from '@angular/core';
  18 +import { CommonModule } from '@angular/common';
  19 +import { SharedModule } from '@app/shared/shared.module';
  20 +import { LedIndicatorComponent } from '@home/components/widget/lib/rpc/led-indicator.component';
  21 +import { RoundSwitchComponent } from '@home/components/widget/lib/rpc/round-switch.component';
  22 +import { SwitchComponent } from '@home/components/widget/lib/rpc/switch.component';
  23 +import { KnobComponent } from '@home/components/widget/lib/rpc/knob.component';
  24 +
  25 +@NgModule({
  26 + declarations:
  27 + [
  28 + LedIndicatorComponent,
  29 + RoundSwitchComponent,
  30 + SwitchComponent,
  31 + KnobComponent
  32 + ],
  33 + imports: [
  34 + CommonModule,
  35 + SharedModule
  36 + ],
  37 + exports: [
  38 + LedIndicatorComponent,
  39 + RoundSwitchComponent,
  40 + SwitchComponent,
  41 + KnobComponent
  42 + ]
  43 +})
  44 +export class RpcWidgetsModule { }
... ...
  1 +<svg id="svg4451" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="28.222mm" width="28.222mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100.00001 100.00001" xmlns:dc="http://purl.org/dc/elements/1.1/">
  2 + <defs id="defs4453">
  3 + <linearGradient id="linearGradient4197">
  4 + <stop id="stop4199" stop-color="#878990" offset="0"/>
  5 + <stop id="stop4201" stop-color="#f6f6f6" offset="1"/>
  6 + </linearGradient>
  7 + <radialGradient id="radialGradient52746-0-4-0" gradientUnits="userSpaceOnUse" cy="991.7" cx="1007" gradientTransform="matrix(-.0050064 -.58050 1.8349 -.015825 -2300 975.09)" r="86.621">
  8 + <stop id="stop4169" stop-color="#bcbdc1" offset="0"/>
  9 + <stop id="stop4171" stop-color="#dfdfdf" offset="1"/>
  10 + </radialGradient>
  11 + <radialGradient id="radialGradient52746-0-9-15-6" xlink:href="#linearGradient4197" gradientUnits="userSpaceOnUse" cy="991.7" cx="1007" gradientTransform="matrix(-.0053377 -.61891 1.9563 -.016872 -2420 1016.8)" r="86.621"/>
  12 + <radialGradient id="radialGradient52746-0-9-6-5-6" gradientUnits="userSpaceOnUse" cy="991.7" cx="1007" gradientTransform="matrix(.0058230 .80069 -2.1343 .015521 1622.1 -511.53)" r="86.621">
  13 + <stop id="stop4270" stop-color="#8c8d93" offset="0"/>
  14 + <stop id="stop4272" stop-color="#fff" offset="1"/>
  15 + </radialGradient>
  16 + <radialGradient id="radialGradient52746-0-9-1-0-9" xlink:href="#linearGradient4197" gradientUnits="userSpaceOnUse" cy="991.7" cx="1007" gradientTransform="matrix(.0031272 .36260 -1.1462 .0098849 645.7 -48.682)" r="86.621"/>
  17 + <linearGradient id="linearGradient4424" y2="392.18" gradientUnits="userSpaceOnUse" x2="-369.32" gradientTransform="translate(109.7 -2.7245)" y1="293.68" x1="-370.65">
  18 + <stop id="stop4418" stop-color="#a0a0a0" offset="0"/>
  19 + <stop id="stop4420" stop-color="#f9faff" offset="1"/>
  20 + </linearGradient>
  21 + </defs>
  22 + <g id="layer1" transform="translate(536.86 -294.93)">
  23 + <g id="g4501">
  24 + <g id="g4497" transform="translate(-226.72 4.7227)">
  25 + <path id="path4414" d="m-260.16 290.67c-19.168-0.22115-37.725 11.849-45.288 29.465-7.973 17.434-4.4546 39.289 8.5831 53.342 12.841 14.512 34.573 20.101 52.82 13.575 18.4-6.082 32.096-23.857 33.289-43.199 1.6579-19.306-9.2817-38.901-26.591-47.615-7.0119-3.6494-14.91-5.5689-22.813-5.5679zm-0.51367 1.6641c18.978-0.21524 37.342 12.022 44.334 29.683 7.0372 17.181 2.95 38.084-9.5553 51.571-0.23129 0.19903-0.58514 0.91551-1.3667 0.57275-0.78156-0.34279-1.868-1.5334-2.5677-2.2508-1.3313-1.365-2.7517-2.5318-1.8623-3.5091 12.362-14.017 15.466-38.947 4.3865-53.994-10.077-14.664-29.972-21.296-46.858-15.752-18.125 5.321-32.394 23.295-31.109 42.522 0.68609 11.361 4.9096 22.828 13.013 30.964-0.52732 1.3864-0.75912 1.4951-2.0893 0.0445-1.4184-1.5468-2.8292-3.0416-5.2228-6.3838-8.8447-13.499-10.547-31.498-3.5798-46.167 6.8006-15.182 22.122-26.317 38.859-27.17 1.2041-0.086 2.4112-0.12896 3.6183-0.13085zm0.43164 28.711c2.6603-0.033 5.7995 0.63181 8.3618 1.8928 2.8152 1.323 5.3991 3.5094 7.1657 5.9467 2.9779 3.9517 4.2346 9.3635 3.5431 13.916-0.63605 5.0851-3.5977 9.93-7.6444 12.856-3.9009 2.9596-9.319 4.2271-13.796 3.5723-4.2078-0.54147-8.0926-2.4558-11.106-5.4642-2.8389-2.8421-4.7656-6.5461-5.3997-10.683-0.41578-2.8552-0.25913-5.5483 0.6416-8.5235 1.1448-3.8517 3.7847-7.4885 6.9779-9.7904 3.1737-2.373 7.3574-3.7297 11.256-3.7236zm-1.7148 0.0859c-0.0536 0.002-0.008 0.009 0 0zm-1.3984 0.18359c-0.0331 0.002-0.002 0.009 0 0zm-13.438 9.377c-0.0247 0.0373 0.004 0.009 0 0zm-0.44727 0.82812c-0.0259 0.0476 0.005 0.009 0 0zm-0.76953 1.7168c-0.0202 0.0478 0.005 0.009 0 0zm-0.71875 2.2363c-0.0164 0.0555 0.006 0.0119 0 0zm-0.50977 6.5332c0.001 0.0536 0.009 0.008 0 0zm0.1836 1.3984c0.003 0.0311 0.008 0.005 0 0zm9.377 13.438c0.0373 0.0247 0.009-0.004 0 0zm0.82812 0.44726c0.0476 0.0259 0.009-0.005 0 0zm1.7168 0.76953c0.0478 0.0202 0.009-0.005 0 0zm2.2363 0.71875c0.0555 0.0164 0.012-0.006 0 0zm6.5332 0.50977c0.0536-0.002 0.008-0.009 0 0zm1.3984-0.1836c0.0331-0.002 0.002-0.009 0 0zm13.438-9.377c0.0247-0.0373-0.004-0.009 0 0zm0.44727-0.82812c0.0259-0.0476-0.005-0.009 0 0zm0.76953-1.7168c0.0202-0.0478-0.005-0.009 0 0zm0.71875-2.2363c0.0164-0.0555-0.006-0.012 0 0zm0.50976-6.5332c-0.002-0.0536-0.009-0.008 0 0zm-0.18359-1.3984c-0.003-0.0311-0.008-0.005 0 0zm-9.377-13.438c-0.0373-0.0247-0.009 0.004 0 0zm-0.82813-0.44726c-0.0476-0.026-0.009 0.005 0 0zm-1.7168-0.76954c-0.0478-0.0202-0.009 0.005 0 0zm-2.2363-0.71875c-0.0555-0.0164-0.012 0.006 0 0zm-20.057 7.7695c-0.0145 0.004 0.008 0.0219 0 0zm-2.6172 16.232 0.006 0.008zm33.26 5.3555 0.002 0.002zm-6.0859 5.5156h0.002zm-3.7949 1.627v0.002z" fill="url(#linearGradient4424)"/>
  26 + <path id="path4402" d="m-280.11 288.16c-6.4503 0.00002-12.898 1.2835-18.857 3.752-5.9593 2.4684-11.425 6.1206-15.986 10.682-4.5611 4.5611-8.2132 10.027-10.682 15.986-2.4684 5.9593-3.7519 12.407-3.752 18.857 0.00002 6.4503 1.2835 12.898 3.752 18.857 2.4684 5.9593 6.1206 11.425 10.682 15.986 4.5611 4.5611 10.027 8.2132 15.986 10.682 5.9593 2.4684 12.407 3.7519 18.857 3.752 6.4503-0.00002 12.898-1.2835 18.857-3.752 5.9593-2.4684 11.425-6.1206 15.986-10.682 4.5611-4.5611 8.2132-10.027 10.682-15.986 2.4684-5.9593 3.7519-12.407 3.752-18.857-0.00002-6.4503-1.2835-12.898-3.752-18.857-2.4684-5.9593-6.1206-11.425-10.682-15.986-4.5611-4.5611-10.027-8.2132-15.986-10.682-5.9593-2.4684-12.407-3.7519-18.857-3.752zm-0.51563 1.1406a0.72311 0.72311 0 0 1 0.002 0c12.714 0.0109 25.044 5.0665 34.031 14.064 8.987 8.9759 13.911 20.601 13.922 33.309-0.0101 13.081-5.1256 24.968-12.645 33.791-0.42485 0.49849-1.011 0.80473-1.6035 0.79101-0.59249-0.0137-1.1124-0.30623-1.5371-0.70703-0.49206-0.46434-1.272-1.1732-1.9512-1.8066-0.33958-0.31669-0.65482-0.61464-0.90234-0.86133-0.12376-0.12334-0.23034-0.23209-0.32031-0.33203-0.09-0.0999-0.15313-0.14986-0.25196-0.3418-0.23991-0.46586-0.19336-0.92164-0.11132-1.2578 0.041-0.16808 0.0951-0.31228 0.15234-0.43554 0.0572-0.12327 0.0585-0.18508 0.24023-0.36719l-0.0371 0.0391c1.8267-2.1277 3.2432-3.7411 4.7402-5.957 1.4986-2.218 2.7891-4.5865 3.8438-7.0801 0.52748-1.247 0.99838-2.5271 1.4043-3.832 0.40577-1.3048 0.74578-2.6349 1.0234-3.9922 0.27784-1.3572 0.49237-2.7395 0.63477-4.1406 0.14215-1.4016 0.21484-2.8241 0.21484-4.2637 0-1.4398-0.0722-2.8624-0.21484-4.2637-0.14239-1.401-0.35691-2.7814-0.63477-4.1387-0.27768-1.3574-0.61772-2.6895-1.0234-3.9941-0.40592-1.305-0.87682-2.585-1.4043-3.832-1.0547-2.4935-2.3451-4.8621-3.8438-7.0801-1.4984-2.2179-3.2054-4.2836-5.0918-6.1699-1.8864-1.8865-3.9502-3.5935-6.168-5.0918-2.218-1.4985-4.5864-2.7891-7.0801-3.8438-1.2464-0.52718-2.5264-0.99628-3.832-1.4023-1.3048-0.4059-2.6391-0.7477-3.9961-1.0254-1.3567-0.27766-2.7353-0.49049-4.1367-0.63281-1.4022-0.14241-2.8266-0.2168-4.2656-0.2168-1.4391 0-2.8677 0.0742-4.2832 0.21875-1.4151 0.14443-2.8162 0.36081-4.1992 0.64258-1.3824 0.28163-2.7461 0.63103-4.0879 1.043-1.3416 0.41186-2.658 0.8853-3.9492 1.4199-2.5825 1.0694-5.0584 2.3774-7.3867 3.8945-2.3284 1.5172-4.5127 3.2441-6.5156 5.1504-2.0029 1.9062-3.8231 3.9924-5.4297 6.2285-0.80355 1.1182-1.5552 2.274-2.2461 3.4629-0.69079 1.1888-1.3223 2.4124-1.8926 3.666-0.57036 1.2538-1.0792 2.5365-1.5195 3.8457-0.44052 1.3098-0.81488 2.6447-1.1172 4.0039-0.30227 1.359-0.532 2.7427-0.6875 4.1445-0.15574 1.4016-0.23633 2.8226-0.23632 4.2598-0.00001 1.4385 0.0756 2.9137 0.22265 4.4062 0.14704 1.4921 0.36785 3.0021 0.65625 4.5137 0.28842 1.5116 0.64471 3.0223 1.0684 4.5195 0.42365 1.4972 0.9143 2.9805 1.4668 4.4297 0.5524 1.4491 1.1672 2.8646 1.8418 4.2324 0.67463 1.3679 1.4079 2.6886 2.1973 3.9414 0.78962 1.2532 1.6328 2.4399 2.5293 3.5449 0.89618 1.1047 1.8475 2.127 2.8438 3.0527 0.17252 0.16076 0.23062 0.36547 0.24414 0.49219 0.0135 0.12672-0.001 0.20699-0.0137 0.27149-0.025 0.12898-0.0562 0.20192-0.0781 0.25976a0.72311 0.72311 0 0 1 -0.0215 0.0508s-0.12243 0.4885-0.74414 0.74024c0.0278-0.0113-0.12936 0.0761-0.35938 0.0742-0.23002-0.002-0.47421-0.12627-0.61523-0.26172-8.2317-7.9053-13.917-21.064-13.928-33.803a0.72311 0.72311 0 0 1 0 -0.002c0.0208-12.334 4.7929-24.185 13.326-33.092l0.002-0.002c9.0244-9.4611 20.692-14.256 33.754-14.277zm0.51563 29.682c-0.97685 0.00002 1.8819 0.0713 0.90625 0.0234-0.97567-0.0479 1.8745 0.16214 0.90234 0.0664-0.97214-0.0957 1.8647 0.25269 0.89844 0.10937-0.96628-0.14331 1.8507 0.34681 0.89258 0.15625-0.95808-0.19055 1.8323 0.43461 0.88476 0.19727-0.94757-0.23734 1.8078 0.52574 0.87305 0.24219-0.93479-0.28355 1.7791 0.61228 0.85938 0.2832-0.91975-0.32908 1.7482 0.69999 0.8457 0.32617-0.9025-0.37381 1.7112 0.78484 0.82812 0.36719-0.88306-0.41765 1.6701 0.86868 0.8086 0.4082-0.86151-0.46047 1.6269 0.94751 0.78906 0.44532-0.83787-0.5022 1.5778 1.029 0.76562 0.48632-0.81222-0.5427 1.5248 1.1034 0.74024 0.52149-0.78462-0.58191 1.468 1.1763 0.71289 0.55664-0.75512-0.61971 1.4093 1.2478 0.68555 0.5918-0.7238-0.65601 1.347 1.3157 0.65625 0.625-0.69074-0.69074 1.281 1.38 0.625 0.65625-0.65601-0.7238 1.2115 1.4407 0.59179 0.68554-0.6197-0.75511 1.1386 1.4975 0.55664 0.71289-0.5819-0.78461 1.0642 1.5525 0.52149 0.74024-0.54271-0.81222 0.98852 1.6035 0.48633 0.76562-0.5022-0.83787 0.90578 1.6506 0.44531 0.78907-0.46048-0.86151 0.82585 1.6916 0.4082 0.80859-0.41765-0.88307 0.741 1.7306 0.36719 0.82812-0.37381-0.90249 0.65525 1.7655 0.32617 0.84571-0.32908-0.91975 0.56676 1.7942 0.2832 0.85937-0.28355-0.93479 0.47953 1.8206 0.24219 0.87305-0.23734-0.94758 0.38783 1.8428 0.19727 0.88476-0.19056-0.95808 0.29956 1.8589 0.15625 0.89258-0.14332-0.96628 0.2051 1.8706 0.10937 0.89844-0.0957-0.97215 0.11432 1.878 0.0664 0.90234-0.0479-0.97567 0.0234 1.8831 0.0234 0.90625-0.00002-0.97684-0.0713 1.8819-0.0234 0.90625 0.0479-0.97567-0.16214 1.8745-0.0664 0.90235 0.0957-0.97215-0.25269 1.8647-0.10937 0.89843 0.14331-0.96627-0.34681 1.8507-0.15625 0.89258 0.19056-0.95808-0.43461 1.8324-0.19727 0.88477 0.23734-0.94758-0.52574 1.8078-0.24219 0.87305 0.28356-0.93479-0.61228 1.7791-0.2832 0.85937 0.32908-0.91975-0.69998 1.7482-0.32617 0.8457 0.37381-0.90249-0.78484 1.7112-0.36719 0.82813s-0.86868 1.6701-0.4082 0.80859c0.46047-0.8615-0.94751 1.6269-0.44531 0.78907 0.50219-0.83788-1.029 1.5778-0.48633 0.76562 0.5427-0.81222-1.1034 1.5248-0.52149 0.74023 0.58191-0.78461-1.1763 1.468-0.55664 0.71289 0.61971-0.75511-1.2478 1.4094-0.59179 0.68555 0.65601-0.7238-1.3157 1.347-0.625 0.65625 0.69073-0.69073-1.38 1.281-0.65625 0.625 0.72379-0.65601-1.4407 1.2115-0.68555 0.5918 0.75511-0.61971-1.4975 1.1385-0.71289 0.55664 0.78461-0.58191-1.5525 1.0642-0.74024 0.52148 0.81223-0.5427-1.6035 0.98853-0.76562 0.48633 0.83787-0.50219-1.6506 0.90579-0.78906 0.44531 0.8615-0.46047-1.6917 0.82586-0.8086 0.40821 0.88307-0.41765-1.7306 0.741-0.82812 0.36719 0.90249-0.37382-1.7654 0.65525-0.8457 0.32617 0.91974-0.32908-1.7942 0.56675-0.85938 0.2832 0.93479-0.28355-1.8206 0.47953-0.87305 0.24219 0.94758-0.23734-1.8428 0.38782-0.88476 0.19726 0.95808-0.19056-1.8589 0.29957-0.89258 0.15625 0.96628-0.14331-1.8706 0.20511-0.89844 0.10938 0.97215-0.0957-1.878 0.11432-0.90234 0.0664 0.97567-0.0479-1.8831 0.0234-0.90625 0.0234 0.97685-0.00002-1.8819-0.0713-0.90625-0.0234 0.97567 0.0479-1.8745-0.16213-0.90235-0.0664 0.97215 0.0957-1.8647-0.25269-0.89843-0.10938 0.96627 0.14332-1.8507-0.3468-0.89258-0.15625 0.95808 0.19056-1.8323-0.4346-0.88477-0.19726 0.94758 0.23734-1.8078-0.52574-0.87304-0.24219 0.93479 0.28355-1.7791-0.61228-0.85938-0.2832 0.91975 0.32908-1.7482-0.69999-0.8457-0.32617 0.90249 0.37381-1.7112-0.78484-0.82813-0.36719s-1.6701-0.86868-0.80859-0.40821c0.86151 0.46048-1.6269-0.9475-0.78906-0.44531 0.83787 0.5022-1.5778-1.029-0.76563-0.48633 0.81222 0.54271-1.5248-1.1034-0.74023-0.52148 0.78461 0.5819-1.468-1.1763-0.71289-0.55664 0.75511 0.6197-1.4094-1.2478-0.68555-0.5918 0.7238 0.65601-1.347-1.3157-0.65625-0.625 0.69074 0.69074-1.281-1.38-0.625-0.65625 0.65601 0.7238-1.2115-1.4407-0.5918-0.68555 0.61971 0.75512-1.1385-1.4975-0.55664-0.71289 0.58191 0.78462-1.0642-1.5524-0.52148-0.74023 0.5427 0.81222-0.98852-1.6035-0.48633-0.76562 0.50219 0.83787-0.90579-1.6506-0.44531-0.78907 0.46047 0.86151-0.82585-1.6917-0.40821-0.80859 0.41765 0.88306-0.741-1.7306-0.36718-0.82813 0.37381 0.9025-0.65525-1.7654-0.32618-0.8457 0.32908 0.91975-0.56675-1.7942-0.2832-0.85937 0.28355 0.93478-0.47953-1.8206-0.24219-0.87305 0.23734 0.94758-0.38782-1.8428-0.19726-0.88477 0.19056 0.95808-0.29957-1.8588-0.15625-0.89258 0.14332 0.96628-0.20511-1.8706-0.10938-0.89843 0.0957 0.97214-0.11431-1.878-0.0664-0.90235 0.0479 0.97568-0.0234-1.8831-0.0234-0.90625 0.00002 0.97685 0.0713-1.8819 0.0234-0.90625-0.0479 0.97568 0.16213-1.8745 0.0664-0.90234-0.0957 0.97215 0.2527-1.8647 0.10938-0.89844-0.14332 0.96628 0.34681-1.8507 0.15625-0.89258-0.19056 0.95809 0.43461-1.8323 0.19726-0.88476-0.23734 0.94758 0.52574-1.8078 0.24219-0.87305-0.28355 0.93479 0.61228-1.7791 0.2832-0.85937-0.32907 0.91975 0.69999-1.7482 0.32618-0.84571-0.37382 0.9025 0.78483-1.7112 0.36718-0.82812-0.41764 0.88306 0.86868-1.6701 0.40821-0.80859-0.46048 0.8615 0.9475-1.6269 0.44531-0.78907-0.50219 0.83788 1.029-1.5778 0.48633-0.76562-0.54271 0.81222 1.1034-1.5248 0.52148-0.74024-0.5819 0.78462 1.1764-1.468 0.55664-0.71289-0.6197 0.75512 1.2478-1.4093 0.5918-0.68554-0.65601 0.72379 1.3157-1.347 0.625-0.65625-0.69074 0.69073 1.38-1.281 0.65625-0.625-0.7238 0.65601 1.4407-1.2115 0.68555-0.5918-0.75512 0.6197 1.4975-1.1386 0.71289-0.55664-0.78462 0.5819 1.5524-1.0642 0.74023-0.52149-0.81222 0.54271 1.6035-0.98852 0.76563-0.48632-0.83788 0.50219 1.6506-0.90579 0.78906-0.44532-0.86151 0.46048 1.6917-0.82585 0.80859-0.4082-0.88306 0.41765 1.7306-0.741 0.82813-0.36719-0.9025 0.37382 1.7654-0.65525 0.8457-0.32617-0.91975 0.32908 1.7942-0.56675 0.85938-0.2832-0.93479 0.28355 1.8206-0.47953 0.87304-0.24219-0.94757 0.23734 1.8428-0.38782 0.88477-0.19727-0.95808 0.19056 1.8588-0.29956 0.89258-0.15625-0.96628 0.14332 1.8706-0.2051 0.89843-0.10937-0.97214 0.0957 1.878-0.11432 0.90235-0.0664-0.97567 0.0479 1.8831-0.0234 0.90625-0.0234z" transform="matrix(1.0147 0 0 1.0147 24.08 -2.1712)" fill="#e6e7e8"/>
  27 + </g>
  28 + <g id="g4957">
  29 + <path id="path52738-1-8-5-0-9" fill="url(#radialGradient52746-0-9-6-5-6)" d="m-486.86 309.22a35.709 35.709 0 0 0 -35.711 35.709 35.709 35.709 0 0 0 35.711 35.711 35.709 35.709 0 0 0 35.709 -35.711 35.709 35.709 0 0 0 -35.709 -35.709zm0 16.533a19.177 19.177 0 0 1 19.176 19.176 19.177 19.177 0 0 1 -19.176 19.178 19.177 19.177 0 0 1 -19.178 -19.178 19.177 19.177 0 0 1 19.178 -19.176z"/>
  30 + <path id="path52738-1-8-4-8" d="m-486.86 312.2a32.733 32.733 0 0 0 -32.732 32.734 32.733 32.733 0 0 0 32.732 32.732 32.733 32.733 0 0 0 32.732 -32.732 32.733 32.733 0 0 0 -32.732 -32.734zm0 13.557a19.177 19.177 0 0 1 19.178 19.178 19.177 19.177 0 0 1 -19.178 19.176 19.177 19.177 0 0 1 -19.178 -19.176 19.177 19.177 0 0 1 19.178 -19.178z" stroke="#8c8d93" stroke-width=".49597" fill="url(#radialGradient52746-0-9-15-6)"/>
  31 + <path id="path52738-1-4-6" fill="url(#radialGradient52746-0-4-0)" d="m-486.86 314.23a30.702 30.702 0 0 0 -30.701 30.701 30.702 30.702 0 0 0 30.701 30.701 30.702 30.702 0 0 0 30.701 -30.701 30.702 30.702 0 0 0 -30.701 -30.701zm0 11.523a19.177 19.177 0 0 1 19.178 19.178 19.177 19.177 0 0 1 -19.178 19.178 19.177 19.177 0 0 1 -19.178 -19.178 19.177 19.177 0 0 1 19.178 -19.178z"/>
  32 + <path id="path52738-1-8-9-4-0" fill="url(#radialGradient52746-0-9-1-0-9)" d="m-486.86 325.76a19.177 19.177 0 0 0 -19.178 19.178 19.177 19.177 0 0 0 19.178 19.178 19.177 19.177 0 0 0 19.178 -19.178 19.177 19.177 0 0 0 -19.178 -19.178zm0 1.4102a17.768 17.768 0 0 1 17.768 17.768 17.768 17.768 0 0 1 -17.768 17.768 17.768 17.768 0 0 1 -17.768 -17.768 17.768 17.768 0 0 1 17.768 -17.768z"/>
  33 + </g>
  34 + </g>
  35 + </g>
  36 +</svg>
... ...
  1 +<svg id="svg5466" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="8.4667mm" width="23.616mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 83.679981 29.999998" xmlns:dc="http://purl.org/dc/elements/1.1/">
  2 + <defs id="defs5468">
  3 + <linearGradient id="linearGradient6001-5" y2="-327.83" gradientUnits="userSpaceOnUse" x2="895.4" gradientTransform="translate(-31.905 97.852)" y1="-357.17" x1="895.4">
  4 + <stop id="stop5439" stop-color="#bdbdc1" offset="0"/>
  5 + <stop id="stop5441" stop-color="#f4f4f4" offset="1"/>
  6 + </linearGradient>
  7 + <linearGradient id="linearGradient6003-6" y2="-392.51" gradientUnits="userSpaceOnUse" x2="892.44" gradientTransform="matrix(1 0 0 .78494 -34.597 55.388)" y1="-366.35" x1="892.44">
  8 + <stop id="stop5486" stop-color="#838383" offset="0"/>
  9 + <stop id="stop5492" stop-color="#979797" offset=".53774"/>
  10 + <stop id="stop5488" stop-color="#a7a7a7" offset="1"/>
  11 + </linearGradient>
  12 + <linearGradient id="linearGradient6436" y2="-334.29" xlink:href="#linearGradient5528" gradientUnits="userSpaceOnUse" x2="913.4" gradientTransform="matrix(.78723 0 0 .78723 192.99 -72.172)" y1="-349.49" x1="913.38"/>
  13 + <linearGradient id="linearGradient5528">
  14 + <stop id="stop5530" stop-color="#3d3d3d" stop-opacity=".86275" offset="0"/>
  15 + <stop id="stop5532" stop-color="#fff" stop-opacity="0" offset="1"/>
  16 + </linearGradient>
  17 + <radialGradient id="radialGradient5502-17-9" xlink:href="#linearGradient5496" gradientUnits="userSpaceOnUse" cy="-341.87" cx="916.16" gradientTransform="matrix(4.5735 -.17574 .13786 3.5876 -3230.9 1043.7)" r="7.5588"/>
  18 + <linearGradient id="linearGradient5496">
  19 + <stop id="stop5498" stop-color="#fff" stop-opacity="0" offset="0"/>
  20 + <stop id="stop5550" stop-color="#fff" stop-opacity=".10435" offset=".17494"/>
  21 + <stop id="stop5552" stop-color="#fff" stop-opacity=".92174" offset=".39279"/>
  22 + <stop id="stop5500" stop-color="#fff" offset="1"/>
  23 + </linearGradient>
  24 + </defs>
  25 + <g id="layer1" transform="translate(-126.73 -454.51)">
  26 + <g id="g6480" transform="translate(-692.33 714.68)">
  27 + <g>
  28 + <path id="rect5195-5-0-1-9-6" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m826.67-260.18c-4.201 0.00074-7.606 3.8877-7.6055 8.6819v12.639c0.00065 4.7933 3.4054 8.6788 7.6055 8.6795h68.469c4.2001-0.00073 7.6049-3.8862 7.6055-8.6795v-12.639c0.00053-4.7942-3.4046-8.6812-7.6055-8.6819z" fill="url(#linearGradient6001-5)"/>
  29 + <path id="rect5195-5-0-1-4-0-6" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m826.67-257.4c-4.201 0.00062-7.606 3.2632-7.6055 7.2872v10.608c0.00065 4.0232 3.4054 7.2845 7.6055 7.2851h68.469c4.2001-0.00062 7.6049-3.2619 7.6055-7.2851v-10.608c0.00053-4.024-3.4046-7.2866-7.6055-7.2872z" fill="#585859"/>
  30 + <path id="rect5195-5-0-1-4-3-0-8" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m826.67-253.64c-4.201 0.00052-7.606 2.7592-7.6055 6.1617v8.9697c0.00065 3.4018 3.4054 6.1594 7.6055 6.16h68.469c4.2001-0.00052 7.6049-2.7581 7.6055-6.16v-8.9697c0.00053-3.4025-3.4046-6.1612-7.6055-6.1617z" fill="url(#linearGradient6003-6)"/>
  31 + </g>
  32 + <g id="g5554-2-1" transform="translate(-29.817 95.669)">
  33 + <circle id="path5494-3-7-8" cx="911.68" cy="-340.85" r="6.0124" fill="url(#linearGradient6436)"/>
  34 + <circle id="path5494-1-9" cx="911.68" cy="-340.85" r="5.3939" fill="url(#radialGradient5502-17-9)"/>
  35 + </g>
  36 + <g id="g5554-8-6-2" transform="translate(-71.652 95.669)">
  37 + <circle id="path5494-3-9-2-9" cx="911.68" cy="-340.85" r="6.0124" fill="url(#linearGradient6436)"/>
  38 + <circle id="path5494-7-0-5" cx="911.68" cy="-340.85" r="5.3939" fill="url(#radialGradient5502-17-9)"/>
  39 + </g>
  40 + <path id="rect5195-5-0-1-4-0-4-4" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m826.67-257.4c-4.201 0.00062-7.606 3.2632-7.6055 7.2872v10.608c0.00065 4.0232 3.4054 7.2845 7.6055 7.2851h68.469c4.2001-0.00062 7.6049-3.2619 7.6055-7.2851v-10.608c0.00053-4.024-3.4046-7.2866-7.6055-7.2872z" fill-opacity=".39080" fill="#ff6e4a"/>
  41 + </g>
  42 + </g>
  43 +</svg>
... ...
  1 +<svg id="svg4749" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="8.4667mm" width="23.616mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 83.679981 29.999998" xmlns:dc="http://purl.org/dc/elements/1.1/">
  2 + <defs id="defs4751">
  3 + <linearGradient id="linearGradient5443" y2="-327.83" gradientUnits="userSpaceOnUse" x2="895.4" y1="-357.17" x1="895.4">
  4 + <stop id="stop5439" stop-color="#bdbdc1" offset="0"/>
  5 + <stop id="stop5441" stop-color="#f4f4f4" offset="1"/>
  6 + </linearGradient>
  7 + <linearGradient id="linearGradient5490" y2="-392.51" gradientUnits="userSpaceOnUse" x2="892.44" gradientTransform="matrix(1 0 0 .78494 -2.6922 -42.464)" y1="-366.35" x1="892.44">
  8 + <stop id="stop5486" stop-color="#838383" offset="0"/>
  9 + <stop id="stop5492" stop-color="#979797" offset=".53774"/>
  10 + <stop id="stop5488" stop-color="#a7a7a7" offset="1"/>
  11 + </linearGradient>
  12 + <linearGradient id="linearGradient5534" y2="-334.29" xlink:href="#linearGradient5528" gradientUnits="userSpaceOnUse" x2="913.4" gradientTransform="matrix(.78723 0 0 .78723 192.99 -72.172)" y1="-349.49" x1="913.38"/>
  13 + <linearGradient id="linearGradient5528">
  14 + <stop id="stop5530" stop-color="#3d3d3d" stop-opacity=".86275" offset="0"/>
  15 + <stop id="stop5532" stop-color="#fff" stop-opacity="0" offset="1"/>
  16 + </linearGradient>
  17 + <radialGradient id="radialGradient5502" xlink:href="#linearGradient5496" gradientUnits="userSpaceOnUse" cy="-341.87" cx="916.16" gradientTransform="matrix(4.5735 -.17574 .13786 3.5876 -3230.9 1043.7)" r="7.5588"/>
  18 + <linearGradient id="linearGradient5496">
  19 + <stop id="stop5498" stop-color="#fff" stop-opacity="0" offset="0"/>
  20 + <stop id="stop5550" stop-color="#fff" stop-opacity=".10435" offset=".17494"/>
  21 + <stop id="stop5552" stop-color="#fff" stop-opacity=".92174" offset=".39279"/>
  22 + <stop id="stop5500" stop-color="#fff" offset="1"/>
  23 + </linearGradient>
  24 + </defs>
  25 + <g id="layer1" transform="translate(-312.45 -714.51)">
  26 + <g id="g5878" transform="translate(-538.52 1072.5)">
  27 + <g>
  28 + <path id="rect5195-5-0-1" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m858.57-358.03c-4.201 0.00074-7.606 3.8877-7.6055 8.6819v12.639c0.00065 4.7933 3.4054 8.6788 7.6055 8.6795h68.469c4.2001-0.00073 7.6049-3.8862 7.6055-8.6795v-12.639c0.00053-4.7942-3.4046-8.6812-7.6055-8.6819z" fill="url(#linearGradient5443)"/>
  29 + <path id="rect5195-5-0-1-4" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m858.57-355.25c-4.201 0.00062-7.606 3.2632-7.6055 7.2872v10.608c0.00065 4.0232 3.4054 7.2845 7.6055 7.2851h68.469c4.2001-0.00062 7.6049-3.2619 7.6055-7.2851v-10.608c0.00053-4.024-3.4046-7.2866-7.6055-7.2872z" fill="#585859"/>
  30 + <path id="rect5195-5-0-1-4-3" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m858.57-351.49c-4.201 0.00052-7.606 2.7592-7.6055 6.1617v8.9697c0.00065 3.4018 3.4054 6.1594 7.6055 6.16h68.469c4.2001-0.00052 7.6049-2.7581 7.6055-6.16v-8.9697c0.00053-3.4025-3.4046-6.1612-7.6055-6.1617z" fill="url(#linearGradient5490)"/>
  31 + </g>
  32 + <g id="g5554" transform="translate(2.0884 -2.1835)">
  33 + <circle id="path5494-3" cx="911.68" cy="-340.85" r="6.0124" fill="url(#linearGradient5534)"/>
  34 + <circle id="path5494" cx="911.68" cy="-340.85" r="5.3939" fill="url(#radialGradient5502)"/>
  35 + </g>
  36 + <g id="g5554-8" transform="translate(-39.746 -2.1835)">
  37 + <circle id="path5494-3-9" cx="911.68" cy="-340.85" r="6.0124" fill="url(#linearGradient5534)"/>
  38 + <circle id="path5494-7" cx="911.68" cy="-340.85" r="5.3939" fill="url(#radialGradient5502)"/>
  39 + </g>
  40 + </g>
  41 + </g>
  42 +</svg>
... ...
  1 +<svg id="svg7523" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="7.6161mm" width="11.594mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 41.08074 26.986356" xmlns:dc="http://purl.org/dc/elements/1.1/">
  2 + <defs id="defs7525">
  3 + <linearGradient id="linearGradient6257" y2="-412.79" gradientUnits="userSpaceOnUse" x2="821.92" gradientTransform="matrix(1 0 0 1.0219 -4.8132 52.955)" y1="-386.98" x1="822.46">
  4 + <stop id="stop5264" stop-color="#3d3d3d" offset="0"/>
  5 + <stop id="stop5266" stop-color="#fbfbfb" offset="1"/>
  6 + </linearGradient>
  7 + <linearGradient id="linearGradient6259" y2="-350.07" gradientUnits="userSpaceOnUse" x2="821.71" gradientTransform="translate(-3.8933 5.8759)" y1="-372.35" x1="821.89">
  8 + <stop id="stop5239" stop-color="#d1d1d1" offset="0"/>
  9 + <stop id="stop5243" stop-color="#e2e3e2" offset=".36037"/>
  10 + <stop id="stop5245" stop-color="#ededed" offset=".67781"/>
  11 + <stop id="stop5241" stop-color="#fcfcfc" offset="1"/>
  12 + </linearGradient>
  13 + <linearGradient id="linearGradient6261" y2="-333.89" gradientUnits="userSpaceOnUse" x2="913.16" gradientTransform="matrix(.78723 0 0 .78723 105.52 -77.537)" y1="-348.85" x1="912.9">
  14 + <stop id="stop5650" stop-color="#939393" offset="0"/>
  15 + <stop id="stop5652" stop-color="#fff" offset="1"/>
  16 + </linearGradient>
  17 + <radialGradient id="radialGradient5502-1-0-6" gradientUnits="userSpaceOnUse" cy="-342.2" cx="916.19" gradientTransform="matrix(1.9993 -.036196 .028926 1.5978 -997.39 230.16)" r="7.5588">
  18 + <stop id="stop5656" stop-color="#fff" stop-opacity="0" offset="0"/>
  19 + <stop id="stop5658" stop-color="#fff" stop-opacity=".18551" offset=".20750"/>
  20 + <stop id="stop5660" stop-color="#fff" stop-opacity=".32464" offset=".31814"/>
  21 + <stop id="stop5662" stop-color="#fff" offset="1"/>
  22 + </radialGradient>
  23 + </defs>
  24 + <g id="layer1" transform="translate(-259.46 -610.3)">
  25 + <g id="g6501" transform="translate(-602.12 869.32)">
  26 + <g id="g5664-5" transform="translate(57.91 100.68)">
  27 + <g id="g5329-3-4" transform="translate(13.694 -2.3675)">
  28 + <path id="rect5195-5-0-8-35" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m797.58-357.34a7.6067 7.8088 0 0 0 -7.6055 7.8098v11.369a7.6067 7.8088 0 0 0 7.6055 7.8076h25.87a7.6067 7.8088 0 0 0 7.6055 -7.8076v-11.369a7.6067 7.8088 0 0 0 -7.6055 -7.8098h-25.87z" fill="#1b1b1b"/>
  29 + <g id="g5300-6-7" transform="translate(-6.6863 11.667)">
  30 + <path id="rect5195-5-03-5" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m804.57-368.63a7.429 7.5918 0 0 0 -7.4279 7.5927v11.053a7.429 7.5918 0 0 0 7.4279 7.5906h25.266a7.429 7.5918 0 0 0 7.4279 -7.5906v-11.053a7.429 7.5918 0 0 0 -7.4279 -7.5927h-25.266z" fill="url(#linearGradient6257)"/>
  31 + <path id="rect5195-3-6" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m805.23-367.67a7.0362 7.0362 0 0 0 -7.0352 7.0371v10.244a7.0362 7.0362 0 0 0 7.0352 7.0352h23.93a7.0362 7.0362 0 0 0 7.0352 -7.0352v-10.244a7.0362 7.0362 0 0 0 -7.0352 -7.0371h-23.93z" fill="url(#linearGradient6259)"/>
  32 + </g>
  33 + </g>
  34 + <circle id="path5494-3-9-7-5" cx="824.21" cy="-346.21" r="6.0124" fill="url(#linearGradient6261)"/>
  35 + <circle id="path5494-7-8-95" cx="824.21" cy="-346.21" r="5.3939" fill="url(#radialGradient5502-1-0-6)"/>
  36 + </g>
  37 + <path id="rect5195-5-0-8-3-8" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m869.18-259.03a7.6067 7.8088 0 0 0 -7.6055 7.8098v11.369a7.6067 7.8088 0 0 0 7.6055 7.8076h25.87a7.6067 7.8088 0 0 0 7.6055 -7.8076v-11.369a7.6067 7.8088 0 0 0 -7.6055 -7.8098h-25.87z" fill-opacity=".78448" fill="#ff6e40"/>
  38 + </g>
  39 + </g>
  40 +</svg>
... ...
  1 +<svg id="svg6201" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="7.6161mm" width="11.594mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 41.08074 26.986349" xmlns:dc="http://purl.org/dc/elements/1.1/">
  2 + <defs id="defs6203">
  3 + <linearGradient id="linearGradient5382" y2="-412.79" gradientUnits="userSpaceOnUse" x2="821.92" gradientTransform="matrix(1 0 0 1.0219 -4.8132 52.955)" y1="-386.98" x1="822.46">
  4 + <stop id="stop5264" stop-color="#3d3d3d" offset="0"/>
  5 + <stop id="stop5266" stop-color="#fbfbfb" offset="1"/>
  6 + </linearGradient>
  7 + <linearGradient id="linearGradient5384" y2="-350.07" gradientUnits="userSpaceOnUse" x2="821.71" gradientTransform="translate(-3.8933 5.8759)" y1="-372.35" x1="821.89">
  8 + <stop id="stop5239" stop-color="#d1d1d1" offset="0"/>
  9 + <stop id="stop5243" stop-color="#e2e3e2" offset=".36037"/>
  10 + <stop id="stop5245" stop-color="#ededed" offset=".67781"/>
  11 + <stop id="stop5241" stop-color="#fcfcfc" offset="1"/>
  12 + </linearGradient>
  13 + <linearGradient id="linearGradient5644" y2="-333.89" gradientUnits="userSpaceOnUse" x2="913.16" gradientTransform="matrix(.78723 0 0 .78723 105.52 -77.537)" y1="-348.85" x1="912.9">
  14 + <stop id="stop5650" stop-color="#939393" offset="0"/>
  15 + <stop id="stop5652" stop-color="#fff" offset="1"/>
  16 + </linearGradient>
  17 + <radialGradient id="radialGradient5502-1-0" gradientUnits="userSpaceOnUse" cy="-342.2" cx="916.19" gradientTransform="matrix(1.9993 -.036196 .028926 1.5978 -997.39 230.16)" r="7.5588">
  18 + <stop id="stop5656" stop-color="#fff" stop-opacity="0" offset="0"/>
  19 + <stop id="stop5658" stop-color="#fff" stop-opacity=".18551" offset=".20750"/>
  20 + <stop id="stop5660" stop-color="#fff" stop-opacity=".32464" offset=".31814"/>
  21 + <stop id="stop5662" stop-color="#fff" offset="1"/>
  22 + </radialGradient>
  23 + </defs>
  24 + <g id="layer1" transform="translate(-288.03 -544.58)">
  25 + <g id="g5664" transform="translate(-515.63 904.29)">
  26 + <g id="g5329-3" transform="translate(13.694 -2.3675)">
  27 + <path id="rect5195-5-0-8" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m797.58-357.34a7.6067 7.8088 0 0 0 -7.6055 7.8098v11.369a7.6067 7.8088 0 0 0 7.6055 7.8076h25.87a7.6067 7.8088 0 0 0 7.6055 -7.8076v-11.369a7.6067 7.8088 0 0 0 -7.6055 -7.8098h-25.87z" fill="#1b1b1b"/>
  28 + <g id="g5300-6" transform="translate(-6.6863 11.667)">
  29 + <path id="rect5195-5-03" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m804.57-368.63a7.429 7.5918 0 0 0 -7.4279 7.5927v11.053a7.429 7.5918 0 0 0 7.4279 7.5906h25.266a7.429 7.5918 0 0 0 7.4279 -7.5906v-11.053a7.429 7.5918 0 0 0 -7.4279 -7.5927h-25.266z" fill="url(#linearGradient5382)"/>
  30 + <path id="rect5195-3" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m805.23-367.67a7.0362 7.0362 0 0 0 -7.0352 7.0371v10.244a7.0362 7.0362 0 0 0 7.0352 7.0352h23.93a7.0362 7.0362 0 0 0 7.0352 -7.0352v-10.244a7.0362 7.0362 0 0 0 -7.0352 -7.0371h-23.93z" fill="url(#linearGradient5384)"/>
  31 + </g>
  32 + </g>
  33 + <circle id="path5494-3-9-7" cy="-346.21" cx="824.21" r="6.0124" fill="url(#linearGradient5644)"/>
  34 + <circle id="path5494-7-8" cy="-346.21" cx="824.21" r="5.3939" fill="url(#radialGradient5502-1-0)"/>
  35 + </g>
  36 + </g>
  37 +</svg>
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div class="tb-switch" fxLayout="column" [ngStyle]="{'pointerEvents': ctx.isEdit ? 'none' : 'all'}">
  19 + <div #switchErrorContainer class="error-container" [ngStyle]="{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}"
  20 + fxLayout="row" fxLayoutAlign="center center">
  21 + <span #switchError class="switch-error">{{ error }}</span>
  22 + </div>
  23 + <div #switchTitleContainer fxFlex="30" class="title-container" fxLayout="row" fxLayoutAlign="center center" [fxShow]="showTitle">
  24 + <span #switchTitle class="switch-title">{{title}}</span>
  25 + </div>
  26 + <div #switchContainer fxFlex="{{showTitle ? 70 : 100}}" id="switch-container" fxLayout="column" fxLayoutAlign="center center">
  27 + <div #switch class="switch">
  28 + <mat-slide-toggle #matSlideToggle [(ngModel)]="value" (change)="onValue()">
  29 + </mat-slide-toggle>
  30 + </div>
  31 + <div #onoffContainer class="onoff-container" fxLayout="row" fxLayoutAlign="center start" [fxShow]="showOnOffLabels">
  32 + <span #offLabel fxFlex class="off-label" [fxShow]="!value" style="text-align: center;">OFF</span>
  33 + <span fxFlex [fxShow]="value"></span>
  34 + <span #onLabel fxFlex class="on-label" [fxShow]="value" style="text-align: center;">ON</span>
  35 + <span fxFlex [fxShow]="!value"></span>
  36 + </div>
  37 + </div>
  38 + <div #textMeasure id="text-measure"></div>
  39 +</div>
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +
  18 +$thumb-img: url("./svg/thumb.svg") !default;
  19 +$thumb-checked-img: url("./svg/thumb-checked.svg") !default;
  20 +$thumb-bar-img: url("./svg/thumb-bar.svg") !default;
  21 +$thumb-bar-checked-img: url("./svg/thumb-bar-checked.svg") !default;
  22 +
  23 +$background-color: #e6e7e8 !default;
  24 +
  25 +$error-height: 14px !default;
  26 +
  27 +:host {
  28 +
  29 + .tb-switch {
  30 + width: 100%;
  31 + height: 100%;
  32 + background: $background-color;
  33 +
  34 + .error-container {
  35 + position: absolute;
  36 + top: 1%;
  37 + right: 0;
  38 + left: 0;
  39 + z-index: 4;
  40 + height: $error-height;
  41 +
  42 + .switch-error {
  43 + color: #ff3315;
  44 + white-space: nowrap;
  45 + }
  46 + }
  47 +
  48 + .onoff-container {
  49 + height: 100%;
  50 + font-weight: 500;
  51 + color: #757575;
  52 + white-space: nowrap;
  53 +
  54 + .off-label {
  55 + color: #b7b5b5;
  56 + }
  57 +
  58 + .on-label {
  59 + color: #ff7e57;
  60 + text-shadow: #ff6e4a 1px 1px 10px, #ffd1c3 1px 1px 10px;
  61 + }
  62 + }
  63 +
  64 + .title-container {
  65 + .switch-title {
  66 + font-weight: 500;
  67 + color: #757575;
  68 + white-space: nowrap;
  69 + }
  70 + }
  71 +
  72 + #switch-container {
  73 + padding-right: 10px;
  74 + padding-left: 10px;
  75 + }
  76 +
  77 + .switch {
  78 + position: relative;
  79 + }
  80 +
  81 + #text-measure {
  82 + position: absolute;
  83 + width: auto;
  84 + height: auto;
  85 + white-space: nowrap;
  86 + visibility: hidden;
  87 + }
  88 + }
  89 +}
  90 +
  91 +:host ::ng-deep {
  92 + .tb-switch {
  93 + .switch {
  94 + mat-slide-toggle {
  95 + position: absolute;
  96 + top: 0;
  97 + right: 0;
  98 + bottom: 0;
  99 + left: 0;
  100 + margin: 0;
  101 +
  102 + .mat-slide-toggle-bar {
  103 + top: 0;
  104 + left: 0;
  105 + width: 100%;
  106 + height: 100%;
  107 + background: $thumb-bar-img no-repeat;
  108 + background-size: contain;
  109 + border-radius: 0;
  110 + }
  111 +
  112 + .mat-slide-toggle-thumb-container {
  113 + top: 5%;
  114 + left: .25%;
  115 + width: 50%;
  116 + height: 90%;
  117 + }
  118 +
  119 + .mat-slide-toggle-thumb {
  120 + top: 0;
  121 + left: 0;
  122 + width: 100%;
  123 + height: 100%;
  124 + background: $thumb-img no-repeat;
  125 + background-size: contain;
  126 + border-radius: 0;
  127 + box-shadow: none;
  128 + }
  129 +
  130 + &.mat-checked {
  131 + .mat-slide-toggle-thumb-container {
  132 + transform: translate3d(100%,0,0);
  133 + }
  134 + .mat-slide-toggle-bar {
  135 + background: $thumb-bar-checked-img no-repeat;
  136 + background-size: contain;
  137 + }
  138 +
  139 + .mat-slide-toggle-thumb {
  140 + background: $thumb-checked-img no-repeat;
  141 + background-size: contain;
  142 + }
  143 + }
  144 + }
  145 + }
  146 + }
  147 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
  18 +import { PageComponent } from '@shared/components/page.component';
  19 +import { WidgetContext } from '@home/models/widget-component.models';
  20 +import { UtilsService } from '@core/services/utils.service';
  21 +import { Store } from '@ngrx/store';
  22 +import { AppState } from '@core/core.state';
  23 +import { isDefined } from '@core/utils';
  24 +import { IWidgetSubscription, SubscriptionInfo, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
  25 +import { DatasourceType, widgetType } from '@shared/models/widget.models';
  26 +import { EntityType } from '@shared/models/entity-type.models';
  27 +import { MatSlideToggle } from '@angular/material/slide-toggle';
  28 +
  29 +const switchAspectRation = 2.7893;
  30 +
  31 +type RetrieveValueMethod = 'rpc' | 'attribute' | 'timeseries';
  32 +
  33 +interface SwitchSettings {
  34 + initialValue: boolean;
  35 + title: string;
  36 + showOnOffLabels: boolean;
  37 + retrieveValueMethod: RetrieveValueMethod;
  38 + valueKey: string;
  39 + getValueMethod: string;
  40 + setValueMethod: string;
  41 + parseValueFunction: string;
  42 + convertValueFunction: string;
  43 + requestTimeout: number;
  44 +}
  45 +
  46 +@Component({
  47 + selector: 'tb-switch',
  48 + templateUrl: './switch.component.html',
  49 + styleUrls: ['./switch.component.scss']
  50 +})
  51 +export class SwitchComponent extends PageComponent implements OnInit, OnDestroy {
  52 +
  53 + @ViewChild('switch', {static: true}) switchElementRef: ElementRef<HTMLElement>;
  54 + @ViewChild('switchContainer', {static: true}) switchContainerRef: ElementRef<HTMLElement>;
  55 + @ViewChild('matSlideToggle', {static: true}) matSlideToggleRef: MatSlideToggle;
  56 + @ViewChild('onoffContainer', {static: true}) onoffContainerRef: ElementRef<HTMLElement>;
  57 + @ViewChild('onLabel', {static: true}) onLabelRef: ElementRef<HTMLElement>;
  58 + @ViewChild('offLabel', {static: true}) offLabelRef: ElementRef<HTMLElement>;
  59 + @ViewChild('switchTitleContainer', {static: true}) switchTitleContainerRef: ElementRef<HTMLElement>;
  60 + @ViewChild('switchTitle', {static: true}) switchTitleRef: ElementRef<HTMLElement>;
  61 + @ViewChild('textMeasure', {static: true}) textMeasureRef: ElementRef<HTMLElement>;
  62 + @ViewChild('switchErrorContainer', {static: true}) switchErrorContainerRef: ElementRef<HTMLElement>;
  63 + @ViewChild('switchError', {static: true}) switchErrorRef: ElementRef<HTMLElement>;
  64 +
  65 + @Input()
  66 + ctx: WidgetContext;
  67 +
  68 + showTitle = false;
  69 + value = false;
  70 + error = '';
  71 + title = '';
  72 + showOnOffLabels = false;
  73 +
  74 + private isSimulated: boolean;
  75 + private requestTimeout: number;
  76 + private retrieveValueMethod: RetrieveValueMethod;
  77 + private valueKey: string;
  78 + private parseValueFunction: (data: any) => boolean;
  79 + private convertValueFunction: (value: any) => any;
  80 + private getValueMethod: string;
  81 + private setValueMethod: string;
  82 +
  83 + private valueSubscription: IWidgetSubscription;
  84 +
  85 + private executingUpdateValue: boolean;
  86 + private scheduledValue: boolean;
  87 + private rpcValue: boolean;
  88 +
  89 + private switchElement: JQuery<HTMLElement>;
  90 + private switchContainer: JQuery<HTMLElement>;
  91 + private matSlideToggle: JQuery<HTMLElement>;
  92 + private onoffContainer: JQuery<HTMLElement>;
  93 + private onLabel: JQuery<HTMLElement>;
  94 + private offLabel: JQuery<HTMLElement>;
  95 + private switchTitleContainer: JQuery<HTMLElement>;
  96 + private switchTitle: JQuery<HTMLElement>;
  97 + private textMeasure: JQuery<HTMLElement>;
  98 + private switchErrorContainer: JQuery<HTMLElement>;
  99 + private switchError: JQuery<HTMLElement>;
  100 +
  101 + private switchResizeListener: any;
  102 +
  103 + constructor(private utils: UtilsService,
  104 + protected store: Store<AppState>) {
  105 + super(store);
  106 + }
  107 +
  108 + ngOnInit(): void {
  109 + this.switchElement = $(this.switchElementRef.nativeElement);
  110 + this.switchContainer = $(this.switchContainerRef.nativeElement);
  111 + this.matSlideToggle = $(this.matSlideToggleRef._elementRef.nativeElement);
  112 + this.onoffContainer = $(this.onoffContainerRef.nativeElement);
  113 + this.onLabel = $(this.onLabelRef.nativeElement);
  114 + this.offLabel = $(this.offLabelRef.nativeElement);
  115 + this.switchTitleContainer = $(this.switchTitleContainerRef.nativeElement);
  116 + this.switchTitle = $(this.switchTitleRef.nativeElement);
  117 + this.textMeasure = $(this.textMeasureRef.nativeElement);
  118 + this.switchErrorContainer = $(this.switchErrorContainerRef.nativeElement);
  119 + this.switchError = $(this.switchErrorRef.nativeElement);
  120 +
  121 + this.switchResizeListener = this.resize.bind(this);
  122 + // @ts-ignore
  123 + addResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener);
  124 + this.init();
  125 + }
  126 +
  127 + ngOnDestroy(): void {
  128 + if (this.valueSubscription) {
  129 + this.ctx.subscriptionApi.removeSubscription(this.valueSubscription.id);
  130 + }
  131 + if (this.switchResizeListener) {
  132 + // @ts-ignore
  133 + removeResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener);
  134 + }
  135 + }
  136 +
  137 + private init() {
  138 + const settings: SwitchSettings = this.ctx.settings;
  139 + this.title = isDefined(settings.title) ? settings.title : '';
  140 + this.showTitle = !!(this.title && this.title.length);
  141 + this.showOnOffLabels = isDefined(settings.showOnOffLabels) ? settings.showOnOffLabels : true;
  142 + const initialValue = isDefined(settings.initialValue) ? settings.initialValue : false;
  143 + this.setValue(initialValue);
  144 +
  145 + const subscription = this.ctx.defaultSubscription;
  146 + const rpcEnabled = subscription.rpcEnabled;
  147 +
  148 + this.isSimulated = this.utils.widgetEditMode;
  149 +
  150 + this.requestTimeout = 500;
  151 + if (settings.requestTimeout) {
  152 + this.requestTimeout = settings.requestTimeout;
  153 + }
  154 + this.retrieveValueMethod = 'rpc';
  155 + if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
  156 + this.retrieveValueMethod = settings.retrieveValueMethod;
  157 + }
  158 + this.valueKey = 'value';
  159 + if (settings.valueKey && settings.valueKey.length) {
  160 + this.valueKey = settings.valueKey;
  161 + }
  162 + this.parseValueFunction = (data) => !!data;
  163 + if (settings.parseValueFunction && settings.parseValueFunction.length) {
  164 + try {
  165 + this.parseValueFunction = new Function('data', settings.parseValueFunction) as (data: any) => boolean;
  166 + } catch (e) {
  167 + this.parseValueFunction = (data) => !!data;
  168 + }
  169 + }
  170 + this.convertValueFunction = (value) => value;
  171 + if (settings.convertValueFunction && settings.convertValueFunction.length) {
  172 + try {
  173 + this.convertValueFunction = new Function('value', settings.convertValueFunction) as (value: any) => any;
  174 + } catch (e) {
  175 + this.convertValueFunction = (value) => value;
  176 + }
  177 + }
  178 + this.getValueMethod = 'getValue';
  179 + if (settings.getValueMethod && settings.getValueMethod.length) {
  180 + this.getValueMethod = settings.getValueMethod;
  181 + }
  182 + this.setValueMethod = 'setValue';
  183 + if (settings.setValueMethod && settings.setValueMethod.length) {
  184 + this.setValueMethod = settings.setValueMethod;
  185 + }
  186 + if (!rpcEnabled) {
  187 + this.onError('Target device is not set!');
  188 + } else {
  189 + if (!this.isSimulated) {
  190 + if (this.retrieveValueMethod === 'rpc') {
  191 + this.rpcRequestValue();
  192 + } else if (this.retrieveValueMethod === 'attribute' || this.retrieveValueMethod === 'timeseries') {
  193 + this.subscribeForValue();
  194 + }
  195 + }
  196 + }
  197 +
  198 + }
  199 +
  200 + private resize() {
  201 + let width = this.switchContainer.width();
  202 + let height = this.switchContainer.height();
  203 + if (this.showOnOffLabels) {
  204 + height = height*2/3;
  205 + }
  206 + const ratio = width/height;
  207 + if (ratio > switchAspectRation) {
  208 + width = height*switchAspectRation;
  209 + } else {
  210 + height = width/switchAspectRation;
  211 + }
  212 + this.switchElement.css({width, height});
  213 + this.matSlideToggle.css({width, height, minWidth: width});
  214 +
  215 + if (this.showTitle) {
  216 + this.setFontSize(this.switchTitle, this.title, this.switchTitleContainer.height() * 2 / 3, this.switchTitleContainer.width());
  217 + }
  218 +
  219 + if (this.showOnOffLabels) {
  220 + this.onoffContainer.css({width, height: this.switchContainer.height() / 3});
  221 + this.setFontSize(this.onLabel, 'OFF', this.onoffContainer.height(), this.onoffContainer.width() / 2);
  222 + this.setFontSize(this.offLabel, 'OFF', this.onoffContainer.height(), this.onoffContainer.width() / 2);
  223 + }
  224 +
  225 + this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width());
  226 + }
  227 +
  228 + private setFontSize(element: JQuery<HTMLElement>, text: string, fontSize: number, maxWidth: number) {
  229 + let textWidth = this.measureTextWidth(text, fontSize);
  230 + while (textWidth > maxWidth) {
  231 + fontSize--;
  232 + textWidth = this.measureTextWidth(text, fontSize);
  233 + }
  234 + element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
  235 + }
  236 +
  237 + private measureTextWidth(text: string, fontSize: number): number {
  238 + this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
  239 + this.textMeasure.text(text);
  240 + return this.textMeasure.width();
  241 + }
  242 +
  243 + private onError(error: string) {
  244 + this.error = error;
  245 + this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width());
  246 + this.ctx.detectChanges();
  247 + }
  248 +
  249 + private setValue(value: boolean) {
  250 + this.value = value ? true : false;
  251 + }
  252 +
  253 + public onValue() {
  254 + this.rpcUpdateValue(this.value);
  255 + }
  256 +
  257 + private rpcRequestValue() {
  258 + this.error = '';
  259 + this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe(
  260 + (responseBody) => {
  261 + this.setValue(this.parseValueFunction(responseBody));
  262 + },
  263 + () => {
  264 + const errorText = this.ctx.defaultSubscription.rpcErrorText;
  265 + this.onError(errorText);
  266 + }
  267 + );
  268 + }
  269 +
  270 + private rpcUpdateValue(value) {
  271 + if (this.executingUpdateValue) {
  272 + this.scheduledValue = value;
  273 + return;
  274 + } else {
  275 + this.scheduledValue = null;
  276 + this.rpcValue = value;
  277 + this.executingUpdateValue = true;
  278 + }
  279 + this.error = '';
  280 + this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe(
  281 + () => {
  282 + this.executingUpdateValue = false;
  283 + if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
  284 + this.rpcUpdateValue(this.scheduledValue);
  285 + }
  286 + },
  287 + () => {
  288 + this.executingUpdateValue = false;
  289 + const errorText = this.ctx.defaultSubscription.rpcErrorText;
  290 + this.onError(errorText);
  291 + }
  292 + );
  293 + }
  294 +
  295 + private subscribeForValue() {
  296 + const valueSubscriptionInfo: SubscriptionInfo[] = [{
  297 + type: DatasourceType.entity,
  298 + entityType: EntityType.DEVICE,
  299 + entityId: this.ctx.defaultSubscription.targetDeviceId
  300 + }];
  301 + if (this.retrieveValueMethod === 'attribute') {
  302 + valueSubscriptionInfo[0].attributes = [
  303 + {name: this.valueKey}
  304 + ];
  305 + } else {
  306 + valueSubscriptionInfo[0].timeseries = [
  307 + {name: this.valueKey}
  308 + ];
  309 + }
  310 + const subscriptionOptions: WidgetSubscriptionOptions = {
  311 + callbacks: {
  312 + onDataUpdated: this.onDataUpdated.bind(this),
  313 + onDataUpdateError: this.onDataUpdateError.bind(this)
  314 + }
  315 + };
  316 + this.ctx.subscriptionApi.createSubscriptionFromInfo (
  317 + widgetType.latest, valueSubscriptionInfo, subscriptionOptions, false, true).subscribe(
  318 + (subscription) => {
  319 + this.valueSubscription = subscription;
  320 + }
  321 + );
  322 + }
  323 +
  324 + private onDataUpdated(subscription: IWidgetSubscription, detectChanges: boolean) {
  325 + let value = false;
  326 + const data = subscription.data;
  327 + if (data.length) {
  328 + const keyData = data[0];
  329 + if (keyData && keyData.data && keyData.data[0]) {
  330 + const attrValue = keyData.data[0][1];
  331 + if (attrValue) {
  332 + let parsed = null;
  333 + try {
  334 + parsed = this.parseValueFunction(JSON.parse(attrValue));
  335 + } catch (e){/**/}
  336 + value = !!parsed;
  337 + }
  338 + }
  339 + }
  340 + this.setValue(value);
  341 + if (detectChanges) {
  342 + this.ctx.detectChanges();
  343 + }
  344 + }
  345 +
  346 + private onDataUpdateError(subscription: IWidgetSubscription, e: any) {
  347 + const exceptionData = this.utils.parseException(e);
  348 + let errorText = exceptionData.name;
  349 + if (exceptionData.message) {
  350 + errorText += ': ' + exceptionData.message;
  351 + }
  352 + this.onError(errorText);
  353 + }
  354 +}
... ...
... ... @@ -25,6 +25,7 @@ import { SharedHomeComponentsModule } from '@home/components/shared-home-compone
25 25 import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/timeseries-table-widget.component';
26 26 import { EntitiesHierarchyWidgetComponent } from '@home/components/widget/lib/entities-hierarchy-widget.component';
27 27 import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
  28 +import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.module';
28 29
29 30 @NgModule({
30 31 declarations:
... ... @@ -39,13 +40,15 @@ import { CustomDialogService } from '@home/components/widget/dialog/custom-dialo
39 40 imports: [
40 41 CommonModule,
41 42 SharedModule,
  43 + RpcWidgetsModule,
42 44 SharedHomeComponentsModule
43 45 ],
44 46 exports: [
45 47 EntitiesTableWidgetComponent,
46 48 AlarmsTableWidgetComponent,
47 49 TimeseriesTableWidgetComponent,
48   - EntitiesHierarchyWidgetComponent
  50 + EntitiesHierarchyWidgetComponent,
  51 + RpcWidgetsModule
49 52 ],
50 53 providers: [
51 54 CustomDialogService
... ...