thingsboard-aplcache

Details

diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json
index ce667af..70f8e9c 100644
--- a/application/src/main/data/json/system/widget_bundles/control_widgets.json
+++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json
@@ -68,6 +68,22 @@
         "dataKeySettingsSchema": "{}\n",
         "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}"
       }
+    },
+    {
+      "alias": "round_switch",
+      "name": "Round switch",
+      "descriptor": {
+        "type": "rpc",
+        "sizeX": 2.5,
+        "sizeY": 2,
+        "resources": [],
+        "templateHtml": "<tb-round-switch ctx='ctx'></tb-round-switch>",
+        "templateCss": "",
+        "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",
+        "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            \"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\": [\"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n    },\n    \"form\": [\n        \"initialValue\",\n        \"title\",\n        \"getValueMethod\",\n        \"setValueMethod\",\n        \"requestTimeout\"\n    ]\n}",
+        "dataKeySettingsSchema": "{}\n",
+        "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\":\"Round switch\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
+      }
     }
   ]
 }
\ No newline at end of file
diff --git a/ui/src/app/widget/lib/rpc/index.js b/ui/src/app/widget/lib/rpc/index.js
index 3edbdff..0536fac 100644
--- a/ui/src/app/widget/lib/rpc/index.js
+++ b/ui/src/app/widget/lib/rpc/index.js
@@ -16,8 +16,10 @@
 
 import tbKnob from './knob.directive';
 import tbSwitch from './switch.directive';
+import tbRoundSwitch from './round-switch.directive';
 
 export default angular.module('thingsboard.widgets.rpc', [
     tbKnob,
-    tbSwitch
+    tbSwitch,
+    tbRoundSwitch
 ]).name;
diff --git a/ui/src/app/widget/lib/rpc/round-switch.directive.js b/ui/src/app/widget/lib/rpc/round-switch.directive.js
new file mode 100644
index 0000000..e930b70
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/round-switch.directive.js
@@ -0,0 +1,199 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './round-switch.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import roundSwitchTemplate from './round-switch.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.widgets.rpc.roundSwitch', [])
+    .directive('tbRoundSwitch', RoundSwitch)
+    .name;
+
+/*@ngInject*/
+function RoundSwitch() {
+    return {
+        restrict: "E",
+        scope: true,
+        bindToController: {
+            ctx: '='
+        },
+        controller: RoundSwitchController,
+        controllerAs: 'vm',
+        templateUrl: roundSwitchTemplate
+    };
+}
+
+/*@ngInject*/
+function RoundSwitchController($element, $scope, utils) {
+    let vm = this;
+
+    vm.showTitle = false;
+    vm.value = false;
+    vm.error = '';
+
+    vm.checkboxId = 'onoff-' + utils.guid();
+
+    var switchElement = angular.element('.switch', $element),
+        switchContainer = angular.element('#switch-container', $element),
+        onoff = angular.element('input', $element),
+        textMeasure = angular.element('#text-measure', $element),
+        switchTitleContainer = angular.element('.title-container', $element),
+        switchTitle = angular.element('.switch-title', $element),
+        switchErrorContainer = angular.element('.error-container', $element),
+        switchError = angular.element('.switch-error', $element);
+
+    onoff.bind('change', () => {
+        vm.value = onoff.prop('checked') === false;
+        onValue();
+    });
+
+    $scope.$watch('vm.ctx', () => {
+        if (vm.ctx) {
+            init();
+        }
+    });
+
+    resize();
+
+    function init() {
+
+        vm.title = angular.isDefined(vm.ctx.settings.title) ? vm.ctx.settings.title : '';
+        vm.showTitle = vm.title && vm.title.length ? true : false;
+        vm.showOnOffLabels = angular.isDefined(vm.ctx.settings.showOnOffLabels) ? vm.ctx.settings.showOnOffLabels : true;
+        vm.ctx.resize = resize;
+        $scope.$applyAsync(() => {
+            resize();
+        });
+        var initialValue = angular.isDefined(vm.ctx.settings.initialValue) ? vm.ctx.settings.initialValue : false;
+        setValue(initialValue);
+
+        var subscription = vm.ctx.defaultSubscription;
+        var rpcEnabled = subscription.rpcEnabled;
+
+        vm.isSimulated = $scope.widgetEditMode;
+
+        vm.requestTimeout = 500;
+        if (vm.ctx.settings.requestTimeout) {
+            vm.requestTimeout = vm.ctx.settings.requestTimeout;
+        }
+        vm.getValueMethod = 'getValue';
+        if (vm.ctx.settings.getValueMethod && vm.ctx.settings.getValueMethod.length) {
+            vm.getValueMethod = vm.ctx.settings.getValueMethod;
+        }
+        vm.setValueMethod = 'setValue';
+        if (vm.ctx.settings.setValueMethod && vm.ctx.settings.setValueMethod.length) {
+            vm.setValueMethod = vm.ctx.settings.setValueMethod;
+        }
+        if (!rpcEnabled) {
+            onError('Target device is not set!');
+        } else {
+            if (!vm.isSimulated) {
+                rpcRequestValue();
+            }
+        }
+    }
+
+    function resize() {
+        var width = switchContainer.width();
+        var height = switchContainer.height();
+        var size = Math.min(width, height);
+        var scale = size/260;
+        switchElement.css({
+            '-webkit-transform': `scale(${scale})`,
+            '-moz-transform': `scale(${scale})`,
+            '-ms-transform': `scale(${scale})`,
+            '-o-transform': `scale(${scale})`,
+            transform: `scale(${scale})`
+        });
+        if (vm.showTitle) {
+            setFontSize(switchTitle, vm.title, switchTitleContainer.height() * 2 / 3, switchTitleContainer.width());
+        }
+        setFontSize(switchError, vm.error, switchErrorContainer.height(), switchErrorContainer.width());
+    }
+
+    function setValue(value) {
+        vm.value = value ? true : false;
+        onoff.prop('checked', !vm.value);
+    }
+
+    function onValue() {
+        rpcUpdateValue(vm.value);
+    }
+
+    function onError(error) {
+        $scope.$applyAsync(() => {
+            vm.error = error;
+            setFontSize(switchError, vm.error, switchErrorContainer.height(), switchErrorContainer.width());
+        });
+    }
+
+    function setFontSize(element, text, fontSize, maxWidth) {
+        var textWidth = measureTextWidth(text, fontSize);
+        while (textWidth > maxWidth) {
+            fontSize--;
+            textWidth = measureTextWidth(text, fontSize);
+        }
+        element.css({'fontSize': fontSize+'px', 'lineHeight': fontSize+'px'});
+    }
+
+    function measureTextWidth(text, fontSize) {
+        textMeasure.css({'fontSize': fontSize+'px', 'lineHeight': fontSize+'px'});
+        textMeasure.text(text);
+        return textMeasure.width();
+    }
+
+    function rpcRequestValue() {
+        vm.error = '';
+        vm.ctx.controlApi.sendTwoWayCommand(vm.getValueMethod, null, vm.requestTimeout).then(
+            (responseBody) => {
+                setValue(responseBody);
+            },
+            () => {
+                var errorText = vm.ctx.defaultSubscription.rpcErrorText;
+                onError(errorText);
+            }
+        );
+    }
+
+    function rpcUpdateValue(value) {
+        if (vm.executingUpdateValue) {
+            vm.scheduledValue = value;
+            return;
+        } else {
+            vm.scheduledValue = null;
+            vm.rpcValue = value;
+            vm.executingUpdateValue = true;
+        }
+        vm.error = '';
+        vm.ctx.controlApi.sendOneWayCommand(vm.setValueMethod, value, vm.requestTimeout).then(
+            () => {
+                vm.executingUpdateValue = false;
+                if (vm.scheduledValue != null && vm.scheduledValue != vm.rpcValue) {
+                    rpcUpdateValue(vm.scheduledValue);
+                }
+            },
+            () => {
+                vm.executingUpdateValue = false;
+                var errorText = vm.ctx.defaultSubscription.rpcErrorText;
+                onError(errorText);
+            }
+        );
+    }
+}
diff --git a/ui/src/app/widget/lib/rpc/round-switch.scss b/ui/src/app/widget/lib/rpc/round-switch.scss
new file mode 100644
index 0000000..fa2399e
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/round-switch.scss
@@ -0,0 +1,195 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import "~compass-sass-mixins/lib/compass";
+
+$error-height: 14px;
+
+$background-color: #e6e7e8;
+
+.tb-round-switch {
+  width:100%;
+  height:100%;
+  background: $background-color;
+
+  .title-container {
+    .switch-title {
+      color: #757575;
+      font-weight: 500;
+      white-space: nowrap;
+    }
+  }
+
+  .error-container {
+    position:absolute;
+    top: 1%;
+    left: 0;
+    right: 0;
+    z-index:4;
+    height: $error-height;
+    .switch-error {
+      color: #ff3315;
+      white-space: nowrap;
+    }
+  }
+  #text-measure {
+    position: absolute;
+    visibility: hidden;
+    height: auto;
+    width: auto;
+    white-space: nowrap;
+  }
+
+  #switch-container {
+    padding: 10px;
+    .switch {
+      cursor: pointer;
+      position: relative;
+      background:#ddd;
+      background: -owg-linear-gradient(270deg, #bbb, #ddd);
+      background: -webkit-linear-gradient(270deg, #bbb, #ddd);
+      background: -moz-linear-gradient(270deg, #bbb, #ddd);
+      background: -o-linear-gradient(270deg, #bbb, #ddd);
+      -pie-background: -pie-linear-gradient(270deg, #bbb, #ddd);
+      background: linear-gradient(180deg, #bbb, #ddd);
+      border-radius:130px;
+      @include box-sizing(border-box);
+      @include box-shadow(
+              0px 0px 0px 8px rgba(0,0,0,.1)
+              ,0px 0px 3px 1px rgba(0,0,0,.1)
+              ,inset 0 8px  3px -8px rgba(255,255,255,.4));
+      height: 260px;
+      min-height: 260px;
+      padding: 25px;
+      width: 260px;
+      min-width: 260px;
+
+      color: #424242;
+      font-family:sans-serif;
+      font-size:48px;
+
+      input {
+        display:none
+      }
+
+      .on,.off {
+        position:absolute;
+        text-align:center;
+        @include text-shadow(1px 1px 4px #4a4a4a);
+        width:100%;
+      }
+
+      .on {
+        color:#444;
+        top:10px;
+        @include transition(all 0.1s);
+        font-family:sans-serif
+      }
+
+      .off {
+        bottom:5px;
+        @include transition(all 0.1s);
+        @include transform(scaleY(0.85));
+      }
+
+      .but {
+        cursor: pointer;
+        background-color:#d8d8d8;
+        border-radius: 400px 400px 400px 400px / 400px 400px 300px 300px;
+        border-bottom-width:0px;
+        @include box-shadow(inset 8px 6px 5px -7px #a2a2a2,
+                inset -8px 6px 5px -7px #a2a2a2,
+                inset 0 -3px 2px -2px rgba(200, 200, 200, 0.5),
+                0 3px 3px -2px #ffffff,
+                inset 0 -230px 60px -200px rgba(255, 255, 255, 0.2),
+                inset 0 220px 40px -200px rgba(0, 0, 0, 0.3));
+        display:block;
+        font-size:48px;
+        height:178px;
+        position:relative;
+        @include transition(all 0.2s);
+        width:200px;
+      }
+
+      .back {
+        cursor: pointer;
+        background-color: #888787;
+        background-image: -owg-linear-gradient(0deg, transparent 30%, transparent 70%), -owg-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%);
+        background-image: -webkit-linear-gradient(0deg, transparent 30%, transparent 70%), -webkit-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%);
+        background-image: -moz-linear-gradient(0deg, transparent 30%, transparent 70%), -moz-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%);
+        background-image: -o-linear-gradient(0deg, transparent 30%, transparent 70%), -o-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%);
+        background-image: linear-gradient(-90deg, transparent 30%, transparent 70%), linear-gradient(0deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%);
+        border-radius:105px;
+        @include box-shadow(30px 30px 30px -20px rgba(58, 58, 58, 0.3),
+                -30px 30px 30px -20px rgba(58, 58, 58, 0.3),
+                0 30px 30px 0px rgba(16, 16, 16, 0.3),
+                inset 0 -1px 0 0 #484848);
+        @include box-sizing(border-box);
+        height:210px;
+        padding:4px 4px;
+        @include transition(all 0.2s);
+        width:210px;
+      }
+
+
+      input:checked + .back .on,input:checked + .back .off{
+        @include text-shadow(1px 1px 4px #4a4a4a);
+      }
+      input:checked + .back .on{
+        color:#4c4c4c;
+        top:10px;
+        @include transform(scaleY(0.85));
+      }
+      input:checked + .back .off{
+        color:#444;
+        bottom:5px;
+        @include transform(scaleY(1));
+      }
+      input:checked + .back .but{
+        background:#dcdcdc;
+        background-image: -owg-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent);
+        background-image: -webkit-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent);
+        background-image: -moz-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent);
+        background-image: -o-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent);
+        background-image: radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent);
+        border-radius: 400px 400px 400px 400px / 300px 300px 400px 400px;
+        @include box-shadow(inset 8px -4px 5px -7px #a9a9a9,
+                inset -8px -4px 5px -7px #808080,
+                0 -3px 8px -4px rgba(50, 50, 50, 0.4),
+                inset 0 3px 4px -2px #9c9c9c,
+                inset 0 280px 40px -200px rgba(0, 0, 0, 0.2),
+                inset 0 -200px 40px -200px rgba(180, 180, 180, 0.2));
+        margin-top:20px;
+      }
+      input:checked + .back{
+
+        background-image: -owg-linear-gradient(90deg, #868686 30%, transparent 70%), -owg-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%);
+        background-image: -webkit-linear-gradient(90deg, #868686 30%, transparent 70%), -webkit-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%);
+        background-image: -moz-linear-gradient(90deg, #868686 30%, transparent 70%), -moz-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%);
+        background-image: -o-linear-gradient(90deg, #868686 30%, transparent 70%), -o-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%);
+        background-image: linear-gradient(0deg, #868686 30%, transparent 70%), linear-gradient(90deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%);
+
+        @include box-shadow(30px 30px 30px -20px rgba(49, 49, 49, 0.1),
+                -30px 30px 30px -20px rgba(111, 111, 111, 0.1),
+                0 30px 30px 0px rgba(0, 0, 0, 0.2),
+                inset 0 1px 2px 0 rgba(167, 167, 167, 0.6));
+        padding:2px 4px;
+      }
+
+    }
+  }
+}
+
diff --git a/ui/src/app/widget/lib/rpc/round-switch.tpl.html b/ui/src/app/widget/lib/rpc/round-switch.tpl.html
new file mode 100644
index 0000000..ad8c1a6
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/round-switch.tpl.html
@@ -0,0 +1,38 @@
+<!--
+
+    Copyright © 2016-2017 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-round-switch" layout="column" ng-style="{'pointerEvents': vm.ctx.isEdit ? 'none' : 'all'}">
+    <div flex="20" class="title-container" layout="row" layout-align="center center" ng-show="vm.showTitle">
+        <span class="switch-title">{{vm.title}}</span>
+    </div>
+    <div flex="{{vm.showTitle ? 80 : 100}}" ng-style="{paddingTop: vm.showTitle ? '5px': '10px'}" id="switch-container" layout="column" layout-align="center center">
+        <div class="switch">
+            <input type="checkbox" id="{{vm.checkboxId}}" name="onoff" />
+            <div class="back">
+                <label class="but" for="{{vm.checkboxId}}">
+                    <span class="on">I</span>
+                    <span class="off">0</span>
+                </label>
+            </div>
+        </div>
+    </div>
+    <div class="error-container" ng-style="{'background': vm.error.length ? 'rgba(255,255,255,0.25)' : 'none'}"
+         layout="row" layout-align="center center">
+        <span class="switch-error">{{ vm.error }}</span>
+    </div>
+    <div id="text-measure"></div>
+</div>