10 changed files with 613 additions and 354 deletions
@ -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); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 15 KiB |
@ -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; |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -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…
Reference in new issue