Browse Source

Merge pull request #211 from thingsboard/feature/TB-70

TB-70: RPC widgets - knob control
pull/214/head
Igor Kulikov 9 years ago
committed by GitHub
parent
commit
e924070084
  1. 16
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  2. 4
      ui/src/app/api/widget.service.js
  3. 7
      ui/src/app/components/widget/widget.controller.js
  4. 38
      ui/src/app/widget/lib/CanvasDigitalGauge.js
  5. 21
      ui/src/app/widget/lib/rpc/index.js
  6. 388
      ui/src/app/widget/lib/rpc/knob.directive.js
  7. 157
      ui/src/app/widget/lib/rpc/knob.scss
  8. 36
      ui/src/app/widget/lib/rpc/knob.svg
  9. 46
      ui/src/app/widget/lib/rpc/knob.tpl.html

16
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": "knob_control",
"name": "Knob Control",
"descriptor": {
"type": "rpc",
"sizeX": 5,
"sizeY": 4.5,
"resources": [],
"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 \"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,\"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}"
}
}
]
}

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

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

38
ui/src/app/widget/lib/CanvasDigitalGauge.js

@ -95,6 +95,15 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge {
options.value = options.minValue;
}
if (options.gaugeType === 'donut') {
if (!options.donutStartAngle) {
options.donutStartAngle = 1.5 * Math.PI;
}
if (!options.donutEndAngle) {
options.donutEndAngle = options.donutStartAngle + 2 * Math.PI;
}
}
var colorsCount = options.levelColors.length;
var inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1;
options.colorsRange = [];
@ -232,6 +241,21 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge {
return this;
}
getValueColor() {
if (this.contextProgressClone) {
var color = this.contextProgressClone.currentColor;
if (!color) {
if (this.options.neonGlowBrightness) {
color = getProgressColor(0, this.options.neonColorsRange);
} else {
color = getProgressColor(0, this.options.colorsRange);
}
}
return color;
} else {
return '#000';
}
}
}
/* eslint-disable angular/document-service */
@ -473,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, 1.5 * Math.PI, 3.5 * 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);
@ -605,7 +629,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, donutEndAngle) {
context.setLineDash([]);
var strokeWidth = Ro - Ri;
var blur = 0.55;
@ -623,7 +647,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 + (donutEndAngle - donutStartAngle) * progress + e);
} else {
context.arc(Cx, Cy, Rm, Math.PI - e, Math.PI + Math.PI * progress + e);
}
@ -660,9 +684,9 @@ function drawBarGlow(context, startX, startY, endX, endY, color, strokeWidth, is
function drawProgress(context, options, progress) {
var neonColor;
if (options.neonGlowBrightness) {
neonColor = getProgressColor(progress, options.neonColorsRange);
context.currentColor = neonColor = getProgressColor(progress, options.neonColorsRange);
} else {
context.strokeStyle = getProgressColor(progress, options.colorsRange);
context.currentColor = context.strokeStyle = getProgressColor(progress, options.colorsRange);
}
let {barLeft, barRight, barTop, baseX, width, barBottom, Cx, Cy, Rm, Ro, Ri, strokeWidth} =
@ -682,10 +706,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 + (options.donutEndAngle - options.donutStartAngle) * 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, options.donutEndAngle);
}
} else if (options.gaugeType === 'arc') {
if (options.neonGlowBrightness) {

21
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 tbKnob from './knob.directive';
export default angular.module('thingsboard.widgets.rpc', [
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;
}
}
}

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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

46
ui/src/app/widget/lib/rpc/knob.tpl.html

@ -0,0 +1,46 @@
<!--
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-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-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="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>
Loading…
Cancel
Save