Browse Source

UI: Improve widgets engine. Use HTML5 canvas to render 'Digital gauges'. Minor layout fixes.

pull/68/head
Igor Kulikov 9 years ago
parent
commit
c73f7d58fa
  1. 85
      dao/src/main/resources/system-data.cql
  2. 8
      ui/package.json
  3. 172
      ui/src/app/api/widget.service.js
  4. 19
      ui/src/app/app.config.js
  5. 5
      ui/src/app/app.js
  6. 50
      ui/src/app/common/raf.provider.js
  7. 20
      ui/src/app/common/utils.service.js
  8. 184
      ui/src/app/components/dashboard.directive.js
  9. 3
      ui/src/app/components/dashboard.scss
  10. 6
      ui/src/app/components/dashboard.tpl.html
  11. 2
      ui/src/app/components/grid.directive.js
  12. 6
      ui/src/app/components/grid.scss
  13. 4
      ui/src/app/components/no-animate.directive.js
  14. 2
      ui/src/app/components/react/styles/thingsboardTheme.js
  15. 3
      ui/src/app/components/timewindow.directive.js
  16. 2
      ui/src/app/components/timewindow.tpl.html
  17. 7
      ui/src/app/components/widget-config.directive.js
  18. 539
      ui/src/app/components/widget.controller.js
  19. 66
      ui/src/app/components/widget.directive.js
  20. 26
      ui/src/app/components/widget.scss
  21. 6
      ui/src/app/dashboard/add-widget.controller.js
  22. 2
      ui/src/app/dashboard/dashboard-settings.controller.js
  23. 10
      ui/src/app/dashboard/dashboard-settings.tpl.html
  24. 45
      ui/src/app/dashboard/dashboard.controller.js
  25. 18
      ui/src/app/dashboard/dashboard.tpl.html
  26. 4
      ui/src/app/dashboard/edit-widget.directive.js
  27. 2
      ui/src/app/device/device-credentials.tpl.html
  28. 4
      ui/src/app/home/home-links.tpl.html
  29. 52
      ui/src/app/layout/home.controller.js
  30. 14
      ui/src/app/layout/home.tpl.html
  31. 701
      ui/src/app/locale/locale.constant.js
  32. 731
      ui/src/app/widget/lib/CanvasDigitalGauge.js
  33. 895
      ui/src/app/widget/lib/analogue-linear-gauge.js
  34. 851
      ui/src/app/widget/lib/analogue-radial-gauge.js
  35. 921
      ui/src/app/widget/lib/canvas-digital-gauge.js
  36. 591
      ui/src/app/widget/lib/digital-gauge.js
  37. 472
      ui/src/app/widget/lib/flot-widget.js
  38. 12
      ui/src/app/widget/lib/google-map.js
  39. 99
      ui/src/app/widget/lib/map-widget.js
  40. 8
      ui/src/app/widget/lib/openstreet-map.js
  41. 21
      ui/src/app/widget/widget-library.controller.js
  42. 4
      ui/src/app/widget/widget-library.tpl.html
  43. BIN
      ui/src/font/Segment7Standard.otf
  44. 2
      ui/src/index.html
  45. 684
      ui/src/locale/en_US.json
  46. 21
      ui/src/scss/fonts.scss
  47. 15
      ui/src/scss/main.scss
  48. 2
      ui/webpack.config.dev.js
  49. 6
      ui/webpack.config.prod.js

85
dao/src/main/resources/system-data.cql

File diff suppressed because one or more lines are too long

8
ui/package.json

@ -48,11 +48,12 @@
"canvas-gauges": "^2.0.9",
"clipboard": "^1.5.15",
"compass-sass-mixins": "^0.12.7",
"flot": "git://github.com/flot/flot.git#0.9-work",
"font-awesome": "^4.6.3",
"javascript-detect-element-resize": "^0.5.3",
"jquery": "^3.1.0",
"js-beautify": "^1.6.4",
"json-schema-defaults": "^0.2.0",
"justgage": "^1.2.2",
"leaflet": "^1.0.3",
"material-ui": "^0.16.1",
"material-ui-number-input": "^5.0.16",
@ -63,7 +64,6 @@
"ngreact": "^0.3.0",
"objectpath": "^1.2.1",
"oclazyload": "^1.0.9",
"raphael": "^2.2.7",
"rc-select": "^6.6.1",
"react": "^15.4.1",
"react-ace": "^4.1.0",
@ -76,6 +76,7 @@
"schema-inspector": "^1.6.6",
"split.js": "^1.0.7",
"tinycolor2": "^1.4.1",
"typeface-roboto": "0.0.22",
"v-accordion": "^1.6.0"
},
"devDependencies": {
@ -113,7 +114,8 @@
"webpack": "^1.13.2",
"webpack-dev-middleware": "^1.6.1",
"webpack-dev-server": "^1.15.1",
"webpack-hot-middleware": "^2.12.2"
"webpack-hot-middleware": "^2.12.2",
"webpack-material-design-icons": "^0.1.0"
},
"engine": "node >= 5.9.0",
"nyc": {

172
ui/src/app/api/widget.service.js

@ -19,9 +19,10 @@ import tinycolor from 'tinycolor2';
import thinsboardLedLight from '../components/led-light.directive';
import TbFlot from '../widget/lib/flot-widget';
import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge';
import TbAnalogueRadialGauge from '../widget/lib/analogue-radial-gauge';
import TbDigitalGauge from '../widget/lib/digital-gauge';
import TbCanvasDigitalGauge from '../widget/lib/canvas-digital-gauge';
import TbMapWidget from '../widget/lib/map-widget';
import 'oclazyload';
@ -38,13 +39,15 @@ export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thinsboa
function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, types, utils) {
$window.$ = $;
$window.jQuery = $;
$window.moment = moment;
$window.tinycolor = tinycolor;
$window.lazyLoad = $ocLazyLoad;
$window.TbFlot = TbFlot;
$window.TbAnalogueLinearGauge = TbAnalogueLinearGauge;
$window.TbAnalogueRadialGauge = TbAnalogueRadialGauge;
$window.TbDigitalGauge = TbDigitalGauge;
$window.TbCanvasDigitalGauge = TbCanvasDigitalGauge;
$window.TbMapWidget = TbMapWidget;
$window.cssjs = cssjs;
@ -57,6 +60,8 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
var editingWidgetType;
var widgetsInfoInMemoryCache = {};
var widgetsTypeFunctionsInMemoryCache = {};
var widgetsInfoFetchQueue = {};
var allWidgetsBundles = undefined;
var systemWidgetsBundles = undefined;
@ -83,6 +88,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
deleteWidgetsBundle: deleteWidgetsBundle,
getBundleWidgetTypes: getBundleWidgetTypes,
getWidgetInfo: getWidgetInfo,
getWidgetTypeFunction: getWidgetTypeFunction,
getInstantWidgetInfo: getInstantWidgetInfo,
deleteWidgetType: deleteWidgetType,
saveWidgetType: saveWidgetType,
@ -228,19 +234,34 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
/** Cache functions **/
function createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem) {
return (isSystem ? 'sys_' : '') + bundleAlias + '_' + widgetTypeAlias;
}
function getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem) {
var key = (isSystem ? 'sys_' : '') + bundleAlias + '_' + widgetTypeAlias;
var key = createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
return widgetsInfoInMemoryCache[key];
}
function getWidgetTypeFunctionFromCache(bundleAlias, widgetTypeAlias, isSystem) {
var key = createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
return widgetsTypeFunctionsInMemoryCache[key];
}
function putWidgetInfoToCache(widgetInfo, bundleAlias, widgetTypeAlias, isSystem) {
var key = (isSystem ? 'sys_' : '') + bundleAlias + '_' + widgetTypeAlias;
var key = createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
widgetsInfoInMemoryCache[key] = widgetInfo;
}
function putWidgetTypeFunctionToCache(widgetTypeFunction, bundleAlias, widgetTypeAlias, isSystem) {
var key = createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
widgetsTypeFunctionsInMemoryCache[key] = widgetTypeFunction;
}
function deleteWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem) {
var key = (isSystem ? 'sys_' : '') + bundleAlias + '_' + widgetTypeAlias;
var key = createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
delete widgetsInfoInMemoryCache[key];
delete widgetsTypeFunctionsInMemoryCache[key];
}
function deleteWidgetsBundleFromCache(bundleAlias, isSystem) {
@ -248,6 +269,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
for (var cacheKey in widgetsInfoInMemoryCache) {
if (cacheKey.startsWith(key)) {
delete widgetsInfoInMemoryCache[cacheKey];
delete widgetsTypeFunctionsInMemoryCache[cacheKey];
}
}
}
@ -456,6 +478,16 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
}
}
function resolveWidgetsInfoFetchQueue(key, widgetInfo) {
var fetchQueue = widgetsInfoFetchQueue[key];
if (fetchQueue) {
for (var q in fetchQueue) {
fetchQueue[q].resolve(widgetInfo);
}
delete widgetsInfoFetchQueue[key];
}
}
function getWidgetInfo(bundleAlias, widgetTypeAlias, isSystem) {
var deferred = $q.defer();
var widgetInfo = getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);
@ -465,32 +497,130 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
if ($rootScope.widgetEditMode) {
loadWidget(editingWidgetType, bundleAlias, isSystem, deferred);
} else {
getWidgetType(bundleAlias, widgetTypeAlias, isSystem).then(
function success(widgetType) {
loadWidget(widgetType, bundleAlias, isSystem, deferred);
}, function fail() {
deferred.resolve(missingWidgetType);
}
);
var key = createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
var fetchQueue = widgetsInfoFetchQueue[key];
if (fetchQueue) {
fetchQueue.push(deferred);
} else {
fetchQueue = [];
widgetsInfoFetchQueue[key] = fetchQueue;
getWidgetType(bundleAlias, widgetTypeAlias, isSystem).then(
function success(widgetType) {
loadWidget(widgetType, bundleAlias, isSystem, deferred);
}, function fail() {
deferred.resolve(missingWidgetType);
resolveWidgetsInfoFetchQueue(key, missingWidgetType);
}
);
}
}
}
return deferred.promise;
}
function getWidgetTypeFunction(bundleAlias, widgetTypeAlias, isSystem) {
var widgetType = getWidgetTypeFunctionFromCache(bundleAlias, widgetTypeAlias, isSystem);
return widgetType;
}
function createWidgetTypeFunction(widgetInfo, name) {
var widgetTypeFunctionBody = 'return function '+ name +' (ctx) {\n' +
' var self = this;\n' +
' self.ctx = ctx;\n\n'; /*+
' self.onInit = function() {\n\n' +
' }\n\n' +
' self.onDataUpdated = function() {\n\n' +
' }\n\n' +
' self.onResize = function() {\n\n' +
' }\n\n' +
' self.onEditModeChanged = function() {\n\n' +
' }\n\n' +
' self.onMobileModeChanged = function() {\n\n' +
' }\n\n' +
' self.getSettingsSchema = function() {\n\n' +
' }\n\n' +
' self.getDataKeySettingsSchema = function() {\n\n' +
' }\n\n' +
' self.onDestroy = function() {\n\n' +
' }\n\n' +
'}';*/
widgetTypeFunctionBody += widgetInfo.controllerScript;
widgetTypeFunctionBody += '\n};\n';
try {
var widgetTypeFunction = new Function(widgetTypeFunctionBody);
var widgetType = widgetTypeFunction.apply(this);
var widgetTypeInstance = new widgetType();
var result = {
widgetTypeFunction: widgetType
};
if (angular.isFunction(widgetTypeInstance.getSettingsSchema)) {
result.settingsSchema = widgetTypeInstance.getSettingsSchema();
}
if (angular.isFunction(widgetTypeInstance.getDataKeySettingsSchema)) {
result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema();
}
return result;
} catch (e) {
utils.processWidgetException(e);
throw e;
}
}
function processWidgetLoadError(errorMessages, cacheKey, deferred) {
var widgetInfo = angular.copy(errorWidgetType);
for (var e in errorMessages) {
var error = errorMessages[e];
widgetInfo.templateHtml += '<div class="tb-widget-error-msg">' + error + '</div>';
}
widgetInfo.templateHtml += '</div>';
deferred.resolve(widgetInfo);
resolveWidgetsInfoFetchQueue(cacheKey, widgetInfo);
}
function loadWidget(widgetType, bundleAlias, isSystem, deferred) {
var widgetInfo = toWidgetInfo(widgetType);
var key = createWidgetInfoCacheKey(bundleAlias, widgetInfo.alias, isSystem);
loadWidgetResources(widgetInfo, bundleAlias, isSystem).then(
function success() {
putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
deferred.resolve(widgetInfo);
}, function fail(errorMessages) {
widgetInfo = angular.copy(errorWidgetType);
for (var e in errorMessages) {
var error = errorMessages[e];
widgetInfo.templateHtml += '<div class="tb-widget-error-msg">' + error + '</div>';
var widgetType = null;
try {
widgetType = createWidgetTypeFunction(widgetInfo, key);
} catch (e) {
var details = utils.parseException(e);
var errorMessage = 'Failed to compile widget script. \n Error: ' + details.message;
processWidgetLoadError([errorMessage], key, deferred);
}
widgetInfo.templateHtml += '</div>';
deferred.resolve(widgetInfo);
if (widgetType) {
if (widgetType.settingsSchema) {
widgetInfo.typeSettingsSchema = widgetType.settingsSchema;
}
if (widgetType.dataKeySettingsSchema) {
widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema;
}
putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem);
deferred.resolve(widgetInfo);
resolveWidgetsInfoFetchQueue(key, widgetInfo);
}
}, function fail(errorMessages) {
processWidgetLoadError(errorMessages, key, deferred);
}
);
}

19
ui/src/app/app.config.js

@ -33,29 +33,30 @@ export default function AppConfig($provide,
$mdThemingProvider,
$httpProvider,
$translateProvider,
storeProvider) {
storeProvider,
locales) {
injectTapEventPlugin();
$locationProvider.html5Mode(true);
$urlRouterProvider.otherwise('/home');
storeProvider.setCaching(false);
$translateProvider.useStaticFilesLoader({
prefix: 'static/locale/',// path to translations files
suffix: '.json'// suffix, currently- extension of the translations
});
$translateProvider.useSanitizeValueStrategy('sanitize');
$translateProvider.preferredLanguage('en_US');
$translateProvider.useLocalStorage();
$translateProvider.useMissingTranslationHandlerLog();
$translateProvider.addInterpolation('$translateMessageFormatInterpolation');
for (var langKey in locales) {
var translationTable = locales[langKey];
$translateProvider.translations(langKey, translationTable);
}
$httpProvider.interceptors.push('globalInterceptor');
$provide.decorator("$exceptionHandler", ['$delegate', '$injector', function ($delegate, $injector) {
$provide.decorator("$exceptionHandler", ['$delegate', '$injector', function ($delegate/*, $injector*/) {
return function (exception, cause) {
var rootScope = $injector.get("$rootScope");
/* var rootScope = $injector.get("$rootScope");
var $window = $injector.get("$window");
var utils = $injector.get("utils");
if (rootScope.widgetEditMode) {
@ -63,7 +64,7 @@ export default function AppConfig($provide,
var data = utils.parseException(exception);
parentScope.$emit('widgetException', data);
parentScope.$apply();
}
}*/
$delegate(exception, cause);
};
}]);

5
ui/src/app/app.js

@ -44,9 +44,11 @@ import 'react-schema-form';
import react from 'ngreact';
import '@flowjs/ng-flow/dist/ng-flow-standalone.min';
import thingsboardLocales from './locale/locale.constant';
import thingsboardLogin from './login';
import thingsboardDialogs from './components/datakey-config-dialog.controller';
import thingsboardMenu from './services/menu.service';
import thingsboardRaf from './common/raf.provider';
import thingsboardUtils from './common/utils.service';
import thingsboardTypes from './common/types.constant';
import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter';
@ -57,6 +59,7 @@ import thingsboardApiLogin from './api/login.service';
import thingsboardApiDevice from './api/device.service';
import thingsboardApiUser from './api/user.service';
import 'typeface-roboto';
import 'font-awesome/css/font-awesome.min.css';
import 'angular-material/angular-material.min.css';
import 'angular-material-icons/angular-material-icons.css';
@ -91,9 +94,11 @@ angular.module('thingsboard', [
'ngclipboard',
react.name,
'flow',
thingsboardLocales,
thingsboardLogin,
thingsboardDialogs,
thingsboardMenu,
thingsboardRaf,
thingsboardUtils,
thingsboardTypes,
thingsboardKeyboardShortcut,

50
ui/src/app/common/raf.provider.js

@ -0,0 +1,50 @@
/*
* 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.
*/
export default angular.module('thingsboard.raf', [])
.provider('tbRaf', TbRAFProvider)
.name;
function TbRAFProvider() {
/*@ngInject*/
this.$get = function($window, $timeout) {
var requestAnimationFrame = $window.requestAnimationFrame ||
$window.webkitRequestAnimationFrame;
var cancelAnimationFrame = $window.cancelAnimationFrame ||
$window.webkitCancelAnimationFrame ||
$window.webkitCancelRequestAnimationFrame;
var rafSupported = !!requestAnimationFrame;
var raf = rafSupported
? function(fn) {
var id = requestAnimationFrame(fn);
return function() {
cancelAnimationFrame(id);
};
}
: function(fn) {
var timer = $timeout(fn, 16.66, false);
return function() {
$timeout.cancel(timer);
};
};
raf.supported = rafSupported;
return raf;
};
}

20
ui/src/app/common/utils.service.js

@ -22,7 +22,7 @@ export default angular.module('thingsboard.utils', [thingsboardTypes])
.name;
/*@ngInject*/
function Utils($mdColorPalette, types) {
function Utils($mdColorPalette, $rootScope, $window, types) {
var predefinedFunctions = {},
predefinedFunctionsList = [],
@ -102,6 +102,7 @@ function Utils($mdColorPalette, types) {
genMaterialColor: genMaterialColor,
objectHashCode: objectHashCode,
parseException: parseException,
processWidgetException: processWidgetException,
isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
filterSearchTextEntities: filterSearchTextEntities
}
@ -147,7 +148,7 @@ function Utils($mdColorPalette, types) {
return hash;
}
function parseException(exception) {
function parseException(exception, lineOffset) {
var data = {};
if (exception) {
if (angular.isString(exception) || exception instanceof String) {
@ -170,7 +171,10 @@ function Utils($mdColorPalette, types) {
var lineInfoRegexp = /(.*<anonymous>):(\d*)(:)?(\d*)?/g;
var lineInfoGroups = lineInfoRegexp.exec(exception.stack);
if (lineInfoGroups != null && lineInfoGroups.length >= 3) {
data.lineNumber = lineInfoGroups[2] - 2;
if (angular.isUndefined(lineOffset)) {
lineOffset = -2;
}
data.lineNumber = Number(lineInfoGroups[2]) + lineOffset;
if (lineInfoGroups.length >= 5) {
data.columnNumber = lineInfoGroups[4];
}
@ -181,6 +185,16 @@ function Utils($mdColorPalette, types) {
return data;
}
function processWidgetException(exception) {
var parentScope = $window.parent.angular.element($window.frameElement).scope();
var data = parseException(exception, -5);
if ($rootScope.widgetEditMode) {
parentScope.$emit('widgetException', data);
parentScope.$apply();
}
return data;
}
function getDefaultDatasource(dataKeySchema) {
var datasource = angular.copy(defaultDatasource);
if (angular.isDefined(dataKeySchema)) {

184
ui/src/app/components/dashboard.directive.js

@ -79,7 +79,7 @@ function Dashboard() {
}
/*@ngInject*/
function DashboardController($scope, $rootScope, $element, $timeout, $log, toast, types) {
function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $log, toast, types) {
var highlightedMode = false;
var highlightedWidget = null;
@ -87,14 +87,13 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
var mouseDownWidget = -1;
var widgetMouseMoved = false;
var gridsterParent = null;
var gridsterElement = null;
var gridsterParent = $('#gridster-parent', $element);
var gridsterElement = angular.element($('#gridster-child', gridsterParent));
var vm = this;
vm.gridster = null;
vm.isMobileDisabled = angular.isDefined(vm.isMobileDisabled) ? vm.isMobileDisabled : false;
vm.dashboardLoading = true;
@ -105,9 +104,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
right: 0
};
vm.gridsterOpts = {
pushing: false,
floating: false,
swapping: false,
maxRows: 100,
columns: vm.columns ? vm.columns : 24,
margins: vm.margins ? vm.margins : [10, 10],
minSizeX: 2,
minSizeY: 2,
defaultSizeX: 8,
@ -118,22 +120,13 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
draggable: {
enabled: vm.isEdit
},
isMobile: vm.isMobileDisabled ? false : vm.isMobile,
mobileBreakPoint: vm.isMobileDisabled ? 0 : (vm.isMobile ? 20000 : 960),
margins: vm.margins ? vm.margins : [10, 10],
saveGridItemCalculatedHeightInMobile: true
};
updateMobileOpts();
vm.widgetItemMap = {
sizeX: 'widget.sizeX',
sizeY: 'widget.sizeY',
row: 'widget.row',
col: 'widget.col',
minSizeY: 'widget.minSizeY',
maxSizeY: 'widget.maxSizeY'
};
vm.mobileWidgetItemMap = {
sizeX: 'widget.sizeX',
sizeX: 'vm.widgetSizeX(widget)',
sizeY: 'vm.widgetSizeY(widget)',
row: 'widget.row',
col: 'widget.col',
@ -141,8 +134,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
maxSizeY: 'widget.maxSizeY'
};
vm.currentWidgetItemMap = vm.gridsterOpts.isMobile ? vm.mobileWidgetItemMap : vm.widgetItemMap;
vm.isWidgetExpanded = false;
vm.isHighlighted = isHighlighted;
vm.isNotHighlighted = isNotHighlighted;
@ -156,6 +147,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
vm.widgetMouseMove = widgetMouseMove;
vm.widgetMouseUp = widgetMouseUp;
vm.widgetSizeX = widgetSizeX;
vm.widgetSizeY = widgetSizeY;
vm.widgetColor = widgetColor;
vm.widgetBackgroundColor = widgetBackgroundColor;
@ -185,9 +177,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
// widgetMouseMove();
// }
gridsterParent = $('#gridster-parent', $element);
gridsterElement = angular.element($('#gridster-child', gridsterParent));
//TODO: widgets visibility
/*gridsterParent.scroll(function () {
updateVisibleRect();
@ -197,34 +186,62 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
updateVisibleRect();
});*/
function updateMobileOpts() {
var isMobileDisabled = vm.isMobileDisabled === true;
var isMobile = vm.isMobile === true && !isMobileDisabled;
var mobileBreakPoint = isMobileDisabled ? 0 : (isMobile ? 20000 : 960);
if (!isMobile && !isMobileDisabled) {
isMobile = !$mdMedia('gt-sm');
}
var rowHeight = isMobile ? 70 : 'match';
if (vm.gridsterOpts.isMobile != isMobile) {
vm.gridsterOpts.isMobile = isMobile;
vm.gridsterOpts.mobileModeEnabled = isMobile;
}
if (vm.gridsterOpts.mobileBreakPoint != mobileBreakPoint) {
vm.gridsterOpts.mobileBreakPoint = mobileBreakPoint;
}
if (vm.gridsterOpts.rowHeight != rowHeight) {
vm.gridsterOpts.rowHeight = rowHeight;
}
}
$scope.$watch(function() { return $mdMedia('gt-sm'); }, function() {
updateMobileOpts();
});
$scope.$watch('vm.isMobile', function () {
vm.gridsterOpts.isMobile = vm.isMobileDisabled ? false : vm.isMobile;
vm.gridsterOpts.mobileBreakPoint = vm.isMobileDisabled ? 0 : (vm.isMobile ? 20000 : 960);
updateMobileOpts();
});
$scope.$watch('vm.isMobileDisabled', function () {
vm.gridsterOpts.isMobile = vm.isMobileDisabled ? false : vm.isMobile;
vm.gridsterOpts.mobileBreakPoint = vm.isMobileDisabled ? 0 : (vm.isMobile ? 20000 : 960);
updateMobileOpts();
});
$scope.$watch('vm.columns', function () {
vm.gridsterOpts.columns = vm.columns ? vm.columns : 24;
if (vm.gridster) {
vm.gridster.columns = vm.columns;
updateGridsterParams();
var columns = vm.columns ? vm.columns : 24;
if (vm.gridsterOpts.columns != columns) {
vm.gridsterOpts.columns = columns;
if (vm.gridster) {
vm.gridster.columns = vm.columns;
updateGridsterParams();
}
//TODO: widgets visibility
//updateVisibleRect();
}
//TODO: widgets visibility
//updateVisibleRect();
});
$scope.$watch('vm.margins', function () {
vm.gridsterOpts.margins = vm.margins ? vm.margins : [10, 10];
if (vm.gridster) {
vm.gridster.margins = vm.margins;
updateGridsterParams();
var margins = vm.margins ? vm.margins : [10, 10];
if (!angular.equals(vm.gridsterOpts.margins, margins)) {
vm.gridsterOpts.margins = margins;
if (vm.gridster) {
vm.gridster.margins = vm.margins;
updateGridsterParams();
}
//TODO: widgets visibility
//updateVisibleRect();
}
//TODO: widgets visibility
//updateVisibleRect();
});
$scope.$watch('vm.isEdit', function () {
@ -248,12 +265,14 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
$scope.$on('gridster-mobile-changed', function (event, theGridster) {
if (checkIsLocalGridsterElement(theGridster)) {
vm.gridster = theGridster;
if (vm.gridster.isMobile) {
vm.gridsterOpts.rowHeight = 70;
} else {
vm.gridsterOpts.rowHeight = 'match';
var rowHeight = vm.gridster.isMobile ? 70 : 'match';
if (vm.gridsterOpts.rowHeight != rowHeight) {
vm.gridsterOpts.rowHeight = rowHeight;
updateGridsterParams();
}
vm.currentWidgetItemMap = vm.gridster.isMobile ? vm.mobileWidgetItemMap : vm.widgetItemMap;
$scope.$broadcast('mobileModeChanged', vm.gridster.isMobile);
//TODO: widgets visibility
/*$timeout(function () {
updateVisibleRect(true);
@ -367,7 +386,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
}*/
function checkIsLocalGridsterElement (gridster) {
return gridsterElement[0] === gridster.$element[0];
return gridsterElement && gridsterElement[0] === gridster.$element[0];
}
function resetWidgetClick () {
@ -440,6 +459,9 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
row: 0,
column: 0
}
if (!gridsterParent) {
return pos;
}
var offset = gridsterParent.offset();
var x = event.pageX - offset.left + gridsterParent.scrollLeft();
var y = event.pageY - offset.top + gridsterParent.scrollTop();
@ -483,35 +505,39 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
function highlightWidget(widget, delay) {
highlightedMode = true;
highlightedWidget = widget;
var item = $('.gridster-item', vm.gridster.$element)[vm.widgets.indexOf(widget)];
if (item) {
var height = $(item).outerHeight(true);
var rectHeight = gridsterParent.height();
var offset = (rectHeight - height) / 2;
var scrollTop = item.offsetTop;
if (offset > 0) {
scrollTop -= offset;
if (vm.gridster) {
var item = $('.gridster-item', vm.gridster.$element)[vm.widgets.indexOf(widget)];
if (item) {
var height = $(item).outerHeight(true);
var rectHeight = gridsterParent.height();
var offset = (rectHeight - height) / 2;
var scrollTop = item.offsetTop;
if (offset > 0) {
scrollTop -= offset;
}
gridsterParent.animate({
scrollTop: scrollTop
}, delay);
}
gridsterParent.animate({
scrollTop: scrollTop
}, delay);
}
}
function selectWidget(widget, delay) {
selectedWidget = widget;
var item = $('.gridster-item', vm.gridster.$element)[vm.widgets.indexOf(widget)];
if (item) {
var height = $(item).outerHeight(true);
var rectHeight = gridsterParent.height();
var offset = (rectHeight - height) / 2;
var scrollTop = item.offsetTop;
if (offset > 0) {
scrollTop -= offset;
if (vm.gridster) {
var item = $('.gridster-item', vm.gridster.$element)[vm.widgets.indexOf(widget)];
if (item) {
var height = $(item).outerHeight(true);
var rectHeight = gridsterParent.height();
var offset = (rectHeight - height) / 2;
var scrollTop = item.offsetTop;
if (offset > 0) {
scrollTop -= offset;
}
gridsterParent.animate({
scrollTop: scrollTop
}, delay);
}
gridsterParent.animate({
scrollTop: scrollTop
}, delay);
}
}
@ -533,9 +559,17 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
return highlightedMode && highlightedWidget != widget;
}
function widgetSizeX(widget) {
return widget.sizeX;
}
function widgetSizeY(widget) {
if (vm.gridster && vm.gridster.isMobile && widget.config.mobileHeight) {
return widget.config.mobileHeight;
if (vm.gridsterOpts.isMobile) {
if (widget.config.mobileHeight) {
return widget.config.mobileHeight;
} else {
return widget.sizeY * 24 / vm.gridsterOpts.columns;
}
} else {
return widget.sizeY;
}
@ -614,11 +648,17 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
}
function dashboardLoaded() {
adoptMaxRows();
vm.dashboardLoading = false;
if (vm.onInit) {
vm.onInit({dashboard: vm});
}
$timeout(function () {
adoptMaxRows();
vm.dashboardLoading = false;
$timeout(function () {
var gridsterScope = gridsterElement.scope();
vm.gridster = gridsterScope.gridster;
if (vm.onInit) {
vm.onInit({dashboard: vm});
}
}, 0, false);
}, 0, false);
}
function loading() {

3
ui/src/app/components/dashboard.scss

@ -93,6 +93,9 @@ md-content.tb-dashboard-content {
right: 0;
bottom: 0;
outline: none;
.gridster-item {
@include transition(none);
}
}
.tb-widget-error-container {

6
ui/src/app/components/dashboard.tpl.html

@ -25,7 +25,7 @@
<div id="gridster-child" gridster="vm.gridsterOpts">
<ul>
<!-- ng-click="widgetClicked($event, widget)" -->
<li gridster-item="vm.currentWidgetItemMap" ng-repeat="widget in vm.widgets">
<li gridster-item="vm.widgetItemMap" ng-repeat="widget in vm.widgets">
<md-menu md-position-mode="target target" tb-mousepoint-menu>
<div tb-expand-fullscreen
fullscreen-background-style="vm.dashboardStyle"
@ -47,7 +47,7 @@
padding: vm.widgetPadding(widget)}">
<div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
<span ng-show="vm.showWidgetTitle(widget)" ng-style="vm.widgetTitleStyle(widget)" class="md-subhead">{{widget.config.title}}</span>
<tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
<tb-timewindow button-color="vm.widgetColor(widget)" ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
</div>
<div class="tb-widget-actions" layout="row" layout-align="start center">
<md-button id="expand-button"
@ -93,7 +93,7 @@
</div>
<div flex layout="column" class="tb-widget-content">
<div flex tb-widget
locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isPreview: vm.isEdit }">
locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isEdit: vm.isEdit }">
</div>
</div>
</div>

2
ui/src/app/components/grid.directive.js

@ -117,7 +117,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
vm.toggleItemSelection = toggleItemSelection;
$scope.$watch(function () {
return $mdMedia('sm');
return $mdMedia('xs') || $mdMedia('sm');
}, function (sm) {
if (sm) {
columnsUpdated(1);

6
ui/src/app/components/grid.scss

@ -21,6 +21,12 @@
.tb-card-item {
@include transition(all .2s ease-in-out);
md-card-content {
max-height: 53px;
}
md-card-title-text {
max-height: 32px;
}
}
.tb-current-item {

4
ui/src/app/components/no-animate.directive.js

@ -24,10 +24,10 @@ function NoAnimate($animate) {
return {
restrict: 'A',
link: function (scope, element) {
$animate.enabled(element, false)
$animate.enabled(element, false);
scope.$watch(function () {
$animate.enabled(element, false)
})
});
}
};
}

2
ui/src/app/components/react/styles/thingsboardTheme.js

@ -58,6 +58,6 @@ var indigoPalette = {
export default {
spacing: spacing,
fontFamily: 'RobotoDraft, Roboto, \'Helvetica Neue\', sans-serif',
fontFamily: 'Roboto, \'Helvetica Neue\', sans-serif',
palette: indigoPalette,
};

3
ui/src/app/components/timewindow.directive.js

@ -203,7 +203,8 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $tra
restrict: "E",
require: "^ngModel",
scope: {
asButton: '=asButton'
asButton: '=asButton',
buttonColor: '=?'
},
link: linker
};

2
ui/src/app/components/timewindow.tpl.html

@ -18,6 +18,6 @@
<section ng-mouseover="onHoverIn()" ng-mouseleave="onHoverOut()" layout='row' layout-align="start center" style="min-height: 32px;">
<span ng-click="openEditMode($event)">{{model.displayValue}}</span>
<md-button class="md-icon-button tb-md-32" aria-label="{{ 'timewindow.edit' | translate }}" ng-show="isHovered" ng-click="openEditMode($event)">
<md-icon aria-label="{{ 'timewindow.date-range' | translate }}" class="material-icons">date_range</md-icon>
<md-icon ng-style="{ color: buttonColor }" aria-label="{{ 'timewindow.date-range' | translate }}" class="material-icons">date_range</md-icon>
</md-button>
</section>

7
ui/src/app/components/widget-config.directive.js

@ -41,7 +41,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar
.name;
/*@ngInject*/
function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, utils) {
var linker = function (scope, element, attrs, ngModelCtrl) {
@ -279,7 +279,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
scope.updateDatasourcesAccordionStatePending = false;
var expand = scope.datasources && scope.datasources.length < 4;
if (expand) {
scope.datasourcesAccordion.expand('datasources-pane');
$timeout(function() {
scope.datasourcesAccordion.expand('datasources-pane');
});
} else {
scope.datasourcesAccordion.collapse('datasources-pane');
}

539
ui/src/app/components/widget.controller.js

@ -14,20 +14,40 @@
* limitations under the License.
*/
import $ from 'jquery';
import 'javascript-detect-element-resize/detect-element-resize';
/* eslint-disable angular/angularelement */
/*@ngInject*/
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, types, visibleRect,
datasourceService, deviceService, isPreview, widget, deviceAliasList, fns) {
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, tbRaf, types, utils,
datasourceService, deviceService, visibleRect, isEdit, widget, deviceAliasList, widgetType) {
var vm = this;
var timeWindow = {};
$scope.$timeout = $timeout;
$scope.$q = $q;
$scope.$injector = $injector;
$scope.tbRaf = tbRaf;
$scope.rpcRejection = null;
$scope.rpcErrorText = null;
$scope.rpcEnabled = false;
$scope.executingRpcRequest = false;
$scope.executingPromises = [];
var gridsterItemInited = false;
var datasourceListeners = [];
var targetDeviceAliasId = null;
var targetDeviceId = null;
var originalTimewindow = null;
var subscriptionTimewindow = {
fixedWindow: null,
realtimeWindowMs: null
};
var timer = null;
var dataUpdateTimer = null;
var dataUpdateCaf = null;
/*
* data = array of datasourceData
@ -39,151 +59,235 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
*
*
*/
var data = [];
var datasourceListeners = [];
var targetDeviceAliasId = null;
var targetDeviceId = null;
var widgetContext = {
inited: false,
$scope: $scope,
$container: $('#container', $element),
$containerParent: $($element),
width: 0,
height: 0,
isEdit: isEdit,
isMobile: false,
settings: widget.config.settings,
datasources: widget.config.datasources,
data: [],
timeWindow: {},
timewindowFunctions: {
onUpdateTimewindow: onUpdateTimewindow,
onResetTimewindow: onResetTimewindow
},
controlApi: {
sendOneWayCommand: function(method, params, timeout) {
return sendCommand(true, method, params, timeout);
},
sendTwoWayCommand: function(method, params, timeout) {
return sendCommand(false, method, params, timeout);
}
}
};
var widgetTypeInstance;
try {
widgetTypeInstance = new widgetType(widgetContext);
} catch (e) {
handleWidgetException(e);
widgetTypeInstance = {};
}
if (!widgetTypeInstance.onInit) {
widgetTypeInstance.onInit = function() {};
}
if (!widgetTypeInstance.onDataUpdated) {
widgetTypeInstance.onDataUpdated = function() {};
}
if (!widgetTypeInstance.onResize) {
widgetTypeInstance.onResize = function() {};
}
if (!widgetTypeInstance.onEditModeChanged) {
widgetTypeInstance.onEditModeChanged = function() {};
}
if (!widgetTypeInstance.onMobileModeChanged) {
widgetTypeInstance.onMobileModeChanged = function() {};
}
if (!widgetTypeInstance.onDestroy) {
widgetTypeInstance.onDestroy = function() {};
}
//var bounds = {top: 0, left: 0, bottom: 0, right: 0};
//TODO: widgets visibility
/*var visible = false;*/
var lastWidth, lastHeight;
var containerParent = $($element);
var container = $('#container', $element);
var containerElement = container[0];
var inited = false;
// var gridsterItemElement;
var gridsterItem;
var timer;
$scope.clearRpcError = function() {
$scope.rpcRejection = null;
$scope.rpcErrorText = null;
}
var init = fns.init || function () {
};
vm.gridsterItemInitialized = gridsterItemInitialized;
var redraw = fns.redraw || function () {
};
//TODO: widgets visibility
/*vm.visibleRectChanged = visibleRectChanged;
var destroy = fns.destroy || function () {
};
function visibleRectChanged(newVisibleRect) {
visibleRect = newVisibleRect;
updateVisibility();
}*/
$scope.$timeout = $timeout;
$scope.$q = $q;
$scope.$injector = $injector;
initialize();
$scope.rpcRejection = null;
$scope.rpcErrorText = null;
$scope.rpcEnabled = false;
$scope.executingRpcRequest = false;
$scope.executingPromises = [];
function handleWidgetException(e) {
$log.error(e);
$scope.widgetErrorData = utils.processWidgetException(e);
}
function sendCommand(oneWayElseTwoWay, method, params, timeout) {
if (!$scope.rpcEnabled) {
return $q.reject();
function onInit() {
if (!widgetContext.inited) {
widgetContext.inited = true;
try {
widgetTypeInstance.onInit();
} catch (e) {
handleWidgetException(e);
}
if (widgetContext.dataUpdatePending) {
widgetContext.dataUpdatePending = false;
onDataUpdated();
}
}
}
if ($scope.rpcRejection && $scope.rpcRejection.status !== 408) {
$scope.rpcRejection = null;
$scope.rpcErrorText = null;
function updateTimewindow() {
if (subscriptionTimewindow.realtimeWindowMs) {
widgetContext.timeWindow.maxTime = (new Date).getTime();
widgetContext.timeWindow.minTime = widgetContext.timeWindow.maxTime - subscriptionTimewindow.realtimeWindowMs;
} else if (subscriptionTimewindow.fixedWindow) {
widgetContext.timeWindow.maxTime = subscriptionTimewindow.fixedWindow.endTimeMs;
widgetContext.timeWindow.minTime = subscriptionTimewindow.fixedWindow.startTimeMs;
}
}
var requestBody = {
method: method,
params: params
};
if (timeout && timeout > 0) {
requestBody.timeout = timeout;
function onDataUpdated() {
if (dataUpdateTimer) {
$timeout.cancel(dataUpdateTimer);
dataUpdateTimer = null;
}
var deferred = $q.defer();
$scope.executingRpcRequest = true;
if ($scope.widgetEditMode) {
$timeout(function() {
$scope.executingRpcRequest = false;
if (oneWayElseTwoWay) {
deferred.resolve();
} else {
deferred.resolve(requestBody);
if (widgetContext.inited) {
if (widget.type === types.widgetType.timeseries.value) {
if (!widgetContext.tickUpdate && timer) {
$timeout.cancel(timer);
timer = $timeout(onTick, 1500, false);
}
}, 500);
} else {
$scope.executingPromises.push(deferred.promise);
var targetSendFunction = oneWayElseTwoWay ? deviceService.sendOneWayRpcCommand : deviceService.sendTwoWayRpcCommand;
targetSendFunction(targetDeviceId, requestBody).then(
function success(responseBody) {
$scope.rpcRejection = null;
$scope.rpcErrorText = null;
var index = $scope.executingPromises.indexOf(deferred.promise);
if (index >= 0) {
$scope.executingPromises.splice( index, 1 );
}
$scope.executingRpcRequest = $scope.executingPromises.length > 0;
deferred.resolve(responseBody);
},
function fail(rejection) {
var index = $scope.executingPromises.indexOf(deferred.promise);
if (index >= 0) {
$scope.executingPromises.splice( index, 1 );
}
$scope.executingRpcRequest = $scope.executingPromises.length > 0;
if (!$scope.executingRpcRequest || rejection.status === 408) {
$scope.rpcRejection = rejection;
if (rejection.status === 408) {
$scope.rpcErrorText = 'Device is offline.';
} else {
$scope.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText;
if (rejection.data && rejection.data.length > 0) {
$scope.rpcErrorText += '</br>';
$scope.rpcErrorText += rejection.data;
}
}
updateTimewindow();
}
if (dataUpdateCaf) {
dataUpdateCaf();
dataUpdateCaf = null;
}
dataUpdateCaf = tbRaf(function() {
try {
widgetTypeInstance.onDataUpdated();
} catch (e) {
handleWidgetException(e);
}
deferred.reject(rejection);
}
);
});
widgetContext.tickUpdate = false;
} else {
widgetContext.dataUpdatePending = true;
}
return deferred.promise;
}
$scope.clearRpcError = function() {
$scope.rpcRejection = null;
$scope.rpcErrorText = null;
function checkSize() {
var width = widgetContext.$containerParent.width();
var height = widgetContext.$containerParent.height();
var sizeChanged = false;
if (!widgetContext.width || widgetContext.width != width || !widgetContext.height || widgetContext.height != height) {
if (width > 0 && height > 0) {
widgetContext.$container.css('height', height + 'px');
widgetContext.$container.css('width', width + 'px');
widgetContext.width = width;
widgetContext.height = height;
sizeChanged = true;
}
}
return sizeChanged;
}
var controlApi = {};
controlApi.sendOneWayCommand = function(method, params, timeout) {
return sendCommand(true, method, params, timeout);
};
function onResize() {
if (checkSize()) {
if (widgetContext.inited) {
tbRaf(function() {
try {
widgetTypeInstance.onResize();
} catch (e) {
handleWidgetException(e);
}
});
} else if (gridsterItemInited) {
onInit();
}
}
}
controlApi.sendTwoWayCommand = function(method, params, timeout) {
return sendCommand(false, method, params, timeout);
};
function gridsterItemInitialized(item) {
if (item && item.gridster) {
widgetContext.isMobile = item.gridster.isMobile;
gridsterItemInited = true;
if (checkSize()) {
onInit();
}
// gridsterItemElement = $(item.$element);
//updateVisibility();
}
}
vm.gridsterItemInitialized = gridsterItemInitialized;
//TODO: widgets visibility
/*vm.visibleRectChanged = visibleRectChanged;
function onEditModeChanged(isEdit) {
if (widgetContext.isEdit != isEdit) {
widgetContext.isEdit = isEdit;
if (widgetContext.inited) {
tbRaf(function() {
try {
widgetTypeInstance.onEditModeChanged();
} catch (e) {
handleWidgetException(e);
}
});
}
}
}
function visibleRectChanged(newVisibleRect) {
visibleRect = newVisibleRect;
updateVisibility();
}*/
function onMobileModeChanged(isMobile) {
if (widgetContext.isMobile != isMobile) {
widgetContext.isMobile = isMobile;
if (widgetContext.inited) {
tbRaf(function() {
try {
widgetTypeInstance.onMobileModeChanged();
} catch (e) {
handleWidgetException(e);
}
});
}
}
}
function gridsterItemInitialized(item) {
if (item) {
gridsterItem = item;
// gridsterItemElement = $(item.$element);
//updateVisibility();
onRedraw();
function onDestroy() {
unsubscribe();
if (widgetContext.inited) {
widgetContext.inited = false;
widgetContext.dataUpdatePending = false;
try {
widgetTypeInstance.onDestroy();
} catch (e) {
handleWidgetException(e);
}
}
}
initWidget();
function onRestart() {
onDestroy();
onInit();
}
function initWidget() {
function initialize() {
if (widget.type !== types.widgetType.rpc.value) {
for (var i in widget.config.datasources) {
var datasource = angular.copy(widget.config.datasources[i]);
@ -194,7 +298,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
dataKey: dataKey,
data: []
};
data.push(datasourceData);
widgetContext.data.push(datasourceData);
}
}
} else {
@ -212,35 +316,29 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
}
$scope.$on('toggleDashboardEditMode', function (event, isEdit) {
isPreview = isEdit;
onRedraw();
});
$scope.$on('gridster-item-resized', function (event, item) {
if (item) {
updateBounds();
}
onEditModeChanged(isEdit);
});
$scope.$on('gridster-item-transition-end', function (event, item) {
if (item) {
updateBounds();
}
});
addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
$scope.$watch(function () {
return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder;
}, function () {
updateBounds();
//updateBounds();
$scope.$emit("widgetPositionChanged", widget);
});
$scope.$on('onWidgetFullscreenChanged', function(event, isWidgetExpanded, fullscreenWidget) {
if (widget === fullscreenWidget) {
onRedraw(0);
$scope.$on('gridster-item-resized', function (event, item) {
if (!widgetContext.isMobile) {
widget.sizeX = item.sizeX;
widget.sizeY = item.sizeY;
}
});
$scope.$on('mobileModeChanged', function (event, newIsMobile) {
onMobileModeChanged(newIsMobile);
});
$scope.$on('deviceAliasListChanged', function (event, newDeviceAliasList) {
deviceAliasList = newDeviceAliasList;
if (widget.type === types.widgetType.rpc.value) {
@ -256,8 +354,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
} else {
$scope.rpcEnabled = $scope.widgetEditMode ? true : false;
}
inited = false;
onRedraw();
onRestart();
}
}
} else {
@ -266,12 +363,10 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
});
$scope.$on("$destroy", function () {
unsubscribe();
destroy();
removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
onDestroy();
});
subscribe();
if (widget.type === types.widgetType.timeseries.value) {
$scope.$watch(function () {
return widget.config.timewindow;
@ -281,12 +376,77 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
subscribe();
}
});
} else if (widget.type === types.widgetType.rpc.value) {
if (!inited) {
init(containerElement, widget.config.settings, widget.config.datasources, data, $scope, controlApi);
inited = true;
}
}
subscribe();
}
function sendCommand(oneWayElseTwoWay, method, params, timeout) {
if (!$scope.rpcEnabled) {
return $q.reject();
}
if ($scope.rpcRejection && $scope.rpcRejection.status !== 408) {
$scope.rpcRejection = null;
$scope.rpcErrorText = null;
}
var requestBody = {
method: method,
params: params
};
if (timeout && timeout > 0) {
requestBody.timeout = timeout;
}
var deferred = $q.defer();
$scope.executingRpcRequest = true;
if ($scope.widgetEditMode) {
$timeout(function() {
$scope.executingRpcRequest = false;
if (oneWayElseTwoWay) {
deferred.resolve();
} else {
deferred.resolve(requestBody);
}
}, 500);
} else {
$scope.executingPromises.push(deferred.promise);
var targetSendFunction = oneWayElseTwoWay ? deviceService.sendOneWayRpcCommand : deviceService.sendTwoWayRpcCommand;
targetSendFunction(targetDeviceId, requestBody).then(
function success(responseBody) {
$scope.rpcRejection = null;
$scope.rpcErrorText = null;
var index = $scope.executingPromises.indexOf(deferred.promise);
if (index >= 0) {
$scope.executingPromises.splice( index, 1 );
}
$scope.executingRpcRequest = $scope.executingPromises.length > 0;
deferred.resolve(responseBody);
},
function fail(rejection) {
var index = $scope.executingPromises.indexOf(deferred.promise);
if (index >= 0) {
$scope.executingPromises.splice( index, 1 );
}
$scope.executingRpcRequest = $scope.executingPromises.length > 0;
if (!$scope.executingRpcRequest || rejection.status === 408) {
$scope.rpcRejection = rejection;
if (rejection.status === 408) {
$scope.rpcErrorText = 'Device is offline.';
} else {
$scope.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText;
if (rejection.data && rejection.data.length > 0) {
$scope.rpcErrorText += '</br>';
$scope.rpcErrorText += rejection.data;
}
}
}
deferred.reject(rejection);
}
);
}
return deferred.promise;
}
//TODO: widgets visibility
@ -316,23 +476,16 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
}
}*/
function updateBounds() {
/*bounds = {
/* function updateBounds() {
bounds = {
top: widget.row,
left: widget.col,
bottom: widget.row + widget.sizeY,
right: widget.col + widget.sizeX
};
updateVisibility(true);*/
updateVisibility(true);
onRedraw();
}
var originalTimewindow;
var timewindowFunctions = {
onUpdateTimewindow: onUpdateTimewindow,
onResetTimewindow: onResetTimewindow
};
}*/
function onResetTimewindow() {
if (originalTimewindow) {
@ -355,55 +508,25 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
};
}
function onRedraw(delay, dataUpdate, tickUpdate) {
//TODO: widgets visibility
/*if (!visible) {
return;
}*/
if (angular.isUndefined(delay)) {
delay = 0;
}
$timeout(function () {
var width = containerParent.width();
var height = containerParent.height();
var sizeChanged = false;
if (!lastWidth || lastWidth != width || !lastHeight || lastHeight != height) {
if (width > 0 && height > 0) {
container.css('height', height + 'px');
container.css('width', width + 'px');
lastWidth = width;
lastHeight = height;
sizeChanged = true;
function dataUpdated(sourceData, datasourceIndex, dataKeyIndex) {
var update = true;
if (widget.type === types.widgetType.latest.value) {
var prevData = widgetContext.data[datasourceIndex + dataKeyIndex].data;
if (prevData && prevData[0] && prevData[0].length > 1 && sourceData.length > 0) {
var prevValue = prevData[0][1];
if (prevValue === sourceData[0][1]) {
update = false;
}
}
if (width > 20 && height > 20) {
if (!inited) {
init(containerElement, widget.config.settings, widget.config.datasources, data, $scope, controlApi, timewindowFunctions, gridsterItem);
inited = true;
}
if (widget.type === types.widgetType.timeseries.value) {
if (dataUpdate && timer) {
$timeout.cancel(timer);
timer = $timeout(onTick, 1500, false);
}
if (subscriptionTimewindow.realtimeWindowMs) {
timeWindow.maxTime = (new Date).getTime();
timeWindow.minTime = timeWindow.maxTime - subscriptionTimewindow.realtimeWindowMs;
} else if (subscriptionTimewindow.fixedWindow) {
timeWindow.maxTime = subscriptionTimewindow.fixedWindow.endTimeMs;
timeWindow.minTime = subscriptionTimewindow.fixedWindow.startTimeMs;
}
}
redraw(containerElement, width, height, data, timeWindow, sizeChanged, $scope, dataUpdate, tickUpdate, gridsterItem);
}
if (update) {
widgetContext.data[datasourceIndex + dataKeyIndex].data = sourceData;
if (widgetContext.data.length > 1 && !dataUpdateTimer) {
dataUpdateTimer = $timeout(onDataUpdated, 100, false);
} else {
onDataUpdated();
}
}, delay, false);
}
function onDataUpdated(sourceData, datasourceIndex, dataKeyIndex) {
data[datasourceIndex + dataKeyIndex].data = sourceData;
onRedraw(0, true);
}
}
function checkSubscriptions() {
@ -438,6 +561,10 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
$timeout.cancel(timer);
timer = null;
}
if (dataUpdateTimer) {
$timeout.cancel(dataUpdateTimer);
dataUpdateTimer = null;
}
for (var i in datasourceListeners) {
var listener = datasourceListeners[i];
datasourceService.unsubscribeFromDatasource(listener);
@ -447,7 +574,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
}
function onTick() {
onRedraw(0, false, true);
widgetContext.tickUpdate = true;
onDataUpdated();
timer = $timeout(onTick, 1000, false);
}
@ -474,6 +602,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
}
}
}
updateTimewindow();
}
for (var i in widget.config.datasources) {
var datasource = widget.config.datasources[i];
@ -492,13 +621,13 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
datasource: datasource,
deviceId: deviceId,
dataUpdated: function (data, datasourceIndex, dataKeyIndex) {
onDataUpdated(data, datasourceIndex, dataKeyIndex);
dataUpdated(data, datasourceIndex, dataKeyIndex);
},
datasourceIndex: index
};
for (var a = 0; a < datasource.dataKeys.length; a++) {
data[index + a].data = [];
widgetContext.data[index + a].data = [];
}
index += datasource.dataKeys.length;

66
ui/src/app/components/widget.directive.js

@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './widget.scss';
import thingsboardTypes from '../common/types.constant';
import thingsboardApiDatasource from '../api/datasource.service';
@ -70,63 +73,18 @@ function Widget($controller, $compile, widgetService) {
+ widget.typeAlias;
elem.addClass(widgetNamespace);
elem.html('<div id="container">' + widgetInfo.templateHtml + '</div>');
elem.html('<div class="tb-absolute-fill tb-widget-error"" ng-if="widgetErrorData">' +
'<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +
'</div>' +
'<div id="container">' + widgetInfo.templateHtml + '</div>');
$compile(elem.contents())(scope);
angular.extend(locals, {$scope: scope, $element: elem});
var controllerFunctionBody = 'var fns = { init: null, redraw: null, destroy: null };';
controllerFunctionBody += widgetInfo.controllerScript;
controllerFunctionBody += '' +
'angular.extend(this, $controller(\'WidgetController\',' +
'{' +
'$scope: $scope,' +
'$timeout: $timeout,' +
'$window: $window,' +
'$element: $element,' +
'$log: $log,' +
'types: types,' +
'visibleRect: visibleRect,' +
'datasourceService: datasourceService,' +
'deviceService: deviceService,' +
'isPreview: isPreview,' +
'widget: widget,' +
'deviceAliasList: deviceAliasList,' +
'fns: fns' +
'}));' +
'';
var controllerFunction = new Function("$scope",
"$timeout",
"$window",
"$element",
"$log",
'types',
"visibleRect",
"datasourceService",
"deviceService",
"$controller",
"isPreview",
"widget",
"deviceAliasList",
controllerFunctionBody);
controllerFunction.$inject = ["$scope",
"$timeout",
"$window",
"$element",
"$log",
'types',
"visibleRect",
"datasourceService",
"deviceService",
"$controller",
"isPreview",
"widget",
"deviceAliasList"];
widgetController = $controller(controllerFunction, locals);
var widgetType = widgetService.getWidgetTypeFunction(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
angular.extend(locals, {$scope: scope, $element: elem, widgetType: widgetType});
widgetController = $controller('WidgetController', locals);
if (gridsterItem) {
widgetController.gridsterItemInitialized(gridsterItem);

26
ui/src/app/components/widget.scss

@ -0,0 +1,26 @@
/**
* 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.
*/
.tb-widget {
.tb-widget-error {
display: flex;
justify-content: center;
align-items: center;
background: rgba(255,255,255,0.5);
span {
color: red;
}
}
}

6
ui/src/app/dashboard/add-widget.controller.js

@ -37,8 +37,10 @@ export default function AddWidgetController($scope, widgetService, deviceService
vm.createDeviceAlias = createDeviceAlias;
vm.widgetConfig = vm.widget.config;
var settingsSchema = vm.widgetInfo.settingsSchema;
var dataKeySettingsSchema = vm.widgetInfo.dataKeySettingsSchema;
var settingsSchema = vm.widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
var dataKeySettingsSchema = vm.widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
if (!settingsSchema || settingsSchema === '') {
vm.settingsSchema = {};
} else {

2
ui/src/app/dashboard/dashboard-settings.controller.js

@ -37,6 +37,8 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti
vm.hMargin = vm.gridSettings.margins[0];
vm.vMargin = vm.gridSettings.margins[1];
vm.gridSettings.backgroundSizeMode = vm.gridSettings.backgroundSizeMode || '100%';
function cancel() {
$mdDialog.cancel();
}

10
ui/src/app/dashboard/dashboard-settings.tpl.html

@ -106,6 +106,16 @@
</div>
</div>
</div>
<md-input-container class="md-block">
<label translate>dashboard.background-size-mode</label>
<md-select ng-model="vm.gridSettings.backgroundSizeMode" placeholder="{{ 'dashboard.background-size-mode' | translate }}">
<md-option value="100%">Fit width</md-option>
<md-option value="auto 100%">Fit height</md-option>
<md-option value="cover">Cover</md-option>
<md-option value="contain">Contain</md-option>
<md-option value="auto">Original size</md-option>
</md-select>
</md-input-container>
</fieldset>
</div>
</md-dialog-content>

45
ui/src/app/dashboard/dashboard.controller.js

@ -45,6 +45,7 @@ export default function DashboardController(types, widgetService, userService,
vm.widgetEditMode = $state.$current.data.widgetEditMode;
vm.iframeMode = $rootScope.iframeMode;
vm.widgets = [];
vm.dashboardInitComplete = false;
vm.addWidget = addWidget;
vm.addWidgetFromType = addWidgetFromType;
@ -58,6 +59,7 @@ export default function DashboardController(types, widgetService, userService,
vm.exportWidget = exportWidget;
vm.importWidget = importWidget;
vm.isTenantAdmin = isTenantAdmin;
vm.isSystemAdmin = isSystemAdmin;
vm.loadDashboard = loadDashboard;
vm.noData = noData;
vm.onAddWidgetClosed = onAddWidgetClosed;
@ -165,13 +167,6 @@ export default function DashboardController(types, widgetService, userService,
var parentScope = $window.parent.angular.element($window.frameElement).scope();
parentScope.$root.$broadcast('widgetEditModeInited');
parentScope.$root.$apply();
$scope.$watch('vm.widgets', function () {
var widget = vm.widgets[0];
parentScope.$root.$broadcast('widgetEditUpdated', widget);
parentScope.$root.$apply();
}, true);
});
} else {
@ -187,7 +182,9 @@ export default function DashboardController(types, widgetService, userService,
if (angular.isUndefined(vm.dashboard.configuration.deviceAliases)) {
vm.dashboard.configuration.deviceAliases = {};
}
vm.widgets = vm.dashboard.configuration.widgets;
//$timeout(function () {
vm.widgets = vm.dashboard.configuration.widgets;
//});
deferred.resolve();
}, function fail(e) {
deferred.reject(e);
@ -201,19 +198,25 @@ export default function DashboardController(types, widgetService, userService,
var parentScope = $window.parent.angular.element($window.frameElement).scope();
parentScope.$emit('widgetEditModeInited');
parentScope.$apply();
vm.dashboardInitComplete = true;
}
function dashboardInited(dashboard) {
vm.dashboardContainer = dashboard;
initHotKeys();
vm.dashboardInitComplete = true;
}
function isTenantAdmin() {
return user.authority === 'TENANT_ADMIN';
}
function isSystemAdmin() {
return user.authority === 'SYS_ADMIN';
}
function noData() {
return vm.widgets.length == 0;
return vm.dashboardInitComplete && vm.widgets.length == 0;
}
function openDeviceAliases($event) {
@ -252,7 +255,20 @@ export default function DashboardController(types, widgetService, userService,
fullscreen: true,
targetEvent: $event
}).then(function (gridSettings) {
var prevColumns = vm.dashboard.configuration.gridSettings.columns;
var ratio = gridSettings.columns / prevColumns;
var currentWidgets = angular.copy(vm.widgets);
vm.widgets = [];
vm.dashboard.configuration.gridSettings = gridSettings;
for (var w in currentWidgets) {
var widget = currentWidgets[w];
widget.sizeX = Math.round(widget.sizeX * ratio);
widget.sizeY = Math.round(widget.sizeY * ratio);
widget.col = Math.round(widget.col * ratio);
widget.row = Math.round(widget.row * ratio);
}
vm.dashboard.configuration.widgets = currentWidgets;
vm.widgets = vm.dashboard.configuration.widgets;
}, function () {
});
}
@ -577,7 +593,9 @@ export default function DashboardController(types, widgetService, userService,
}
} else {
if (vm.widgetEditMode) {
vm.widgets = vm.prevWidgets;
if (revert) {
vm.widgets = vm.prevWidgets;
}
} else {
if (vm.dashboardContainer) {
vm.dashboardContainer.resetHighlight();
@ -600,7 +618,12 @@ export default function DashboardController(types, widgetService, userService,
}
function notifyDashboardUpdated() {
if (!vm.widgetEditMode) {
if (vm.widgetEditMode) {
var parentScope = $window.parent.angular.element($window.frameElement).scope();
var widget = vm.widgets[0];
parentScope.$root.$broadcast('widgetEditUpdated', widget);
parentScope.$root.$apply();
} else {
dashboardService.saveDashboard(vm.dashboard);
}
}

18
ui/src/app/dashboard/dashboard.tpl.html

@ -70,16 +70,16 @@
'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
'background-repeat': 'no-repeat',
'background-attachment': 'scroll',
'background-size': '100%',
'background-size': vm.dashboard.configuration.gridSettings.backgroundSizeMode || '100%',
'background-position': '0% 0%'}"
widgets="vm.widgets"
columns="vm.dashboard.configuration.gridSettings.columns"
margins="vm.dashboard.configuration.gridSettings.margins"
device-alias-list="vm.dashboard.configuration.deviceAliases"
is-edit="vm.isEdit || vm.widgetEditMode"
is-edit="vm.isEdit"
is-mobile="vm.forceDashboardMobileMode"
is-mobile-disabled="vm.widgetEditMode"
is-edit-action-enabled="vm.isEdit || vm.widgetEditMode"
is-edit-action-enabled="vm.isEdit"
is-export-action-enabled="vm.isEdit && !vm.widgetEditMode"
is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
on-edit-widget="vm.editWidget(event, widget)"
@ -108,7 +108,7 @@
<details-buttons tb-help="vm.helpLinkIdForWidgetType()" help-container-id="help-container">
<div id="help-container"></div>
</details-buttons>
<form name="vm.widgetForm">
<form name="vm.widgetForm" ng-if="vm.isEditingWidget">
<tb-edit-widget
dashboard="vm.dashboard"
widget="vm.editingWidget"
@ -123,7 +123,7 @@
is-open="vm.isAddingWidget"
is-edit="false"
on-close-details="vm.onAddWidgetClosed()">
<header-pane>
<header-pane ng-if="vm.isAddingWidget">
<div layout="row">
<span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>
<tb-widgets-bundle-select flex-offset="5"
@ -134,7 +134,7 @@
</tb-widgets-bundle-select>
</div>
</header-pane>
<div>
<div ng-if="vm.isAddingWidget">
<md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 ||
vm.rpcWidgetTypes.length > 0 || vm.staticWidgetTypes.length > 0"
flex
@ -224,7 +224,7 @@
</md-button>
</md-fab-actions>
</md-fab-speed-dial>
<md-button ng-if="vm.isTenantAdmin()" ng-show="vm.isEdit && !vm.isAddingWidget && !loading && !vm.widgetEditMode" ng-disabled="loading"
<md-button ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-show="vm.isEdit && !vm.isAddingWidget && !loading" ng-disabled="loading"
class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'action.apply' | translate }}"
ng-click="vm.saveDashboard()">
@ -233,8 +233,8 @@
</md-tooltip>
<ng-md-icon icon="done"></ng-md-icon>
</md-button>
<md-button ng-show="!vm.isAddingWidget && !loading && !vm.widgetEditMode"
ng-if="vm.isTenantAdmin()" ng-disabled="loading"
<md-button ng-show="!vm.isAddingWidget && !loading"
ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-disabled="loading"
class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'action.edit-mode' | translate }}"
ng-click="vm.toggleDashboardEditMode()">

4
ui/src/app/dashboard/edit-widget.directive.js

@ -35,8 +35,8 @@ export default function EditWidgetDirective($compile, $templateCache, widgetServ
function(widgetInfo) {
scope.$applyAsync(function(scope) {
scope.widgetConfig = scope.widget.config;
var settingsSchema = widgetInfo.settingsSchema;
var dataKeySettingsSchema = widgetInfo.dataKeySettingsSchema;
var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
if (!settingsSchema || settingsSchema === '') {
scope.settingsSchema = {};
} else {

2
ui/src/app/device/device-credentials.tpl.html

@ -52,7 +52,7 @@
<md-input-container class="md-block" ng-if="vm.deviceCredentials.credentialsType === 'X509_CERTIFICATE'">
<label translate>device.rsa-key</label>
<textarea required name="rsaKey" ng-model="vm.deviceCredentials.credentialsValue"
cols="15" rows="5" />
cols="15" rows="5"></textarea>
<div ng-messages="theForm.rsaKey.$error">
<div translate ng-message="required">device.rsa-key-required</div>
</div>

4
ui/src/app/home/home-links.tpl.html

@ -15,8 +15,8 @@
limitations under the License.
-->
<md-grid-list md-cols="2" md-cols-gt-xs="3" md-cols-gt-sm="4" md-row-height="280px">
<md-grid-tile md-colspan="{{section.places.length}}" ng-repeat="section in vm.model">
<md-grid-list md-cols="2" md-cols-gt-sm="4" md-row-height="280px">
<md-grid-tile md-colspan="2" md-colspan-gt-sm="{{section.places.length}}" ng-repeat="section in vm.model">
<md-card style='width: 100%;'>
<md-card-title>
<md-card-title-text>

52
ui/src/app/layout/home.controller.js

@ -13,18 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import $ from 'jquery';
/* eslint-disable import/no-unresolved, import/default */
import logoSvg from '../../svg/logo_title_white.svg';
/* eslint-enable import/no-unresolved, import/default */
/* eslint-disable angular/angularelement */
/*@ngInject*/
export default function HomeController(loginService, userService, deviceService, Fullscreen, $scope, $rootScope, $document, $state,
$log, $mdMedia, $translate) {
export default function HomeController(loginService, userService, deviceService, Fullscreen, $scope, $element, $rootScope, $document, $state,
$log, $mdMedia, $animate, $timeout, $translate) {
var dashboardUser = userService.getCurrentUser();
var isShowSidenav = false,
dashboardUser = userService.getCurrentUser();
var siteSideNav = $('.tb-site-sidenav', $element);
var vm = this;
@ -39,13 +45,14 @@ export default function HomeController(loginService, userService, deviceService,
};
}
vm.isShowSidenav = false;
vm.isLockSidenav = false;
vm.authorityName = authorityName;
vm.displaySearchMode = displaySearchMode;
vm.lockSidenav = lockSidenav;
vm.logout = logout;
vm.openProfile = openProfile;
vm.openSidenav = openSidenav;
vm.showSidenav = showSidenav;
vm.searchTextUpdated = searchTextUpdated;
vm.sidenavClicked = sidenavClicked;
vm.toggleFullscreen = toggleFullscreen;
@ -65,6 +72,23 @@ export default function HomeController(loginService, userService, deviceService,
}
});
if ($mdMedia('gt-sm')) {
vm.isLockSidenav = true;
$animate.enabled(siteSideNav, false);
}
$scope.$watch(function() { return $mdMedia('gt-sm'); }, function(isGtSm) {
vm.isLockSidenav = isGtSm;
vm.isShowSidenav = isGtSm;
if (!isGtSm) {
$timeout(function() {
$animate.enabled(siteSideNav, true);
}, 0, false);
} else {
$animate.enabled(siteSideNav, false);
}
});
function displaySearchMode() {
return $scope.searchConfig.searchEnabled &&
$scope.searchConfig.showSearch;
@ -127,25 +151,19 @@ export default function HomeController(loginService, userService, deviceService,
}
function openSidenav() {
isShowSidenav = true;
vm.isShowSidenav = true;
}
function closeSidenav() {
isShowSidenav = false;
}
function lockSidenav() {
return $mdMedia('gt-sm');
vm.isShowSidenav = false;
}
function sidenavClicked() {
if (!$mdMedia('gt-sm')) {
if (!vm.isLockSidenav) {
closeSidenav();
}
}
function showSidenav() {
return isShowSidenav || $mdMedia('gt-sm');
}
}
}
/* eslint-enable angular/angularelement */

14
ui/src/app/layout/home.tpl.html

@ -16,13 +16,13 @@
-->
<md-sidenav class="tb-site-sidenav md-sidenav-left md-whiteframe-z2"
hide-print=""
md-component-id="left"
aria-label="Toggle Nav"
ng-click="vm.sidenavClicked()"
md-is-open="vm.showSidenav()"
md-is-locked-open="vm.lockSidenav()"
layout="column">
hide-print=""
md-component-id="left"
aria-label="Toggle Nav"
ng-click="vm.sidenavClicked()"
md-is-open="vm.isShowSidenav"
md-is-locked-open="vm.isLockSidenav"
layout="column">
<header class="tb-nav-header">
<md-toolbar md-scroll-shrink class="tb-nav-header-toolbar">
<div flex layout="row" layout-align="start center" class="md-toolbar-tools inset">

701
ui/src/app/locale/locale.constant.js

@ -0,0 +1,701 @@
/*
* 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.
*/
export default angular.module('thingsboard.locale', [])
.constant('locales',
{
'en_US': {
"access": {
"unauthorized": "Unauthorized",
"unauthorized-access": "Unauthorized Access",
"unauthorized-access-text": "You should sign in to have access to this resource!",
"access-forbidden": "Access Forbidden",
"access-forbidden-text": "You haven't access rights to this location!<br/>Try to sign in with different user if you still wish to gain access to this location.",
"refresh-token-expired": "Session has expired",
"refresh-token-failed": "Unable to refresh session"
},
"action": {
"activate": "Activate",
"suspend": "Suspend",
"save": "Save",
"saveAs": "Save as",
"cancel": "Cancel",
"ok": "OK",
"delete": "Delete",
"add": "Add",
"yes": "Yes",
"no": "No",
"update": "Update",
"remove": "Remove",
"search": "Search",
"assign": "Assign",
"unassign": "Unassign",
"apply": "Apply",
"apply-changes": "Apply changes",
"edit-mode": "Edit mode",
"enter-edit-mode": "Enter edit mode",
"decline-changes": "Decline changes",
"close": "Close",
"back": "Back",
"run": "Run",
"sign-in": "Sign in!",
"edit": "Edit",
"view": "View",
"create": "Create",
"drag": "Drag",
"refresh": "Refresh",
"undo": "Undo",
"copy": "Copy",
"paste": "Paste",
"import": "Import",
"export": "Export"
},
"admin": {
"general": "General",
"general-settings": "General Settings",
"outgoing-mail": "Outgoing Mail",
"outgoing-mail-settings": "Outgoing Mail Settings",
"system-settings": "System Settings",
"test-mail-sent": "Test mail was successfully sent!",
"base-url": "Base URL",
"base-url-required": "Base URL is required.",
"mail-from": "Mail From",
"mail-from-required": "Mail From is required.",
"smtp-protocol": "SMTP protocol",
"smtp-host": "SMTP host",
"smtp-host-required": "SMTP host is required.",
"smtp-port": "SMTP port",
"smtp-port-required": "You must supply a smtp port.",
"smtp-port-invalid": "That doesn't look like a valid smtp port.",
"timeout-msec": "Timeout (msec)",
"timeout-required": "Timeout is required.",
"timeout-invalid": "That doesn't look like a valid timeout.",
"enable-tls": "Enable TLS",
"send-test-mail": "Send test mail"
},
"attribute": {
"attributes": "Attributes",
"latest-telemetry": "Latest telemetry",
"attributes-scope": "Device attributes scope",
"scope-latest-telemetry": "Latest telemetry",
"scope-client": "Client attributes",
"scope-server": "Server attributes",
"scope-shared": "Shared attributes",
"add": "Add attribute",
"key": "Key",
"key-required": "Attribute key is required.",
"value": "Value",
"value-required": "Attribute value is required.",
"delete-attributes-title": "Are you sure you want to delete { count, select, 1 {1 attribute} other {# attributes} }?",
"delete-attributes-text": "Be careful, after the confirmation all selected attributes will be removed.",
"delete-attributes": "Delete attributes",
"enter-attribute-value": "Enter attribute value",
"show-on-widget": "Show on widget",
"widget-mode": "Widget mode",
"next-widget": "Next widget",
"prev-widget": "Previous widget",
"add-to-dashboard": "Add to dashboard",
"add-widget-to-dashboard": "Add widget to dashboard",
"selected-attributes": "{ count, select, 1 {1 attribute} other {# attributes} } selected",
"selected-telemetry": "{ count, select, 1 {1 telemetry unit} other {# telemetry units} } selected"
},
"confirm-on-exit": {
"message": "You have unsaved changes. Are you sure you want to leave this page?",
"html-message": "You have unsaved changes.<br/>Are you sure you want to leave this page?",
"title": "Unsaved changes"
},
"contact": {
"country": "Country",
"city": "City",
"state": "State",
"postal-code": "Postal code",
"postal-code-invalid": "Only digits are allowed.",
"address": "Address",
"address2": "Address 2",
"phone": "Phone",
"email": "Email",
"no-address": "No address"
},
"common": {
"username": "Username",
"password": "Password",
"enter-username": "Enter username",
"enter-password": "Enter password",
"enter-search": "Enter search"
},
"customer": {
"customers": "Customers",
"management": "Customer management",
"dashboard": "Customer Dashboard",
"dashboards": "Customer Dashboards",
"devices": "Customer Devices",
"add": "Add Customer",
"delete": "Delete customer",
"manage-customer-users": "Manage customer users",
"manage-customer-devices": "Manage customer devices",
"manage-customer-dashboards": "Manage customer dashboards",
"add-customer-text": "Add new customer",
"no-customers-text": "No customers found",
"customer-details": "Customer details",
"delete-customer-title": "Are you sure you want to delete the customer '{{customerTitle}}'?",
"delete-customer-text": "Be careful, after the confirmation the customer and all related data will become unrecoverable.",
"delete-customers-title": "Are you sure you want to delete { count, select, 1 {1 customer} other {# customers} }?",
"delete-customers-action-title": "Delete { count, select, 1 {1 customer} other {# customers} }",
"delete-customers-text": "Be careful, after the confirmation all selected customers will be removed and all related data will become unrecoverable.",
"manage-users": "Manage users",
"manage-devices": "Manage devices",
"manage-dashboards": "Manage dashboards",
"title": "Title",
"title-required": "Title is required.",
"description": "Description"
},
"datetime": {
"date-from": "Date from",
"time-from": "Time from",
"date-to": "Date to",
"time-to": "Time to"
},
"dashboard": {
"dashboard": "Dashboard",
"dashboards": "Dashboards",
"management": "Dashboard management",
"view-dashboards": "View Dashboards",
"add": "Add Dashboard",
"assign-dashboard-to-customer": "Assign Dashboard(s) To Customer",
"assign-dashboard-to-customer-text": "Please select the dashboards to assign to the customer",
"assign-to-customer-text": "Please select the customer to assign the dashboard(s)",
"assign-to-customer": "Assign to customer",
"unassign-from-customer": "Unassign from customer",
"no-dashboards-text": "No dashboards found",
"no-widgets": "No widgets configured",
"add-widget": "Add new widget",
"title": "Title",
"select-widget-title": "Select widget",
"select-widget-subtitle": "List of available widget types",
"delete": "Delete dashboard",
"title-required": "Title is required.",
"description": "Description",
"details": "Details",
"dashboard-details": "Dashboard details",
"add-dashboard-text": "Add new dashboard",
"assign-dashboards": "Assign dashboards",
"assign-new-dashboard": "Assign new dashboard",
"assign-dashboards-text": "Assign { count, select, 1 {1 dashboard} other {# dashboards} } to customer",
"delete-dashboards": "Delete dashboards",
"unassign-dashboards": "Unassign dashboards",
"unassign-dashboards-action-title": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customer",
"delete-dashboard-title": "Are you sure you want to delete the dashboard '{{dashboardTitle}}'?",
"delete-dashboard-text": "Be careful, after the confirmation the dashboard and all related data will become unrecoverable.",
"delete-dashboards-title": "Are you sure you want to delete { count, select, 1 {1 dashboard} other {# dashboards} }?",
"delete-dashboards-action-title": "Delete { count, select, 1 {1 dashboard} other {# dashboards} }",
"delete-dashboards-text": "Be careful, after the confirmation all selected dashboards will be removed and all related data will become unrecoverable.",
"unassign-dashboard-title": "Are you sure you want to unassign the dashboard '{{dashboardTitle}}'?",
"unassign-dashboard-text": "After the confirmation the dashboard will be unassigned and won't be accessible by the customer.",
"unassign-dashboard": "Unassign dashboard",
"unassign-dashboards-title": "Are you sure you want to unassign { count, select, 1 {1 dashboard} other {# dashboards} }?",
"unassign-dashboards-text": "After the confirmation all selected dashboards will be unassigned and won't be accessible by the customer.",
"select-dashboard": "Select dashboard",
"no-dashboards-matching": "No dashboards matching '{{dashboard}}' were found.",
"dashboard-required": "Dashboard is required.",
"select-existing": "Select existing dashboard",
"create-new": "Create new dashboard",
"new-dashboard-title": "New dashboard title",
"open-dashboard": "Open dashboard",
"set-background": "Set background",
"background-color": "Background color",
"background-image": "Background image",
"background-size-mode": "Background size mode",
"no-image": "No image selected",
"drop-image": "Drop an image or click to select a file to upload.",
"settings": "Settings",
"columns-count": "Columns count",
"columns-count-required": "Columns count is required.",
"min-columns-count-message": "Only 10 minimum column count is allowed.",
"max-columns-count-message": "Only 1000 maximum column count is allowed.",
"widgets-margins": "Margin between widgets",
"horizontal-margin": "Horizontal margin",
"horizontal-margin-required": "Horizontal margin value is required.",
"min-horizontal-margin-message": "Only 0 is allowed as minimum horizontal margin value.",
"max-horizontal-margin-message": "Only 50 is allowed as maximum horizontal margin value.",
"vertical-margin": "Vertical margin",
"vertical-margin-required": "Vertical margin value is required.",
"min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.",
"max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.",
"display-title": "Display dashboard title",
"import": "Import dashboard",
"export": "Export dashboard",
"export-failed-error": "Unable to export dashboard: {error}",
"create-new-dashboard": "Create new dashboard",
"dashboard-file": "Dashboard file",
"invalid-dashboard-file-error": "Unable to import dashboard: Invalid dashboard data structure.",
"dashboard-import-missing-aliases-title": "Select missing devices for dashboard aliases",
"create-new-widget": "Create new widget",
"import-widget": "Import widget",
"widget-file": "Widget file",
"invalid-widget-file-error": "Unable to import widget: Invalid widget data structure.",
"widget-import-missing-aliases-title": "Select missing devices used by widget"
},
"datakey": {
"settings": "Settings",
"advanced": "Advanced",
"label": "Label",
"color": "Color",
"data-generation-func": "Data generation function",
"use-data-post-processing-func": "Use data post-processing function",
"configuration": "Data key configuration",
"timeseries": "Timeseries",
"attributes": "Attributes",
"timeseries-required": "Device timeseries is required.",
"timeseries-or-attributes-required": "Device timeseries/attributes is required.",
"function-types": "Function types",
"function-types-required": "Function types is required."
},
"datasource": {
"type": "Datasource type",
"add-datasource-prompt": "Please add datasource"
},
"details": {
"edit-mode": "Edit mode",
"toggle-edit-mode": "Toggle edit mode"
},
"device": {
"device": "Device",
"device-required": "Device is required.",
"devices": "Devices",
"management": "Device management",
"view-devices": "View Devices",
"device-alias": "Device alias",
"aliases": "Device aliases",
"no-alias-matching": "'{{alias}}' not found.",
"no-aliases-found": "No aliases found.",
"no-key-matching": "'{{key}}' not found.",
"no-keys-found": "No keys found.",
"create-new-alias": "Create a new one!",
"create-new-key": "Create a new one!",
"duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Device aliases must be unique whithin the dashboard.",
"select-device-for-alias": "Select device for '{{alias}}' alias",
"no-devices-matching": "No devices matching '{{device}}' were found.",
"alias": "Alias",
"alias-required": "Device alias is required.",
"remove-alias": "Remove device alias",
"add-alias": "Add device alias",
"add": "Add Device",
"assign-to-customer": "Assign to customer",
"assign-device-to-customer": "Assign Device(s) To Customer",
"assign-device-to-customer-text": "Please select the devices to assign to the customer",
"no-devices-text": "No devices found",
"assign-to-customer-text": "Please select the customer to assign the device(s)",
"device-details": "Device details",
"add-device-text": "Add new device",
"credentials": "Credentials",
"manage-credentials": "Manage credentials",
"delete": "Delete device",
"assign-devices": "Assign devices",
"assign-devices-text": "Assign { count, select, 1 {1 device} other {# devices} } to customer",
"delete-devices": "Delete devices",
"unassign-from-customer": "Unassign from customer",
"unassign-devices": "Unassign devices",
"unassign-devices-action-title": "Unassign { count, select, 1 {1 device} other {# devices} } from customer",
"assign-new-device": "Assign new device",
"view-credentials": "View credentials",
"delete-device-title": "Are you sure you want to delete the device '{{deviceName}}'?",
"delete-device-text": "Be careful, after the confirmation the device and all related data will become unrecoverable.",
"delete-devices-title": "Are you sure you want to delete { count, select, 1 {1 device} other {# devices} }?",
"delete-devices-action-title": "Delete { count, select, 1 {1 device} other {# devices} }",
"delete-devices-text": "Be careful, after the confirmation all selected devices will be removed and all related data will become unrecoverable.",
"unassign-device-title": "Are you sure you want to unassign the device '{{deviceName}}'?",
"unassign-device-text": "After the confirmation the device will be unassigned and won't be accessible by the customer.",
"unassign-device": "Unassign device",
"unassign-devices-title": "Are you sure you want to unassign { count, select, 1 {1 device} other {# devices} }?",
"unassign-devices-text": "After the confirmation all selected devices will be unassigned and won't be accessible by the customer.",
"device-credentials": "Device Credentials",
"credentials-type": "Credentials type",
"access-token": "Access token",
"access-token-required": "Access token is required.",
"access-token-invalid": "Access token length must be from 1 to 20 characters.",
"rsa-key": "RSA public key",
"rsa-key-required": "RSA public key is required.",
"secret": "Secret",
"secret-required": "Secret is required.",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
"events": "Events",
"details": "Details",
"copyId": "Copy device Id",
"copyAccessToken": "Copy access token",
"idCopiedMessage": "Device Id has been copied to clipboard",
"accessTokenCopiedMessage": "Device access token has been copied to clipboard",
"assignedToCustomer": "Assigned to customer",
"unable-delete-device-alias-title": "Unable to delete device alias",
"unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
"is-gateway": "Is gateway"
},
"dialog": {
"close": "Close dialog"
},
"error": {
"unable-to-connect": "Unable to connect to the server! Please check your internet connection.",
"unhandled-error-code": "Unhandled error code: {{errorCode}}",
"unknown-error": "Unknown error"
},
"event": {
"event-type": "Event type",
"type-alarm": "Alarm",
"type-error": "Error",
"type-lc-event": "Lifecycle event",
"type-stats": "Statistics",
"no-events-prompt": "No events found",
"error": "Error",
"alarm": "Alarm",
"event-time": "Event time",
"server": "Server",
"body": "Body",
"method": "Method",
"event": "Event",
"status": "Status",
"success": "Success",
"failed": "Failed",
"messages-processed": "Messages processed",
"errors-occurred": "Errors occurred"
},
"fullscreen": {
"expand": "Expand to fullscreen",
"exit": "Exit fullscreen",
"toggle": "Toggle fullscreen mode",
"fullscreen": "Fullscreen"
},
"function": {
"function": "Function"
},
"grid": {
"delete-item-title": "Are you sure you want to delete this item?",
"delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.",
"delete-items-title": "Are you sure you want to delete { count, select, 1 {1 item} other {# items} }?",
"delete-items-action-title": "Delete { count, select, 1 {1 item} other {# items} }",
"delete-items-text": "Be careful, after the confirmation all selected items will be removed and all related data will become unrecoverable.",
"add-item-text": "Add new item",
"no-items-text": "No items found",
"item-details": "Item details",
"delete-item": "Delete Item",
"delete-items": "Delete Items",
"scroll-to-top": "Scroll to top"
},
"help": {
"goto-help-page": "Go to help page"
},
"home": {
"home": "Home",
"profile": "Profile",
"logout": "Logout",
"menu": "Menu",
"avatar": "Avatar",
"open-user-menu": "Open user menu"
},
"import": {
"no-file": "No file selected",
"drop-file": "Drop a JSON file or click to select a file to upload."
},
"item": {
"selected": "Selected"
},
"js-func": {
"no-return-error": "Function must return value!",
"return-type-mismatch": "Function must return value of '{{type}}' type!"
},
"login": {
"login": "Login",
"request-password-reset": "Request Password Reset",
"reset-password": "Reset Password",
"create-password": "Create Password",
"passwords-mismatch-error": "Entered passwords must be same!",
"password-again": "Password again",
"sign-in": "Please sign in",
"username": "Username (email)",
"remember-me": "Remember me",
"forgot-password": "Forgot Password?",
"password-reset": "Password reset",
"new-password": "New password",
"new-password-again": "New password again",
"password-link-sent-message": "Password reset link was successfully sent!",
"email": "Email"
},
"plugin": {
"plugins": "Plugins",
"delete": "Delete plugin",
"activate": "Activate plugin",
"suspend": "Suspend plugin",
"active": "Active",
"suspended": "Suspended",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
"add": "Add Plugin",
"delete-plugin-title": "Are you sure you want to delete the plugin '{{pluginName}}'?",
"delete-plugin-text": "Be careful, after the confirmation the plugin and all related data will become unrecoverable.",
"delete-plugins-title": "Are you sure you want to delete { count, select, 1 {1 plugin} other {# plugins} }?",
"delete-plugins-action-title": "Delete { count, select, 1 {1 plugin} other {# plugins} }",
"delete-plugins-text": "Be careful, after the confirmation all selected plugins will be removed and all related data will become unrecoverable.",
"add-plugin-text": "Add new plugin",
"no-plugins-text": "No plugins found",
"plugin-details": "Plugin details",
"api-token": "API token",
"api-token-required": "API token is required.",
"type": "Plugin type",
"type-required": "Plugin type is required.",
"configuration": "Plugin configuration",
"system": "System",
"select-plugin": "Select plugin",
"plugin": "Plugin",
"no-plugins-matching": "No plugins matching '{{plugin}}' were found.",
"plugin-required": "Plugin is required.",
"plugin-require-match": "Please select an existing plugin.",
"events": "Events",
"details": "Details"
},
"profile": {
"profile": "Profile",
"change-password": "Change Password",
"current-password": "Current password"
},
"rule": {
"rules": "Rules",
"delete": "Delete rule",
"activate": "Activate rule",
"suspend": "Suspend rule",
"active": "Active",
"suspended": "Suspended",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
"add": "Add Rule",
"delete-rule-title": "Are you sure you want to delete the rule '{{ruleName}}'?",
"delete-rule-text": "Be careful, after the confirmation the rule and all related data will become unrecoverable.",
"delete-rules-title": "Are you sure you want to delete { count, select, 1 {1 rule} other {# rules} }?",
"delete-rules-action-title": "Delete { count, select, 1 {1 rule} other {# rules} }",
"delete-rules-text": "Be careful, after the confirmation all selected rules will be removed and all related data will become unrecoverable.",
"add-rule-text": "Add new rule",
"no-rules-text": "No rules found",
"rule-details": "Rule details",
"filters": "Filters",
"filter": "Filter",
"add-filter-prompt": "Please add filter",
"remove-filter": "Remove filter",
"add-filter": "Add filter",
"filter-name": "Filter name",
"filter-type": "Filter type",
"edit-filter": "Edit filter",
"view-filter": "View filter",
"component-name": "Name",
"component-name-required": "Name is required.",
"component-type": "Type",
"component-type-required": "Type is required.",
"processor": "Processor",
"no-processor-configured": "No processor configured",
"create-processor": "Create processor",
"processor-name": "Processor name",
"processor-type": "Processor type",
"plugin-action": "Plugin action",
"action-name": "Action name",
"action-type": "Action type",
"create-action-prompt": "Please create action",
"create-action": "Create action",
"details": "Details",
"events": "Events",
"system": "System"
},
"rule-plugin": {
"management": "Rules and plugins management"
},
"tenant": {
"tenants": "Tenants",
"management": "Tenant management",
"add": "Add Tenant",
"admins": "Admins",
"manage-tenant-admins": "Manage tenant admins",
"delete": "Delete tenant",
"add-tenant-text": "Add new tenant",
"no-tenants-text": "No tenants found",
"tenant-details": "Tenant details",
"delete-tenant-title": "Are you sure you want to delete the tenant '{{tenantTitle}}'?",
"delete-tenant-text": "Be careful, after the confirmation the tenant and all related data will become unrecoverable.",
"delete-tenants-title": "Are you sure you want to delete { count, select, 1 {1 tenant} other {# tenants} }?",
"delete-tenants-action-title": "Delete { count, select, 1 {1 tenant} other {# tenants} }",
"delete-tenants-text": "Be careful, after the confirmation all selected tenants will be removed and all related data will become unrecoverable.",
"title": "Title",
"title-required": "Title is required.",
"description": "Description"
},
"timeinterval": {
"seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }",
"minutes-interval": "{ minutes, select, 1 {1 minute} other {# minutes} }",
"hours-interval": "{ hours, select, 1 {1 hour} other {# hours} }",
"days-interval": "{ days, select, 1 {1 day} other {# days} }",
"days": "Days",
"hours": "Hours",
"minutes": "Minutes",
"seconds": "Seconds"
},
"timewindow": {
"days": "{ days, select, 1 { day } other {# days } }",
"hours": "{ hours, select, 0 { hour } 1 {1 hour } other {# hours } }",
"minutes": "{ minutes, select, 0 { minute } 1 {1 minute } other {# minutes } }",
"seconds": "{ seconds, select, 0 { second } 1 {1 second } other {# seconds } }",
"realtime": "Realtime",
"history": "History",
"last-prefix": "last",
"period": "from {{ startTime }} to {{ endTime }}",
"edit": "Edit timewindow",
"date-range": "Date range",
"last": "Last",
"time-period": "Time period"
},
"user": {
"users": "Users",
"customer-users": "Customer Users",
"tenant-admins": "Tenant Admins",
"sys-admin": "System administrator",
"tenant-admin": "Tenant administrator",
"customer": "Customer",
"anonymous": "Anonymous",
"add": "Add User",
"delete": "Delete user",
"add-user-text": "Add new user",
"no-users-text": "No users found",
"user-details": "User details",
"delete-user-title": "Are you sure you want to delete the user '{{userEmail}}'?",
"delete-user-text": "Be careful, after the confirmation the user and all related data will become unrecoverable.",
"delete-users-title": "Are you sure you want to delete { count, select, 1 {1 user} other {# users} }?",
"delete-users-action-title": "Delete { count, select, 1 {1 user} other {# users} }",
"delete-users-text": "Be careful, after the confirmation all selected users will be removed and all related data will become unrecoverable.",
"activation-email-sent-message": "Activation email was successfully sent!",
"resend-activation": "Resend activation",
"email": "Email",
"email-required": "Email is required.",
"first-name": "First Name",
"last-name": "Last Name",
"description": "Description"
},
"value": {
"type": "Value type",
"string": "String",
"string-value": "String value",
"integer": "Integer",
"integer-value": "Integer value",
"invalid-integer-value": "Invalid integer value",
"double": "Double",
"double-value": "Double value",
"boolean": "Boolean",
"boolean-value": "Boolean value",
"false": "False",
"true": "True"
},
"widget": {
"widget-library": "Widgets Library",
"widget-bundle": "Widgets Bundle",
"select-widgets-bundle": "Select widgets bundle",
"management": "Widget management",
"editor": "Widget Editor",
"widget-type-not-found": "Problem loading widget configuration.<br>Probably associated\n widget type was removed.",
"widget-type-load-error": "Widget wasn't loaded due to the following errors:",
"remove": "Remove widget",
"edit": "Edit widget",
"remove-widget-title": "Are you sure you want to remove the widget '{{widgetTitle}}'?",
"remove-widget-text": "After the confirmation the widget and all related data will become unrecoverable.",
"timeseries": "Time series",
"latest-values": "Latest values",
"rpc": "Control widget",
"static": "Static widget",
"select-widget-type": "Select widget type",
"missing-widget-title-error": "Widget title must be specified!",
"widget-saved": "Widget saved",
"unable-to-save-widget-error": "Unable to save widget! Widget has errors!",
"save": "Save widget",
"saveAs": "Save widget as",
"save-widget-type-as": "Save widget type as",
"save-widget-type-as-text": "Please enter new widget title and/or select target widgets bundle",
"toggle-fullscreen": "Toggle fullscreen",
"run": "Run widget",
"title": "Widget title",
"title-required": "Widget title is required.",
"type": "Widget type",
"resources": "Resources",
"resource-url": "JavaScript/CSS URI",
"remove-resource": "Remove resource",
"add-resource": "Add resource",
"html": "HTML",
"tidy": "Tidy",
"css": "CSS",
"settings-schema": "Settings schema",
"datakey-settings-schema": "Data key settings schema",
"javascript": "Javascript",
"remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?",
"remove-widget-type-text": "After the confirmation the widget type and all related data will become unrecoverable.",
"remove-widget-type": "Remove widget type",
"add-widget-type": "Add new widget type",
"widget-type-load-failed-error": "Failed to load widget type!",
"widget-template-load-failed-error": "Failed to load widget template!",
"add": "Add Widget",
"undo": "Undo widget changes",
"export": "Export widget"
},
"widgets-bundle": {
"current": "Current bundle",
"widgets-bundles": "Widgets Bundles",
"add": "Add Widgets Bundle",
"delete": "Delete widgets bundle",
"title": "Title",
"title-required": "Title is required.",
"add-widgets-bundle-text": "Add new widgets bundle",
"no-widgets-bundles-text": "No widgets bundles found",
"empty": "Widgets bundle is empty",
"details": "Details",
"widgets-bundle-details": "Widgets bundle details",
"delete-widgets-bundle-title": "Are you sure you want to delete the widgets bundle '{{widgetsBundleTitle}}'?",
"delete-widgets-bundle-text": "Be careful, after the confirmation the widgets bundle and all related data will become unrecoverable.",
"delete-widgets-bundles-title": "Are you sure you want to delete { count, select, 1 {1 widgets bundle} other {# widgets bundles} }?",
"delete-widgets-bundles-action-title": "Delete { count, select, 1 {1 widgets bundle} other {# widgets bundles} }",
"delete-widgets-bundles-text": "Be careful, after the confirmation all selected widgets bundles will be removed and all related data will become unrecoverable.",
"no-widgets-bundles-matching": "No widgets bundles matching '{{widgetsBundle}}' were found.",
"widgets-bundle-required": "Widgets bundle is required.",
"system": "System"
},
"widget-config": {
"settings": "Settings",
"advanced": "Advanced",
"title": "Title",
"general-settings": "General settings",
"display-title": "Display title",
"drop-shadow": "Drop shadow",
"enable-fullscreen": "Enable fullscreen",
"background-color": "Background color",
"text-color": "Text color",
"padding": "Padding",
"title-style": "Title style",
"mobile-mode-settings": "Mobile mode settings",
"order": "Order",
"height": "Height",
"timewindow": "Timewindow",
"datasources": "Datasources",
"datasource-type": "Type",
"datasource-parameters": "Parameters",
"remove-datasource": "Remove datasource",
"add-datasource": "Add datasource",
"target-device": "Target device"
}
}
}
).name;

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

@ -0,0 +1,731 @@
/*
* 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 $ from 'jquery';
import canvasGauges from 'canvas-gauges';
import tinycolor from 'tinycolor2';
/* eslint-disable angular/angularelement */
let defaultDigitalGaugeOptions = Object.assign({}, canvasGauges.GenericOptions, {
gaugeType: 'arc',
gaugeWithScale: 0.75,
dashThickness: 0,
roundedLineCap: false,
gaugeColor: '#777',
levelColors: ['blue'],
symbol: '',
label: '',
hideValue: false,
hideMinMax: false,
fontTitle: 'Roboto',
fontValue: 'Roboto',
fontMinMaxSize: 10,
fontMinMaxStyle: 'normal',
fontMinMaxWeight: '500',
colorMinMax: '#eee',
fontMinMax: 'Roboto',
fontLabelSize: 8,
fontLabelStyle: 'normal',
fontLabelWeight: '500',
colorLabel: '#eee',
fontLabel: 'Roboto',
neonGlowBrightness: 0,
isMobile: false
});
const round = Math.round;
export default class CanvasDigitalGauge extends canvasGauges.BaseGauge {
constructor(options) {
canvasGauges.performance = window.performance; // eslint-disable-line no-undef, angular/window-service
options = Object.assign({}, defaultDigitalGaugeOptions, options || {});
super(CanvasDigitalGauge.configure(options));
this.initValueClone();
}
initValueClone() {
let canvas = this.canvas;
this.elementValueClone = canvas.element.cloneNode(true);
this.contextValueClone = this.elementValueClone.getContext('2d');
this.elementValueClone.initialized = false;
this.contextValueClone.translate(canvas.drawX, canvas.drawY);
this.contextValueClone.save();
this.elementProgressClone = canvas.element.cloneNode(true);
this.contextProgressClone = this.elementProgressClone.getContext('2d');
this.elementProgressClone.initialized = false;
this.contextProgressClone.translate(canvas.drawX, canvas.drawY);
this.contextProgressClone.save();
}
static configure(options) {
if (options.value > options.maxValue) {
options.value = options.maxValue;
}
if (options.value < options.minValue) {
options.value = options.minValue;
}
var colorsCount = options.levelColors.length;
var inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1;
options.colorsRange = [];
if (options.neonGlowBrightness) {
options.neonColorsRange = [];
}
for (var i = 0; i < options.levelColors.length; i++) {
var percentage = inc * i;
var tColor = tinycolor(options.levelColors[i]);
options.colorsRange[i] = {
pct: percentage,
color: tColor.toRgb(),
rgbString: tColor.toRgbString()
};
if (options.neonGlowBrightness) {
tColor = tinycolor(options.levelColors[i]).brighten(options.neonGlowBrightness);
options.neonColorsRange[i] = {
pct: percentage,
color: tColor.toRgb(),
rgbString: tColor.toRgbString()
};
}
}
if (options.neonGlowBrightness) {
options.neonColorTitle = tinycolor(options.colorTitle).brighten(options.neonGlowBrightness).toHexString();
options.neonColorLabel = tinycolor(options.colorLabel).brighten(options.neonGlowBrightness).toHexString();
options.neonColorValue = tinycolor(options.colorValue).brighten(options.neonGlowBrightness).toHexString();
options.neonColorMinMax = tinycolor(options.colorMinMax).brighten(options.neonGlowBrightness).toHexString();
}
return canvasGauges.BaseGauge.configure(options);
}
destroy() {
this.contextValueClone = null;
this.elementValueClone = null;
this.contextProgressClone = null;
this.elementProgressClone = null;
super.destroy();
}
update(options) {
this.canvas.onRedraw = null;
var result = super.update(options);
this.initValueClone();
this.canvas.onRedraw = this.draw.bind(this);
this.draw();
return result;
}
draw() {
try {
let canvas = this.canvas;
let [x, y, w, h] = [
-canvas.drawX,
-canvas.drawY,
canvas.drawWidth,
canvas.drawHeight
];
let options = this.options;
if (!canvas.elementClone.initialized) {
let context = canvas.contextClone;
// clear the cache
context.clearRect(x, y, w, h);
context.save();
canvas.context.barDimensions = barDimensions(context, options, x, y, w, h);
this.contextValueClone.barDimensions = canvas.context.barDimensions;
this.contextProgressClone.barDimensions = canvas.context.barDimensions;
drawBackground(context, options);
drawDigitalTitle(context, options);
drawDigitalLabel(context, options);
drawDigitalMinMax(context, options);
canvas.elementClone.initialized = true;
}
if (!this.elementValueClone.initialized || this.elementValueClone.renderedValue !== this.value) {
let context = this.contextValueClone;
// clear the cache
context.clearRect(x, y, w, h);
context.save();
context.drawImage(canvas.elementClone, x, y, w, h);
context.save();
drawDigitalValue(context, options, this.value);
this.elementValueClone.initialized = true;
this.elementValueClone.renderedValue = this.value;
}
var progress = (canvasGauges.drawings.normalizedValue(options).normal - options.minValue) /
(options.maxValue - options.minValue);
var fixedProgress = progress.toFixed(3);
if (!this.elementProgressClone.initialized || this.elementProgressClone.renderedProgress !== fixedProgress) {
let context = this.contextProgressClone;
// clear the cache
context.clearRect(x, y, w, h);
context.save();
context.drawImage(this.elementValueClone, x, y, w, h);
context.save();
if (Number(fixedProgress) > 0) {
drawProgress(context, options, progress);
}
this.elementProgressClone.initialized = true;
this.elementProgressClone.renderedProgress = fixedProgress;
}
this.canvas.commit();
// clear the canvas
canvas.context.clearRect(x, y, w, h);
canvas.context.save();
canvas.context.drawImage(this.elementProgressClone, x, y, w, h);
canvas.context.save();
super.draw();
} catch (err) {
canvasGauges.drawings.verifyError(err);
}
return this;
}
}
/* eslint-disable angular/document-service */
/* eslint-disable no-undef */
function determineFontHeight (options, target, baseSize) {
var fontStyleStr = 'font-style:' + options['font' + target + 'Style'] + ';font-weight:' +
options['font' + target + 'Weight'] + ';font-size:' +
options['font' + target + 'Size'] * baseSize + 'px;font-family:' +
options['font' + target];
var result = CanvasDigitalGauge.heightCache[fontStyleStr];
if (!result)
{
var fontStyle = {
fontFamily: options['font' + target],
fontSize: options['font' + target + 'Size'] * baseSize + 'px',
fontWeight: options['font' + target + 'Weight'],
fontStyle: options['font' + target + 'Style']
};
var text = $('<span>Hg</span>').css(fontStyle);
var block = $('<div style="display: inline-block; width: 1px; height: 0px;"></div>');
var div = $('<div></div>');
div.append(text, block);
var body = $('body');
body.append(div);
try {
result = {};
block.css({ verticalAlign: 'baseline' });
result.ascent = block.offset().top - text.offset().top;
block.css({ verticalAlign: 'bottom' });
result.height = block.offset().top - text.offset().top;
result.descent = result.height - result.ascent;
} finally {
div.remove();
}
CanvasDigitalGauge.heightCache[fontStyleStr] = result;
}
return result;
}
/* eslint-enable angular/document-service */
/* eslint-enable no-undef */
function barDimensions(context, options, x, y, w, h) {
context.barDimensions = {
baseX: x,
baseY: y,
width: w,
height: h
};
var bd = context.barDimensions;
var aspect = 1;
if (options.gaugeType === 'horizontalBar') {
aspect = options.title === '' ? 2.5 : 2;
} else if (options.gaugeType === 'verticalBar') {
aspect = options.hideMinMax ? 0.35 : 0.5;
} else if (options.gaugeType === 'arc') {
aspect = 1.5;
}
var currentAspect = w / h;
if (currentAspect > aspect) {
bd.width = (h * aspect);
bd.height = h;
} else {
bd.width = w;
bd.height = w / aspect;
}
bd.baseX += (w - bd.width) / 2;
bd.baseY += (h - bd.height) / 2;
if (options.gaugeType === 'donut') {
bd.fontSizeFactor = Math.max(bd.width, bd.height) / 125;
} else if (options.gaugeType === 'verticalBar' || (options.gaugeType === 'arc' && options.title === '')) {
bd.fontSizeFactor = Math.max(bd.width, bd.height) / 150;
} else {
bd.fontSizeFactor = Math.max(bd.width, bd.height) / 200;
}
var gws = options.gaugeWidthScale;
if (options.neonGlowBrightness) {
options.fontTitleHeight = determineFontHeight(options, 'Title', bd.fontSizeFactor);
options.fontLabelHeight = determineFontHeight(options, 'Label', bd.fontSizeFactor);
options.fontValueHeight = determineFontHeight(options, 'Value', bd.fontSizeFactor);
options.fontMinMaxHeight = determineFontHeight(options, 'MinMax', bd.fontSizeFactor);
}
if (options.gaugeType === 'donut') {
bd.Ro = bd.width / 2 - bd.width / 20;
bd.Cy = bd.baseY + bd.height / 2;
if (options.title && options.title.length > 0) {
var titleOffset = determineFontHeight(options, 'Title', bd.fontSizeFactor).height;
titleOffset += bd.fontSizeFactor * 2;
bd.titleY = bd.baseY + titleOffset;
titleOffset += bd.fontSizeFactor * 2;
bd.Cy += titleOffset/2;
bd.Ro -= titleOffset/2;
}
bd.Ri = bd.Ro - bd.width / 6.666666666666667 * gws * 1.2;
bd.Cx = bd.baseX + bd.width / 2;
} else if (options.gaugeType === 'arc') {
if (options.title && options.title.length > 0) {
bd.Ro = bd.width / 2 - bd.width / 7;
bd.Ri = bd.Ro - bd.width / 6.666666666666667 * gws;
} else {
bd.Ro = bd.width / 2 - bd.fontSizeFactor * 4;
bd.Ri = bd.Ro - bd.width / 6.666666666666667 * gws * 1.2;
}
bd.Cx = bd.baseX + bd.width / 2;
bd.Cy = bd.baseY + bd.height / 1.25;
} else if (options.gaugeType === 'verticalBar') {
bd.Ro = bd.width / 2 - bd.width / 10;
bd.Ri = bd.Ro - bd.width / 6.666666666666667 * gws * (options.hideMinMax ? 4 : 2.5);
} else { //horizontalBar
bd.Ro = bd.width / 2 - bd.width / 10;
bd.Ri = bd.Ro - bd.width / 6.666666666666667 * gws;
}
bd.strokeWidth = bd.Ro - bd.Ri;
bd.Rm = bd.Ri + bd.strokeWidth * 0.5;
bd.fontValueBaseline = 'alphabetic';
bd.fontMinMaxBaseline = 'alphabetic';
bd.fontMinMaxAlign = 'center';
if (options.gaugeType === 'donut') {
bd.fontValueBaseline = 'middle';
if (options.label && options.label.length > 0) {
var valueHeight = determineFontHeight(options, 'Value', bd.fontSizeFactor).height;
var labelHeight = determineFontHeight(options, 'Label', bd.fontSizeFactor).height;
var total = valueHeight + labelHeight;
bd.labelY = bd.Cy + total/2;
bd.valueY = bd.Cy - total/2 + valueHeight/2;
} else {
bd.valueY = bd.Cy;
}
} else if (options.gaugeType === 'arc') {
bd.titleY = bd.Cy - bd.Ro - 12 * bd.fontSizeFactor;
bd.valueY = bd.Cy;
bd.labelY = bd.Cy + (8 + options.fontLabelSize) * bd.fontSizeFactor;
bd.minY = bd.maxY = bd.labelY;
if (options.roundedLineCap) {
bd.minY += bd.strokeWidth/2;
bd.maxY += bd.strokeWidth/2;
}
bd.minX = bd.Cx - bd.Rm;
bd.maxX = bd.Cx + bd.Rm;
} else if (options.gaugeType === 'horizontalBar') {
bd.titleY = bd.baseY + 4 * bd.fontSizeFactor +
(options.title === '' ? 0 : options.fontTitleSize * bd.fontSizeFactor);
bd.titleBottom = bd.titleY + (options.title === '' ? 0 : 4) * bd.fontSizeFactor;
bd.valueY = bd.titleBottom +
(options.hideValue ? 0 : options.fontValueSize * bd.fontSizeFactor);
bd.barTop = bd.valueY + 8 * bd.fontSizeFactor;
bd.barBottom = bd.barTop + bd.strokeWidth;
if (options.hideMinMax && options.label === '') {
bd.labelY = bd.barBottom;
bd.barLeft = options.fontMinMaxSize/3 * bd.fontSizeFactor;
bd.barRight = bd.width - options.fontMinMaxSize/3 * bd.fontSizeFactor;
} else {
context.font = canvasGauges.drawings.font(options, 'MinMax', bd.fontSizeFactor);
var minTextWidth = context.measureText(options.minValue+'').width;
var maxTextWidth = context.measureText(options.maxValue+'').width;
var maxW = Math.max(minTextWidth, maxTextWidth);
bd.minX = bd.baseX + maxW/2 + options.fontMinMaxSize/3 * bd.fontSizeFactor;
bd.maxX = bd.baseX + bd.width - maxW/2 - options.fontMinMaxSize/3 * bd.fontSizeFactor;
bd.barLeft = bd.minX;
bd.barRight = bd.maxX;
bd.labelY = bd.barBottom + (8 + options.fontLabelSize) * bd.fontSizeFactor;
bd.minY = bd.maxY = bd.labelY;
}
} else if (options.gaugeType === 'verticalBar') {
bd.titleY = bd.baseY + ((options.title === '' ? 0 : options.fontTitleSize) + 8) * bd.fontSizeFactor;
bd.titleBottom = bd.titleY + (options.title === '' ? 0 : 4) * bd.fontSizeFactor;
bd.valueY = bd.titleBottom + (options.hideValue ? 0 : options.fontValueSize * bd.fontSizeFactor);
bd.barTop = bd.valueY + 8 * bd.fontSizeFactor;
bd.labelY = bd.baseY + bd.height - 16;
if (options.label === '') {
bd.barBottom = bd.labelY;
} else {
bd.barBottom = bd.labelY - (8 + options.fontLabelSize) * bd.fontSizeFactor;
}
bd.minX = bd.maxX =
bd.baseX + bd.width/2 + bd.strokeWidth/2 + options.fontMinMaxSize/3 * bd.fontSizeFactor;
bd.minY = bd.barBottom;
bd.maxY = bd.barTop;
bd.fontMinMaxBaseline = 'middle';
bd.fontMinMaxAlign = 'left';
}
if (options.dashThickness) {
var circumference;
if (options.gaugeType === 'donut') {
circumference = Math.PI * bd.Rm * 2;
} else if (options.gaugeType === 'arc') {
circumference = Math.PI * bd.Rm;
} else if (options.gaugeType === 'horizontalBar') {
circumference = bd.barRight - bd.barLeft;
} else if (options.gaugeType === 'verticalBar') {
circumference = bd.barBottom - bd.barTop;
}
var dashCount = Math.floor(circumference / (options.dashThickness * bd.fontSizeFactor));
if (options.gaugeType === 'donut') {
dashCount = (dashCount | 1) - 1;
} else {
dashCount = (dashCount - 1) | 1;
}
bd.dashLength = Math.ceil(circumference/dashCount);
}
return bd;
}
function drawBackground(context, options) {
let {barLeft, barRight, barTop, barBottom, width, baseX, strokeWidth} =
context.barDimensions;
if (context.barDimensions.dashLength) {
context.setLineDash([context.barDimensions.dashLength]);
}
context.beginPath();
context.strokeStyle = options.gaugeColor;
context.lineWidth = strokeWidth;
if (options.roundedLineCap) {
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.stroke();
} else if (options.gaugeType === 'arc') {
context.arc(context.barDimensions.Cx, context.barDimensions.Cy, context.barDimensions.Rm, Math.PI, 2*Math.PI);
context.stroke();
} else if (options.gaugeType === 'horizontalBar') {
context.moveTo(barLeft,barTop + strokeWidth/2);
context.lineTo(barRight,barTop + strokeWidth/2);
context.stroke();
} else if (options.gaugeType === 'verticalBar') {
context.moveTo(baseX + width/2, barBottom);
context.lineTo(baseX + width/2, barTop);
context.stroke();
}
}
function drawText(context, options, target, text, textX, textY) {
context.fillStyle = options[(options.neonGlowBrightness ? 'neonColor' : 'color') + target];
context.fillText(text, textX, textY);
}
function drawDigitalTitle(context, options) {
if (!options.title) return;
let {titleY, width, baseX, fontSizeFactor} =
context.barDimensions;
let textX = round(baseX + width / 2);
let textY = titleY;
context.save();
context.textAlign = 'center';
context.font = canvasGauges.drawings.font(options, 'Title', fontSizeFactor);
context.lineWidth = 0;
drawText(context, options, 'Title', options.title.toUpperCase(), textX, textY);
}
function drawDigitalLabel(context, options) {
if (!options.label || options.label === '') return;
let {labelY, baseX, width, fontSizeFactor} =
context.barDimensions;
let textX = round(baseX + width / 2);
let textY = labelY;
context.save();
context.textAlign = 'center';
context.font = canvasGauges.drawings.font(options, 'Label', fontSizeFactor);
context.lineWidth = 0;
drawText(context, options, 'Label', options.label.toUpperCase(), textX, textY);
}
function drawDigitalMinMax(context, options) {
if (options.hideMinMax || options.gaugeType === 'donut') return;
let {minY, maxY, minX, maxX, fontSizeFactor, fontMinMaxAlign, fontMinMaxBaseline} =
context.barDimensions;
context.save();
context.textAlign = fontMinMaxAlign;
context.textBaseline = fontMinMaxBaseline;
context.font = canvasGauges.drawings.font(options, 'MinMax', fontSizeFactor);
context.lineWidth = 0;
drawText(context, options, 'MinMax', options.minValue+'', minX, minY);
drawText(context, options, 'MinMax', options.maxValue+'', maxX, maxY);
}
function padValue(val, options) {
let dec = options.valueDec;
let strVal, n;
val = parseFloat(val);
n = (val < 0);
val = Math.abs(val);
if (dec > 0) {
strVal = val.toFixed(dec).toString()
} else {
strVal = round(val).toString();
}
strVal = (n ? '-' : '') + strVal;
return strVal;
}
function drawDigitalValue(context, options, value) {
if (options.hideValue) return;
let {valueY, baseX, width, fontSizeFactor, fontValueBaseline} =
context.barDimensions;
let textX = round(baseX + width / 2);
let textY = valueY;
let text = options.valueText || padValue(value, options);
text += options.symbol;
context.save();
context.textAlign = 'center';
context.textBaseline = fontValueBaseline;
context.font = canvasGauges.drawings.font(options, 'Value', fontSizeFactor);
context.lineWidth = 0;
drawText(context, options, 'Value', text, textX, textY);
}
function getProgressColor(progress, colorsRange) {
var lower, upper, range, rangePct, pctLower, pctUpper, color;
if (progress === 0 || colorsRange.length === 1) {
return colorsRange[0].rgbString;
}
for (var j = 0; j < colorsRange.length; j++) {
if (progress <= colorsRange[j].pct) {
lower = colorsRange[j - 1];
upper = colorsRange[j];
range = upper.pct - lower.pct;
rangePct = (progress - lower.pct) / range;
pctLower = 1 - rangePct;
pctUpper = rangePct;
color = tinycolor({
r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
});
return color.toRgbString();
}
}
}
function drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, color, progress, isDonut) {
context.setLineDash([]);
var strokeWidth = Ro - Ri;
var blur = 0.55;
var edge = strokeWidth*blur;
context.lineWidth = strokeWidth+edge;
var stop = blur/(2*blur+2);
var glowGradient = context.createRadialGradient(Cx,Cy,Ri-edge/2,Cx,Cy,Ro+edge/2);
var color1 = tinycolor(color).setAlpha(0.5).toRgbString();
var color2 = tinycolor(color).setAlpha(0).toRgbString();
glowGradient.addColorStop(0,color2);
glowGradient.addColorStop(stop,color1);
glowGradient.addColorStop(1.0-stop,color1);
glowGradient.addColorStop(1,color2);
context.strokeStyle = glowGradient;
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);
} else {
context.arc(Cx, Cy, Rm, Math.PI - e, Math.PI + Math.PI * progress + e);
}
context.stroke();
}
function drawBarGlow(context, startX, startY, endX, endY, color, strokeWidth, isVertical) {
context.setLineDash([]);
var blur = 0.55;
var edge = strokeWidth*blur;
context.lineWidth = strokeWidth+edge;
var stop = blur/(2*blur+2);
var gradientStartX = isVertical ? startX - context.lineWidth/2 : 0;
var gradientStartY = isVertical ? 0 : startY - context.lineWidth/2;
var gradientStopX = isVertical ? startX + context.lineWidth/2 : 0;
var gradientStopY = isVertical ? 0 : startY + context.lineWidth/2;
var glowGradient = context.createLinearGradient(gradientStartX,gradientStartY,gradientStopX,gradientStopY);
var color1 = tinycolor(color).setAlpha(0.5).toRgbString();
var color2 = tinycolor(color).setAlpha(0).toRgbString();
glowGradient.addColorStop(0,color2);
glowGradient.addColorStop(stop,color1);
glowGradient.addColorStop(1.0-stop,color1);
glowGradient.addColorStop(1,color2);
context.strokeStyle = glowGradient;
var dx = isVertical ? 0 : 0.05 * context.lineWidth;
var dy = isVertical ? 0.05 * context.lineWidth : 0;
context.beginPath();
context.moveTo(startX - dx, startY + dy);
context.lineTo(endX + dx, endY - dy);
context.stroke();
}
function drawProgress(context, options, progress) {
var neonColor;
if (options.neonGlowBrightness) {
neonColor = getProgressColor(progress, options.neonColorsRange);
} else {
context.strokeStyle = getProgressColor(progress, options.colorsRange);
}
let {barLeft, barRight, barTop, baseX, width, barBottom, Cx, Cy, Rm, Ro, Ri, strokeWidth} =
context.barDimensions;
if (context.barDimensions.dashLength) {
context.setLineDash([context.barDimensions.dashLength]);
}
context.lineWidth = strokeWidth;
if (options.roundedLineCap) {
context.lineCap = 'round';
} else {
context.lineCap = 'butt';
}
if (options.gaugeType === 'donut') {
if (options.neonGlowBrightness) {
context.strokeStyle = neonColor;
}
context.beginPath();
context.arc(Cx, Cy, Rm, 1.5 * Math.PI, 1.5 * Math.PI + 2 * Math.PI * progress);
context.stroke();
if (options.neonGlowBrightness && !options.isMobile) {
drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, true);
}
} else if (options.gaugeType === 'arc') {
if (options.neonGlowBrightness) {
context.strokeStyle = neonColor;
}
context.beginPath();
context.arc(Cx, Cy, Rm, Math.PI, Math.PI + Math.PI * progress);
context.stroke();
if (options.neonGlowBrightness && !options.isMobile) {
drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, false);
}
} else if (options.gaugeType === 'horizontalBar') {
if (options.neonGlowBrightness) {
context.strokeStyle = neonColor;
}
context.beginPath();
context.moveTo(barLeft,barTop + strokeWidth/2);
context.lineTo(barLeft + (barRight-barLeft)*progress, barTop + strokeWidth/2);
context.stroke();
if (options.neonGlowBrightness && !options.isMobile) {
drawBarGlow(context, barLeft, barTop + strokeWidth/2, barLeft + (barRight-barLeft)*progress, barTop + strokeWidth/2,
neonColor, strokeWidth, false);
}
} else if (options.gaugeType === 'verticalBar') {
if (options.neonGlowBrightness) {
context.strokeStyle = neonColor;
}
context.beginPath();
context.moveTo(baseX + width/2, barBottom);
context.lineTo(baseX + width/2, barBottom - (barBottom-barTop)*progress);
context.stroke();
if (options.neonGlowBrightness && !options.isMobile) {
drawBarGlow(context, baseX + width/2, barBottom, baseX + width/2, barBottom - (barBottom-barTop)*progress,
neonColor, strokeWidth, true);
}
}
}
CanvasDigitalGauge.heightCache = [];
canvasGauges.BaseGauge.initialize('CanvasDigitalGauge', defaultDigitalGaugeOptions);
/* eslint-enable angular/angularelement */

895
ui/src/app/widget/lib/analogue-linear-gauge.js

@ -19,16 +19,20 @@ import tinycolor from 'tinycolor2';
/* eslint-disable angular/angularelement */
export default class TbAnalogueLinearGauge {
constructor(containerElement, settings, data, canvasId) {
constructor(ctx, canvasId) {
this.ctx = ctx;
canvasGauges.performance = window.performance; // eslint-disable-line no-undef, angular/window-service
var gaugeElement = $('#'+canvasId, containerElement);
var gaugeElement = $('#'+canvasId, ctx.$container);
var settings = ctx.settings;
var minValue = settings.minValue || 0;
var maxValue = settings.maxValue || 100;
var dataKey = data[0].dataKey;
var dataKey = ctx.data[0].dataKey;
var keyColor = settings.defaultColor || dataKey.color;
var majorTicksCount = settings.majorTicksCount || 10;
@ -44,16 +48,15 @@ export default class TbAnalogueLinearGauge {
var majorTicks = [];
var highlights = [];
var tick = 0;
var tick = minValue;
while(tick<=total) {
var majorTick = tick + minValue;
majorTicks.push(majorTick);
while(tick<=maxValue) {
majorTicks.push(tick);
var nextTick = tick+step;
nextTick = parseFloat(parseFloat(nextTick).toFixed(valueDec));
if (tick<total) {
if (tick<maxValue) {
var highlightColor = tinycolor(keyColor);
var percent = tick/total;
var percent = (tick-minValue)/total;
highlightColor.setAlpha(percent);
var highlight = {
from: tick,
@ -71,6 +74,14 @@ export default class TbAnalogueLinearGauge {
var progressColorStart = tinycolor(keyColor).setAlpha(0.05).toRgbString();
var progressColorEnd = tinycolor(keyColor).darken().toRgbString();
function getFontFamily(fontSettings) {
var family = fontSettings && fontSettings.family ? fontSettings.family : 'Roboto';
if (family === 'RobotoDraft') {
family = 'Roboto';
}
return family;
}
var gaugeData = {
renderTo: gaugeElement[0],
@ -112,10 +123,10 @@ export default class TbAnalogueLinearGauge {
highlightsWidth: (angular.isDefined(settings.highlightsWidth) && settings.highlightsWidth !== null) ? settings.highlightsWidth : 10,
//fonts
fontNumbers: settings.numbersFont && settings.numbersFont.family ? settings.numbersFont.family : 'RobotoDraft',
fontTitle: settings.titleFont && settings.titleFont.family ? settings.titleFont.family : 'RobotoDraft',
fontUnits: settings.unitsFont && settings.unitsFont.family ? settings.unitsFont.family : 'RobotoDraft',
fontValue: settings.valueFont && settings.valueFont.family ? settings.valueFont.family : 'RobotoDraft',
fontNumbers: getFontFamily(settings.numbersFont),
fontTitle: getFontFamily(settings.titleFont),
fontUnits: getFontFamily(settings.unitsFont),
fontValue: getFontFamily(settings.valueFont),
fontNumbersSize: settings.numbersFont && settings.numbersFont.size ? settings.numbersFont.size : 18,
fontTitleSize: settings.titleFont && settings.titleFont.size ? settings.titleFont.size : 24,
@ -153,7 +164,7 @@ export default class TbAnalogueLinearGauge {
colorNeedleShadowDown: settings.colorNeedleShadowDown || 'rgba(188,143,143,0.45)',
// animations
animation: settings.animation !== false,
animation: settings.animation !== false && !ctx.isMobile,
animationDuration: (angular.isDefined(settings.animationDuration) && settings.animationDuration !== null) ? settings.animationDuration : 500,
animationRule: settings.animationRule || 'cycle',
@ -169,13 +180,9 @@ export default class TbAnalogueLinearGauge {
this.gauge = new canvasGauges.LinearGauge(gaugeData).draw();
}
redraw(width, height, data, sizeChanged) {
if (sizeChanged) {
this.gauge.update({width: width, height: height});
}
if (data.length > 0) {
var cellData = data[0];
update() {
if (this.ctx.data.length > 0) {
var cellData = this.ctx.data[0];
if (cellData.data.length > 0) {
var tvPair = cellData.data[cellData.data.length -
1];
@ -184,6 +191,852 @@ export default class TbAnalogueLinearGauge {
}
}
}
mobileModeChanged() {
var animation = this.ctx.settings.animation !== false && !this.ctx.isMobile;
this.gauge.update({animation: animation});
}
resize() {
this.gauge.update({width: this.ctx.width, height: this.ctx.height});
}
static get settingsSchema() {
return {
"schema": {
"type": "object",
"title": "Settings",
"properties": {
"minValue": {
"title": "Minimum value",
"type": "number",
"default": 0
},
"maxValue": {
"title": "Maximum value",
"type": "number",
"default": 100
},
"unitTitle": {
"title": "Unit title",
"type": "string",
"default": null
},
"showUnitTitle": {
"title": "Show unit title",
"type": "boolean",
"default": true
},
"units": {
"title": "Units",
"type": "string",
"default": ""
},
"majorTicksCount": {
"title": "Major ticks count",
"type": "number",
"default": null
},
"minorTicks": {
"title": "Minor ticks count",
"type": "number",
"default": 2
},
"valueBox": {
"title": "Show value box",
"type": "boolean",
"default": true
},
"valueInt": {
"title": "Digits count for integer part of value",
"type": "number",
"default": 3
},
"valueDec": {
"title": "Digits count for decimal part of value",
"type": "number",
"default": 2
},
"defaultColor": {
"title": "Default color",
"type": "string",
"default": null
},
"colorPlate": {
"title": "Plate color",
"type": "string",
"default": "#fff"
},
"colorMajorTicks": {
"title": "Major ticks color",
"type": "string",
"default": "#444"
},
"colorMinorTicks": {
"title": "Minor ticks color",
"type": "string",
"default": "#666"
},
"colorNeedle": {
"title": "Needle color",
"type": "string",
"default": null
},
"colorNeedleEnd": {
"title": "Needle color - end gradient",
"type": "string",
"default": null
},
"colorNeedleShadowUp": {
"title": "Upper half of the needle shadow color",
"type": "string",
"default": "rgba(2,255,255,0.2)"
},
"colorNeedleShadowDown": {
"title": "Drop shadow needle color.",
"type": "string",
"default": "rgba(188,143,143,0.45)"
},
"colorValueBoxRect": {
"title": "Value box rectangle stroke color",
"type": "string",
"default": "#888"
},
"colorValueBoxRectEnd": {
"title": "Value box rectangle stroke color - end gradient",
"type": "string",
"default": "#666"
},
"colorValueBoxBackground": {
"title": "Value box background color",
"type": "string",
"default": "#babab2"
},
"colorValueBoxShadow": {
"title": "Value box shadow color",
"type": "string",
"default": "rgba(0,0,0,1)"
},
"highlights": {
"title": "Highlights",
"type": "array",
"items": {
"title": "Highlight",
"type": "object",
"properties": {
"from": {
"title": "From",
"type": "number"
},
"to": {
"title": "To",
"type": "number"
},
"color": {
"title": "Color",
"type": "string"
}
}
}
},
"highlightsWidth": {
"title": "Highlights width",
"type": "number",
"default": 15
},
"showBorder": {
"title": "Show border",
"type": "boolean",
"default": true
},
"numbersFont": {
"title": "Tick numbers font",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 18
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": null
}
}
},
"titleFont": {
"title": "Title text font",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 24
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": "#888"
}
}
},
"unitsFont": {
"title": "Units text font",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 22
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": "#888"
}
}
},
"valueFont": {
"title": "Value text font",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 40
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": "#444"
},
"shadowColor": {
"title": "Shadow color",
"type": "string",
"default": "rgba(0,0,0,0.3)"
}
}
},
"animation": {
"title": "Enable animation",
"type": "boolean",
"default": true
},
"animationDuration": {
"title": "Animation duration",
"type": "number",
"default": 500
},
"animationRule": {
"title": "Animation rule",
"type": "string",
"default": "cycle"
},
"barStrokeWidth": {
"title": "Bar stroke width",
"type": "number",
"default": 2.5
},
"colorBarStroke": {
"title": "Bar stroke color",
"type": "string",
"default": null
},
"colorBar": {
"title": "Bar background color",
"type": "string",
"default": "#fff"
},
"colorBarEnd": {
"title": "Bar background color - end gradient",
"type": "string",
"default": "#ddd"
},
"colorBarProgress": {
"title": "Progress bar color",
"type": "string",
"default": null
},
"colorBarProgressEnd": {
"title": "Progress bar color - end gradient",
"type": "string",
"default": null
}
},
"required": []
},
"form": [
"barStrokeWidth",
{
"key": "colorBarStroke",
"type": "color"
},
{
"key": "colorBar",
"type": "color"
},
{
"key": "colorBarEnd",
"type": "color"
},
{
"key": "colorBarProgress",
"type": "color"
},
{
"key": "colorBarProgressEnd",
"type": "color"
},
"minValue",
"maxValue",
"unitTitle",
"showUnitTitle",
"units",
"majorTicksCount",
"minorTicks",
"valueBox",
"valueInt",
"valueDec",
{
"key": "defaultColor",
"type": "color"
},
{
"key": "colorPlate",
"type": "color"
},
{
"key": "colorMajorTicks",
"type": "color"
},
{
"key": "colorMinorTicks",
"type": "color"
},
{
"key": "colorNeedle",
"type": "color"
},
{
"key": "colorNeedleEnd",
"type": "color"
},
{
"key": "colorNeedleShadowUp",
"type": "color"
},
{
"key": "colorNeedleShadowDown",
"type": "color"
},
{
"key": "colorValueBoxRect",
"type": "color"
},
{
"key": "colorValueBoxRectEnd",
"type": "color"
},
{
"key": "colorValueBoxBackground",
"type": "color"
},
{
"key": "colorValueBoxShadow",
"type": "color"
},
{
"key": "highlights",
"items": [
"highlights[].from",
"highlights[].to",
{
"key": "highlights[].color",
"type": "color"
}
]
},
"highlightsWidth",
"showBorder",
{
"key": "numbersFont",
"items": [
"numbersFont.family",
"numbersFont.size",
{
"key": "numbersFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "numbersFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "numbersFont.color",
"type": "color"
}
]
},
{
"key": "titleFont",
"items": [
"titleFont.family",
"titleFont.size",
{
"key": "titleFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "titleFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "titleFont.color",
"type": "color"
}
]
},
{
"key": "unitsFont",
"items": [
"unitsFont.family",
"unitsFont.size",
{
"key": "unitsFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "unitsFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "unitsFont.color",
"type": "color"
}
]
},
{
"key": "valueFont",
"items": [
"valueFont.family",
"valueFont.size",
{
"key": "valueFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "valueFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "valueFont.color",
"type": "color"
},
{
"key": "valueFont.shadowColor",
"type": "color"
}
]
},
"animation",
"animationDuration",
{
"key": "animationRule",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "linear",
"label": "Linear"
},
{
"value": "quad",
"label": "Quad"
},
{
"value": "quint",
"label": "Quint"
},
{
"value": "cycle",
"label": "Cycle"
},
{
"value": "bounce",
"label": "Bounce"
},
{
"value": "elastic",
"label": "Elastic"
},
{
"value": "dequad",
"label": "Dequad"
},
{
"value": "dequint",
"label": "Dequint"
},
{
"value": "decycle",
"label": "Decycle"
},
{
"value": "debounce",
"label": "Debounce"
},
{
"value": "delastic",
"label": "Delastic"
}
]
}
]
};
}
}
/* eslint-enable angular/angularelement */

851
ui/src/app/widget/lib/analogue-radial-gauge.js

@ -20,16 +20,20 @@ import tinycolor from 'tinycolor2';
/* eslint-disable angular/angularelement */
export default class TbAnalogueRadialGauge {
constructor(containerElement, settings, data, canvasId) {
constructor(ctx, canvasId) {
this.ctx = ctx;
canvasGauges.performance = window.performance; // eslint-disable-line no-undef, angular/window-service
var gaugeElement = $('#'+canvasId, containerElement);
var gaugeElement = $('#'+canvasId, ctx.$container);
var settings = ctx.settings;
var minValue = settings.minValue || 0;
var maxValue = settings.maxValue || 100;
var dataKey = data[0].dataKey;
var dataKey = ctx.data[0].dataKey;
var keyColor = settings.defaultColor || dataKey.color;
var majorTicksCount = settings.majorTicksCount || 10;
@ -67,6 +71,14 @@ export default class TbAnalogueRadialGauge {
var colorNumbers = tinycolor(keyColor).darken(20).toRgbString();
function getFontFamily(fontSettings) {
var family = fontSettings && fontSettings.family ? fontSettings.family : 'Roboto';
if (family === 'RobotoDraft') {
family = 'Roboto';
}
return family;
}
var gaugeData = {
renderTo: gaugeElement[0],
@ -109,10 +121,10 @@ export default class TbAnalogueRadialGauge {
highlightsWidth: (angular.isDefined(settings.highlightsWidth) && settings.highlightsWidth !== null) ? settings.highlightsWidth : 15,
//fonts
fontNumbers: settings.numbersFont && settings.numbersFont.family ? settings.numbersFont.family : 'RobotoDraft',
fontTitle: settings.titleFont && settings.titleFont.family ? settings.titleFont.family : 'RobotoDraft',
fontUnits: settings.unitsFont && settings.unitsFont.family ? settings.unitsFont.family : 'RobotoDraft',
fontValue: settings.valueFont && settings.valueFont.family ? settings.valueFont.family : 'RobotoDraft',
fontNumbers: getFontFamily(settings.numbersFont),
fontTitle: getFontFamily(settings.titleFont),
fontUnits: getFontFamily(settings.unitsFont),
fontValue: getFontFamily(settings.valueFont),
fontNumbersSize: settings.numbersFont && settings.numbersFont.size ? settings.numbersFont.size : 18,
fontTitleSize: settings.titleFont && settings.titleFont.size ? settings.titleFont.size : 24,
@ -150,7 +162,7 @@ export default class TbAnalogueRadialGauge {
colorNeedleShadowDown: settings.colorNeedleShadowDown || 'rgba(188,143,143,0.45)',
// animations
animation: settings.animation !== false,
animation: settings.animation !== false && !ctx.isMobile,
animationDuration: (angular.isDefined(settings.animationDuration) && settings.animationDuration !== null) ? settings.animationDuration : 500,
animationRule: settings.animationRule || 'cycle',
@ -178,13 +190,9 @@ export default class TbAnalogueRadialGauge {
this.gauge = new canvasGauges.RadialGauge(gaugeData).draw();
}
redraw(width, height, data, sizeChanged) {
if (sizeChanged) {
this.gauge.update({width: width, height: height});
}
if (data.length > 0) {
var cellData = data[0];
update() {
if (this.ctx.data.length > 0) {
var cellData = this.ctx.data[0];
if (cellData.data.length > 0) {
var tvPair = cellData.data[cellData.data.length -
1];
@ -192,7 +200,820 @@ export default class TbAnalogueRadialGauge {
this.gauge.value = value;
}
}
}
mobileModeChanged() {
var animation = this.ctx.settings.animation !== false && !this.ctx.isMobile;
this.gauge.update({animation: animation});
}
resize() {
this.gauge.update({width: this.ctx.width, height: this.ctx.height});
}
static get settingsSchema() {
return {
"schema": {
"type": "object",
"title": "Settings",
"properties": {
"minValue": {
"title": "Minimum value",
"type": "number",
"default": 0
},
"maxValue": {
"title": "Maximum value",
"type": "number",
"default": 100
},
"unitTitle": {
"title": "Unit title",
"type": "string",
"default": null
},
"showUnitTitle": {
"title": "Show unit title",
"type": "boolean",
"default": true
},
"units": {
"title": "Units",
"type": "string",
"default": ""
},
"majorTicksCount": {
"title": "Major ticks count",
"type": "number",
"default": null
},
"minorTicks": {
"title": "Minor ticks count",
"type": "number",
"default": 2
},
"valueBox": {
"title": "Show value box",
"type": "boolean",
"default": true
},
"valueInt": {
"title": "Digits count for integer part of value",
"type": "number",
"default": 3
},
"valueDec": {
"title": "Digits count for decimal part of value",
"type": "number",
"default": 2
},
"defaultColor": {
"title": "Default color",
"type": "string",
"default": null
},
"colorPlate": {
"title": "Plate color",
"type": "string",
"default": "#fff"
},
"colorMajorTicks": {
"title": "Major ticks color",
"type": "string",
"default": "#444"
},
"colorMinorTicks": {
"title": "Minor ticks color",
"type": "string",
"default": "#666"
},
"colorNeedle": {
"title": "Needle color",
"type": "string",
"default": null
},
"colorNeedleEnd": {
"title": "Needle color - end gradient",
"type": "string",
"default": null
},
"colorNeedleShadowUp": {
"title": "Upper half of the needle shadow color",
"type": "string",
"default": "rgba(2,255,255,0.2)"
},
"colorNeedleShadowDown": {
"title": "Drop shadow needle color.",
"type": "string",
"default": "rgba(188,143,143,0.45)"
},
"colorValueBoxRect": {
"title": "Value box rectangle stroke color",
"type": "string",
"default": "#888"
},
"colorValueBoxRectEnd": {
"title": "Value box rectangle stroke color - end gradient",
"type": "string",
"default": "#666"
},
"colorValueBoxBackground": {
"title": "Value box background color",
"type": "string",
"default": "#babab2"
},
"colorValueBoxShadow": {
"title": "Value box shadow color",
"type": "string",
"default": "rgba(0,0,0,1)"
},
"highlights": {
"title": "Highlights",
"type": "array",
"items": {
"title": "Highlight",
"type": "object",
"properties": {
"from": {
"title": "From",
"type": "number"
},
"to": {
"title": "To",
"type": "number"
},
"color": {
"title": "Color",
"type": "string"
}
}
}
},
"highlightsWidth": {
"title": "Highlights width",
"type": "number",
"default": 15
},
"showBorder": {
"title": "Show border",
"type": "boolean",
"default": true
},
"numbersFont": {
"title": "Tick numbers font",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 18
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": null
}
}
},
"titleFont": {
"title": "Title text font",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 24
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": "#888"
}
}
},
"unitsFont": {
"title": "Units text font",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 22
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": "#888"
}
}
},
"valueFont": {
"title": "Value text font",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 40
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": "#444"
},
"shadowColor": {
"title": "Shadow color",
"type": "string",
"default": "rgba(0,0,0,0.3)"
}
}
},
"animation": {
"title": "Enable animation",
"type": "boolean",
"default": true
},
"animationDuration": {
"title": "Animation duration",
"type": "number",
"default": 500
},
"animationRule": {
"title": "Animation rule",
"type": "string",
"default": "cycle"
},
"startAngle": {
"title": "Start ticks angle",
"type": "number",
"default": 45
},
"ticksAngle": {
"title": "Ticks angle",
"type": "number",
"default": 270
},
"needleCircleSize": {
"title": "Needle circle size",
"type": "number",
"default": 10
}
},
"required": []
},
"form": [
"startAngle",
"ticksAngle",
"needleCircleSize",
"minValue",
"maxValue",
"unitTitle",
"showUnitTitle",
"units",
"majorTicksCount",
"minorTicks",
"valueBox",
"valueInt",
"valueDec",
{
"key": "defaultColor",
"type": "color"
},
{
"key": "colorPlate",
"type": "color"
},
{
"key": "colorMajorTicks",
"type": "color"
},
{
"key": "colorMinorTicks",
"type": "color"
},
{
"key": "colorNeedle",
"type": "color"
},
{
"key": "colorNeedleEnd",
"type": "color"
},
{
"key": "colorNeedleShadowUp",
"type": "color"
},
{
"key": "colorNeedleShadowDown",
"type": "color"
},
{
"key": "colorValueBoxRect",
"type": "color"
},
{
"key": "colorValueBoxRectEnd",
"type": "color"
},
{
"key": "colorValueBoxBackground",
"type": "color"
},
{
"key": "colorValueBoxShadow",
"type": "color"
},
{
"key": "highlights",
"items": [
"highlights[].from",
"highlights[].to",
{
"key": "highlights[].color",
"type": "color"
}
]
},
"highlightsWidth",
"showBorder",
{
"key": "numbersFont",
"items": [
"numbersFont.family",
"numbersFont.size",
{
"key": "numbersFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "numbersFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "numbersFont.color",
"type": "color"
}
]
},
{
"key": "titleFont",
"items": [
"titleFont.family",
"titleFont.size",
{
"key": "titleFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "titleFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "titleFont.color",
"type": "color"
}
]
},
{
"key": "unitsFont",
"items": [
"unitsFont.family",
"unitsFont.size",
{
"key": "unitsFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "unitsFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "unitsFont.color",
"type": "color"
}
]
},
{
"key": "valueFont",
"items": [
"valueFont.family",
"valueFont.size",
{
"key": "valueFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "valueFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "valueFont.color",
"type": "color"
},
{
"key": "valueFont.shadowColor",
"type": "color"
}
]
},
"animation",
"animationDuration",
{
"key": "animationRule",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "linear",
"label": "Linear"
},
{
"value": "quad",
"label": "Quad"
},
{
"value": "quint",
"label": "Quint"
},
{
"value": "cycle",
"label": "Cycle"
},
{
"value": "bounce",
"label": "Bounce"
},
{
"value": "elastic",
"label": "Elastic"
},
{
"value": "dequad",
"label": "Dequad"
},
{
"value": "dequint",
"label": "Dequint"
},
{
"value": "decycle",
"label": "Decycle"
},
{
"value": "debounce",
"label": "Debounce"
},
{
"value": "delastic",
"label": "Delastic"
}
]
}
]
};
}
}
/* eslint-enable angular/angularelement */

921
ui/src/app/widget/lib/canvas-digital-gauge.js

@ -0,0 +1,921 @@
/*
* 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 $ from 'jquery';
import tinycolor from 'tinycolor2';
import canvasGauges from 'canvas-gauges';
import CanvasDigitalGauge from './CanvasDigitalGauge';
/* eslint-disable angular/angularelement */
export default class TbCanvasDigitalGauge {
constructor(ctx, canvasId) {
this.ctx = ctx;
canvasGauges.performance = window.performance; // eslint-disable-line no-undef, angular/window-service
var gaugeElement = $('#'+canvasId, ctx.$container);
var settings = ctx.settings;
this.localSettings = {};
this.localSettings.minValue = settings.minValue || 0;
this.localSettings.maxValue = settings.maxValue || 100;
this.localSettings.gaugeType = settings.gaugeType || 'arc';
this.localSettings.neonGlowBrightness = settings.neonGlowBrightness || 0;
this.localSettings.dashThickness = settings.dashThickness || 0;
this.localSettings.roundedLineCap = settings.roundedLineCap === true;
var dataKey = ctx.data[0].dataKey;
var keyColor = settings.defaultColor || dataKey.color;
this.localSettings.unitTitle = ((settings.showUnitTitle === true) ?
(settings.unitTitle && settings.unitTitle.length > 0 ?
settings.unitTitle : dataKey.label) : '');
this.localSettings.gaugeWidthScale = settings.gaugeWidthScale || 0.75;
this.localSettings.gaugeColor = settings.gaugeColor || tinycolor(keyColor).setAlpha(0.2).toRgbString();
if (!settings.levelColors || settings.levelColors.length <= 0) {
this.localSettings.levelColors = [keyColor];
} else {
this.localSettings.levelColors = settings.levelColors.slice();
}
this.localSettings.decimals = (angular.isDefined(settings.decimals) && settings.decimals !== null)
? settings.decimals : 0;
this.localSettings.units = settings.units || '';
this.localSettings.hideValue = settings.showValue !== true;
this.localSettings.hideMinMax = settings.showMinMax !== true;
this.localSettings.title = ((settings.showTitle === true) ?
(settings.title && settings.title.length > 0 ?
settings.title : dataKey.label) : '');
this.localSettings.titleFont = {};
var settingsTitleFont = settings.titleFont;
if (!settingsTitleFont) {
settingsTitleFont = {};
}
function getFontFamily(fontSettings) {
var family = fontSettings && fontSettings.family ? fontSettings.family : 'Roboto';
if (family === 'RobotoDraft') {
family = 'Roboto';
}
return family;
}
this.localSettings.titleFont.family = getFontFamily(settingsTitleFont);
this.localSettings.titleFont.size = settingsTitleFont.size ? settingsTitleFont.size : 12;
this.localSettings.titleFont.style = settingsTitleFont.style ? settingsTitleFont.style : 'normal';
this.localSettings.titleFont.weight = settingsTitleFont.weight ? settingsTitleFont.weight : '500';
this.localSettings.titleFont.color = settingsTitleFont.color ? settingsTitleFont.color : keyColor;
this.localSettings.valueFont = {};
var settingsValueFont = settings.valueFont;
if (!settingsValueFont) {
settingsValueFont = {};
}
this.localSettings.valueFont.family = getFontFamily(settingsValueFont);
this.localSettings.valueFont.size = settingsValueFont.size ? settingsValueFont.size : 18;
this.localSettings.valueFont.style = settingsValueFont.style ? settingsValueFont.style : 'normal';
this.localSettings.valueFont.weight = settingsValueFont.weight ? settingsValueFont.weight : '500';
this.localSettings.valueFont.color = settingsValueFont.color ? settingsValueFont.color : keyColor;
this.localSettings.minMaxFont = {};
var settingsMinMaxFont = settings.minMaxFont;
if (!settingsMinMaxFont) {
settingsMinMaxFont = {};
}
this.localSettings.minMaxFont.family = getFontFamily(settingsMinMaxFont);
this.localSettings.minMaxFont.size = settingsMinMaxFont.size ? settingsMinMaxFont.size : 10;
this.localSettings.minMaxFont.style = settingsMinMaxFont.style ? settingsMinMaxFont.style : 'normal';
this.localSettings.minMaxFont.weight = settingsMinMaxFont.weight ? settingsMinMaxFont.weight : '500';
this.localSettings.minMaxFont.color = settingsMinMaxFont.color ? settingsMinMaxFont.color : keyColor;
this.localSettings.labelFont = {};
var settingsLabelFont = settings.labelFont;
if (!settingsLabelFont) {
settingsLabelFont = {};
}
this.localSettings.labelFont.family = getFontFamily(settingsLabelFont);
this.localSettings.labelFont.size = settingsLabelFont.size ? settingsLabelFont.size : 8;
this.localSettings.labelFont.style = settingsLabelFont.style ? settingsLabelFont.style : 'normal';
this.localSettings.labelFont.weight = settingsLabelFont.weight ? settingsLabelFont.weight : '500';
this.localSettings.labelFont.color = settingsLabelFont.color ? settingsLabelFont.color : keyColor;
var gaugeData = {
renderTo: gaugeElement[0],
gaugeWidthScale: this.localSettings.gaugeWidthScale,
gaugeColor: this.localSettings.gaugeColor,
levelColors: this.localSettings.levelColors,
title: this.localSettings.title,
fontTitleSize: this.localSettings.titleFont.size,
fontTitleStyle: this.localSettings.titleFont.style,
fontTitleWeight: this.localSettings.titleFont.weight,
colorTitle: this.localSettings.titleFont.color,
fontTitle: this.localSettings.titleFont.family,
fontValueSize: this.localSettings.valueFont.size,
fontValueStyle: this.localSettings.valueFont.style,
fontValueWeight: this.localSettings.valueFont.weight,
colorValue: this.localSettings.valueFont.color,
fontValue: this.localSettings.valueFont.family,
fontMinMaxSize: this.localSettings.minMaxFont.size,
fontMinMaxStyle: this.localSettings.minMaxFont.style,
fontMinMaxWeight: this.localSettings.minMaxFont.weight,
colorMinMax: this.localSettings.minMaxFont.color,
fontMinMax: this.localSettings.minMaxFont.family,
fontLabelSize: this.localSettings.labelFont.size,
fontLabelStyle: this.localSettings.labelFont.style,
fontLabelWeight: this.localSettings.labelFont.weight,
colorLabel: this.localSettings.labelFont.color,
fontLabel: this.localSettings.labelFont.family,
minValue: this.localSettings.minValue,
maxValue: this.localSettings.maxValue,
gaugeType: this.localSettings.gaugeType,
dashThickness: this.localSettings.dashThickness,
roundedLineCap: this.localSettings.roundedLineCap,
symbol: this.localSettings.units,
label: this.localSettings.unitTitle,
hideValue: this.localSettings.hideValue,
hideMinMax: this.localSettings.hideMinMax,
valueDec: this.localSettings.decimals,
neonGlowBrightness: this.localSettings.neonGlowBrightness,
// animations
animation: settings.animation !== false && !ctx.isMobile,
animationDuration: (angular.isDefined(settings.animationDuration) && settings.animationDuration !== null) ? settings.animationDuration : 500,
animationRule: settings.animationRule || 'linear',
isMobile: ctx.isMobile
};
this.gauge = new CanvasDigitalGauge(gaugeData).draw();
}
update() {
if (this.ctx.data.length > 0) {
var cellData = this.ctx.data[0];
if (cellData.data.length > 0) {
var tvPair = cellData.data[cellData.data.length -
1];
var value = tvPair[1];
this.gauge.value = value;
}
}
}
mobileModeChanged() {
var animation = this.ctx.settings.animation !== false && !this.ctx.isMobile;
this.gauge.update({animation: animation, isMobile: this.ctx.isMobile});
}
resize() {
this.gauge.update({width: this.ctx.width, height: this.ctx.height});
}
static get settingsSchema() {
return {
"schema": {
"type": "object",
"title": "Settings",
"properties": {
"minValue": {
"title": "Minimum value",
"type": "number",
"default": 0
},
"maxValue": {
"title": "Maximum value",
"type": "number",
"default": 100
},
"gaugeType": {
"title": "Gauge type",
"type": "string",
"default": "arc"
},
"donutStartAngle": {
"title": "Angle to start from when in donut mode",
"type": "number",
"default": 90
},
"neonGlowBrightness": {
"title": "Neon glow effect brightness, (0-100), 0 - disable effect",
"type": "number",
"default": 0
},
"dashThickness": {
"title": "Thickness of the stripes, 0 - no stripes",
"type": "number",
"default": 0
},
"roundedLineCap": {
"title": "Display rounded line cap",
"type": "boolean",
"default": false
},
"title": {
"title": "Gauge title",
"type": "string",
"default": null
},
"showTitle": {
"title": "Show gauge title",
"type": "boolean",
"default": false
},
"unitTitle": {
"title": "Unit title",
"type": "string",
"default": null
},
"showUnitTitle": {
"title": "Show unit title",
"type": "boolean",
"default": false
},
"showValue": {
"title": "Show value text",
"type": "boolean",
"default": true
},
"showMinMax": {
"title": "Show min and max values",
"type": "boolean",
"default": true
},
"gaugeWidthScale": {
"title": "Width of the gauge element",
"type": "number",
"default": 0.75
},
"defaultColor": {
"title": "Default color",
"type": "string",
"default": null
},
"gaugeColor": {
"title": "Background color of the gauge element",
"type": "string",
"default": null
},
"levelColors": {
"title": "Colors of indicator, from lower to upper",
"type": "array",
"items": {
"title": "Color",
"type": "string"
}
},
"animation": {
"title": "Enable animation",
"type": "boolean",
"default": true
},
"animationDuration": {
"title": "Animation duration",
"type": "number",
"default": 500
},
"animationRule": {
"title": "Animation rule",
"type": "string",
"default": "linear"
},
"decimals": {
"title": "Number of digits after floating point",
"type": "number",
"default": 0
},
"units": {
"title": "Special symbol to show next to value",
"type": "string",
"default": ""
},
"titleFont": {
"title": "Gauge title font",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 12
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": null
}
}
},
"labelFont": {
"title": "Font of label showing under value",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 8
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": null
}
}
},
"valueFont": {
"title": "Font of label showing current value",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 18
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": null
}
}
},
"minMaxFont": {
"title": "Font of minimum and maximum labels",
"type": "object",
"properties": {
"family": {
"title": "Font family",
"type": "string",
"default": "Roboto"
},
"size": {
"title": "Size",
"type": "number",
"default": 10
},
"style": {
"title": "Style",
"type": "string",
"default": "normal"
},
"weight": {
"title": "Weight",
"type": "string",
"default": "500"
},
"color": {
"title": "color",
"type": "string",
"default": null
}
}
}
}
},
"form": [
"minValue",
"maxValue",
{
"key": "gaugeType",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "arc",
"label": "Arc"
},
{
"value": "donut",
"label": "Donut"
},
{
"value": "horizontalBar",
"label": "Horizontal bar"
},
{
"value": "verticalBar",
"label": "Vertical bar"
}
]
},
"donutStartAngle",
"neonGlowBrightness",
"dashThickness",
"roundedLineCap",
"title",
"showTitle",
"unitTitle",
"showUnitTitle",
"showValue",
"showMinMax",
"gaugeWidthScale",
{
"key": "defaultColor",
"type": "color"
},
{
"key": "gaugeColor",
"type": "color"
},
{
"key": "levelColors",
"items": [
{
"key": "levelColors[]",
"type": "color"
}
]
},
"animation",
"animationDuration",
{
"key": "animationRule",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "linear",
"label": "Linear"
},
{
"value": "quad",
"label": "Quad"
},
{
"value": "quint",
"label": "Quint"
},
{
"value": "cycle",
"label": "Cycle"
},
{
"value": "bounce",
"label": "Bounce"
},
{
"value": "elastic",
"label": "Elastic"
},
{
"value": "dequad",
"label": "Dequad"
},
{
"value": "dequint",
"label": "Dequint"
},
{
"value": "decycle",
"label": "Decycle"
},
{
"value": "debounce",
"label": "Debounce"
},
{
"value": "delastic",
"label": "Delastic"
}
]
},
"decimals",
"units",
{
"key": "titleFont",
"items": [
"titleFont.family",
"titleFont.size",
{
"key": "titleFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "titleFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "titleFont.color",
"type": "color"
}
]
},
{
"key": "labelFont",
"items": [
"labelFont.family",
"labelFont.size",
{
"key": "labelFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "labelFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "labelFont.color",
"type": "color"
}
]
},
{
"key": "valueFont",
"items": [
"valueFont.family",
"valueFont.size",
{
"key": "valueFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "valueFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "valueFont.color",
"type": "color"
}
]
},
{
"key": "minMaxFont",
"items": [
"minMaxFont.family",
"minMaxFont.size",
{
"key": "minMaxFont.style",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "italic",
"label": "Italic"
},
{
"value": "oblique",
"label": "Oblique"
}
]
},
{
"key": "minMaxFont.weight",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "normal",
"label": "Normal"
},
{
"value": "bold",
"label": "Bold"
},
{
"value": "bolder",
"label": "Bolder"
},
{
"value": "lighter",
"label": "Lighter"
},
{
"value": "100",
"label": "100"
},
{
"value": "200",
"label": "200"
},
{
"value": "300",
"label": "300"
},
{
"value": "400",
"label": "400"
},
{
"value": "500",
"label": "500"
},
{
"value": "600",
"label": "600"
},
{
"value": "700",
"label": "800"
},
{
"value": "800",
"label": "800"
},
{
"value": "900",
"label": "900"
}
]
},
{
"key": "minMaxFont.color",
"type": "color"
}
]
}
]
};
}
}
/* eslint-enable angular/angularelement */

591
ui/src/app/widget/lib/digital-gauge.js

@ -1,591 +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 $ from 'jquery';
import tinycolor from 'tinycolor2';
import 'justgage';
import Raphael from 'raphael';
/* eslint-disable angular/angularelement */
export default class TbDigitalGauge {
constructor(containerElement, settings, data) {
var tbGauge = this;
window.Raphael = Raphael; // eslint-disable-line no-undef, angular/window-service
var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; // eslint-disable-line no-undef
var gaugeElement = $(containerElement);
this.localSettings = {};
this.localSettings.minValue = settings.minValue || 0;
this.localSettings.maxValue = settings.maxValue || 100;
this.localSettings.gaugeType = settings.gaugeType || 'arc';
this.localSettings.donutStartAngle = (angular.isDefined(settings.donutStartAngle) && settings.donutStartAngle !== null)
? settings.donutStartAngle : 90;
this.localSettings.neonGlowBrightness = settings.neonGlowBrightness || 0;
this.localSettings.dashThickness = settings.dashThickness || 0;
this.localSettings.roundedLineCap = settings.roundedLineCap === true;
var dataKey = data[0].dataKey;
var keyColor = settings.defaultColor || dataKey.color;
this.localSettings.title = ((settings.showTitle === true) ?
(settings.title && settings.title.length > 0 ?
settings.title : dataKey.label) : '');
this.localSettings.unitTitle = ((settings.showUnitTitle === true) ?
(settings.unitTitle && settings.unitTitle.length > 0 ?
settings.unitTitle : dataKey.label) : '');
this.localSettings.gaugeWidthScale = settings.gaugeWidthScale || 0.75;
this.localSettings.gaugeColor = settings.gaugeColor || tinycolor(keyColor).setAlpha(0.2).toRgbString();
if (!settings.levelColors || settings.levelColors.length <= 0) {
this.localSettings.levelColors = [keyColor, keyColor];
} else {
this.localSettings.levelColors = settings.levelColors.slice();
}
if (this.localSettings.neonGlowBrightness) {
this.localSettings.origLevelColors = [];
for (var i = 0; i < this.localSettings.levelColors.length; i++) {
this.localSettings.origLevelColors.push(this.localSettings.levelColors[i]);
this.localSettings.levelColors[i] = tinycolor(this.localSettings.levelColors[i]).brighten(this.localSettings.neonGlowBrightness).toHexString();
}
var colorsCount = this.localSettings.origLevelColors.length;
var inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1;
this.localSettings.colorsRange = [];
for (i = 0; i < this.localSettings.origLevelColors.length; i++) {
var percentage = inc * i;
var tColor = tinycolor(this.localSettings.origLevelColors[i]);
this.localSettings.colorsRange[i] = {
pct: percentage,
color: tColor.toRgb(),
rgbString: tColor.toRgbString
};
}
}
this.localSettings.refreshAnimationType = settings.refreshAnimationType || '>';
this.localSettings.refreshAnimationTime = settings.refreshAnimationTime || 700;
this.localSettings.startAnimationType = settings.startAnimationType || '>';
this.localSettings.startAnimationTime = settings.startAnimationTime || 700;
this.localSettings.decimals = (angular.isDefined(settings.decimals) && settings.decimals !== null)
? settings.decimals : 0;
this.localSettings.units = settings.units || '';
this.localSettings.hideValue = settings.showValue !== true;
this.localSettings.hideMinMax = settings.showMinMax !== true;
this.localSettings.titleFont = {};
var settingsTitleFont = settings.titleFont;
if (!settingsTitleFont) {
settingsTitleFont = {};
}
this.localSettings.titleFont.family = settingsTitleFont.family || 'RobotoDraft';
this.localSettings.titleFont.size = settingsTitleFont.size ? settingsTitleFont.size : 12;
this.localSettings.titleFont.style = settingsTitleFont.style ? settingsTitleFont.style : 'normal';
this.localSettings.titleFont.weight = settingsTitleFont.weight ? settingsTitleFont.weight : '500';
this.localSettings.titleFont.color = settingsTitleFont.color ? settingsTitleFont.color : keyColor;
this.localSettings.labelFont = {};
var settingsLabelFont = settings.labelFont;
if (!settingsLabelFont) {
settingsLabelFont = {};
}
this.localSettings.labelFont.family = settingsLabelFont.family || 'RobotoDraft';
this.localSettings.labelFont.size = settingsLabelFont.size ? settingsLabelFont.size : 8;
this.localSettings.labelFont.style = settingsLabelFont.style ? settingsLabelFont.style : 'normal';
this.localSettings.labelFont.weight = settingsLabelFont.weight ? settingsLabelFont.weight : '500';
this.localSettings.labelFont.color = settingsLabelFont.color ? settingsLabelFont.color : keyColor;
this.localSettings.valueFont = {};
var settingsValueFont = settings.valueFont;
if (!settingsValueFont) {
settingsValueFont = {};
}
this.localSettings.valueFont.family = settingsValueFont.family || 'RobotoDraft';
this.localSettings.valueFont.size = settingsValueFont.size ? settingsValueFont.size : 18;
this.localSettings.valueFont.style = settingsValueFont.style ? settingsValueFont.style : 'normal';
this.localSettings.valueFont.weight = settingsValueFont.weight ? settingsValueFont.weight : '500';
this.localSettings.valueFont.color = settingsValueFont.color ? settingsValueFont.color : keyColor;
this.localSettings.minMaxFont = {};
var settingsMinMaxFont = settings.minMaxFont;
if (!settingsMinMaxFont) {
settingsMinMaxFont = {};
}
this.localSettings.minMaxFont.family = settingsMinMaxFont.family || 'RobotoDraft';
this.localSettings.minMaxFont.size = settingsMinMaxFont.size ? settingsMinMaxFont.size : 10;
this.localSettings.minMaxFont.style = settingsMinMaxFont.style ? settingsMinMaxFont.style : 'normal';
this.localSettings.minMaxFont.weight = settingsMinMaxFont.weight ? settingsMinMaxFont.weight : '500';
this.localSettings.minMaxFont.color = settingsMinMaxFont.color ? settingsMinMaxFont.color : keyColor;
if (this.localSettings.neonGlowBrightness) {
this.localSettings.titleFont.origColor = this.localSettings.titleFont.color;
this.localSettings.titleFont.color = tinycolor(this.localSettings.titleFont.color).brighten(this.localSettings.neonGlowBrightness).toHexString();
this.localSettings.labelFont.origColor = this.localSettings.labelFont.color;
this.localSettings.labelFont.color = tinycolor(this.localSettings.labelFont.color).brighten(this.localSettings.neonGlowBrightness).toHexString();
this.localSettings.valueFont.origColor = this.localSettings.valueFont.color;
this.localSettings.valueFont.color = tinycolor(this.localSettings.valueFont.color).brighten(this.localSettings.neonGlowBrightness).toHexString();
this.localSettings.minMaxFont.origColor = this.localSettings.minMaxFont.color;
this.localSettings.minMaxFont.color = tinycolor(this.localSettings.minMaxFont.color).brighten(this.localSettings.neonGlowBrightness).toHexString();
}
var gaugeOptions = {
parentNode: gaugeElement[0],
value: 0,
min: this.localSettings.minValue,
max: this.localSettings.maxValue,
title: this.localSettings.title,
label: this.localSettings.unitTitle,
humanFriendlyDecimal: 0,
gaugeWidthScale: this.localSettings.gaugeWidthScale,
relativeGaugeSize: true,
gaugeColor: this.localSettings.gaugeColor,
levelColors: this.localSettings.levelColors,
refreshAnimationType: this.localSettings.refreshAnimationType,
refreshAnimationTime: this.localSettings.refreshAnimationTime,
startAnimationType: this.localSettings.startAnimationType,
startAnimationTime: this.localSettings.startAnimationTime,
humanFriendly: false,
donut: this.localSettings.gaugeType === 'donut',
donutStartAngle: this.localSettings.donutStartAngle,
decimals: this.localSettings.decimals,
pointer: false,
symbol: this.localSettings.units,
hideValue: this.localSettings.hideValue,
hideMinMax: this.localSettings.hideMinMax,
titleFontColor: this.localSettings.titleFont.color,
labelFontColor: this.localSettings.labelFont.color,
valueFontColor: this.localSettings.valueFont.color,
valueFontFamily: this.localSettings.valueFont.family
};
this.gauge = new JustGage(gaugeOptions); // eslint-disable-line no-undef
var gParams = this.gauge.params;
var titleTextElement = $(this.gauge.txtTitle.node);
titleTextElement.css('fontFamily', this.localSettings.titleFont.family);
titleTextElement.css('fontSize', this.localSettings.titleFont.size + 'px');
titleTextElement.css('fontStyle', this.localSettings.titleFont.style);
titleTextElement.css('fontWeight', this.localSettings.titleFont.weight);
titleTextElement.css('textTransform', 'uppercase');
var labelTextElement = $(this.gauge.txtLabel.node);
labelTextElement.css('fontFamily', this.localSettings.labelFont.family);
labelTextElement.css('fontSize', this.localSettings.labelFont.size + 'px');
labelTextElement.css('fontStyle', this.localSettings.labelFont.style);
labelTextElement.css('fontWeight', this.localSettings.labelFont.weight);
labelTextElement.css('textTransform', 'uppercase');
var valueTextElement = $(this.gauge.txtValue.node);
valueTextElement.css('fontSize', this.localSettings.valueFont.size + 'px');
valueTextElement.css('fontStyle', this.localSettings.valueFont.style);
valueTextElement.css('fontWeight', this.localSettings.valueFont.weight);
var minValTextElement = $(this.gauge.txtMin.node);
var maxValTextElement = $(this.gauge.txtMax.node);
minValTextElement.css('fontFamily', this.localSettings.minMaxFont.family);
maxValTextElement.css('fontFamily', this.localSettings.minMaxFont.family);
minValTextElement.css('fontSize', this.localSettings.minMaxFont.size+'px');
maxValTextElement.css('fontSize', this.localSettings.minMaxFont.size+'px');
minValTextElement.css('fontStyle', this.localSettings.minMaxFont.style);
maxValTextElement.css('fontStyle', this.localSettings.minMaxFont.style);
minValTextElement.css('fontWeight', this.localSettings.minMaxFont.weight);
maxValTextElement.css('fontWeight', this.localSettings.minMaxFont.weight);
minValTextElement.css('fill', this.localSettings.minMaxFont.color);
maxValTextElement.css('fill', this.localSettings.minMaxFont.color);
var gaugeLevelElement = $(this.gauge.level.node);
var gaugeBackElement = $(this.gauge.gauge.node);
var w = gParams.widgetW;
var gws = this.localSettings.gaugeWidthScale;
var Ro, Ri;
if (this.localSettings.gaugeType === 'donut') {
Ro = w / 2 - w / 7;
} else {
Ro = w / 2 - w / 10;
}
Ri = Ro - w / 6.666666666666667 * gws;
gParams.strokeWidth = Ro - Ri;
gParams.viewport = {
x: 0,
y: 0,
width: gParams.canvasW,
height: gParams.canvasH
}
var maxW;
if (this.localSettings.gaugeType === 'donut') {
if (gaugeOptions.title && gaugeOptions.title.length > 0) {
gParams.viewport.height = 140;
} else {
gParams.viewport.y = 17;
gParams.viewport.height = 120;
}
gParams.viewport.x = 40;
gParams.viewport.width = 120;
$('tspan', labelTextElement).attr('dy', '6');
if (!this.localSettings.unitTitle || this.localSettings.unitTitle.length === 0) {
var Cy = gParams.widgetH / 1.95 + gParams.dy;
gParams.valueY = Cy + (this.localSettings.valueFont.size-4)/2;
this.gauge.txtValue.attr({"y": gParams.valueY });
}
} else if (this.localSettings.gaugeType === 'arc') {
if (gaugeOptions.title && gaugeOptions.title.length > 0) {
gParams.viewport.y = 5;
gParams.viewport.height = 140;
} else {
gParams.viewport.y = 40;
gParams.viewport.height = 100;
}
if (this.localSettings.roundedLineCap) {
$('tspan', minValTextElement).attr('dy', ''+(gParams.strokeWidth/2));
$('tspan', maxValTextElement).attr('dy', ''+(gParams.strokeWidth/2));
}
} else if (this.localSettings.gaugeType === 'horizontalBar') {
gParams.titleY = gParams.dy + gParams.widgetH / 3.5 + (this.localSettings.title === '' ? 0 : this.localSettings.titleFont.size);
this.gauge.txtTitle.attr({"y": gParams.titleY });
gParams.titleBottom = gParams.titleY + (this.localSettings.title === '' ? 0 : 8);
gParams.valueY = gParams.titleBottom + (this.localSettings.hideValue ? 0 : this.localSettings.valueFont.size);
gParams.barTop = gParams.valueY + 8;
gParams.barBottom = gParams.barTop + gParams.strokeWidth;
this.gauge.txtValue.attr({"y": gParams.valueY });
if (this.localSettings.hideMinMax && this.localSettings.unitTitle === '') {
gParams.labelY = gParams.barBottom;
gParams.barLeft = this.localSettings.minMaxFont.size/3;
gParams.barRight = gParams.viewport.width - this.localSettings.minMaxFont.size/3;
} else {
maxW = Math.max(this.gauge.txtMin.node.getComputedTextLength(), this.gauge.txtMax.node.getComputedTextLength());
gParams.minX = maxW/2 + this.localSettings.minMaxFont.size/3;
gParams.maxX = gParams.viewport.width - maxW/2 - this.localSettings.minMaxFont.size/3;
gParams.barLeft = gParams.minX;
gParams.barRight = gParams.maxX;
gParams.labelY = gParams.barBottom + 4 + this.localSettings.labelFont.size;
this.gauge.txtLabel.attr({"y": gParams.labelY });
this.gauge.txtMin.attr({"x": gParams.minX, "y": gParams.labelY });
this.gauge.txtMax.attr({"x": gParams.maxX, "y": gParams.labelY });
}
gParams.viewport.y = 40;
gParams.viewport.height = gParams.labelY-25;
} else if (this.localSettings.gaugeType === 'verticalBar') {
gParams.titleY = (this.localSettings.title === '' ? 0 : this.localSettings.titleFont.size) + 8;
this.gauge.txtTitle.attr({"y": gParams.titleY });
gParams.titleBottom = gParams.titleY + (this.localSettings.title === '' ? 0 : 8);
gParams.valueY = gParams.titleBottom + (this.localSettings.hideValue ? 0 : this.localSettings.valueFont.size);
gParams.barTop = gParams.valueY + 8;
this.gauge.txtValue.attr({"y": gParams.valueY });
gParams.labelY = gParams.widgetH - 16;
if (this.localSettings.unitTitle === '') {
gParams.barBottom = gParams.labelY;
} else {
gParams.barBottom = gParams.labelY - 4 - this.localSettings.labelFont.size;
this.gauge.txtLabel.attr({"y": gParams.labelY });
}
gParams.minX = gParams.maxX = (gParams.widgetW/2 + gParams.dx) + gParams.strokeWidth/2 + this.localSettings.minMaxFont.size/3;
gParams.minY = gParams.barBottom;
gParams.maxY = gParams.barTop;
this.gauge.txtMin.attr({"text-anchor": "start", "x": gParams.minX, "y": gParams.minY });
this.gauge.txtMax.attr({"text-anchor": "start", "x": gParams.maxX, "y": gParams.maxY });
gParams.prefWidth = gParams.strokeWidth;
if (!this.localSettings.hideMinMax) {
maxW = Math.max(this.gauge.txtMin.node.getComputedTextLength(), this.gauge.txtMax.node.getComputedTextLength());
gParams.prefWidth += (maxW + this.localSettings.minMaxFont.size ) * 2;
}
gParams.viewport.x = (gParams.canvasW - gParams.prefWidth)/2;
gParams.viewport.width = gParams.prefWidth;
}
this.gauge.canvas.setViewBox(gParams.viewport.x, gParams.viewport.y, gParams.viewport.width, gParams.viewport.height, true);
if (this.localSettings.dashThickness) {
var Rm = Ri + gParams.strokeWidth * 0.5;
var circumference = Math.PI * Rm;
if (this.localSettings.gaugeType === 'donut') {
circumference *=2;
}
var dashCount = Math.floor(circumference / (this.localSettings.dashThickness));
if (this.localSettings.gaugeType === 'donut') {
dashCount = (dashCount | 1) - 1;
} else {
dashCount = (dashCount - 1) | 1;
}
var dashLength = circumference/dashCount;
gaugeLevelElement.attr('stroke-dasharray', '' + dashLength + 'px');
gaugeBackElement.attr('stroke-dasharray', '' + dashLength + 'px');
}
function getColor(val, pct) {
var lower, upper, range, rangePct, pctLower, pctUpper, color;
if (tbGauge.localSettings.colorsRange.length === 1) {
return tbGauge.localSettings.colorsRange[0].rgbString;
}
if (pct === 0) {
return tbGauge.localSettings.colorsRange[0].rgbString;
}
for (var j = 0; j < tbGauge.localSettings.colorsRange.length; j++) {
if (pct <= tbGauge.localSettings.colorsRange[j].pct) {
lower = tbGauge.localSettings.colorsRange[j - 1];
upper = tbGauge.localSettings.colorsRange[j];
range = upper.pct - lower.pct;
rangePct = (pct - lower.pct) / range;
pctLower = 1 - rangePct;
pctUpper = rangePct;
color = tinycolor({
r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
});
return color.toRgbString();
}
}
}
this.gauge.canvas.customAttributes.pki = function(value, min, max, w, h, dx, dy, gws, donut, reverse) { // eslint-disable-line no-unused-vars
var alpha, Rm, Ro, Ri, Cx, Cy, Xm, Ym, Xo, Yo, path;
if (tbGauge.localSettings.neonGlowBrightness && !isFirefox
&& tbGauge.floodColorElement1 && tbGauge.floodColorElement2) {
var progress = (value - min) / (max - min);
var resultColor = getColor(value, progress);
var brightenColor1 = tinycolor(resultColor).brighten(tbGauge.localSettings.neonGlowBrightness).toRgbString();
var brightenColor2 = resultColor;
tbGauge.floodColorElement1.setAttribute('flood-color', brightenColor1);
tbGauge.floodColorElement2.setAttribute('flood-color', brightenColor2);
}
var gaugeType = tbGauge.localSettings.gaugeType;
if (gaugeType === 'donut') {
alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI;
Ro = w / 2 - w / 7;
Ri = Ro - w / 6.666666666666667 * gws;
Rm = Ri + (Ro - Ri)/2;
Cx = w / 2 + dx;
Cy = h / 1.95 + dy;
Xm = w / 2 + dx + Rm * Math.cos(alpha);
Ym = h - (h - Cy) - Rm * Math.sin(alpha);
path = "M" + (Cx - Rm) + "," + Cy + " ";
if ((value - min) > ((max - min) / 2)) {
path += "A" + Rm + "," + Rm + " 0 0 1 " + (Cx + Rm) + "," + Cy + " ";
path += "A" + Rm + "," + Rm + " 0 0 1 " + Xm + "," + Ym + " ";
} else {
path += "A" + Rm + "," + Rm + " 0 0 1 " + Xm + "," + Ym + " ";
}
return {
path: path
};
} else if (gaugeType === 'arc') {
alpha = (1 - (value - min) / (max - min)) * Math.PI;
Ro = w / 2 - w / 10;
Ri = Ro - w / 6.666666666666667 * gws;
Rm = Ri + (Ro - Ri)/2;
Cx = w / 2 + dx;
Cy = h / 1.25 + dy;
Xm = w / 2 + dx + Rm * Math.cos(alpha);
Ym = h - (h - Cy) - Rm * Math.sin(alpha);
path = "M" + (Cx - Rm) + "," + Cy + " ";
path += "A" + Rm + "," + Rm + " 0 0 1 " + Xm + "," + Ym + " ";
return {
path: path
};
} else if (gaugeType === 'horizontalBar') {
Cx = tbGauge.gauge.params.barLeft;
Cy = tbGauge.gauge.params.barTop + tbGauge.gauge.params.strokeWidth/2;
Ro = (tbGauge.gauge.params.barRight - tbGauge.gauge.params.barLeft)/2;
alpha = (value - min) / (max - min);
Xo = Cx + 2 * Ro * alpha;
path = "M" + Cx + "," + Cy + " ";
path += "H" + " " + Xo;
return {
path: path
};
} else if (gaugeType === 'verticalBar') {
Cx = w / 2 + dx;
Cy = tbGauge.gauge.params.barBottom;
Ro = (tbGauge.gauge.params.barBottom - tbGauge.gauge.params.barTop)/2;
alpha = (value - min) / (max - min);
Yo = Cy - 2 * Ro * alpha;
path = "M" + Cx + "," + Cy + " ";
path += "V" + " " + Yo;
return {
path: path
};
}
};
var gaugeAttrs = {
"stroke": this.gauge.gauge.attrs.fill,
"fill": 'rgba(0,0,0,0)',
pki: [ this.gauge.config.max,
this.gauge.config.min,
this.gauge.config.max,
gParams.widgetW,
gParams.widgetH,
gParams.dx,
gParams.dy,
this.gauge.config.gaugeWidthScale,
this.gauge.config.donut,
this.gauge.config.reverse
]
};
gaugeAttrs['stroke-width'] = gParams.strokeWidth;
var gaugeLevelAttrs = {
"stroke": this.gauge.level.attrs.fill,
"fill": 'rgba(0,0,0,0)'
};
gaugeLevelAttrs['stroke-width'] = gParams.strokeWidth;
if (this.localSettings.roundedLineCap) {
gaugeAttrs['stroke-linecap'] = 'round';
gaugeLevelAttrs['stroke-linecap'] = 'round';
}
this.gauge.gauge.attr(gaugeAttrs);
this.gauge.level.attr(gaugeLevelAttrs);
this.gauge.level.animate = function(attrs, refreshAnimationTime, refreshAnimationType) {
if (attrs.fill) {
attrs.stroke = attrs.fill;
attrs.fill = 'rgba(0,0,0,0)';
}
return Raphael.el.animate.call(tbGauge.gauge.level, attrs, refreshAnimationTime, refreshAnimationType);
}
function neonShadow(color) {
var brightenColor = tinycolor(color).brighten(tbGauge.localSettings.neonGlowBrightness);
return '0 0 10px '+brightenColor+','+
'0 0 20px '+brightenColor+','+
'0 0 30px '+brightenColor+','+
'0 0 40px '+ color +','+
'0 0 70px '+ color +','+
'0 0 80px '+ color +','+
'0 0 100px '+ color +','+
'0 0 150px '+ color;
}
if (this.localSettings.neonGlowBrightness) {
titleTextElement.css('textShadow', neonShadow(this.localSettings.titleFont.origColor));
valueTextElement.css('textShadow', neonShadow(this.localSettings.valueFont.origColor));
labelTextElement.css('textShadow', neonShadow(this.localSettings.labelFont.origColor));
minValTextElement.css('textShadow', neonShadow(this.localSettings.minMaxFont.origColor));
maxValTextElement.css('textShadow', neonShadow(this.localSettings.minMaxFont.origColor));
}
if (this.localSettings.neonGlowBrightness && !isFirefox) {
var filterX = (gParams.viewport.x / gParams.viewport.width)*100 + '%';
var filterY = (gParams.viewport.y / gParams.viewport.height)*100 + '%';
var svgBackFilterId = 'backBlurFilter' + Math.random();
var svgBackFilter = document.createElementNS("http://www.w3.org/2000/svg", "filter"); // eslint-disable-line no-undef, angular/document-service
svgBackFilter.setAttribute('id', svgBackFilterId);
svgBackFilter.setAttribute('filterUnits', 'userSpaceOnUse');
svgBackFilter.setAttribute('x', filterX);
svgBackFilter.setAttribute('y', filterY);
svgBackFilter.setAttribute('width', '100%');
svgBackFilter.setAttribute('height', '100%');
svgBackFilter.innerHTML =
'<feComponentTransfer>'+
'<feFuncR type="linear" slope="1.5"/>'+
'<feFuncG type="linear" slope="1.5"/>'+
'<feFuncB type="linear" slope="1.5"/>'+
'</feComponentTransfer>'+
'<feGaussianBlur stdDeviation="3" result="coloredBlur"></feGaussianBlur>'+
'<feMerge>'+
'<feMergeNode in="coloredBlur"/>'+
'<feMergeNode in="SourceGraphic"/>'+
'</feMerge>';
gaugeBackElement.attr('filter', 'url(#'+svgBackFilterId+')');
var svgFillFilterId = 'fillBlurFilter' + Math.random();
var svgFillFilter = document.createElementNS("http://www.w3.org/2000/svg", "filter"); // eslint-disable-line no-undef, angular/document-service
svgFillFilter.setAttribute('id', svgFillFilterId);
svgFillFilter.setAttribute('filterUnits', 'userSpaceOnUse');
svgFillFilter.setAttribute('x', filterX);
svgFillFilter.setAttribute('y', filterY);
svgFillFilter.setAttribute('width', '100%');
svgFillFilter.setAttribute('height', '100%');
var brightenColor1 = tinycolor(this.localSettings.origLevelColors[0]).brighten(this.localSettings.neonGlowBrightness).toRgbString();
var brightenColor2 = tinycolor(this.localSettings.origLevelColors[0]).toRgbString();
svgFillFilter.innerHTML =
'<feFlood flood-color="'+brightenColor1+'" result="flood1" />'+
'<feComposite in="flood1" in2="SourceGraphic" operator="in" result="floodShape" />'+
'<feGaussianBlur in="floodShape" stdDeviation="3" result="blur" />'+
'<feFlood flood-color="'+brightenColor2+'" result="flood2" />'+
'<feComposite in="flood2" in2="SourceGraphic" operator="in" result="floodShape2" />'+
'<feGaussianBlur in="floodShape2" stdDeviation="12" result="blur2" />'+
'<feMerge result="blurs">'+
' <feMergeNode in="blur2"/>'+
' <feMergeNode in="blur2"/>'+
' <feMergeNode in="blur"/>'+
' <feMergeNode in="blur"/>'+
' <feMergeNode in="SourceGraphic"/>'+
'</feMerge>';
this.floodColorElement1 = $('feFlood:nth-of-type(1)', svgFillFilter)[0];
this.floodColorElement2 = $('feFlood:nth-of-type(2)', svgFillFilter)[0];
gaugeLevelElement.attr('filter', 'url(#'+svgFillFilterId+')');
var svgDefsElement = $('svg > defs', containerElement);
svgDefsElement[0].appendChild(svgBackFilter);
svgDefsElement[0].appendChild(svgFillFilter);
} else {
gaugeBackElement.attr('filter', '');
gaugeLevelElement.attr('filter', '');
}
}
redraw(data) {
if (data.length > 0) {
var cellData = data[0];
if (cellData.data.length > 0) {
var tvPair = cellData.data[cellData.data.length -
1];
var value = tvPair[1];
if (this.gauge.config.value !== value) {
this.gauge.refresh(value);
}
}
}
}
}
/* eslint-enable angular/angularelement */

472
ui/src/app/widget/lib/flot-widget.js

@ -0,0 +1,472 @@
/*
* 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 $ from 'jquery';
import tinycolor from 'tinycolor2';
import moment from 'moment';
import 'flot/lib/jquery.colorhelpers';
import 'flot/src/jquery.flot';
import 'flot/src/plugins/jquery.flot.time';
import 'flot/src/plugins/jquery.flot.selection';
import 'flot/src/plugins/jquery.flot.pie';
/* eslint-disable angular/angularelement */
export default class TbFlot {
constructor(ctx, chartType) {
this.ctx = ctx;
this.chartType = chartType || 'line';
var colors = [];
for (var i in ctx.data) {
var series = ctx.data[i];
series.label = series.dataKey.label;
colors.push(series.dataKey.color);
var keySettings = series.dataKey.settings;
series.lines = {
fill: keySettings.fillLines || false,
show: keySettings.showLines || true
};
series.points = {
show: false,
radius: 8
};
if (keySettings.showPoints === true) {
series.points.show = true;
series.points.lineWidth = 5;
series.points.radius = 3;
}
var lineColor = tinycolor(series.dataKey.color);
lineColor.setAlpha(.75);
series.highlightColor = lineColor.toRgbString();
}
var tbFlot = this;
ctx.tooltip = $('#flot-series-tooltip');
if (ctx.tooltip.length === 0) {
ctx.tooltip = $("<div id=flot-series-tooltip' class='flot-mouse-value'></div>");
ctx.tooltip.css({
fontSize: "12px",
fontFamily: "Roboto",
lineHeight: "24px",
opacity: "1",
backgroundColor: "rgba(0,0,0,0.7)",
color: "#fff",
position: "absolute",
display: "none",
zIndex: "100",
padding: "2px 8px",
borderRadius: "4px"
}).appendTo("body");
}
ctx.tooltipFormatter = function(item) {
var label = item.series.label;
var color = item.series.color;
var content = '';
if (tbFlot.chartType === 'line') {
var timestamp = parseInt(item.datapoint[0]);
var date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');
content += '<b>' + date + '</b></br>';
}
var lineSpan = $('<span></span>');
lineSpan.css({
backgroundColor: color,
width: "20px",
height: "3px",
display: "inline-block",
verticalAlign: "middle",
marginRight: "5px"
});
content += lineSpan.prop('outerHTML');
var labelSpan = $('<span>' + label + ':</span>');
labelSpan.css({
marginRight: "10px"
});
content += labelSpan.prop('outerHTML');
var value = tbFlot.chartType === 'line' ? item.datapoint[1] : item.datapoint[1][0][1];
content += ' <b>' + value.toFixed(ctx.trackDecimals);
if (settings.units) {
content += ' ' + settings.units;
}
if (tbFlot.chartType === 'pie') {
content += ' (' + Math.round(item.series.percent) + ' %)';
}
content += '</b>';
return content;
};
var settings = ctx.settings;
ctx.trackDecimals = angular.isDefined(settings.decimals) ? settings.decimals : 1;
var font = {
color: settings.fontColor || "#545454",
size: settings.fontSize || 10,
family: "Roboto"
};
var options = {
colors: colors,
title: null,
subtitle: null,
shadowSize: settings.shadowSize || 4,
HtmlText: false,
grid: {
hoverable: true,
mouseActiveRadius: 10,
autoHighlight: true
},
selection : { mode : ctx.isMobile ? null : 'x' },
legend : {
show: true,
position : 'nw',
labelBoxBorderColor: '#CCCCCC',
backgroundColor : '#F0F0F0',
backgroundOpacity: 0.85,
font: angular.copy(font)
}
};
if (settings.legend) {
options.legend.show = settings.legend.show !== false;
options.legend.position = settings.legend.position || 'nw';
options.legend.labelBoxBorderColor = settings.legend.labelBoxBorderColor || null;
options.legend.backgroundColor = settings.legend.backgroundColor || null;
options.legend.backgroundOpacity = angular.isDefined(settings.legend.backgroundOpacity) ?
settings.legend.backgroundOpacity : 0.85;
}
if (this.chartType === 'line') {
options.xaxis = {
mode: 'time',
timezone: 'browser',
font: angular.copy(font),
labelFont: angular.copy(font)
};
options.yaxis = {
font: angular.copy(font),
labelFont: angular.copy(font)
};
if (settings.xaxis) {
if (settings.xaxis.showLabels === false) {
options.xaxis.tickFormatter = function() {
return '';
};
}
options.xaxis.font.color = settings.xaxis.color || options.xaxis.font.color;
options.xaxis.label = settings.xaxis.title || null;
options.xaxis.labelFont.color = options.xaxis.font.color;
options.xaxis.labelFont.size = options.xaxis.font.size+2;
options.xaxis.labelFont.weight = "bold";
}
if (settings.yaxis) {
if (settings.yaxis.showLabels === false) {
options.yaxis.tickFormatter = function() {
return '';
};
}
options.yaxis.font.color = settings.yaxis.color || options.yaxis.font.color;
options.yaxis.label = settings.yaxis.title || null;
options.yaxis.labelFont.color = options.yaxis.font.color;
options.yaxis.labelFont.size = options.yaxis.font.size+2;
options.yaxis.labelFont.weight = "bold";
}
options.grid.borderWidth = 1;
options.grid.color = settings.fontColor || "#545454";
if (settings.grid) {
options.grid.color = settings.grid.color || "#545454";
options.grid.backgroundColor = settings.grid.backgroundColor || null;
options.grid.tickColor = settings.grid.tickColor || "#DDDDDD";
options.grid.borderWidth = angular.isDefined(settings.grid.outlineWidth) ?
settings.grid.outlineWidth : 1;
if (settings.grid.verticalLines === false) {
options.xaxis.tickLength = 0;
}
if (settings.grid.horizontalLines === false) {
options.yaxis.tickLength = 0;
}
}
options.xaxis.min = ctx.timeWindow.minTime;
options.xaxis.max = ctx.timeWindow.maxTime;
} else if (this.chartType === 'pie') {
options.series = {
pie: {
show: true,
label: {
show: settings.showLabels === true
},
radius: settings.radius || 1,
innerRadius: settings.innerRadius || 0,
stroke: {
color: '#fff',
width: 0
},
tilt: settings.tilt || 1,
shadow: {
left: 5,
top: 15,
alpha: 0.02
}
}
}
if (settings.stroke) {
options.series.pie.stroke.color = settings.stroke.color || '#fff';
options.series.pie.stroke.width = settings.stroke.width || 0;
}
if (options.series.pie.label.show) {
options.series.pie.label.formatter = function (label, series) {
return "<div class='pie-label'>" + label + "<br/>" + Math.round(series.percent) + "%</div>";
}
options.series.pie.label.radius = 3/4;
options.series.pie.label.background = {
opacity: 0.8
};
}
}
//Experimental
this.ctx.animatedPie = settings.animatedPie === true;
this.options = options;
if (this.chartType === 'pie' && this.ctx.animatedPie) {
this.ctx.pieDataAnimationDuration = 250;
this.ctx.pieData = angular.copy(this.ctx.data);
this.ctx.pieRenderedData = [];
this.ctx.pieTargetData = [];
for (i in this.ctx.data) {
this.ctx.pieTargetData[i] = (this.ctx.data[i].data && this.ctx.data[i].data[0])
? this.ctx.data[i].data[0][1] : 0;
}
this.pieDataRendered();
this.ctx.plot = $.plot(this.ctx.$container, this.ctx.pieData, this.options);
} else {
this.ctx.plot = $.plot(this.ctx.$container, this.ctx.data, this.options);
}
this.checkMouseEvents();
}
update() {
if (!this.isMouseInteraction) {
if (this.chartType === 'line') {
this.ctx.plot.getOptions().xaxes[0].min = this.ctx.timeWindow.minTime;
this.ctx.plot.getOptions().xaxes[0].max = this.ctx.timeWindow.maxTime;
}
if (this.chartType === 'line') {
this.ctx.plot.setData(this.ctx.data);
this.ctx.plot.setupGrid();
this.ctx.plot.draw();
} else if (this.chartType === 'pie') {
if (this.ctx.animatedPie) {
this.nextPieDataAnimation(true);
} else {
this.ctx.plot.setData(this.ctx.data);
this.ctx.plot.draw();
}
}
}
}
pieDataRendered() {
for (var i in this.ctx.pieTargetData) {
var value = this.ctx.pieTargetData[i] ? this.ctx.pieTargetData[i] : 0;
this.ctx.pieRenderedData[i] = value;
if (!this.ctx.pieData[i].data[0]) {
this.ctx.pieData[i].data[0] = [0,0];
}
this.ctx.pieData[i].data[0][1] = value;
}
}
nextPieDataAnimation(start) {
if (start) {
this.finishPieDataAnimation();
this.ctx.pieAnimationStartTime = this.ctx.pieAnimationLastTime = Date.now();
for (var i in this.ctx.data) {
this.ctx.pieTargetData[i] = (this.ctx.data[i].data && this.ctx.data[i].data[0])
? this.ctx.data[i].data[0][1] : 0;
}
}
if (this.ctx.pieAnimationCaf) {
this.ctx.pieAnimationCaf();
this.ctx.pieAnimationCaf = null;
}
var self = this;
this.ctx.pieAnimationCaf = this.ctx.$scope.tbRaf(
function () {
self.onPieDataAnimation();
}
);
}
onPieDataAnimation() {
var time = Date.now();
var elapsed = time - this.ctx.pieAnimationLastTime;//this.ctx.pieAnimationStartTime;
var progress = (time - this.ctx.pieAnimationStartTime) / this.ctx.pieDataAnimationDuration;
if (progress >= 1) {
this.finishPieDataAnimation();
} else {
if (elapsed >= 40) {
for (var i in this.ctx.pieTargetData) {
var prevValue = this.ctx.pieRenderedData[i];
var targetValue = this.ctx.pieTargetData[i];
var value = prevValue + (targetValue - prevValue) * progress;
if (!this.ctx.pieData[i].data[0]) {
this.ctx.pieData[i].data[0] = [0,0];
}
this.ctx.pieData[i].data[0][1] = value;
}
this.ctx.plot.setData(this.ctx.pieData);
this.ctx.plot.draw();
this.ctx.pieAnimationLastTime = time;
}
this.nextPieDataAnimation(false);
}
}
finishPieDataAnimation() {
this.pieDataRendered();
this.ctx.plot.setData(this.ctx.pieData);
this.ctx.plot.draw();
}
resize() {
this.ctx.plot.resize();
if (this.chartType === 'line') {
this.ctx.plot.setupGrid();
}
this.ctx.plot.draw();
}
checkMouseEvents() {
if (this.ctx.isMobile || this.ctx.isEdit) {
this.disableMouseEvents();
} else if (!this.ctx.isEdit) {
this.enableMouseEvents();
}
}
enableMouseEvents() {
this.ctx.$container.css('pointer-events','');
this.ctx.$container.addClass('mouse-events');
this.options.selection = { mode : 'x' };
var tbFlot = this;
if (!this.flotHoverHandler) {
this.flotHoverHandler = function (event, pos, item) {
if (item) {
var pageX = item.pageX || pos.pageX;
var pageY = item.pageY || pos.pageY;
tbFlot.ctx.tooltip.html(tbFlot.ctx.tooltipFormatter(item))
.css({top: pageY+5, left: 0})
.fadeIn(200);
var windowWidth = $( window ).width(); //eslint-disable-line
var tooltipWidth = tbFlot.ctx.tooltip.width();
var left = pageX+5;
if (windowWidth - pageX < tooltipWidth + 50) {
left = pageX - tooltipWidth - 10;
}
tbFlot.ctx.tooltip.css({
left: left
});
} else {
tbFlot.ctx.tooltip.stop(true);
tbFlot.ctx.tooltip.hide();
}
};
this.ctx.$container.bind('plothover', this.flotHoverHandler);
}
if (!this.flotSelectHandler) {
this.flotSelectHandler = function (event, ranges) {
tbFlot.ctx.plot.clearSelection();
tbFlot.ctx.timewindowFunctions.onUpdateTimewindow(ranges.xaxis.from, ranges.xaxis.to);
};
this.ctx.$container.bind('plotselected', this.flotSelectHandler);
}
if (!this.dblclickHandler) {
this.dblclickHandler = function () {
tbFlot.ctx.timewindowFunctions.onResetTimewindow();
};
this.ctx.$container.bind('dblclick', this.dblclickHandler);
}
if (!this.mousedownHandler) {
this.mousedownHandler = function () {
tbFlot.isMouseInteraction = true;
};
this.ctx.$container.bind('mousedown', this.mousedownHandler);
}
if (!this.mouseupHandler) {
this.mouseupHandler = function () {
tbFlot.isMouseInteraction = false;
};
this.ctx.$container.bind('mouseup', this.mouseupHandler);
}
if (!this.mouseleaveHandler) {
this.mouseleaveHandler = function () {
tbFlot.ctx.tooltip.stop(true);
tbFlot.ctx.tooltip.hide();
tbFlot.isMouseInteraction = false;
};
this.ctx.$container.bind('mouseleave', this.mouseleaveHandler);
}
}
disableMouseEvents() {
this.ctx.$container.css('pointer-events','none');
this.ctx.$container.removeClass('mouse-events');
this.options.selection = { mode : null };
if (this.flotHoverHandler) {
this.ctx.$container.unbind('plothover', this.flotHoverHandler);
this.flotHoverHandler = null;
}
if (this.flotSelectHandler) {
this.ctx.$container.unbind('plotselected', this.flotSelectHandler);
this.flotSelectHandler = null;
}
if (this.dblclickHandler) {
this.ctx.$container.unbind('dblclick', this.dblclickHandler);
this.dblclickHandler = null;
}
if (this.mousedownHandler) {
this.ctx.$container.unbind('mousedown', this.mousedownHandler);
this.mousedownHandler = null;
}
if (this.mouseupHandler) {
this.ctx.$container.unbind('mouseup', this.mouseupHandler);
this.mouseupHandler = null;
}
if (this.mouseleaveHandler) {
this.ctx.$container.unbind('mouseleave', this.mouseleaveHandler);
this.mouseleaveHandler = null;
}
}
}
/* eslint-enable angular/angularelement */

12
ui/src/app/widget/lib/google-map.js

@ -14,15 +14,13 @@
* limitations under the License.
*/
import $ from 'jquery';
var gmGlobals = {
loadingGmId: null,
gmApiKeys: {}
}
export default class TbGoogleMap {
constructor(containerElement, defaultZoomLevel, dontFitMapBounds, minZoomLevel, gmApiKey) {
constructor($containerElement, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, gmApiKey) {
var tbMap = this;
this.defaultZoomLevel = defaultZoomLevel;
@ -37,18 +35,22 @@ export default class TbGoogleMap {
}
function displayError(message) {
$(containerElement).html( // eslint-disable-line angular/angularelement
$containerElement.html( // eslint-disable-line angular/angularelement
"<div class='error'>"+ message + "</div>"
);
}
function initGoogleMap() {
tbMap.map = new google.maps.Map(containerElement, { // eslint-disable-line no-undef
tbMap.map = new google.maps.Map($containerElement[0], { // eslint-disable-line no-undef
scrollwheel: false,
zoom: tbMap.defaultZoomLevel || 8
});
if (initCallback) {
initCallback();
}
}
this.mapId = '' + Math.random().toString(36).substr(2, 9);

99
ui/src/app/widget/lib/map-widget.js

@ -20,9 +20,10 @@ import TbGoogleMap from './google-map';
import TbOpenStreetMap from './openstreet-map';
export default class TbMapWidget {
constructor(mapProvider, drawRoutes, containerElement, settings, datasources) {
constructor(mapProvider, drawRoutes, ctx) {
var tbMap = this;
this.ctx = ctx;
this.drawRoutes = drawRoutes;
this.markers = [];
@ -31,7 +32,8 @@ export default class TbMapWidget {
}
this.locationsSettings = [];
this.varsRegex = /\$\{([^\}]*)\}/g;
this.tooltipsUpdated = false;
var settings = ctx.settings;
if (settings.defaultZoomLevel) {
if (settings.defaultZoomLevel > 0 && settings.defaultZoomLevel < 21) {
@ -68,8 +70,8 @@ export default class TbMapWidget {
}
if (variableInfo.dataKeyIndex === -1) {
var offset = 0;
for (var i=0;i<datasources.length;i++) {
var datasource = datasources[i];
for (var i=0;i<ctx.datasources.length;i++) {
var datasource = ctx.datasources[i];
for (var k = 0; k < datasource.dataKeys.length; k++) {
var dataKey = datasource.dataKeys[k];
if (dataKey.label === label) {
@ -162,15 +164,21 @@ export default class TbMapWidget {
}
var minZoomLevel = this.drawRoutes ? 18 : 15;
var initCallback = function() {
tbMap.update();
tbMap.resize();
};
if (mapProvider === 'google-map') {
this.map = new TbGoogleMap(containerElement, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.gmApiKey);
this.map = new TbGoogleMap(ctx.$container, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.gmApiKey);
} else if (mapProvider === 'openstreet-map') {
this.map = new TbOpenStreetMap(containerElement, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel);
this.map = new TbOpenStreetMap(ctx.$container, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel);
}
}
redraw(data, sizeChanged, dataUpdate) {
update() {
var tbMap = this;
@ -404,58 +412,55 @@ export default class TbMapWidget {
}
if (this.map && this.map.inited()) {
if (data) {
if (this.ctx.data) {
if (!this.locations) {
loadLocations(data);
} else if (dataUpdate) {
updateLocations(data);
loadLocations(this.ctx.data);
} else {
updateLocations(this.ctx.data);
}
}
if (sizeChanged) {
this.map.invalidateSize();
if (!this.dontFitMapBounds) {
var bounds = this.map.createBounds();
for (var m in this.markers) {
bounds.extend(this.map.getMarkerPosition(this.markers[m]));
}
if (this.polylines) {
for (var p in this.polylines) {
this.map.extendBounds(bounds, this.polylines[p]);
var tooltips = this.map.getTooltips();
for (var t in tooltips) {
var tooltip = tooltips[t];
var text = tooltip.pattern;
var replaceInfo = tooltip.replaceInfo;
for (var v in replaceInfo.variables) {
var variableInfo = replaceInfo.variables[v];
var txtVal = '';
if (variableInfo.dataKeyIndex > -1) {
var varData = this.ctx.data[variableInfo.dataKeyIndex].data;
if (varData.length > 0) {
var val = varData[varData.length - 1][1];
if (isNumber(val)) {
txtVal = padValue(val, variableInfo.valDec, 0);
} else {
txtVal = val;
}
}
}
this.map.fitBounds(bounds);
text = text.split(variableInfo.variable).join(txtVal);
}
tooltip.popup.setContent(text);
}
}
}
if (!this.tooltipsUpdated || dataUpdate) {
this.tooltipsUpdated = true;
var tooltips = this.map.getTooltips();
for (var t in tooltips) {
var tooltip = tooltips[t];
var text = tooltip.pattern;
var replaceInfo = tooltip.replaceInfo;
for (var v in replaceInfo.variables) {
var variableInfo = replaceInfo.variables[v];
var txtVal = '';
if (variableInfo.dataKeyIndex > -1) {
var varData = data[variableInfo.dataKeyIndex].data;
if (varData.length > 0) {
var val = varData[varData.length - 1][1];
if (isNumber(val)) {
txtVal = padValue(val, variableInfo.valDec, 0);
} else {
txtVal = val;
}
}
}
text = text.split(variableInfo.variable).join(txtVal);
resize() {
if (this.map && this.map.inited()) {
this.map.invalidateSize();
if (!this.dontFitMapBounds && this.locations && this.locations.size > 0) {
var bounds = this.map.createBounds();
for (var m in this.markers) {
bounds.extend(this.map.getMarkerPosition(this.markers[m]));
}
if (this.polylines) {
for (var p in this.polylines) {
this.map.extendBounds(bounds, this.polylines[p]);
}
tooltip.popup.setContent(text);
}
this.map.fitBounds(bounds);
}
}
}
}

8
ui/src/app/widget/lib/openstreet-map.js

@ -19,19 +19,23 @@ import L from 'leaflet/dist/leaflet';
export default class TbOpenStreetMap {
constructor(containerElement, defaultZoomLevel, dontFitMapBounds, minZoomLevel) {
constructor($containerElement, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel) {
this.defaultZoomLevel = defaultZoomLevel;
this.dontFitMapBounds = dontFitMapBounds;
this.minZoomLevel = minZoomLevel;
this.tooltips = [];
this.map = L.map(containerElement).setView([0, 0], this.defaultZoomLevel || 8);
this.map = L.map($containerElement[0]).setView([0, 0], this.defaultZoomLevel || 8);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(this.map);
if (initCallback) {
setTimeout(initCallback, 0); //eslint-disable-line
}
}
inited() {

21
ui/src/app/widget/widget-library.controller.js

@ -20,7 +20,7 @@ import selectWidgetTypeTemplate from './select-widget-type.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function WidgetLibraryController($scope, $rootScope, widgetService, userService,
export default function WidgetLibraryController($scope, $rootScope, $q, widgetService, userService,
$state, $stateParams, $document, $mdDialog, $translate, $filter, types) {
var vm = this;
@ -29,8 +29,11 @@ export default function WidgetLibraryController($scope, $rootScope, widgetServic
vm.widgetsBundle;
vm.widgetTypes = [];
vm.dashboardInitComplete = false;
vm.noData = noData;
vm.dashboardInited = dashboardInited;
vm.dashboardInitFailed = dashboardInitFailed;
vm.addWidgetType = addWidgetType;
vm.openWidgetType = openWidgetType;
vm.removeWidgetType = removeWidgetType;
@ -39,6 +42,7 @@ export default function WidgetLibraryController($scope, $rootScope, widgetServic
vm.isReadOnly = isReadOnly;
function loadWidgetLibrary() {
var deferred = $q.defer();
$rootScope.loading = true;
widgetService.getWidgetsBundle(widgetsBundleId).then(
function success(widgetsBundle) {
@ -61,6 +65,7 @@ export default function WidgetLibraryController($scope, $rootScope, widgetServic
loadNext(0);
} else {
$rootScope.loading = false;
deferred.resolve();
}
function loadNextOrComplete(i) {
@ -69,6 +74,7 @@ export default function WidgetLibraryController($scope, $rootScope, widgetServic
loadNext(i);
} else {
$rootScope.loading = false;
deferred.resolve();
}
}
@ -110,15 +116,26 @@ export default function WidgetLibraryController($scope, $rootScope, widgetServic
);
} else {
$rootScope.loading = false;
deferred.resolve();
}
}, function fail() {
$rootScope.loading = false;
deferred.reject();
}
);
return deferred.promise;
}
function noData() {
return vm.widgetTypes.length == 0;
return vm.dashboardInitComplete && vm.widgetTypes.length == 0;
}
function dashboardInitFailed() {
vm.dashboardInitComplete = true;
}
function dashboardInited() {
vm.dashboardInitComplete = true;
}
function addWidgetType($event) {

4
ui/src/app/widget/widget-library.tpl.html

@ -34,7 +34,9 @@
is-remove-action-enabled="!vm.isReadOnly()"
on-edit-widget="vm.openWidgetType(event, widget)"
on-remove-widget="vm.removeWidgetType(event, widget)"
load-widgets="vm.loadWidgetLibrary()">
load-widgets="vm.loadWidgetLibrary()"
on-init="vm.dashboardInited(dashboard)"
on-init-failed="vm.dashboardInitFailed(e)">
</tb-dashboard>
<section layout="row" layout-wrap class="tb-footer-buttons md-fab ">
<md-button ng-if="!vm.isReadOnly()" ng-disabled="loading" class="tb-btn-footer md-accent md-hue-2 md-fab" ng-click="vm.addWidgetType($event)" aria-label="{{ 'widget.add-widget-type' | translate }}" >

BIN
ui/src/font/Segment7Standard.otf

Binary file not shown.

2
ui/src/index.html

@ -27,8 +27,6 @@
<link rel="icon"
type="image/x-icon"
href="static/thingsboard.ico" />
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=RobotoDraft:100,100italic,300,300italic,400,500,700,900,400italic" />
<link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons" />
</head>
<body>
<ui-view layout="row" layout-fill>

684
ui/src/locale/en_US.json

@ -1,684 +0,0 @@
{
"access": {
"unauthorized": "Unauthorized",
"unauthorized-access": "Unauthorized Access",
"unauthorized-access-text": "You should sign in to have access to this resource!",
"access-forbidden": "Access Forbidden",
"access-forbidden-text": "You haven't access rights to this location!<br/>Try to sign in with different user if you still wish to gain access to this location.",
"refresh-token-expired": "Session has expired",
"refresh-token-failed": "Unable to refresh session"
},
"action": {
"activate": "Activate",
"suspend": "Suspend",
"save": "Save",
"saveAs": "Save as",
"cancel": "Cancel",
"ok": "OK",
"delete": "Delete",
"add": "Add",
"yes": "Yes",
"no": "No",
"update": "Update",
"remove": "Remove",
"search": "Search",
"assign": "Assign",
"unassign": "Unassign",
"apply": "Apply",
"apply-changes": "Apply changes",
"edit-mode": "Edit mode",
"enter-edit-mode": "Enter edit mode",
"decline-changes": "Decline changes",
"close": "Close",
"back": "Back",
"run": "Run",
"sign-in": "Sign in!",
"edit": "Edit",
"view": "View",
"create": "Create",
"drag": "Drag",
"refresh": "Refresh",
"undo": "Undo",
"copy": "Copy",
"paste": "Paste",
"import": "Import",
"export": "Export"
},
"admin": {
"general": "General",
"general-settings": "General Settings",
"outgoing-mail": "Outgoing Mail",
"outgoing-mail-settings": "Outgoing Mail Settings",
"system-settings": "System Settings",
"test-mail-sent": "Test mail was successfully sent!",
"base-url": "Base URL",
"base-url-required": "Base URL is required.",
"mail-from": "Mail From",
"mail-from-required": "Mail From is required.",
"smtp-protocol": "SMTP protocol",
"smtp-host": "SMTP host",
"smtp-host-required": "SMTP host is required.",
"smtp-port": "SMTP port",
"smtp-port-required": "You must supply a smtp port.",
"smtp-port-invalid": "That doesn't look like a valid smtp port.",
"timeout-msec": "Timeout (msec)",
"timeout-required": "Timeout is required.",
"timeout-invalid": "That doesn't look like a valid timeout.",
"enable-tls": "Enable TLS",
"send-test-mail": "Send test mail"
},
"attribute": {
"attributes": "Attributes",
"latest-telemetry": "Latest telemetry",
"attributes-scope": "Device attributes scope",
"scope-latest-telemetry": "Latest telemetry",
"scope-client": "Client attributes",
"scope-server": "Server attributes",
"scope-shared": "Shared attributes",
"add": "Add attribute",
"key": "Key",
"key-required": "Attribute key is required.",
"value": "Value",
"value-required": "Attribute value is required.",
"delete-attributes-title": "Are you sure you want to delete { count, select, 1 {1 attribute} other {# attributes} }?",
"delete-attributes-text": "Be careful, after the confirmation all selected attributes will be removed.",
"delete-attributes": "Delete attributes",
"enter-attribute-value": "Enter attribute value",
"show-on-widget": "Show on widget",
"widget-mode": "Widget mode",
"next-widget": "Next widget",
"prev-widget": "Previous widget",
"add-to-dashboard": "Add to dashboard",
"add-widget-to-dashboard": "Add widget to dashboard",
"selected-attributes": "{ count, select, 1 {1 attribute} other {# attributes} } selected",
"selected-telemetry": "{ count, select, 1 {1 telemetry unit} other {# telemetry units} } selected"
},
"confirm-on-exit": {
"message": "You have unsaved changes. Are you sure you want to leave this page?",
"html-message": "You have unsaved changes.<br/>Are you sure you want to leave this page?",
"title": "Unsaved changes"
},
"contact": {
"country": "Country",
"city": "City",
"state": "State",
"postal-code": "Postal code",
"postal-code-invalid": "Only digits are allowed.",
"address": "Address",
"address2": "Address 2",
"phone": "Phone",
"email": "Email",
"no-address": "No address"
},
"common": {
"username": "Username",
"password": "Password",
"enter-username": "Enter username",
"enter-password": "Enter password",
"enter-search": "Enter search"
},
"customer": {
"customers": "Customers",
"management": "Customer management",
"dashboard": "Customer Dashboard",
"dashboards": "Customer Dashboards",
"devices": "Customer Devices",
"add": "Add Customer",
"delete": "Delete customer",
"manage-customer-users": "Manage customer users",
"manage-customer-devices": "Manage customer devices",
"manage-customer-dashboards": "Manage customer dashboards",
"add-customer-text": "Add new customer",
"no-customers-text": "No customers found",
"customer-details": "Customer details",
"delete-customer-title": "Are you sure you want to delete the customer '{{customerTitle}}'?",
"delete-customer-text": "Be careful, after the confirmation the customer and all related data will become unrecoverable.",
"delete-customers-title": "Are you sure you want to delete { count, select, 1 {1 customer} other {# customers} }?",
"delete-customers-action-title": "Delete { count, select, 1 {1 customer} other {# customers} }",
"delete-customers-text": "Be careful, after the confirmation all selected customers will be removed and all related data will become unrecoverable.",
"manage-users": "Manage users",
"manage-devices": "Manage devices",
"manage-dashboards": "Manage dashboards",
"title": "Title",
"title-required": "Title is required.",
"description": "Description"
},
"datetime": {
"date-from": "Date from",
"time-from": "Time from",
"date-to": "Date to",
"time-to": "Time to"
},
"dashboard": {
"dashboard": "Dashboard",
"dashboards": "Dashboards",
"management": "Dashboard management",
"view-dashboards": "View Dashboards",
"add": "Add Dashboard",
"assign-dashboard-to-customer": "Assign Dashboard(s) To Customer",
"assign-dashboard-to-customer-text": "Please select the dashboards to assign to the customer",
"assign-to-customer-text": "Please select the customer to assign the dashboard(s)",
"assign-to-customer": "Assign to customer",
"unassign-from-customer": "Unassign from customer",
"no-dashboards-text": "No dashboards found",
"no-widgets": "No widgets configured",
"add-widget": "Add new widget",
"title": "Title",
"select-widget-title": "Select widget",
"select-widget-subtitle": "List of available widget types",
"delete": "Delete dashboard",
"title": "Title",
"title-required": "Title is required.",
"description": "Description",
"details": "Details",
"dashboard-details": "Dashboard details",
"add-dashboard-text": "Add new dashboard",
"no-dashboards-text": "No dashboards found",
"assign-dashboards": "Assign dashboards",
"assign-new-dashboard": "Assign new dashboard",
"assign-dashboards-text": "Assign { count, select, 1 {1 dashboard} other {# dashboards} } to customer",
"delete-dashboards": "Delete dashboards",
"unassign-dashboards": "Unassign dashboards",
"unassign-dashboards-action-title": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customer",
"delete-dashboard-title": "Are you sure you want to delete the dashboard '{{dashboardTitle}}'?",
"delete-dashboard-text": "Be careful, after the confirmation the dashboard and all related data will become unrecoverable.",
"delete-dashboards-title": "Are you sure you want to delete { count, select, 1 {1 dashboard} other {# dashboards} }?",
"delete-dashboards-action-title": "Delete { count, select, 1 {1 dashboard} other {# dashboards} }",
"delete-dashboards-text": "Be careful, after the confirmation all selected dashboards will be removed and all related data will become unrecoverable.",
"unassign-dashboard-title": "Are you sure you want to unassign the dashboard '{{dashboardTitle}}'?",
"unassign-dashboard-text": "After the confirmation the dashboard will be unassigned and won't be accessible by the customer.",
"unassign-dashboard": "Unassign dashboard",
"unassign-dashboards-title": "Are you sure you want to unassign { count, select, 1 {1 dashboard} other {# dashboards} }?",
"unassign-dashboards-text": "After the confirmation all selected dashboards will be unassigned and won't be accessible by the customer.",
"select-dashboard": "Select dashboard",
"no-dashboards-matching": "No dashboards matching '{{dashboard}}' were found.",
"dashboard-required": "Dashboard is required.",
"select-existing": "Select existing dashboard",
"create-new": "Create new dashboard",
"new-dashboard-title": "New dashboard title",
"open-dashboard": "Open dashboard",
"set-background": "Set background",
"background-color": "Background color",
"background-image": "Background image",
"no-image": "No image selected",
"drop-image": "Drop an image or click to select a file to upload.",
"settings": "Settings",
"columns-count": "Columns count",
"columns-count-required": "Columns count is required.",
"min-columns-count-message": "Only 10 minimum column count is allowed.",
"max-columns-count-message": "Only 1000 maximum column count is allowed.",
"widgets-margins": "Margin between widgets",
"horizontal-margin": "Horizontal margin",
"horizontal-margin-required": "Horizontal margin value is required.",
"min-horizontal-margin-message": "Only 0 is allowed as minimum horizontal margin value.",
"max-horizontal-margin-message": "Only 50 is allowed as maximum horizontal margin value.",
"vertical-margin": "Vertical margin",
"vertical-margin-required": "Vertical margin value is required.",
"min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.",
"max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.",
"display-title": "Display dashboard title",
"import": "Import dashboard",
"export": "Export dashboard",
"export-failed-error": "Unable to export dashboard: {error}",
"create-new-dashboard": "Create new dashboard",
"dashboard-file": "Dashboard file",
"invalid-dashboard-file-error": "Unable to import dashboard: Invalid dashboard data structure.",
"dashboard-import-missing-aliases-title": "Select missing devices for dashboard aliases",
"create-new-widget": "Create new widget",
"import-widget": "Import widget",
"widget-file": "Widget file",
"invalid-widget-file-error": "Unable to import widget: Invalid widget data structure.",
"widget-import-missing-aliases-title": "Select missing devices used by widget"
},
"datakey": {
"settings": "Settings",
"advanced": "Advanced",
"label": "Label",
"color": "Color",
"data-generation-func": "Data generation function",
"use-data-post-processing-func": "Use data post-processing function",
"configuration": "Data key configuration",
"timeseries": "Timeseries",
"attributes": "Attributes",
"timeseries-required": "Device timeseries is required.",
"timeseries-or-attributes-required": "Device timeseries/attributes is required.",
"function-types": "Function types",
"function-types-required": "Function types is required."
},
"datasource": {
"type": "Datasource type",
"add-datasource-prompt": "Please add datasource"
},
"details": {
"edit-mode": "Edit mode",
"toggle-edit-mode": "Toggle edit mode"
},
"device": {
"device": "Device",
"device-required": "Device is required.",
"devices": "Devices",
"management": "Device management",
"view-devices": "View Devices",
"device-alias": "Device alias",
"aliases": "Device aliases",
"no-alias-matching": "'{{alias}}' not found.",
"no-aliases-found": "No aliases found.",
"no-key-matching": "'{{key}}' not found.",
"no-keys-found": "No keys found.",
"create-new-alias": "Create a new one!",
"create-new-key": "Create a new one!",
"duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Device aliases must be unique whithin the dashboard.",
"select-device-for-alias": "Select device for '{{alias}}' alias",
"no-devices-matching": "No devices matching '{{device}}' were found.",
"alias": "Alias",
"alias-required": "Device alias is required.",
"remove-alias": "Remove device alias",
"add-alias": "Add device alias",
"add": "Add Device",
"assign-to-customer": "Assign to customer",
"assign-device-to-customer": "Assign Device(s) To Customer",
"assign-device-to-customer-text": "Please select the devices to assign to the customer",
"no-devices-text": "No devices found",
"assign-to-customer-text": "Please select the customer to assign the device(s)",
"device-details": "Device details",
"add-device-text": "Add new device",
"credentials": "Credentials",
"manage-credentials": "Manage credentials",
"delete": "Delete device",
"assign-devices": "Assign devices",
"assign-devices-text": "Assign { count, select, 1 {1 device} other {# devices} } to customer",
"delete-devices": "Delete devices",
"unassign-from-customer": "Unassign from customer",
"unassign-devices": "Unassign devices",
"unassign-devices-action-title": "Unassign { count, select, 1 {1 device} other {# devices} } from customer",
"assign-new-device": "Assign new device",
"view-credentials": "View credentials",
"delete-device-title": "Are you sure you want to delete the device '{{deviceName}}'?",
"delete-device-text": "Be careful, after the confirmation the device and all related data will become unrecoverable.",
"delete-devices-title": "Are you sure you want to delete { count, select, 1 {1 device} other {# devices} }?",
"delete-devices-action-title": "Delete { count, select, 1 {1 device} other {# devices} }",
"delete-devices-text": "Be careful, after the confirmation all selected devices will be removed and all related data will become unrecoverable.",
"unassign-device-title": "Are you sure you want to unassign the device '{{deviceName}}'?",
"unassign-device-text": "After the confirmation the device will be unassigned and won't be accessible by the customer.",
"unassign-device": "Unassign device",
"unassign-devices-title": "Are you sure you want to unassign { count, select, 1 {1 device} other {# devices} }?",
"unassign-devices-text": "After the confirmation all selected devices will be unassigned and won't be accessible by the customer.",
"device-credentials": "Device Credentials",
"credentials-type": "Credentials type",
"access-token": "Access token",
"access-token-required": "Access token is required.",
"access-token-invalid": "Access token length must be from 1 to 20 characters.",
"rsa-key": "RSA public key",
"access-token-required": "RSA public key is required.",
"secret": "Secret",
"secret-required": "Secret is required.",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
"events": "Events",
"details": "Details",
"copyId": "Copy device Id",
"copyAccessToken": "Copy access token",
"idCopiedMessage": "Device Id has been copied to clipboard",
"accessTokenCopiedMessage": "Device access token has been copied to clipboard",
"assignedToCustomer": "Assigned to customer",
"unable-delete-device-alias-title": "Unable to delete device alias",
"unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
"is-gateway": "Is gateway"
},
"dialog": {
"close": "Close dialog"
},
"error": {
"unable-to-connect": "Unable to connect to the server! Please check your internet connection.",
"unhandled-error-code": "Unhandled error code: {{errorCode}}",
"unknown-error": "Unknown error"
},
"event": {
"event-type": "Event type",
"type-alarm": "Alarm",
"type-error": "Error",
"type-lc-event": "Lifecycle event",
"type-stats": "Statistics",
"no-events-prompt": "No events found",
"error": "Error",
"alarm": "Alarm",
"event-time": "Event time",
"server": "Server",
"body": "Body",
"method": "Method",
"event": "Event",
"status": "Status",
"success": "Success",
"failed": "Failed",
"messages-processed": "Messages processed",
"errors-occurred": "Errors occurred"
},
"fullscreen": {
"expand": "Expand to fullscreen",
"exit": "Exit fullscreen",
"toggle": "Toggle fullscreen mode",
"fullscreen": "Fullscreen"
},
"function": {
"function": "Function"
},
"grid": {
"delete-item-title": "Are you sure you want to delete this item?",
"delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.",
"delete-items-title": "Are you sure you want to delete { count, select, 1 {1 item} other {# items} }?",
"delete-items-action-title": "Delete { count, select, 1 {1 item} other {# items} }",
"delete-items-text": "Be careful, after the confirmation all selected items will be removed and all related data will become unrecoverable.",
"add-item-text": "Add new item",
"no-items-text": "No items found",
"item-details": "Item details",
"delete-item": "Delete Item",
"delete-items": "Delete Items",
"scroll-to-top": "Scroll to top"
},
"help": {
"goto-help-page": "Go to help page"
},
"home": {
"home": "Home",
"profile": "Profile",
"logout": "Logout",
"menu": "Menu",
"avatar": "Avatar",
"open-user-menu": "Open user menu"
},
"import": {
"no-file": "No file selected",
"drop-file": "Drop a JSON file or click to select a file to upload."
},
"item": {
"selected": "Selected"
},
"js-func": {
"no-return-error": "Function must return value!",
"return-type-mismatch": "Function must return value of '{{type}}' type!"
},
"login": {
"login": "Login",
"request-password-reset": "Request Password Reset",
"reset-password": "Reset Password",
"create-password": "Create Password",
"passwords-mismatch-error": "Entered passwords must be same!",
"password-again": "Password again",
"sign-in": "Please sign in",
"username": "Username (email)",
"remember-me": "Remember me",
"forgot-password": "Forgot Password?",
"login": "Login",
"password-reset": "Password reset",
"new-password": "New password",
"new-password-again": "New password again",
"password-link-sent-message": "Password reset link was successfully sent!",
"request-password-reset": "Request password reset",
"email": "Email"
},
"plugin": {
"plugins": "Plugins",
"delete": "Delete plugin",
"activate": "Activate plugin",
"suspend": "Suspend plugin",
"active": "Active",
"suspended": "Suspended",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
"add": "Add Plugin",
"delete-plugin-title": "Are you sure you want to delete the plugin '{{pluginName}}'?",
"delete-plugin-text": "Be careful, after the confirmation the plugin and all related data will become unrecoverable.",
"delete-plugins-title": "Are you sure you want to delete { count, select, 1 {1 plugin} other {# plugins} }?",
"delete-plugins-action-title": "Delete { count, select, 1 {1 plugin} other {# plugins} }",
"delete-plugins-text": "Be careful, after the confirmation all selected plugins will be removed and all related data will become unrecoverable.",
"add-plugin-text": "Add new plugin",
"no-plugins-text": "No plugins found",
"plugin-details": "Plugin details",
"api-token": "API token",
"api-token-required": "API token is required.",
"type": "Plugin type",
"type-required": "Plugin type is required.",
"configuration": "Plugin configuration",
"system": "System",
"select-plugin": "Select plugin",
"plugin": "Plugin",
"no-plugins-matching": "No plugins matching '{{plugin}}' were found.",
"plugin-required": "Plugin is required.",
"plugin-require-match": "Please select an existing plugin.",
"events": "Events",
"details": "Details"
},
"profile": {
"profile": "Profile",
"change-password": "Change Password",
"current-password": "Current password"
},
"rule": {
"rules": "Rules",
"delete": "Delete rule",
"activate": "Activate rule",
"suspend": "Suspend rule",
"active": "Active",
"suspended": "Suspended",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
"add": "Add Rule",
"delete-rule-title": "Are you sure you want to delete the rule '{{ruleName}}'?",
"delete-rule-text": "Be careful, after the confirmation the rule and all related data will become unrecoverable.",
"delete-rules-title": "Are you sure you want to delete { count, select, 1 {1 rule} other {# rules} }?",
"delete-rules-action-title": "Delete { count, select, 1 {1 rule} other {# rules} }",
"delete-rules-text": "Be careful, after the confirmation all selected rules will be removed and all related data will become unrecoverable.",
"add-rule-text": "Add new rule",
"no-rules-text": "No rules found",
"rule-details": "Rule details",
"filters": "Filters",
"filter": "Filter",
"add-filter-prompt": "Please add filter",
"remove-filter": "Remove filter",
"add-filter": "Add filter",
"filter-name": "Filter name",
"filter-type": "Filter type",
"edit-filter": "Edit filter",
"view-filter": "View filter",
"component-name": "Name",
"component-name-required": "Name is required.",
"component-type": "Type",
"component-type-required": "Type is required.",
"processor": "Processor",
"no-processor-configured": "No processor configured",
"create-processor": "Create processor",
"processor": "Processor",
"processor-name": "Processor name",
"processor-type": "Processor type",
"plugin-action": "Plugin action",
"action-name": "Action name",
"action-type": "Action type",
"create-action-prompt": "Please create action",
"create-action": "Create action",
"details": "Details",
"events": "Events",
"system": "System"
},
"rule-plugin": {
"management": "Rules and plugins management"
},
"tenant": {
"tenants": "Tenants",
"management": "Tenant management",
"add": "Add Tenant",
"admins": "Admins",
"manage-tenant-admins": "Manage tenant admins",
"delete": "Delete tenant",
"add-tenant-text": "Add new tenant",
"no-tenants-text": "No tenants found",
"tenant-details": "Tenant details",
"delete-tenant-title": "Are you sure you want to delete the tenant '{{tenantTitle}}'?",
"delete-tenant-text": "Be careful, after the confirmation the tenant and all related data will become unrecoverable.",
"delete-tenants-title": "Are you sure you want to delete { count, select, 1 {1 tenant} other {# tenants} }?",
"delete-tenants-action-title": "Delete { count, select, 1 {1 tenant} other {# tenants} }",
"delete-tenants-text": "Be careful, after the confirmation all selected tenants will be removed and all related data will become unrecoverable.",
"title": "Title",
"title-required": "Title is required.",
"description": "Description"
},
"timeinterval": {
"seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }",
"minutes-interval": "{ minutes, select, 1 {1 minute} other {# minutes} }",
"hours-interval": "{ hours, select, 1 {1 hour} other {# hours} }",
"days-interval": "{ days, select, 1 {1 day} other {# days} }",
"days": "Days",
"hours": "Hours",
"minutes": "Minutes",
"seconds": "Seconds"
},
"timewindow": {
"days": "{ days, select, 1 { day } other {# days } }",
"hours": "{ hours, select, 0 { hour } 1 {1 hour } other {# hours } }",
"minutes": "{ minutes, select, 0 { minute } 1 {1 minute } other {# minutes } }",
"seconds": "{ seconds, select, 0 { second } 1 {1 second } other {# seconds } }",
"realtime": "Realtime",
"history": "History",
"last-prefix": "last",
"period": "from {{ startTime }} to {{ endTime }}",
"edit": "Edit timewindow",
"date-range": "Date range",
"last": "Last",
"time-period": "Time period"
},
"user": {
"users": "Users",
"customer-users": "Customer Users",
"tenant-admins": "Tenant Admins",
"sys-admin": "System administrator",
"tenant-admin": "Tenant administrator",
"customer": "Customer",
"anonymous": "Anonymous",
"add": "Add User",
"delete": "Delete user",
"add-user-text": "Add new user",
"no-users-text": "No users found",
"user-details": "User details",
"delete-user-title": "Are you sure you want to delete the user '{{userEmail}}'?",
"delete-user-text": "Be careful, after the confirmation the user and all related data will become unrecoverable.",
"delete-users-title": "Are you sure you want to delete { count, select, 1 {1 user} other {# users} }?",
"delete-users-action-title": "Delete { count, select, 1 {1 user} other {# users} }",
"delete-users-text": "Be careful, after the confirmation all selected users will be removed and all related data will become unrecoverable.",
"activation-email-sent-message": "Activation email was successfully sent!",
"resend-activation": "Resend activation",
"email": "Email",
"email-required": "Email is required.",
"first-name": "First Name",
"last-name": "Last Name",
"description": "Description"
},
"value": {
"type": "Value type",
"string": "String",
"string-value": "String value",
"integer": "Integer",
"integer-value": "Integer value",
"invalid-integer-value": "Invalid integer value",
"double": "Double",
"double-value": "Double value",
"boolean": "Boolean",
"boolean-value": "Boolean value",
"false": "False",
"true": "True"
},
"widget": {
"widget-library": "Widgets Library",
"widget-bundle": "Widgets Bundle",
"select-widgets-bundle": "Select widgets bundle",
"management": "Widget management",
"editor": "Widget Editor",
"widget-type-not-found": "Problem loading widget configuration.<br>Probably associated\n widget type was removed.",
"widget-type-load-error": "Widget wasn't loaded due to the following errors:",
"remove": "Remove widget",
"edit": "Edit widget",
"remove-widget-title": "Are you sure you want to remove the widget '{{widgetTitle}}'?",
"remove-widget-text": "After the confirmation the widget and all related data will become unrecoverable.",
"timeseries": "Time series",
"latest-values": "Latest values",
"rpc": "Control widget",
"static": "Static widget",
"select-widget-type": "Select widget type",
"missing-widget-title-error": "Widget title must be specified!",
"widget-saved": "Widget saved",
"unable-to-save-widget-error": "Unable to save widget! Widget has errors!",
"save": "Save widget",
"saveAs": "Save widget as",
"save-widget-type-as": "Save widget type as",
"save-widget-type-as-text": "Please enter new widget title and/or select target widgets bundle",
"toggle-fullscreen": "Toggle fullscreen",
"run": "Run widget",
"title": "Widget title",
"title-required": "Widget title is required.",
"type": "Widget type",
"resources": "Resources",
"resource-url": "JavaScript/CSS URI",
"remove-resource": "Remove resource",
"add-resource": "Add resource",
"html": "HTML",
"tidy": "Tidy",
"css": "CSS",
"settings-schema": "Settings schema",
"datakey-settings-schema": "Data key settings schema",
"javascript": "Javascript",
"remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?",
"remove-widget-type-text": "After the confirmation the widget type and all related data will become unrecoverable.",
"remove-widget-type": "Remove widget type",
"add-widget-type": "Add new widget type",
"widget-type-load-failed-error": "Failed to load widget type!",
"widget-template-load-failed-error": "Failed to load widget template!",
"add": "Add Widget",
"undo": "Undo widget changes",
"export": "Export widget"
},
"widgets-bundle": {
"current": "Current bundle",
"widgets-bundles": "Widgets Bundles",
"add": "Add Widgets Bundle",
"delete": "Delete widgets bundle",
"title": "Title",
"title-required": "Title is required.",
"add-widgets-bundle-text": "Add new widgets bundle",
"no-widgets-bundles-text": "No widgets bundles found",
"empty": "Widgets bundle is empty",
"details": "Details",
"widgets-bundle-details": "Widgets bundle details",
"delete-widgets-bundle-title": "Are you sure you want to delete the widgets bundle '{{widgetsBundleTitle}}'?",
"delete-widgets-bundle-text": "Be careful, after the confirmation the widgets bundle and all related data will become unrecoverable.",
"delete-widgets-bundles-title": "Are you sure you want to delete { count, select, 1 {1 widgets bundle} other {# widgets bundles} }?",
"delete-widgets-bundles-action-title": "Delete { count, select, 1 {1 widgets bundle} other {# widgets bundles} }",
"delete-widgets-bundles-text": "Be careful, after the confirmation all selected widgets bundles will be removed and all related data will become unrecoverable.",
"no-widgets-bundles-matching": "No widgets bundles matching '{{widgetsBundle}}' were found.",
"widgets-bundle-required": "Widgets bundle is required.",
"system": "System"
},
"widget-config": {
"settings": "Settings",
"advanced": "Advanced",
"title": "Title",
"general-settings": "General settings",
"display-title": "Display title",
"drop-shadow": "Drop shadow",
"enable-fullscreen": "Enable fullscreen",
"background-color": "Background color",
"text-color": "Text color",
"padding": "Padding",
"title-style": "Title style",
"mobile-mode-settings": "Mobile mode settings",
"order": "Order",
"height": "Height",
"timewindow": "Timewindow",
"datasources": "Datasources",
"datasource-type": "Type",
"datasource-parameters": "Parameters",
"remove-datasource": "Remove datasource",
"add-datasource": "Add datasource",
"target-device": "Target device"
}
}

21
ui/src/scss/fonts.scss

File diff suppressed because one or more lines are too long

15
ui/src/scss/main.scss

@ -16,20 +16,14 @@
@import "~compass-sass-mixins/lib/compass";
@import "constants";
@import "animations";
@font-face {
font-family: 'Segment7Standard';
src: url('../font/Segment7Standard.otf') format('opentype');
font-weight: normal;
font-style: italic;
}
@import "fonts";
/***************
* TYPE DEFAULTS
***************/
button, html, input, select, textarea {
font-family: RobotoDraft, Roboto, 'Helvetica Neue', sans-serif;
font-family: Roboto, 'Helvetica Neue', sans-serif;
}
.mdi-set {
@ -124,11 +118,11 @@ form {
}
md-bottom-sheet .md-subheader {
font-family: RobotoDraft, Roboto, 'Helvetica Neue', sans-serif;
font-family: Roboto, 'Helvetica Neue', sans-serif;
}
.md-chips {
font-family: RobotoDraft, Roboto, 'Helvetica Neue', sans-serif;
font-family: Roboto, 'Helvetica Neue', sans-serif;
}
md-content.md-default-theme, md-content {
@ -329,6 +323,7 @@ section.tb-footer-buttons {
right: 20px;
bottom: 20px;
z-index: 2;
pointer-events: none;
}
.tb-footer-buttons .tb-btn-footer {

2
ui/webpack.config.dev.js

@ -28,6 +28,7 @@ module.exports = {
entry: [
'./src/app/app.js',
'webpack-hot-middleware/client?reload=true',
'webpack-material-design-icons'
],
output: {
path: path.resolve(__dirname, 'target/generated-resources/public/static'),
@ -44,7 +45,6 @@ module.exports = {
moment: "moment"
}),
new CopyWebpackPlugin([
{ from: './src/locale', to: 'locale' },
{ from: './src/thingsboard.ico', to: 'thingsboard.ico' }
]),
new webpack.HotModuleReplacementPlugin(),

6
ui/webpack.config.prod.js

@ -23,7 +23,10 @@ const path = require('path');
module.exports = {
devtool: 'source-map',
entry: ['./src/app/app.js'],
entry: [
'./src/app/app.js',
'webpack-material-design-icons'
],
output: {
path: path.resolve(__dirname, 'target/generated-resources/public/static'),
publicPath: '/static/',
@ -39,7 +42,6 @@ module.exports = {
moment: "moment"
}),
new CopyWebpackPlugin([
{from: './src/locale', to: 'locale'},
{from: './src/thingsboard.ico', to: 'thingsboard.ico'}
]),
new HtmlWebpackPlugin({

Loading…
Cancel
Save