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 b05a144c0a..ce667afe47 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 @@ -48,9 +48,25 @@ "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 \"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 \"getValueMethod\",\n \"setValueMethod\",\n \"title\",\n \"requestTimeout\"\n ]\n}", + "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}", "dataKeySettingsSchema": "{}\n", - "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\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" + "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}" + } + }, + { + "alias": "switch_control", + "name": "Switch Control", + "descriptor": { + "type": "rpc", + "sizeX": 4, + "sizeY": 2.5, + "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 \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\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 \"showOnOffLabels\",\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\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" } } ] diff --git a/ui/src/app/widget/lib/rpc/index.js b/ui/src/app/widget/lib/rpc/index.js index ba07eee559..3edbdff9aa 100644 --- a/ui/src/app/widget/lib/rpc/index.js +++ b/ui/src/app/widget/lib/rpc/index.js @@ -15,7 +15,9 @@ */ import tbKnob from './knob.directive'; +import tbSwitch from './switch.directive'; export default angular.module('thingsboard.widgets.rpc', [ - tbKnob + tbKnob, + tbSwitch ]).name; diff --git a/ui/src/app/widget/lib/rpc/knob.directive.js b/ui/src/app/widget/lib/rpc/knob.directive.js index d62d0fbba8..f921854a3b 100644 --- a/ui/src/app/widget/lib/rpc/knob.directive.js +++ b/ui/src/app/widget/lib/rpc/knob.directive.js @@ -63,7 +63,7 @@ function KnobController($element, $scope, $document) { knobTitleContainer = knob.find('.title-container'), knobTitle = knob.find('.knob-title'), knobMinmaxContainer = knob.find('.minmax-container'), - minmaxLanel = knob.find('.minmax-label'), + minmaxLabel = knob.find('.minmax-label'), textMeasure = knob.find('#text-measure'), startDeg = -1, currentDeg = 0, @@ -92,8 +92,6 @@ function KnobController($element, $scope, $document) { vm.maxValue = angular.isDefined(vm.ctx.settings.maxValue) ? vm.ctx.settings.maxValue : 100; vm.title = angular.isDefined(vm.ctx.settings.title) ? vm.ctx.settings.title : ''; - vm.darkTheme = vm.ctx.settings.theme == 'dark'; - var canvasBarData = { renderTo: canvasBarElement[0], hideValue: true, @@ -267,7 +265,7 @@ function KnobController($element, $scope, $document) { setFontSize(knobTitle, vm.title, knobTitleContainer.height(), knobTitleContainer.width()); setFontSize(knobError, vm.error, knobErrorContainer.height(), knobErrorContainer.width()); var minmaxHeight = knobMinmaxContainer.height(); - minmaxLanel.css({'fontSize': minmaxHeight+'px', 'lineHeight': minmaxHeight+'px'}); + minmaxLabel.css({'fontSize': minmaxHeight+'px', 'lineHeight': minmaxHeight+'px'}); checkValueSize(); } diff --git a/ui/src/app/widget/lib/rpc/knob.scss b/ui/src/app/widget/lib/rpc/knob.scss index 3e57f663c0..afd9077876 100644 --- a/ui/src/app/widget/lib/rpc/knob.scss +++ b/ui/src/app/widget/lib/rpc/knob.scss @@ -14,7 +14,7 @@ * limitations under the License. */ -$knob-img: url('./knob.svg'); +$knob-img: url('./svg/knob.svg'); $bars-margin-pct: percentage(0.033); $background-margin-pct: percentage(0.05); @@ -33,9 +33,6 @@ $background-color: #e6e7e8; width:100%; height:100%; background: $background-color; - &.dark { - background: #000; - } .knob { position: relative; diff --git a/ui/src/app/widget/lib/rpc/knob.tpl.html b/ui/src/app/widget/lib/rpc/knob.tpl.html index 93276b44ed..ffd670fb3c 100644 --- a/ui/src/app/widget/lib/rpc/knob.tpl.html +++ b/ui/src/app/widget/lib/rpc/knob.tpl.html @@ -16,7 +16,7 @@ --> -
+
diff --git a/ui/src/app/widget/lib/rpc/knob.svg b/ui/src/app/widget/lib/rpc/svg/knob.svg similarity index 100% rename from ui/src/app/widget/lib/rpc/knob.svg rename to ui/src/app/widget/lib/rpc/svg/knob.svg diff --git a/ui/src/app/widget/lib/rpc/svg/thumb-bar-checked.svg b/ui/src/app/widget/lib/rpc/svg/thumb-bar-checked.svg new file mode 100644 index 0000000000..57fa2b423e --- /dev/null +++ b/ui/src/app/widget/lib/rpc/svg/thumb-bar-checked.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/src/app/widget/lib/rpc/svg/thumb-bar.svg b/ui/src/app/widget/lib/rpc/svg/thumb-bar.svg new file mode 100644 index 0000000000..0b952933a4 --- /dev/null +++ b/ui/src/app/widget/lib/rpc/svg/thumb-bar.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/src/app/widget/lib/rpc/svg/thumb-checked.svg b/ui/src/app/widget/lib/rpc/svg/thumb-checked.svg new file mode 100644 index 0000000000..6d59b94736 --- /dev/null +++ b/ui/src/app/widget/lib/rpc/svg/thumb-checked.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/src/app/widget/lib/rpc/svg/thumb.svg b/ui/src/app/widget/lib/rpc/svg/thumb.svg new file mode 100644 index 0000000000..03e9ea0f77 --- /dev/null +++ b/ui/src/app/widget/lib/rpc/svg/thumb.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/src/app/widget/lib/rpc/switch.directive.js b/ui/src/app/widget/lib/rpc/switch.directive.js new file mode 100644 index 0000000000..b84b2f1c36 --- /dev/null +++ b/ui/src/app/widget/lib/rpc/switch.directive.js @@ -0,0 +1,214 @@ +/* + * 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 './switch.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import switchTemplate from './switch.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +export default angular.module('thingsboard.widgets.rpc.switch', []) + .directive('tbSwitch', Switch) + .name; + +/*@ngInject*/ +function Switch() { + return { + restrict: "E", + scope: true, + bindToController: { + ctx: '=' + }, + controller: SwitchController, + controllerAs: 'vm', + templateUrl: switchTemplate + }; +} + +/*@ngInject*/ +function SwitchController($element, $scope) { + let vm = this; + + vm.showTitle = false; + vm.value = false; + vm.error = ''; + + var switchElement = angular.element('.switch', $element), + switchContainer = angular.element('#switch-container', $element), + mdSwitch = angular.element('md-switch', switchElement), + onoffContainer = angular.element('.onoff-container', $element), + onLabel = angular.element('.on-label', $element), + offLabel = angular.element('.off-label', $element), + switchTitleContainer = angular.element('.title-container', $element), + switchTitle = angular.element('.switch-title', $element), + textMeasure = angular.element('#text-measure', $element), + switchErrorContainer = angular.element('.error-container', $element), + switchError = angular.element('.switch-error', $element); + + + vm.onValue = onValue; + + $scope.$watch('vm.ctx', () => { + if (vm.ctx) { + init(); + } + }); + + 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(); + } + } + } + + const switchAspectRation = 2.7893; + + function resize() { + var width = switchContainer.width(); + var height; + if (vm.showOnOffLabels) { + height = switchContainer.height()*2/3; + } else { + height = switchContainer.height(); + } + var ratio = width/height; + if (ratio > switchAspectRation) { + width = height*switchAspectRation; + } else { + height = width/switchAspectRation; + } + switchElement.css({width: width, height: height}); + mdSwitch.css('height', height+'px'); + mdSwitch.css('width', width+'px'); + mdSwitch.css('min-width', width+'px'); + angular.element('.md-container', mdSwitch).css('height', height+'px'); + angular.element('.md-container', mdSwitch).css('width', width+'px'); + + + if (vm.showTitle) { + setFontSize(switchTitle, vm.title, switchTitleContainer.height() * 2 / 3, switchTitleContainer.width()); + } + + if (vm.showOnOffLabels) { + onoffContainer.css({width: width, height: switchContainer.height() / 3}); + setFontSize(onLabel, 'OFF', onoffContainer.height(), onoffContainer.width() / 2); + setFontSize(offLabel, 'OFF', onoffContainer.height(), onoffContainer.width() / 2); + } + + setFontSize(switchError, vm.error, switchErrorContainer.height(), switchErrorContainer.width()); + } + + function setValue(value) { + vm.value = value ? true : false; + } + + 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/switch.scss b/ui/src/app/widget/lib/rpc/switch.scss new file mode 100644 index 0000000000..9d696328e4 --- /dev/null +++ b/ui/src/app/widget/lib/rpc/switch.scss @@ -0,0 +1,127 @@ +/** + * 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. + */ + +$thumb-img: url('./svg/thumb.svg'); +$thumb-checked-img: url('./svg/thumb-checked.svg'); +$thumb-bar-img: url('./svg/thumb-bar.svg'); +$thumb-bar-checked-img: url('./svg/thumb-bar-checked.svg'); + +$background-color: #e6e7e8; + +$error-height: 14px; + +.tb-switch { + width:100%; + height:100%; + background: $background-color; + + .error-container { + position:absolute; + top: 1%; + left: 0; + right: 0; + z-index:4; + height: $error-height; + .switch-error { + color: #ff3315; + white-space: nowrap; + } + } + + .onoff-container { + height: 100%; + color: #757575; + font-weight: 500; + white-space: nowrap; + .off-label { + color: #b7b5b5; + } + .on-label { + color: #ff7e57; + text-shadow: #ff6e4a 1px 1px 10px, #ffd1c3 1px 1px 10px; + } + } + .title-container { + .switch-title { + color: #757575; + font-weight: 500; + white-space: nowrap; + } + } + + #switch-container { + padding-left: 10px; + padding-right: 10px; + } + .switch { + position: relative; + md-switch { + margin: 0; + position:absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + .md-container { + margin: 0; + } + .md-bar { + left: 0; + width: 100%; + top: 0; + height: 100%; + border-radius: 0; + background:$thumb-bar-img no-repeat; + background-size: contain; + } + .md-thumb-container { + left: 0.25%; + width: 50%; + top: 5%; + height: 90%; + } + .md-thumb { + top: 0; + left: 0; + height: 100%; + width: 100%; + background:$thumb-img no-repeat; + background-size: contain; + border-radius: 0; + box-shadow: none; + } + + &.md-checked { + .md-bar { + background:$thumb-bar-checked-img no-repeat; + background-size: contain; + } + .md-thumb { + background:$thumb-checked-img no-repeat; + background-size: contain; + } + } + + } + } + #text-measure { + position: absolute; + visibility: hidden; + height: auto; + width: auto; + white-space: nowrap; + } +} diff --git a/ui/src/app/widget/lib/rpc/switch.tpl.html b/ui/src/app/widget/lib/rpc/switch.tpl.html new file mode 100644 index 0000000000..387b1596ac --- /dev/null +++ b/ui/src/app/widget/lib/rpc/switch.tpl.html @@ -0,0 +1,40 @@ + + +
+
+ {{ vm.error }} +
+
+ {{vm.title}} +
+
+
+ + +
+
+ OFF + + ON + +
+
+
+