thingsboard-aplcache
Changes
ui/src/app/api/widget.service.js 4(+3 -1)
ui/src/app/widget/lib/CanvasDigitalGauge.js 16(+11 -5)
ui/src/app/widget/lib/rpc/index.js 21(+21 -0)
ui/src/app/widget/lib/rpc/knob.png 0(+0 -0)
ui/src/app/widget/lib/rpc/shiny-knob.directive.js 198(+198 -0)
ui/src/app/widget/lib/rpc/shiny-knob.scss 80(+80 -0)
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 ff920b1..b06c979 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
@@ -36,6 +36,22 @@
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC remote shell\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
+ },
+ {
+ "alias": "shiny_knob_control",
+ "name": "Shiny Knob Control",
+ "descriptor": {
+ "type": "rpc",
+ "sizeX": 2.5,
+ "sizeY": 3,
+ "resources": [],
+ "templateHtml": "<tb-shiny-knob ctx='ctx'></tb-shiny-knob>",
+ "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel md-switch {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel md-switch > div.md-container {\n margin: 0;\n}\n\n.switch-panel.col-0 md-switch {\n padding-left: 8px;\n padding-right: 4px;\n}\n\n.switch-panel.col-1 md-switch {\n padding-left: 4px;\n padding-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}",
+ "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}",
+ "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\":{}}"
+ }
}
]
}
\ No newline at end of file
ui/src/app/api/widget.service.js 4(+3 -1)
diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js
index 8580b80..842b976 100644
--- a/ui/src/app/api/widget.service.js
+++ b/ui/src/app/api/widget.service.js
@@ -22,6 +22,8 @@ import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-wid
import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget';
import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget';
+import thingsboardRpcWidgets from '../widget/lib/rpc';
+
import TbFlot from '../widget/lib/flot-widget';
import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge';
import TbAnalogueRadialGauge from '../widget/lib/analogue-radial-gauge';
@@ -39,7 +41,7 @@ import thingsboardTypes from '../common/types.constant';
import thingsboardUtils from '../common/utils.service';
export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget,
- thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardTypes, thingsboardUtils])
+ thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils])
.factory('widgetService', WidgetService)
.name;
ui/src/app/widget/lib/CanvasDigitalGauge.js 16(+11 -5)
diff --git a/ui/src/app/widget/lib/CanvasDigitalGauge.js b/ui/src/app/widget/lib/CanvasDigitalGauge.js
index e51c558..e6c2e27 100644
--- a/ui/src/app/widget/lib/CanvasDigitalGauge.js
+++ b/ui/src/app/widget/lib/CanvasDigitalGauge.js
@@ -95,6 +95,12 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge {
options.value = options.minValue;
}
+ if (options.gaugeType === 'donut') {
+ if (!options.donutStartAngle) {
+ options.donutStartAngle = 1.5 * Math.PI;
+ }
+ }
+
var colorsCount = options.levelColors.length;
var inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1;
options.colorsRange = [];
@@ -473,7 +479,7 @@ function drawBackground(context, options) {
context.lineCap = 'round';
}
if (options.gaugeType === 'donut') {
- context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, 1.5 * Math.PI, 3.5 * Math.PI);
+ context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, options.donutStartAngle, options.donutStartAngle + 2 * Math.PI);
context.stroke();
} else if (options.gaugeType === 'arc') {
context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, Math.PI, 2*Math.PI);
@@ -605,7 +611,7 @@ function getProgressColor(progress, colorsRange) {
}
}
-function drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, color, progress, isDonut) {
+function drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, color, progress, isDonut, donutStartAngle) {
context.setLineDash([]);
var strokeWidth = Ro - Ri;
var blur = 0.55;
@@ -623,7 +629,7 @@ function drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, color, progress, isDonut) {
context.beginPath();
var e = 0.01 * Math.PI;
if (isDonut) {
- context.arc(Cx, Cy, Rm, 1.5 * Math.PI - e, 1.5 * Math.PI + 2 * Math.PI * progress + e);
+ context.arc(Cx, Cy, Rm, donutStartAngle - e, donutStartAngle + 2 * Math.PI * progress + e);
} else {
context.arc(Cx, Cy, Rm, Math.PI - e, Math.PI + Math.PI * progress + e);
}
@@ -682,10 +688,10 @@ function drawProgress(context, options, progress) {
context.strokeStyle = neonColor;
}
context.beginPath();
- context.arc(Cx, Cy, Rm, 1.5 * Math.PI, 1.5 * Math.PI + 2 * Math.PI * progress);
+ context.arc(Cx, Cy, Rm, options.donutStartAngle, options.donutStartAngle + 2 * Math.PI * progress);
context.stroke();
if (options.neonGlowBrightness && !options.isMobile) {
- drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, true);
+ drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, true, options.donutStartAngle);
}
} else if (options.gaugeType === 'arc') {
if (options.neonGlowBrightness) {
ui/src/app/widget/lib/rpc/index.js 21(+21 -0)
diff --git a/ui/src/app/widget/lib/rpc/index.js b/ui/src/app/widget/lib/rpc/index.js
new file mode 100644
index 0000000..9f27989
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/index.js
@@ -0,0 +1,21 @@
+/*
+ * 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 tbShinyKnob from './shiny-knob.directive';
+
+export default angular.module('thingsboard.widgets.rpc', [
+ tbShinyKnob
+]).name;
ui/src/app/widget/lib/rpc/knob.png 0(+0 -0)
diff --git a/ui/src/app/widget/lib/rpc/knob.png b/ui/src/app/widget/lib/rpc/knob.png
new file mode 100644
index 0000000..24675b2
Binary files /dev/null and b/ui/src/app/widget/lib/rpc/knob.png differ
ui/src/app/widget/lib/rpc/shiny-knob.directive.js 198(+198 -0)
diff --git a/ui/src/app/widget/lib/rpc/shiny-knob.directive.js b/ui/src/app/widget/lib/rpc/shiny-knob.directive.js
new file mode 100644
index 0000000..b186799
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/shiny-knob.directive.js
@@ -0,0 +1,198 @@
+/*
+ * 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';
+//import tinycolor from 'tinycolor2';
+
+/* 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'),
+ 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(244, 67, 54)'];
+ 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: vm.darkTheme ? 40 : 0,
+ gaugeWidthScale: 0.5,
+ gaugeColor: vm.darkTheme ? 'rgb(23, 26, 28)' : 'rgba(0,0,0,0)',
+ levelColors: levelColors,
+ minValue: vm.minValue,
+ maxValue: vm.maxValue,
+ gaugeType: 'donut',
+ dashThickness: 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;
+
+ knobTop.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});
+ }
+
+ function turn(ratio) {
+ var value = (vm.minValue + (vm.maxValue - vm.minValue)*ratio).toFixed(2);
+ if (canvasBar.value != value) {
+ canvasBar.value = value;
+ }
+ onValue(value);
+ }
+
+ function setValue(value) {
+ var ratio = (value-vm.minValue) / (vm.maxValue - vm.minValue);
+ rotation = lastDeg = currentDeg = ratio*360;
+ knobTop.css('transform','rotate('+(currentDeg)+'deg)');
+ if (canvasBar.value != value) {
+ canvasBar.value = value;
+ }
+ vm.value = value;
+ }
+
+ function onValue(value) {
+ console.log(`onValue ${value}`); //eslint-disable-line
+ $scope.$applyAsync(() => {
+ vm.value = value;
+ });
+ }
+
+}
\ No newline at end of file
ui/src/app/widget/lib/rpc/shiny-knob.scss 80(+80 -0)
diff --git a/ui/src/app/widget/lib/rpc/shiny-knob.scss b/ui/src/app/widget/lib/rpc/shiny-knob.scss
new file mode 100644
index 0000000..7ff204c
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/shiny-knob.scss
@@ -0,0 +1,80 @@
+/**
+ * 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.png');
+
+$bars-margin-pct: percentage(0.2);
+$shadow-size: 5;
+$shadow-size-px: $shadow-size+px;
+$shadow-offset-px: $shadow-size/2+px;
+
+.tb-shiny-knob {
+ width:100%;
+ height:100%;
+ &.dark {
+ background: #000;//$dark-bg-img #1f2129;
+ }
+
+ .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;
+ }
+ .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:10;
+ cursor:default !important;
+ &:after {
+ content:'';
+ width:10px;
+ height:10px;
+ background-color:#666;
+ position:absolute;
+ top:50%;
+ left:10px;
+ margin-top:-5px;
+ border-radius: 50%;
+ cursor:default !important;
+ box-shadow: 0 0 1px #5a5a5a inset;
+ }
+ }
+ .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;
+ }
+ }
+}
diff --git a/ui/src/app/widget/lib/rpc/shiny-knob.tpl.html b/ui/src/app/widget/lib/rpc/shiny-knob.tpl.html
new file mode 100644
index 0000000..18094b3
--- /dev/null
+++ b/ui/src/app/widget/lib/rpc/shiny-knob.tpl.html
@@ -0,0 +1,30 @@
+<!--
+
+ 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-shiny-knob" layout="column" ng-class="{'dark': vm.darkTheme}">
+ <div layout="row" layout-align="center start" class="md-padding">
+ <span>{{ vm.value }}</span>
+ </div>
+ <div id="knob-container" flex layout="column" layout-align="center center">
+ <div class="knob">
+ <canvas id="canvasBar"></canvas>
+ <div class="top"></div>
+ <div class="base"></div>
+ </div>
+ </div>
+</div>
\ No newline at end of file