From 3f0d947f05d2eb756303b3afbf766761093ced79 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 20 Jul 2017 15:10:16 +0300 Subject: [PATCH] TB-70: RPC widgets: Implemented knob control widget. --- .../widget_bundles/control_widgets.json | 14 +- .../components/widget/widget.controller.js | 7 +- ui/src/app/widget/lib/CanvasDigitalGauge.js | 13 +- ui/src/app/widget/lib/rpc/index.js | 4 +- ui/src/app/widget/lib/rpc/knob.directive.js | 388 ++++++++++++++++++ ui/src/app/widget/lib/rpc/knob.scss | 157 +++++++ ui/src/app/widget/lib/rpc/knob.svg | 56 +-- .../{shiny-knob.tpl.html => knob.tpl.html} | 19 +- .../widget/lib/rpc/shiny-knob.directive.js | 207 ---------- ui/src/app/widget/lib/rpc/shiny-knob.scss | 102 ----- 10 files changed, 613 insertions(+), 354 deletions(-) create mode 100644 ui/src/app/widget/lib/rpc/knob.directive.js create mode 100644 ui/src/app/widget/lib/rpc/knob.scss rename ui/src/app/widget/lib/rpc/{shiny-knob.tpl.html => knob.tpl.html} (51%) delete mode 100644 ui/src/app/widget/lib/rpc/shiny-knob.directive.js delete mode 100644 ui/src/app/widget/lib/rpc/shiny-knob.scss 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 a1cd3c9be9..b05a144c0a 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 @@ -38,19 +38,19 @@ } }, { - "alias": "shiny_knob_control", - "name": "Shiny Knob Control", + "alias": "knob_control", + "name": "Knob Control", "descriptor": { "type": "rpc", - "sizeX": 4, - "sizeY": 3.5, + "sizeX": 5, + "sizeY": 4.5, "resources": [], - "templateHtml": "", + "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 \"theme\": {\n \"title\": \"Knob theme\",\n \"type\": \"string\",\n \"default\": \"light\"\n }, \n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n {\n \"key\": \"theme\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"light\",\n \"label\": \"Light\"\n },\n {\n \"value\": \"dark\",\n \"label\": \"Dark\"\n }\n ]\n },\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 \"getValueMethod\",\n \"setValueMethod\",\n \"title\",\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,\"theme\":\"light\",\"initialValue\":50},\"title\":\"Shiny Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + "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}" } } ] diff --git a/ui/src/app/components/widget/widget.controller.js b/ui/src/app/components/widget/widget.controller.js index 1efbf7f8ea..f9ff7a6f5f 100644 --- a/ui/src/app/components/widget/widget.controller.js +++ b/ui/src/app/components/widget/widget.controller.js @@ -811,14 +811,17 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele return (val - parseFloat( val ) + 1) >= 0; } - function formatValue(value, dec, units) { + function formatValue(value, dec, units, showZeroDecimals) { if (angular.isDefined(value) && value !== null && isNumeric(value)) { var formatted = Number(value); if (angular.isDefined(dec)) { formatted = formatted.toFixed(dec); } - formatted = (formatted * 1).toString(); + if (!showZeroDecimals) { + formatted = (formatted * 1); + } + formatted = formatted.toString(); if (angular.isDefined(units) && units.length > 0) { formatted += ' ' + units; } diff --git a/ui/src/app/widget/lib/CanvasDigitalGauge.js b/ui/src/app/widget/lib/CanvasDigitalGauge.js index 1a40235191..08a54d818a 100644 --- a/ui/src/app/widget/lib/CanvasDigitalGauge.js +++ b/ui/src/app/widget/lib/CanvasDigitalGauge.js @@ -99,6 +99,9 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { if (!options.donutStartAngle) { options.donutStartAngle = 1.5 * Math.PI; } + if (!options.donutEndAngle) { + options.donutEndAngle = options.donutStartAngle + 2 * Math.PI; + } } var colorsCount = options.levelColors.length; @@ -494,7 +497,7 @@ function drawBackground(context, options) { context.lineCap = 'round'; } if (options.gaugeType === 'donut') { - context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, options.donutStartAngle, options.donutStartAngle + 2 * Math.PI); + context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, options.donutStartAngle, options.donutEndAngle); context.stroke(); } else if (options.gaugeType === 'arc') { context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, Math.PI, 2*Math.PI); @@ -626,7 +629,7 @@ function getProgressColor(progress, colorsRange) { } } -function drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, color, progress, isDonut, donutStartAngle) { +function drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, color, progress, isDonut, donutStartAngle, donutEndAngle) { context.setLineDash([]); var strokeWidth = Ro - Ri; var blur = 0.55; @@ -644,7 +647,7 @@ function drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, color, progress, isDonut, donu context.beginPath(); var e = 0.01 * Math.PI; if (isDonut) { - context.arc(Cx, Cy, Rm, donutStartAngle - e, donutStartAngle + 2 * Math.PI * progress + e); + context.arc(Cx, Cy, Rm, donutStartAngle - e, donutStartAngle + (donutEndAngle - donutStartAngle) * progress + e); } else { context.arc(Cx, Cy, Rm, Math.PI - e, Math.PI + Math.PI * progress + e); } @@ -703,10 +706,10 @@ function drawProgress(context, options, progress) { context.strokeStyle = neonColor; } context.beginPath(); - context.arc(Cx, Cy, Rm, options.donutStartAngle, options.donutStartAngle + 2 * Math.PI * progress); + context.arc(Cx, Cy, Rm, options.donutStartAngle, options.donutStartAngle + (options.donutEndAngle - options.donutStartAngle) * progress); context.stroke(); if (options.neonGlowBrightness && !options.isMobile) { - drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, true, options.donutStartAngle); + drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, true, options.donutStartAngle, options.donutEndAngle); } } else if (options.gaugeType === 'arc') { if (options.neonGlowBrightness) { diff --git a/ui/src/app/widget/lib/rpc/index.js b/ui/src/app/widget/lib/rpc/index.js index 9f2798974b..ba07eee559 100644 --- a/ui/src/app/widget/lib/rpc/index.js +++ b/ui/src/app/widget/lib/rpc/index.js @@ -14,8 +14,8 @@ * limitations under the License. */ -import tbShinyKnob from './shiny-knob.directive'; +import tbKnob from './knob.directive'; export default angular.module('thingsboard.widgets.rpc', [ - tbShinyKnob + tbKnob ]).name; diff --git a/ui/src/app/widget/lib/rpc/knob.directive.js b/ui/src/app/widget/lib/rpc/knob.directive.js new file mode 100644 index 0000000000..d62d0fbba8 --- /dev/null +++ b/ui/src/app/widget/lib/rpc/knob.directive.js @@ -0,0 +1,388 @@ +/* + * 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 './knob.scss'; + +import CanvasDigitalGauge from './../CanvasDigitalGauge'; +import tinycolor from 'tinycolor2'; + +import { isNumber } from '../widget-utils'; + +/* eslint-disable import/no-unresolved, import/default */ + +import knobTemplate from './knob.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +export default angular.module('thingsboard.widgets.rpc.knob', []) + .directive('tbKnob', Knob) + .name; + +/*@ngInject*/ +function Knob() { + return { + restrict: "E", + scope: true, + bindToController: { + ctx: '=' + }, + controller: KnobController, + controllerAs: 'vm', + templateUrl: knobTemplate + }; +} + +/*@ngInject*/ +function KnobController($element, $scope, $document) { + let vm = this; + + vm.value = 0; + vm.error = ''; + + var knob = angular.element('.knob', $element), + knobContainer = angular.element('#knob-container', $element), + knobTopPointerContainer = knob.find('.top-pointer-container'), + knobTopPointer = knob.find('.top-pointer'), + knobValueContainer = knob.find('.value-container'), + knobValue = knob.find('.knob-value'), + knobErrorContainer = knob.find('.error-container'), + knobError = knob.find('.knob-error'), + knobTitleContainer = knob.find('.title-container'), + knobTitle = knob.find('.knob-title'), + knobMinmaxContainer = knob.find('.minmax-container'), + minmaxLanel = knob.find('.minmax-label'), + textMeasure = knob.find('#text-measure'), + startDeg = -1, + currentDeg = 0, + rotation = 0, + lastDeg = 0, + moving = false; + + var minDeg = -45; + var maxDeg = 225; + + var canvasBarElement = angular.element('#canvasBar', $element); + + var levelColors = ['#19ff4b', '#ffff19', '#ff3232']; + + var canvasBar; + + $scope.$watch('vm.ctx', () => { + if (vm.ctx) { + init(); + } + }); + + function init() { + + vm.minValue = angular.isDefined(vm.ctx.settings.minValue) ? vm.ctx.settings.minValue : 0; + 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, + neonGlowBrightness: 0, + gaugeWidthScale: 0.4, + gaugeColor: 'rgba(0, 0, 0, 0)', + levelColors: levelColors, + minValue: vm.minValue, + maxValue: vm.maxValue, + gaugeType: 'donut', + dashThickness: 2, + donutStartAngle: 3/4*Math.PI, + donutEndAngle: 9/4*Math.PI, + animation: false + }; + + canvasBar = new CanvasDigitalGauge(canvasBarData).draw(); + + knob.on('click', (e) => { + if (moving) { + moving = false; + return false; + } + e.preventDefault(); + + var offset = knob.offset(); + var center = { + y : offset.top + knob.height()/2, + x: offset.left + knob.width()/2 + }; + var a, b, deg, + rad2deg = 180/Math.PI; + + e = (e.originalEvent.touches) ? e.originalEvent.touches[0] : e; + + a = center.y - e.pageY; + b = center.x - e.pageX; + deg = Math.atan2(a,b)*rad2deg; + if(deg < 0){ + deg = 360 + deg; + } + if (deg > maxDeg) { + if (deg - 360 > minDeg) { + deg = deg - 360; + } else { + return false; + } + } + currentDeg = deg; + lastDeg = deg; + knobTopPointerContainer.css('transform','rotate('+(currentDeg)+'deg)'); + turn(degreeToRatio(currentDeg)); + rotation = currentDeg; + startDeg = -1; + }); + + knob.on('mousedown touchstart', (e) => { + e.preventDefault(); + var offset = knob.offset(); + var center = { + y : offset.top + knob.height()/2, + x: offset.left + knob.width()/2 + }; + + var a, b, deg, tmp, + rad2deg = 180/Math.PI; + + knob.on('mousemove.rem touchmove.rem', (e) => { + moving = true; + e = (e.originalEvent.touches) ? e.originalEvent.touches[0] : e; + + a = center.y - e.pageY; + b = center.x - e.pageX; + deg = Math.atan2(a,b)*rad2deg; + if(deg < 0){ + deg = 360 + deg; + } + + if(startDeg == -1){ + startDeg = deg; + } + + tmp = Math.floor((deg-startDeg) + rotation); + + if(tmp < 0){ + tmp = 360 + tmp; + } + else if(tmp > 359){ + tmp = tmp % 360; + } + + if (tmp > maxDeg) { + if (tmp - 360 > minDeg) { + tmp = tmp - 360; + } else { + var deltaMax = Math.abs(maxDeg - lastDeg); + var deltaMin = Math.abs(minDeg - lastDeg); + if (deltaMax < deltaMin) { + tmp = maxDeg; + } else { + tmp = minDeg; + } + } + } + if(Math.abs(tmp - lastDeg) > 180){ + startDeg = deg; + rotation = currentDeg; + return false; + } + + currentDeg = tmp; + lastDeg = tmp; + + knobTopPointerContainer.css('transform','rotate('+(currentDeg)+'deg)'); + turn(degreeToRatio(currentDeg)); + }); + + $document.on('mouseup.rem touchend.rem',() => { + knob.off('.rem'); + $document.off('.rem'); + rotation = currentDeg; + startDeg = -1; + }); + + }); + vm.ctx.resize = resize; + resize(); + var initialValue = angular.isDefined(vm.ctx.settings.initialValue) ? vm.ctx.settings.initialValue : vm.minValue; + 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 ratioToDegree(ratio) { + return minDeg + ratio*(maxDeg-minDeg); + } + + function degreeToRatio(degree) { + return (degree-minDeg)/(maxDeg-minDeg); + } + + function resize() { + var width = knobContainer.width(); + var height = knobContainer.height(); + var size = Math.min(width, height); + knob.css({width: size, height: size}); + canvasBar.update({width: size, height: size}); + 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'}); + checkValueSize(); + } + + function turn(ratio) { + var value = (vm.minValue + (vm.maxValue - vm.minValue)*ratio).toFixed(vm.ctx.decimals); + if (canvasBar.value != value) { + canvasBar.value = value; + } + updateColor(canvasBar.getValueColor()); + onValue(value); + } + + function setValue(value) { + var ratio = (value-vm.minValue) / (vm.maxValue - vm.minValue); + rotation = lastDeg = currentDeg = ratioToDegree(ratio); + knobTopPointerContainer.css('transform','rotate('+(currentDeg)+'deg)'); + if (canvasBar.value != value) { + canvasBar.value = value; + } + updateColor(canvasBar.getValueColor()); + vm.value = formatValue(value); + checkValueSize(); + } + + function updateColor(color) { + var glowColor = tinycolor(color).brighten(30).toHexString(); + knobValue.css({'color': glowColor}); + var textShadow = `${color} 1px 1px 10px, ${glowColor} 1px 1px 10px`; + knobValue.css({'textShadow': textShadow}); + knobTopPointer.css({'backgroundColor': glowColor}); + var boxShadow = `inset 1px 0 2px #040404, 1px 1px 8px 2px ${glowColor}`; + knobTopPointer.css({'boxShadow': boxShadow}); + } + + function onValue(value) { + $scope.$applyAsync(() => { + vm.value = formatValue(value); + checkValueSize(); + rpcUpdateValue(value); + }); + } + + function onError(error) { + $scope.$applyAsync(() => { + vm.error = error; + setFontSize(knobError, vm.error, knobErrorContainer.height(), knobErrorContainer.width()); + }); + } + + function formatValue(value) { + return vm.ctx.utils.formatValue(value, vm.ctx.decimals, vm.ctx.units, true); + } + + function checkValueSize() { + var fontSize = knobValueContainer.height()/3.3; + var containerWidth = knobValueContainer.width(); + setFontSize(knobValue, vm.value, fontSize, containerWidth); + } + + 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) => { + if (isNumber(responseBody)) { + var numValue = Number(responseBody).toFixed(vm.ctx.decimals); + setValue(numValue); + } else { + var errorText = `Unable to parse response: ${responseBody}`; + onError(errorText); + } + }, + () => { + 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/knob.scss b/ui/src/app/widget/lib/rpc/knob.scss new file mode 100644 index 0000000000..3e57f663c0 --- /dev/null +++ b/ui/src/app/widget/lib/rpc/knob.scss @@ -0,0 +1,157 @@ +/** + * 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. + */ + +$knob-img: url('./knob.svg'); + +$bars-margin-pct: percentage(0.033); +$background-margin-pct: percentage(0.05); +$value-container-margin-pct: percentage(0.35); +$error-height: percentage(0.05); +$title-height: percentage(0.066); +$title-container-margin-pct: percentage(0.2); +$title-container-margin-bottom-pct: percentage(0.05); +$minmax-height: percentage(0.04); +$minmax-container-margin-pct: percentage(0.18); +$minmax-container-margin-bottom-pct: percentage(0.12); + +$background-color: #e6e7e8; + +.tb-knob { + width:100%; + height:100%; + background: $background-color; + &.dark { + background: #000; + } + + .knob { + position: relative; + &[draggable] { + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; + } + #canvasBar { + position:absolute; + top:0; + left:0; + bottom: 0; + right: 0; + z-index: 2; + } + .canvas-background { + position:absolute; + top: $background-margin-pct; + left: $background-margin-pct; + bottom: $background-margin-pct; + right: $background-margin-pct; + border-radius: 50%; + background: #3f4346; + z-index:2; + } + .value-container { + position:absolute; + top: $value-container-margin-pct; + left: $value-container-margin-pct; + bottom: $value-container-margin-pct; + right: $value-container-margin-pct; + z-index:4; + .knob-value { + color: #fff; + font-weight: 500; + white-space: nowrap; + } + } + .error-container { + position:absolute; + top: 1%; + left: 0; + right: 0; + z-index:4; + height: $error-height; + .knob-error { + color: #ff3315; + white-space: nowrap; + } + } + .title-container { + position:absolute; + left: $title-container-margin-pct; + bottom: $title-container-margin-bottom-pct; + right: $title-container-margin-pct; + z-index:4; + height: $title-height; + .knob-title { + color: #757575; + font-weight: 500; + white-space: nowrap; + } + } + .minmax-container { + position:absolute; + left: $minmax-container-margin-pct; + bottom: $minmax-container-margin-bottom-pct; + right: $minmax-container-margin-pct; + z-index:4; + height: $minmax-height; + .minmax-label { + color: #757575; + font-weight: 500; + white-space: nowrap; + } + } + .top-pointer-container { + position:absolute; + top: $bars-margin-pct; + left: $bars-margin-pct; + bottom: $bars-margin-pct; + right: $bars-margin-pct; + z-index:3; + cursor:pointer !important; + .top-pointer { + content:''; + width:5%; + height:5%; + background-color:#b5b5b5; + position:absolute; + top:50%; + left:22%; + margin-top:-2.5%; + border-radius: 50%; + cursor:pointer !important; + box-shadow: 1px 0 2px #040404; + } + } + .top{ + position:absolute; + top: $bars-margin-pct; + left: $bars-margin-pct; + bottom: $bars-margin-pct; + right: $bars-margin-pct; + background:$knob-img no-repeat; + background-size: contain; + z-index:2; + cursor:pointer !important; + } + #text-measure { + position: absolute; + visibility: hidden; + height: auto; + width: auto; + white-space: nowrap; + } + } +} diff --git a/ui/src/app/widget/lib/rpc/knob.svg b/ui/src/app/widget/lib/rpc/knob.svg index d75c2b131e..81fe9949b7 100644 --- a/ui/src/app/widget/lib/rpc/knob.svg +++ b/ui/src/app/widget/lib/rpc/knob.svg @@ -1,32 +1,36 @@ - - - - - - - - + + + + + - - - + + + - + + + + + + + + + + - - - - image/svg+xml - - - - - - - - - - + + + + + + + + + + + + diff --git a/ui/src/app/widget/lib/rpc/shiny-knob.tpl.html b/ui/src/app/widget/lib/rpc/knob.tpl.html similarity index 51% rename from ui/src/app/widget/lib/rpc/shiny-knob.tpl.html rename to ui/src/app/widget/lib/rpc/knob.tpl.html index be48067684..93276b44ed 100644 --- a/ui/src/app/widget/lib/rpc/shiny-knob.tpl.html +++ b/ui/src/app/widget/lib/rpc/knob.tpl.html @@ -16,18 +16,31 @@ --> -
+
-
+
{{ vm.value }}
+
-
+
+ {{ vm.error }} +
+
+ {{ vm.title }} +
+
+ min + + max +
+
\ No newline at end of file diff --git a/ui/src/app/widget/lib/rpc/shiny-knob.directive.js b/ui/src/app/widget/lib/rpc/shiny-knob.directive.js deleted file mode 100644 index e79f03b2d9..0000000000 --- a/ui/src/app/widget/lib/rpc/shiny-knob.directive.js +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 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 './shiny-knob.scss'; - -import CanvasDigitalGauge from './../CanvasDigitalGauge'; - -/* eslint-disable import/no-unresolved, import/default */ - -import shinyKnobTemplate from './shiny-knob.tpl.html'; - -/* eslint-enable import/no-unresolved, import/default */ - -export default angular.module('thingsboard.widgets.rpc.shinyKnob', []) - .directive('tbShinyKnob', ShinyKnob) - .name; - -/*@ngInject*/ -function ShinyKnob() { - return { - restrict: "E", - scope: true, - bindToController: { - ctx: '=' - }, - controller: ShinyKnobController, - controllerAs: 'vm', - templateUrl: shinyKnobTemplate - }; -} - -/*@ngInject*/ -function ShinyKnobController($element, $scope, $document) { - let vm = this; - - vm.value = 0; - - var snap = 0; - - var knob = angular.element('.knob', $element), - knobContainer = angular.element('#knob-container', $element), - // knobTop = knob.find('.top'), - knobTopPointerContainer = knob.find('.top-pointer-container'), - knobTopPointer = knob.find('.top-pointer'), - knobValueBackground = knob.find('.value-background'), - knobValue = knob.find('.knob-value'), - startDeg = -1, - currentDeg = 0, - rotation = 0, - lastDeg = 0; - - var canvasBarElement = angular.element('#canvasBar', $element); - - var levelColors = ['rgb(0, 128, 0)', 'rgb(251, 192, 45)', 'rgb(255, 0, 0)'];//['cyan'];// - var canvasBar; - - $scope.$watch('vm.ctx', () => { - if (vm.ctx) { - init(); - } - }); - - function init() { - - vm.minValue = angular.isDefined(vm.ctx.settings.minValue) ? vm.ctx.settings.minValue : 0; - vm.maxValue = angular.isDefined(vm.ctx.settings.maxValue) ? vm.ctx.settings.maxValue : 100; - - vm.darkTheme = vm.ctx.settings.theme == 'dark'; - - var canvasBarData = { - renderTo: canvasBarElement[0], - hideValue: true, - neonGlowBrightness: 40,//40,//vm.darkTheme ? 40 : 0, - gaugeWidthScale: 0.2, - gaugeColor: vm.darkTheme ? 'rgb(23, 26, 28)' : 'rgba(0, 0, 0, 0.75)', - levelColors: levelColors, - minValue: vm.minValue, - maxValue: vm.maxValue, - gaugeType: 'donut', - dashThickness: 0,//1.5, - donutStartAngle: Math.PI, - animation: false, - animationDuration: 250, - animationRule: 'linear' - }; - - canvasBar = new CanvasDigitalGauge(canvasBarData).draw(); - - knob.on('mousedown touchstart', (e) => { - e.preventDefault(); - var offset = knob.offset(); - var center = { - y : offset.top + knob.height()/2, - x: offset.left + knob.width()/2 - }; - - var a, b, deg, tmp, - rad2deg = 180/Math.PI; - - knob.on('mousemove.rem touchmove.rem', (e) => { - - e = (e.originalEvent.touches) ? e.originalEvent.touches[0] : e; - - a = center.y - e.pageY; - b = center.x - e.pageX; - deg = Math.atan2(a,b)*rad2deg; - - if(deg<0){ - deg = 360 + deg; - } - - if(startDeg == -1){ - startDeg = deg; - } - - tmp = Math.floor((deg-startDeg) + rotation); - - if(tmp < 0){ - tmp = 360 + tmp; - } - else if(tmp > 359){ - tmp = tmp % 360; - } - - if(snap && tmp < snap){ - tmp = 0; - } - if(Math.abs(tmp - lastDeg) > 180){ - return false; - } - currentDeg = tmp; - lastDeg = tmp; - - knobTopPointerContainer.css('transform','rotate('+(currentDeg)+'deg)'); - turn(currentDeg/359); - }); - - $document.on('mouseup.rem touchend.rem',() => { - knob.off('.rem'); - $document.off('.rem'); - rotation = currentDeg; - startDeg = -1; - }); - - }); - - vm.ctx.resize = resize; - resize(); - - var initialValue = angular.isDefined(vm.ctx.settings.initialValue) ? vm.ctx.settings.initialValue : vm.minValue; - - setValue(initialValue); - } - - function resize() { - var width = knobContainer.width(); - var height = knobContainer.height(); - var size = Math.min(width, height); - knob.css({width: size, height: size}); - canvasBar.update({width: size, height: size}); - var valHeight = knobValueBackground.height()/3.3; - knobValue.css({'fontSize': valHeight+'px', 'lineHeight': valHeight+'px'}); - } - - function turn(ratio) { - var value = (vm.minValue + (vm.maxValue - vm.minValue)*ratio).toFixed(2); - if (canvasBar.value != value) { - canvasBar.value = value; - } - knobTopPointer.css({'backgroundColor': canvasBar.getValueColor()}); - knobValue.css({'color': 'cyan'});//canvasBar.getValueColor()}); - onValue(value); - } - - function setValue(value) { - var ratio = (value-vm.minValue) / (vm.maxValue - vm.minValue); - rotation = lastDeg = currentDeg = ratio*360; - knobTopPointerContainer.css('transform','rotate('+(currentDeg)+'deg)'); - if (canvasBar.value != value) { - canvasBar.value = value; - } - knobTopPointer.css({'backgroundColor': canvasBar.getValueColor()}); - knobValue.css({'color': 'cyan'});//canvasBar.getValueColor()}); - vm.value = value; - } - - function onValue(value) { - console.log(`onValue ${value}`); //eslint-disable-line - $scope.$applyAsync(() => { - vm.value = value; - }); - } - -} \ No newline at end of file diff --git a/ui/src/app/widget/lib/rpc/shiny-knob.scss b/ui/src/app/widget/lib/rpc/shiny-knob.scss deleted file mode 100644 index a636ec54ea..0000000000 --- a/ui/src/app/widget/lib/rpc/shiny-knob.scss +++ /dev/null @@ -1,102 +0,0 @@ -/** - * 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. - */ - -$knob-img: url('./knob.svg'); - -$bars-margin-pct: percentage(0.08); -$value-background-margin-pct: percentage(0.30); -$shadow-size: 0; -$shadow-size-px: $shadow-size+px; -$shadow-offset-px: $shadow-size/2+px; - -.tb-shiny-knob { - width:100%; - height:100%; - &.dark { - background: #000; - } - - .knob { - position: relative; - &[draggable] { - -moz-user-select: none; - -webkit-user-select: none; - user-select: none; - } - #canvasBar { - position:absolute; - top:0; - left:0; - bottom: 0; - right: 0; - } - .value-background { - position:absolute; - top: $value-background-margin-pct; - left: $value-background-margin-pct; - bottom: $value-background-margin-pct; - right: $value-background-margin-pct; - border-radius: 50%; - background: #000; - color: #fff; - font-weight: 500; - z-index:4; - } - .top-pointer-container { - position:absolute; - top: calc(#{$bars-margin-pct} - #{$shadow-offset-px}); - left: $bars-margin-pct; - bottom: calc(#{$bars-margin-pct} + #{$shadow-offset-px}); - right: $bars-margin-pct; - z-index:3; - cursor:default !important; - .top-pointer { - content:''; - width:5%; - height:5%; - background-color:#b5b5b5; - position:absolute; - top:50%; - left:5%; - margin-top:-2.5%; - border-radius: 50%; - cursor:default !important; - box-shadow: 1px 0 2px #040404; - } - } - .top{ - position:absolute; - top: calc(#{$bars-margin-pct} - #{$shadow-offset-px}); - left: $bars-margin-pct; - bottom: calc(#{$bars-margin-pct} + #{$shadow-offset-px}); - right: $bars-margin-pct; - background:$knob-img no-repeat; - background-size: contain; - z-index:2; - cursor:default !important; - } - .base{ - top: calc(#{$bars-margin-pct} - #{$shadow-offset-px}); - left: $bars-margin-pct; - bottom: calc(#{$bars-margin-pct} + #{$shadow-offset-px}); - right: $bars-margin-pct; - border-radius:50%; - box-shadow:0 $shadow-size-px 0 #4a5056,$shadow-size-px $shadow-size-px $shadow-size-px #000; - position:absolute; - z-index:1; - } - } -}