Browse Source

TB-64: Implement widget actions table.

pull/177/head
Igor Kulikov 9 years ago
parent
commit
8e90c903bb
  1. 1
      ui/package.json
  2. 31
      ui/src/app/api/widget.service.js
  3. 24
      ui/src/app/common/types.constant.js
  4. 41
      ui/src/app/common/utils.service.js
  5. 2
      ui/src/app/components/dashboard.directive.js
  6. 33
      ui/src/app/components/finish-render.directive.js
  7. 88
      ui/src/app/components/material-icon-select.directive.js
  8. 26
      ui/src/app/components/material-icon-select.tpl.html
  9. 59
      ui/src/app/components/material-icons-dialog.controller.js
  10. 31
      ui/src/app/components/material-icons-dialog.scss
  11. 62
      ui/src/app/components/material-icons-dialog.tpl.html
  12. 255
      ui/src/app/components/widget/action/manage-widget-actions.directive.js
  13. 33
      ui/src/app/components/widget/action/manage-widget-actions.scss
  14. 99
      ui/src/app/components/widget/action/manage-widget-actions.tpl.html
  15. 46
      ui/src/app/components/widget/action/widget-action-dialog.controller.js
  16. 81
      ui/src/app/components/widget/action/widget-action-dialog.tpl.html
  17. 34
      ui/src/app/components/widget/widget-config.directive.js
  18. 6
      ui/src/app/components/widget/widget-config.tpl.html
  19. 2
      ui/src/app/components/widget/widget.controller.js
  20. 6
      ui/src/app/components/widget/widget.directive.js
  21. 0
      ui/src/app/components/widget/widget.scss
  22. 1
      ui/src/app/dashboard/add-widget.tpl.html
  23. 1
      ui/src/app/dashboard/edit-widget.directive.js
  24. 1
      ui/src/app/dashboard/edit-widget.tpl.html
  25. 2
      ui/src/app/dashboard/index.js
  26. 4
      ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html
  27. 2
      ui/src/app/dashboard/states/manage-dashboard-states.controller.js
  28. 2
      ui/src/app/layout/index.js
  29. 30
      ui/src/app/locale/locale.constant.js

1
ui/package.json

@ -111,6 +111,7 @@
"ngtemplate-loader": "^1.3.1",
"node-sass": "^3.9.3",
"postcss-loader": "^0.13.0",
"raw-loader": "^0.5.1",
"react-hot-loader": "^3.0.0-beta.6",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.1",

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

@ -40,7 +40,7 @@ export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsbo
.name;
/*@ngInject*/
function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, types, utils) {
function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $translate, types, utils) {
$window.$ = $;
$window.jQuery = $;
@ -548,13 +548,21 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
' }\n\n' +
' self.typeParameters = function() {\n\n' +
{
useCustomDatasources: false,
maxDatasources: -1 //unlimited
maxDataKeys: -1 //unlimited
}
return {
useCustomDatasources: false,
maxDatasources: -1 //unlimited
maxDataKeys: -1 //unlimited
};
' }\n\n' +
' self.actionSources = function() {\n\n' +
return {
'headerButton': {
name: 'Header button',
multiple: true
}
};
}\n\n' +
' self.onResize = function() {\n\n' +
' }\n\n' +
@ -611,6 +619,16 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
if (angular.isUndefined(result.typeParameters.maxDataKeys)) {
result.typeParameters.maxDataKeys = -1;
}
if (angular.isFunction(widgetTypeInstance.actionSources)) {
result.actionSources = widgetTypeInstance.actionSources();
} else {
result.actionSources = {};
}
for (var actionSourceId in types.widgetActionSources) {
result.actionSources[actionSourceId] = angular.copy(types.widgetActionSources[actionSourceId]);
result.actionSources[actionSourceId].name = $translate.instant(result.actionSources[actionSourceId].name);
}
return result;
} catch (e) {
utils.processWidgetException(e);
@ -650,6 +668,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema;
}
widgetInfo.typeParameters = widgetType.typeParameters;
widgetInfo.actionSources = widgetType.actionSources;
putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem);
deferred.resolve(widgetInfo);

24
ui/src/app/common/types.constant.js

@ -389,6 +389,30 @@ export default angular.module('thingsboard.types', [])
}
}
},
widgetActionSources: {
'headerButton': {
name: 'widget-action.header-button',
multiple: true
}
},
widgetActionTypes: {
openDashboardState: {
name: 'widget-action.open-dashboard-state',
value: 'openDashboardState'
},
updateDashboardState: {
name: 'widget-action.update-dashboard-state',
value: 'updateDashboardState'
},
openDashboard: {
name: 'widget-action.open-dashboard',
value: 'openDashboard'
},
custom: {
name: 'widget-action.custom',
value: 'custom'
}
},
systemBundleAlias: {
charts: "charts",
cards: "cards"

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

@ -13,6 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import materialIconsCodepoints from 'raw-loader!material-design-icons/iconfont/codepoints';
/* eslint-enable import/no-unresolved, import/default */
import tinycolor from "tinycolor2";
import jsonSchemaDefaults from "json-schema-defaults";
import thingsboardTypes from "./types.constant";
@ -24,11 +31,14 @@ export default angular.module('thingsboard.utils', [thingsboardTypes])
const varsRegex = /\$\{([^\}]*)\}/g;
/*@ngInject*/
function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, types) {
var predefinedFunctions = {},
predefinedFunctionsList = [],
materialColors = [];
materialColors = [],
materialIcons = [];
var commonUsedMaterialIcons = [ 'more_horiz', 'close', 'play_arrow' ];
predefinedFunctions['Sin'] = "return Math.round(1000*Math.sin(time/5000));";
predefinedFunctions['Cos'] = "return Math.round(1000*Math.cos(time/5000));";
@ -122,6 +132,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
getDefaultDatasourceJson: getDefaultDatasourceJson,
getDefaultAlarmDataKeys: getDefaultAlarmDataKeys,
getMaterialColor: getMaterialColor,
getMaterialIcons: getMaterialIcons,
getCommonMaterialIcons: getCommonMaterialIcons,
getPredefinedFunctionBody: getPredefinedFunctionBody,
getPredefinedFunctionsList: getPredefinedFunctionsList,
genMaterialColor: genMaterialColor,
@ -154,6 +166,31 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
return materialColors[colorIndex].value;
}
function getMaterialIcons() {
var deferred = $q.defer();
if (materialIcons.length) {
deferred.resolve(materialIcons);
} else {
$timeout(function() {
var codepointsArray = materialIconsCodepoints.split("\n");
codepointsArray.forEach(function (codepoint) {
if (codepoint && codepoint.length) {
var values = codepoint.split(' ');
if (values && values.length == 2) {
materialIcons.push(values[0]);
}
}
});
deferred.resolve(materialIcons);
});
}
return deferred.promise;
}
function getCommonMaterialIcons() {
return commonUsedMaterialIcons;
}
function genMaterialColor(str) {
var hash = Math.abs(hashCode(str));
return getMaterialColor(hash);

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

@ -20,7 +20,7 @@ import 'javascript-detect-element-resize/detect-element-resize';
import angularGridster from 'angular-gridster';
import thingsboardTypes from '../common/types.constant';
import thingsboardApiWidget from '../api/widget.service';
import thingsboardWidget from './widget.directive';
import thingsboardWidget from './widget/widget.directive';
import thingsboardToast from '../services/toast';
import thingsboardTimewindow from './timewindow.directive';
import thingsboardEvents from './tb-event-directives';

33
ui/src/app/components/finish-render.directive.js

@ -0,0 +1,33 @@
/*
* 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.directives.finishRender', [])
.directive('tbOnFinishRender', OnFinishRender)
.name;
/*@ngInject*/
function OnFinishRender($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit(attr.tbOnFinishRender);
});
}
}
};
}

88
ui/src/app/components/material-icon-select.directive.js

@ -0,0 +1,88 @@
/*
* 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 MaterialIconsDialogController from './material-icons-dialog.controller';
/* eslint-disable import/no-unresolved, import/default */
import materialIconSelectTemplate from './material-icon-select.tpl.html';
import materialIconsDialogTemplate from './material-icons-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.materialIconSelect', [])
.controller('MaterialIconsDialogController', MaterialIconsDialogController)
.directive('tbMaterialIconSelect', MaterialIconSelect)
.name;
/*@ngInject*/
function MaterialIconSelect($compile, $templateCache, $document, $mdDialog) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(materialIconSelectTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.icon = null;
scope.updateView = function () {
ngModelCtrl.$setViewValue(scope.icon);
}
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
scope.icon = ngModelCtrl.$viewValue;
}
if (!scope.icon || !scope.icon.length) {
scope.icon = 'more_horiz';
}
}
scope.$watch('icon', function () {
scope.updateView();
});
scope.openIconDialog = function($event) {
if ($event) {
$event.stopPropagation();
}
$mdDialog.show({
controller: 'MaterialIconsDialogController',
controllerAs: 'vm',
templateUrl: materialIconsDialogTemplate,
parent: angular.element($document[0].body),
locals: {icon: scope.icon},
skipHide: true,
fullscreen: true,
targetEvent: $event
}).then(function (icon) {
scope.icon = icon;
});
}
$compile(element.contents())(scope);
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
tbRequired: '=?',
}
};
}

26
ui/src/app/components/material-icon-select.tpl.html

@ -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.
-->
<div layout="row">
<md-icon class="material-icons" ng-click="openIconDialog($event)">{{icon}}</md-icon>
<md-input-container flex>
<md-input-container class="md-block">
<label translate>icon.icon</label>
<input ng-click="openIconDialog($event)" ng-model="icon">
</md-input-container>
</md-input-container>
</div>

59
ui/src/app/components/material-icons-dialog.controller.js

@ -0,0 +1,59 @@
/*
* 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 './material-icons-dialog.scss';
/*@ngInject*/
export default function MaterialIconsDialogController($scope, $mdDialog, $timeout, utils, icon) {
var vm = this;
vm.selectedIcon = icon;
vm.showAll = false;
vm.loadingIcons = false;
$scope.$watch('vm.showAll', function(showAll) {
if (showAll) {
vm.loadingIcons = true;
$timeout(function() {
utils.getMaterialIcons().then(
function success(icons) {
vm.icons = icons;
}
);
});
} else {
vm.icons = utils.getCommonMaterialIcons();
}
});
$scope.$on('iconsLoadFinished', function() {
vm.loadingIcons = false;
});
vm.cancel = cancel;
vm.selectIcon = selectIcon;
function cancel() {
$mdDialog.cancel();
}
function selectIcon($event, icon) {
vm.selectedIcon = icon;
$mdDialog.hide(vm.selectedIcon);
}
}

31
ui/src/app/components/material-icons-dialog.scss

@ -0,0 +1,31 @@
/**
* 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-material-icons-dialog {
button.md-icon-button.tb-select-icon-button {
border: solid 1px orange;
border-radius: 0%;
padding: 16px;
height: 56px;
width: 56px;
margin: 10px;
}
.tb-icons-load {
top: 64px;
background: rgba(255,255,255,0.75);
z-index: 3;
}
}

62
ui/src/app/components/material-icons-dialog.tpl.html

@ -0,0 +1,62 @@
<!--
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.
-->
<md-dialog class="tb-material-icons-dialog" aria-label="{{'icon.material-icons' | translate }}" style="min-width: 600px;">
<form>
<md-toolbar>
<div class="md-toolbar-tools">
<h2>{{ 'icon.select-icon' | translate }}</h2>
<span flex></span>
<section layout="row" layout-align="start center">
<md-switch ng-model="vm.showAll"
aria-label="{{ 'icon.show-all' | translate }}">
</md-switch>
<label translate>icon.show-all</label>
</section>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'action.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<div class="tb-absolute-fill tb-icons-load" ng-show="vm.loadingIcons" layout="column" layout-align="center center">
<md-progress-circular md-mode="indeterminate" ng-disabled="!vm.loadingIcons" class="md-accent" md-diameter="40"></md-progress-circular>
</div>
<md-dialog-content>
<div class="md-dialog-content">
<md-content class="md-padding" layout="column">
<fieldset ng-disabled="loading">
<md-button ng-class="{'md-primary md-raised': icon == vm.selectedIcon}" class="tb-select-icon-button md-icon-button"
ng-repeat="icon in vm.icons" ng-click="vm.selectIcon($event, icon)" tb-on-finish-render="iconsLoadFinished">
<md-icon class="material-icons">{{icon}}</md-icon>
<md-tooltip md-direction="bottom">
{{ icon }}
</md-tooltip>
</md-button>
</fieldset>
</md-content>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="loading" ng-click="vm.cancel()">
{{ 'action.cancel' | translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

255
ui/src/app/components/widget/action/manage-widget-actions.directive.js

@ -0,0 +1,255 @@
/**
* Created by igor on 6/20/17.
*/
/*
* 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 './manage-widget-actions.scss';
import thingsboardMaterialIconSelect from '../../material-icon-select.directive';
import WidgetActionDialogController from './widget-action-dialog.controller';
/* eslint-disable import/no-unresolved, import/default */
import manageWidgetActionsTemplate from './manage-widget-actions.tpl.html';
import widgetActionDialogTemplate from './widget-action-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.widgetActions', [thingsboardMaterialIconSelect])
.controller('WidgetActionDialogController', WidgetActionDialogController)
.directive('tbManageWidgetActions', ManageWidgetActions)
.name;
/*@ngInject*/
function ManageWidgetActions() {
return {
restrict: "E",
scope: true,
bindToController: {
actionSources: '=',
widgetActions: '='
},
controller: ManageWidgetActionsController,
controllerAs: 'vm',
templateUrl: manageWidgetActionsTemplate
};
}
/* eslint-disable angular/angularelement */
/*@ngInject*/
function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog, $q, $filter,
$translate, $timeout, types) {
let vm = this;
vm.allActions = [];
vm.actions = [];
vm.actionsCount = 0;
vm.query = {
order: 'actionSourceName',
limit: 10,
page: 1,
search: null
};
vm.enterFilterMode = enterFilterMode;
vm.exitFilterMode = exitFilterMode;
vm.onReorder = onReorder;
vm.onPaginate = onPaginate;
vm.addAction = addAction;
vm.editAction = editAction;
vm.deleteAction = deleteAction;
$timeout(function(){
$scope.manageWidgetActionsForm.querySearchInput.$pristine = false;
});
$scope.$watch('vm.widgetActions', function() {
if (vm.widgetActions) {
reloadActions();
}
});
$scope.$watch("vm.query.search", function(newVal, prevVal) {
if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
updateActions();
}
});
function enterFilterMode () {
vm.query.search = '';
}
function exitFilterMode () {
vm.query.search = null;
updateActions();
}
function onReorder () {
updateActions();
}
function onPaginate () {
updateActions();
}
function addAction($event) {
if ($event) {
$event.stopPropagation();
}
openWidgetActionDialog($event, null, true);
}
function editAction ($event, action) {
if ($event) {
$event.stopPropagation();
}
openWidgetActionDialog($event, action, false);
}
function deleteAction($event, action) {
if ($event) {
$event.stopPropagation();
}
if (action) {
var title = $translate.instant('widget-config.delete-action-title');
var content = $translate.instant('widget-config.delete-action-text', {actionName: action.name});
var confirm = $mdDialog.confirm()
.targetEvent($event)
.title(title)
.htmlContent(content)
.ariaLabel(title)
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
confirm._options.skipHide = true;
confirm._options.fullscreen = true;
$mdDialog.show(confirm).then(function () {
var index = getActionIndex(action.id, vm.allActions);
if (index > -1) {
vm.allActions.splice(index, 1);
}
var targetActions = vm.widgetActions[action.actionSourceId];
index = getActionIndex(action.id, targetActions);
if (index > -1) {
targetActions.splice(index, 1);
}
$scope.manageWidgetActionsForm.$setDirty();
updateActions();
});
}
}
function openWidgetActionDialog($event, action, isAdd) {
var prevActionId = null;
if (!isAdd) {
prevActionId = action.id;
}
$mdDialog.show({
controller: 'WidgetActionDialogController',
controllerAs: 'vm',
templateUrl: widgetActionDialogTemplate,
parent: angular.element($document[0].body),
locals: {isAdd: isAdd, actionSources: vm.actionSources, action: angular.copy(action)},
skipHide: true,
fullscreen: true,
targetEvent: $event
}).then(function (action) {
saveAction(action, prevActionId);
updateActions();
});
}
function getActionIndex(id, actions) {
var result = $filter('filter')(actions, {id: id}, true);
if (result && result.length) {
return actions.indexOf(result[0]);
}
return -1;
}
function saveAction(action, prevActionId) {
action.actionSourceName = vm.actionSources[action.actionSourceId].name;
action.typeName = $translate.instant(types.widgetActionTypes[action.type].name);
var actionSourceId = action.actionSourceId;
var widgetAction = angular.copy(action);
delete widgetAction.actionSourceId;
delete widgetAction.actionSourceName;
delete widgetAction.typeName;
var targetActions = vm.widgetActions[actionSourceId];
if (!targetActions) {
targetActions = [];
vm.widgetActions[actionSourceId] = targetActions;
}
if (prevActionId) {
var index = getActionIndex(prevActionId, vm.allActions);
if (index > -1) {
vm.allActions[index] = action;
}
index = getActionIndex(prevActionId, targetActions);
if (index > -1) {
targetActions[index] = widgetAction;
}
} else {
vm.allActions.push(action);
targetActions.push(widgetAction);
}
$scope.manageWidgetActionsForm.$setDirty();
}
function reloadActions() {
vm.allActions = [];
vm.actions = [];
vm.actionsCount = 0;
for (var actionSourceId in vm.widgetActions) {
var actionSource = vm.actionSources[actionSourceId];
var actionSourceActions = vm.widgetActions[actionSourceId];
for (var i=0;i<actionSourceActions.length;i++) {
var actionSourceAction = actionSourceActions[i];
var action = {
id: actionSourceAction.id,
actionSourceId: actionSourceId,
actionSourceName: actionSource.name,
name: actionSourceAction.name,
icon: actionSourceAction.icon,
type: actionSourceAction.type,
typeName: $translate.instant(types.widgetActionTypes[actionSourceAction.type].name)
};
vm.allActions.push(action);
}
}
updateActions ();
}
function updateActions () {
var result = $filter('orderBy')(vm.allActions, vm.query.order);
if (vm.query.search != null) {
result = $filter('filter')(result, {$: vm.query.search});
}
vm.actionsCount = result.length;
var startIndex = vm.query.limit * (vm.query.page - 1);
vm.actions = result.slice(startIndex, startIndex + vm.query.limit);
}
}

33
ui/src/app/components/widget/action/manage-widget-actions.scss

@ -0,0 +1,33 @@
/**
* 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-manage-widget-actions {
table.md-table {
tbody {
tr {
td {
&.tb-action-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 100px;
max-width: 100px;
width: 100px;
}
}
}
}
}
}

99
ui/src/app/components/widget/action/manage-widget-actions.tpl.html

@ -0,0 +1,99 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div ng-form="manageWidgetActionsForm" class="tb-manage-widget-actions md-whiteframe-z1" layout="column">
<md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search === null">
<div class="md-toolbar-tools">
<span translate>widget-config.actions</span>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.addAction($event)">
<md-icon>add</md-icon>
<md-tooltip md-direction="top">
{{ 'widget-config.add-action' | translate }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
<md-icon>search</md-icon>
<md-tooltip md-direction="top">
{{ 'action.search' | translate }}
</md-tooltip>
</md-button>
</div>
</md-toolbar>
<md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search != null">
<div class="md-toolbar-tools">
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
<md-tooltip md-direction="top">
{{ 'widget-config.search-actions' | translate }}
</md-tooltip>
</md-button>
<md-input-container flex>
<label>&nbsp;</label>
<input ng-model="vm.query.search" name="querySearchInput" placeholder="{{ 'widget-config.search-actions' | translate }}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
<md-icon aria-label="Close" class="material-icons">close</md-icon>
<md-tooltip md-direction="top">
{{ 'action.close' | translate }}
</md-tooltip>
</md-button>
</div>
</md-toolbar>
<md-table-container>
<table md-table>
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row>
<th md-column md-order-by="actionSourceName"><span translate>widget-config.action-source</span></th>
<th md-column md-order-by="name"><span translate>widget-config.action-name</span></th>
<th md-column md-order-by="icon"><span translate>widget-config.action-icon</span></th>
<th md-column md-order-by="typeName"><span translate>widget-config.action-type</span></th>
<th md-column><span>&nbsp</span></th>
</tr>
</thead>
<tbody md-body>
<tr md-row ng-repeat="action in vm.actions">
<td md-cell>{{action.actionSourceName}}</td>
<td md-cell>{{action.name}}</td>
<td md-cell>
<md-icon aria-label="{{ 'widget-config.action-icon' | translate }}" class="material-icons">{{action.icon}}</md-icon>
</td>
<td md-cell>{{action.typeName}}</td>
<td md-cell class="tb-action-cell">
<md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}"
ng-click="vm.editAction($event, action)">
<md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
<md-tooltip md-direction="top">
{{ 'widget-config.edit-action' | translate }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button" aria-label="{{'action.delete' | translate}}" ng-click="vm.deleteAction($event, action)">
<md-icon aria-label="Delete" class="material-icons">delete</md-icon>
<md-tooltip md-direction="top">
{{ 'widget-config.delete-action' | translate }}
</md-tooltip>
</md-button>
</td>
</tr>
</tbody>
</table>
</md-table-container>
<md-table-pagination md-limit="vm.query.limit" md-limit-options="[10, 15, 20]"
md-page="vm.query.page" md-total="{{vm.actionsCount}}"
md-on-paginate="vm.onPaginate" md-page-select>
</md-table-pagination>
</div>

46
ui/src/app/components/widget/action/widget-action-dialog.controller.js

@ -0,0 +1,46 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*@ngInject*/
export default function WidgetActionDialogController($scope, $mdDialog, types, utils, isAdd, actionSources, action) {
var vm = this;
vm.types = types;
vm.isAdd = isAdd;
vm.actionSources = actionSources;
if (vm.isAdd) {
vm.action = {
id: utils.guid()
};
} else {
vm.action = action;
}
vm.cancel = cancel;
vm.save = save;
function cancel() {
$mdDialog.cancel();
}
function save() {
$scope.theForm.$setPristine();
$mdDialog.hide(vm.action);
}
}

81
ui/src/app/components/widget/action/widget-action-dialog.tpl.html

@ -0,0 +1,81 @@
<!--
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.
-->
<md-dialog class="tb-widget-action-dialog" aria-label="{{'widget-config.action' | translate }}" style="min-width: 600px;">
<form name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2>{{ (vm.isAdd ? 'widget-config.add-action' : 'widget-config.edit-action') | translate }}</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'action.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<md-content class="md-padding" layout="column">
<fieldset ng-disabled="loading">
<md-input-container class="md-block">
<label translate>widget-config.action-source</label>
<md-select name="actionSource" required aria-label="{{ 'widget-config.action-source' | translate }}" ng-model="vm.action.actionSourceId">
<md-option ng-repeat="(actionSourceId, actionSource) in vm.actionSources" ng-value="actionSourceId">
{{actionSource.name}}
</md-option>
</md-select>
<div ng-messages="theForm.actionSource.$error">
<div ng-message="required" translate>widget-config.action-source-required</div>
</div>
</md-input-container>
<md-input-container class="md-block">
<label translate>widget-config.action-name</label>
<input name="name" required ng-model="vm.action.name">
<div ng-messages="theForm.name.$error">
<div ng-message="required" translate>widget-config.action-name-required</div>
</div>
</md-input-container>
<tb-material-icon-select ng-model="vm.action.icon">
</tb-material-icon-select>
<md-input-container class="md-block">
<label translate>widget-config.action-type</label>
<md-select name="actionType" required aria-label="{{ 'widget-config.action-type' | translate }}" ng-model="vm.action.type">
<md-option ng-repeat="actionType in vm.types.widgetActionTypes" ng-value="actionType.value">
{{ actionType.name | translate }}
</md-option>
</md-select>
<div ng-messages="theForm.actionType.$error">
<div ng-message="required" translate>widget-config.action-type-required</div>
</div>
</md-input-container>
</fieldset>
</md-content>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
class="md-raised md-primary">
{{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
</md-button>
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">
{{ 'action.cancel' | translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

34
ui/src/app/components/widget-config.directive.js → ui/src/app/components/widget/widget-config.directive.js

@ -14,13 +14,14 @@
* limitations under the License.
*/
import jsonSchemaDefaults from 'json-schema-defaults';
import thingsboardTypes from '../common/types.constant';
import thingsboardUtils from '../common/utils.service';
import thingsboardEntityAliasSelect from './entity-alias-select.directive';
import thingsboardDatasource from './datasource.directive';
import thingsboardTimewindow from './timewindow.directive';
import thingsboardLegendConfig from './legend-config.directive';
import thingsboardJsonForm from "./json-form.directive";
import thingsboardTypes from '../../common/types.constant';
import thingsboardUtils from '../../common/utils.service';
import thingsboardEntityAliasSelect from '../entity-alias-select.directive';
import thingsboardDatasource from '../datasource.directive';
import thingsboardTimewindow from '../timewindow.directive';
import thingsboardLegendConfig from '../legend-config.directive';
import thingsboardJsonForm from '../json-form.directive';
import thingsboardManageWidgetActions from './action/manage-widget-actions.directive';
import 'angular-ui-ace';
/* eslint-disable import/no-unresolved, import/default */
@ -38,6 +39,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar
thingsboardDatasource,
thingsboardTimewindow,
thingsboardLegendConfig,
thingsboardManageWidgetActions,
'ui.ace'])
.directive('tbWidgetConfig', WidgetConfig)
.name;
@ -117,6 +119,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
scope.showLegend = angular.isDefined(config.showLegend) ?
config.showLegend : scope.widgetType === types.widgetType.timeseries.value;
scope.legendConfig = config.legendConfig;
scope.actions = config.actions;
if (!scope.actions) {
scope.actions = {};
}
if (scope.widgetType !== types.widgetType.rpc.value &&
scope.widgetType !== types.widgetType.alarm.value &&
scope.widgetType !== types.widgetType.static.value
@ -324,6 +330,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
}
});
scope.$watch('actions', function () {
if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config) {
var value = ngModelCtrl.$viewValue;
var config = value.config;
config.actions = scope.actions;
ngModelCtrl.$setViewValue(value);
scope.updateValidity();
/*if (scope.theForm) {
scope.theForm.$setDirty();
}*/
}
}, true);
scope.addDatasource = function () {
var newDatasource;
if (scope.functionsOnly) {
@ -443,6 +462,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
isDataEnabled: '=?',
widgetType: '=',
typeParameters: '=',
actionSources: '=',
widgetSettingsSchema: '=',
datakeySettingsSchema: '=',
aliasController: '=',

6
ui/src/app/components/widget-config.tpl.html → ui/src/app/components/widget/widget-config.tpl.html

@ -275,4 +275,10 @@
</ng-form>
</md-content>
</md-tab>
<md-tab label="{{ 'widget-config.actions' | translate }}">
<md-content class="md-padding" layout="column">
<tb-manage-widget-actions action-sources="actionSources" widget-actions="actions">
</tb-manage-widget-actions>
</md-content>
</md-tab>
</md-tabs>

2
ui/src/app/components/widget.controller.js → ui/src/app/components/widget/widget.controller.js

@ -15,7 +15,7 @@
*/
import $ from 'jquery';
import 'javascript-detect-element-resize/detect-element-resize';
import Subscription from '../api/subscription';
import Subscription from '../../api/subscription';
/* eslint-disable angular/angularelement */

6
ui/src/app/components/widget.directive.js → ui/src/app/components/widget/widget.directive.js

@ -16,9 +16,9 @@
import './widget.scss';
import thingsboardLegend from './legend.directive';
import thingsboardTypes from '../common/types.constant';
import thingsboardApiDatasource from '../api/datasource.service';
import thingsboardLegend from '../legend.directive';
import thingsboardTypes from '../../common/types.constant';
import thingsboardApiDatasource from '../../api/datasource.service';
import WidgetController from './widget.controller';

0
ui/src/app/components/widget.scss → ui/src/app/components/widget/widget.scss

1
ui/src/app/dashboard/add-widget.tpl.html

@ -34,6 +34,7 @@
<fieldset ng-disabled="loading" style="position: relative; height: 600px;">
<tb-widget-config widget-type="vm.widget.type"
type-parameters="vm.widgetInfo.typeParameters"
action-sources="vm.widgetInfo.actionSources"
force-expand-datasources="true"
ng-model="vm.widgetConfig"
widget-settings-schema="vm.settingsSchema"

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

@ -41,6 +41,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
scope.typeParameters = widgetInfo.typeParameters;
scope.actionSources = widgetInfo.actionSources;
scope.isDataEnabled = !widgetInfo.typeParameters.useCustomDatasources;
if (!settingsSchema || settingsSchema === '') {
scope.settingsSchema = {};

1
ui/src/app/dashboard/edit-widget.tpl.html

@ -18,6 +18,7 @@
<fieldset ng-disabled="loading">
<tb-widget-config widget-type="widget.type"
type-parameters="typeParameters"
action-sources="actionSources"
ng-model="widgetConfig"
is-data-enabled="isDataEnabled"
widget-settings-schema="settingsSchema"

2
ui/src/app/dashboard/index.js

@ -23,7 +23,7 @@ import thingsboardApiUser from '../api/user.service';
import thingsboardApiDashboard from '../api/dashboard.service';
import thingsboardApiCustomer from '../api/customer.service';
import thingsboardDetailsSidenav from '../components/details-sidenav.directive';
import thingsboardWidgetConfig from '../components/widget-config.directive';
import thingsboardWidgetConfig from '../components/widget/widget-config.directive';
import thingsboardDashboardSelect from '../components/dashboard-select.directive';
import thingsboardRelatedEntityAutocomplete from '../components/related-entity-autocomplete.directive';
import thingsboardDashboard from '../components/dashboard.directive';

4
ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html

@ -59,10 +59,10 @@
<span flex></span>
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
class="md-raised md-primary">
{{ vm.isAdd ? 'Add' : 'Save' }}
{{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
</md-button>
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">
Cancel
{{ 'action.cancel' | translate }}
</md-button>
</md-dialog-actions>
</form>

2
ui/src/app/dashboard/states/manage-dashboard-states.controller.js

@ -175,8 +175,6 @@ export default function ManageDashboardStatesController($scope, $mdDialog, $filt
$scope.theForm.$setDirty();
updateStates();
});
}
}

2
ui/src/app/layout/index.js

@ -26,6 +26,7 @@ import thingsboardApiLogin from '../api/login.service';
import thingsboardApiUser from '../api/user.service';
import thingsboardNoAnimate from '../components/no-animate.directive';
import thingsboardOnFinishRender from '../components/finish-render.directive';
import thingsboardSideMenu from '../components/side-menu.directive';
import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive';
@ -81,6 +82,7 @@ export default angular.module('thingsboard.home', [
thingsboardApiLogin,
thingsboardApiUser,
thingsboardNoAnimate,
thingsboardOnFinishRender,
thingsboardSideMenu,
thingsboardDashboardAutocomplete
])

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

@ -1103,6 +1103,13 @@ export default angular.module('thingsboard.locale', [])
"undo": "Undo widget changes",
"export": "Export widget"
},
"widget-action": {
"header-button": "Header button",
"open-dashboard-state": "Navigate to new dashboard state",
"update-dashboard-state": "Update current dashboard state",
"open-dashboard": "Navigate to other dashboard",
"custom": "Custom action"
},
"widgets-bundle": {
"current": "Current bundle",
"widgets-bundles": "Widgets Bundles",
@ -1158,7 +1165,22 @@ export default angular.module('thingsboard.locale', [])
"remove-datasource": "Remove datasource",
"add-datasource": "Add datasource",
"target-device": "Target device",
"alarm-source": "Alarm source"
"alarm-source": "Alarm source",
"actions": "Actions",
"action": "Action",
"add-action": "Add action",
"search-actions": "Search actions",
"action-source": "Action source",
"action-source-required": "Action source is required.",
"action-name": "Name",
"action-name-required": "Action name is required.",
"action-icon": "Icon",
"action-type": "Type",
"action-type-required": "Action type is required.",
"edit-action": "Edit action",
"delete-action": "Delete action",
"delete-action-title": "Delete widget action",
"delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?"
},
"widget-type": {
"import": "Import widget type",
@ -1168,6 +1190,12 @@ export default angular.module('thingsboard.locale', [])
"widget-type-file": "Widget type file",
"invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure."
},
"icon": {
"icon": "Icon",
"select-icon": "Select icon",
"material-icons": "Material icons",
"show-all": "Show all icons"
},
"language": {
"language": "Language",
"en_US": "English",

Loading…
Cancel
Save