From ce48c4ac6a7666e5956fb44b0f5a3db0341f20d9 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 25 Jul 2017 14:03:28 +0300 Subject: [PATCH] TB-70: RPC widgets: Add round switch control widget. --- .../widget_bundles/control_widgets.json | 16 ++ ui/src/app/widget/lib/rpc/index.js | 4 +- .../widget/lib/rpc/round-switch.directive.js | 199 ++++++++++++++++++ ui/src/app/widget/lib/rpc/round-switch.scss | 195 +++++++++++++++++ .../app/widget/lib/rpc/round-switch.tpl.html | 38 ++++ 5 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 ui/src/app/widget/lib/rpc/round-switch.directive.js create mode 100644 ui/src/app/widget/lib/rpc/round-switch.scss create mode 100644 ui/src/app/widget/lib/rpc/round-switch.tpl.html 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 ce667afe47..70f8e9cf6a 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": "", + "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 3edbdff9aa..0536fac58f 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 0000000000..e930b7077f --- /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 0000000000..fa2399e939 --- /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 0000000000..ad8c1a6029 --- /dev/null +++ b/ui/src/app/widget/lib/rpc/round-switch.tpl.html @@ -0,0 +1,38 @@ + +
+
+ {{vm.title}} +
+
+
+ +
+ +
+
+
+
+ {{ vm.error }} +
+
+