Browse Source

TB-70: RPC widgets: Implemented knob control widget.

pull/211/head
Igor Kulikov 9 years ago
parent
commit
3f0d947f05
  1. 14
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  2. 7
      ui/src/app/components/widget/widget.controller.js
  3. 13
      ui/src/app/widget/lib/CanvasDigitalGauge.js
  4. 4
      ui/src/app/widget/lib/rpc/index.js
  5. 388
      ui/src/app/widget/lib/rpc/knob.directive.js
  6. 157
      ui/src/app/widget/lib/rpc/knob.scss
  7. 56
      ui/src/app/widget/lib/rpc/knob.svg
  8. 19
      ui/src/app/widget/lib/rpc/knob.tpl.html
  9. 207
      ui/src/app/widget/lib/rpc/shiny-knob.directive.js
  10. 102
      ui/src/app/widget/lib/rpc/shiny-knob.scss

14
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": "<tb-shiny-knob ctx='ctx'></tb-shiny-knob>",
"templateHtml": "<tb-knob ctx='ctx'></tb-knob>",
"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}"
}
}
]

7
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;
}

13
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) {

4
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;

388
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);
}
);
}
}

157
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;
}
}
}

56
ui/src/app/widget/lib/rpc/knob.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

19
ui/src/app/widget/lib/rpc/shiny-knob.tpl.html → ui/src/app/widget/lib/rpc/knob.tpl.html

@ -16,18 +16,31 @@
-->
<div class="tb-shiny-knob" layout="column" ng-class="{'dark': vm.darkTheme}">
<div class="tb-knob" layout="column" ng-class="{'dark': vm.darkTheme}" ng-style="{'pointerEvents': vm.ctx.isEdit ? 'none' : 'all'}">
<div id="knob-container" flex layout="column" layout-align="center center">
<div class="knob">
<div class="value-background" layout="row" layout-align="center center">
<div class="value-container" layout="row" layout-align="center center">
<span class="knob-value">{{ vm.value }}</span>
</div>
<div class="canvas-background"></div>
<canvas id="canvasBar"></canvas>
<div class="top-pointer-container">
<div class="top-pointer"></div>
</div>
<div class="top"></div>
<div class="base"></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="knob-error">{{ vm.error }}</span>
</div>
<div class="title-container" layout="row" layout-align="center center">
<span class="knob-title">{{ vm.title }}</span>
</div>
<div class="minmax-container" layout="row" layout-align="start center">
<span class="minmax-label">min</span>
<span flex></span>
<span class="minmax-label">max</span>
</div>
<div id="text-measure"></div>
</div>
</div>
</div>

207
ui/src/app/widget/lib/rpc/shiny-knob.directive.js

@ -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;
});
}
}

102
ui/src/app/widget/lib/rpc/shiny-knob.scss

@ -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;
}
}
}
Loading…
Cancel
Save