From 8e90c903bb2ce29efbf9d734402af7d6bf2ecfb4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 21 Jun 2017 13:43:49 +0300 Subject: [PATCH] TB-64: Implement widget actions table. --- ui/package.json | 1 + ui/src/app/api/widget.service.js | 31 ++- ui/src/app/common/types.constant.js | 24 ++ ui/src/app/common/utils.service.js | 41 ++- ui/src/app/components/dashboard.directive.js | 2 +- .../app/components/finish-render.directive.js | 33 +++ .../material-icon-select.directive.js | 88 ++++++ .../components/material-icon-select.tpl.html | 26 ++ .../material-icons-dialog.controller.js | 59 ++++ .../app/components/material-icons-dialog.scss | 31 +++ .../components/material-icons-dialog.tpl.html | 62 +++++ .../action/manage-widget-actions.directive.js | 255 ++++++++++++++++++ .../widget/action/manage-widget-actions.scss | 33 +++ .../action/manage-widget-actions.tpl.html | 99 +++++++ .../action/widget-action-dialog.controller.js | 46 ++++ .../action/widget-action-dialog.tpl.html | 81 ++++++ .../{ => widget}/widget-config.directive.js | 34 ++- .../{ => widget}/widget-config.tpl.html | 6 + .../{ => widget}/widget.controller.js | 2 +- .../{ => widget}/widget.directive.js | 6 +- .../app/components/{ => widget}/widget.scss | 0 ui/src/app/dashboard/add-widget.tpl.html | 1 + ui/src/app/dashboard/edit-widget.directive.js | 1 + ui/src/app/dashboard/edit-widget.tpl.html | 1 + ui/src/app/dashboard/index.js | 2 +- .../states/dashboard-state-dialog.tpl.html | 4 +- .../manage-dashboard-states.controller.js | 2 - ui/src/app/layout/index.js | 2 + ui/src/app/locale/locale.constant.js | 30 ++- 29 files changed, 977 insertions(+), 26 deletions(-) create mode 100644 ui/src/app/components/finish-render.directive.js create mode 100644 ui/src/app/components/material-icon-select.directive.js create mode 100644 ui/src/app/components/material-icon-select.tpl.html create mode 100644 ui/src/app/components/material-icons-dialog.controller.js create mode 100644 ui/src/app/components/material-icons-dialog.scss create mode 100644 ui/src/app/components/material-icons-dialog.tpl.html create mode 100644 ui/src/app/components/widget/action/manage-widget-actions.directive.js create mode 100644 ui/src/app/components/widget/action/manage-widget-actions.scss create mode 100644 ui/src/app/components/widget/action/manage-widget-actions.tpl.html create mode 100644 ui/src/app/components/widget/action/widget-action-dialog.controller.js create mode 100644 ui/src/app/components/widget/action/widget-action-dialog.tpl.html rename ui/src/app/components/{ => widget}/widget-config.directive.js (93%) rename ui/src/app/components/{ => widget}/widget-config.tpl.html (98%) rename ui/src/app/components/{ => widget}/widget.controller.js (99%) rename ui/src/app/components/{ => widget}/widget.directive.js (94%) rename ui/src/app/components/{ => widget}/widget.scss (100%) diff --git a/ui/package.json b/ui/package.json index 73d7bcd347..6884543d77 100644 --- a/ui/package.json +++ b/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", diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js index f8f27ed932..e23e916cca 100644 --- a/ui/src/app/api/widget.service.js +++ b/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); diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 81ce0fa4f9..1a51a1af2c 100644 --- a/ui/src/app/common/types.constant.js +++ b/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" diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js index a3433c255f..e6d48ae13a 100644 --- a/ui/src/app/common/utils.service.js +++ b/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); diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js index d1427b6091..e61abcc644 100644 --- a/ui/src/app/components/dashboard.directive.js +++ b/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'; diff --git a/ui/src/app/components/finish-render.directive.js b/ui/src/app/components/finish-render.directive.js new file mode 100644 index 0000000000..9092e2db4d --- /dev/null +++ b/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); + }); + } + } + }; +} diff --git a/ui/src/app/components/material-icon-select.directive.js b/ui/src/app/components/material-icon-select.directive.js new file mode 100644 index 0000000000..45ab2cdd2c --- /dev/null +++ b/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: '=?', + } + }; +} diff --git a/ui/src/app/components/material-icon-select.tpl.html b/ui/src/app/components/material-icon-select.tpl.html new file mode 100644 index 0000000000..77ef87f2e3 --- /dev/null +++ b/ui/src/app/components/material-icon-select.tpl.html @@ -0,0 +1,26 @@ + +
+ {{icon}} + + + + + + +
\ No newline at end of file diff --git a/ui/src/app/components/material-icons-dialog.controller.js b/ui/src/app/components/material-icons-dialog.controller.js new file mode 100644 index 0000000000..94b18dccb4 --- /dev/null +++ b/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); + } +} diff --git a/ui/src/app/components/material-icons-dialog.scss b/ui/src/app/components/material-icons-dialog.scss new file mode 100644 index 0000000000..dbd83951b2 --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/ui/src/app/components/material-icons-dialog.tpl.html b/ui/src/app/components/material-icons-dialog.tpl.html new file mode 100644 index 0000000000..8a2eb6537b --- /dev/null +++ b/ui/src/app/components/material-icons-dialog.tpl.html @@ -0,0 +1,62 @@ + + +
+ +
+

{{ 'icon.select-icon' | translate }}

+ +
+ + + +
+ + + +
+
+ + +
+ +
+ +
+ +
+ + {{icon}} + + {{ icon }} + + +
+
+
+
+ + + + {{ 'action.cancel' | translate }} + + +
+
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.directive.js b/ui/src/app/components/widget/action/manage-widget-actions.directive.js new file mode 100644 index 0000000000..b2737296a8 --- /dev/null +++ b/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 +
+ +
+ widget-config.actions + + + add + + {{ 'widget-config.add-action' | translate }} + + + + search + + {{ 'action.search' | translate }} + + +
+
+ +
+ + search + + {{ 'widget-config.search-actions' | translate }} + + + + + + + + close + + {{ 'action.close' | translate }} + + +
+
+ + + + + + + + + + + + + + + + + + + + +
widget-config.action-sourcewidget-config.action-namewidget-config.action-iconwidget-config.action-type 
{{action.actionSourceName}}{{action.name}} + {{action.icon}} + {{action.typeName}} + + edit + + {{ 'widget-config.edit-action' | translate }} + + + + delete + + {{ 'widget-config.delete-action' | translate }} + + +
+
+ + +
diff --git a/ui/src/app/components/widget/action/widget-action-dialog.controller.js b/ui/src/app/components/widget/action/widget-action-dialog.controller.js new file mode 100644 index 0000000000..948308b591 --- /dev/null +++ b/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); + } +} diff --git a/ui/src/app/components/widget/action/widget-action-dialog.tpl.html b/ui/src/app/components/widget/action/widget-action-dialog.tpl.html new file mode 100644 index 0000000000..ccb9d41bd6 --- /dev/null +++ b/ui/src/app/components/widget/action/widget-action-dialog.tpl.html @@ -0,0 +1,81 @@ + + +
+ +
+

{{ (vm.isAdd ? 'widget-config.add-action' : 'widget-config.edit-action') | translate }}

+ + + + +
+
+ + + +
+ +
+ + + + + {{actionSource.name}} + + +
+
widget-config.action-source-required
+
+
+ + + +
+
widget-config.action-name-required
+
+
+ + + + + + + {{ actionType.name | translate }} + + +
+
widget-config.action-type-required
+
+
+
+
+
+
+ + + + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} + + + {{ 'action.cancel' | translate }} + + +
+
diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget/widget-config.directive.js similarity index 93% rename from ui/src/app/components/widget-config.directive.js rename to ui/src/app/components/widget/widget-config.directive.js index 4a083c10a7..3df9263c59 100644 --- a/ui/src/app/components/widget-config.directive.js +++ b/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: '=', diff --git a/ui/src/app/components/widget-config.tpl.html b/ui/src/app/components/widget/widget-config.tpl.html similarity index 98% rename from ui/src/app/components/widget-config.tpl.html rename to ui/src/app/components/widget/widget-config.tpl.html index 14cb27c8cd..2cc264c0d7 100644 --- a/ui/src/app/components/widget-config.tpl.html +++ b/ui/src/app/components/widget/widget-config.tpl.html @@ -275,4 +275,10 @@ + + + + + + diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget/widget.controller.js similarity index 99% rename from ui/src/app/components/widget.controller.js rename to ui/src/app/components/widget/widget.controller.js index f2e231fa29..3bda80991a 100644 --- a/ui/src/app/components/widget.controller.js +++ b/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 */ diff --git a/ui/src/app/components/widget.directive.js b/ui/src/app/components/widget/widget.directive.js similarity index 94% rename from ui/src/app/components/widget.directive.js rename to ui/src/app/components/widget/widget.directive.js index a0f1b83c48..40d5a83976 100644 --- a/ui/src/app/components/widget.directive.js +++ b/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'; diff --git a/ui/src/app/components/widget.scss b/ui/src/app/components/widget/widget.scss similarity index 100% rename from ui/src/app/components/widget.scss rename to ui/src/app/components/widget/widget.scss diff --git a/ui/src/app/dashboard/add-widget.tpl.html b/ui/src/app/dashboard/add-widget.tpl.html index 894c23d482..3920f91aeb 100644 --- a/ui/src/app/dashboard/add-widget.tpl.html +++ b/ui/src/app/dashboard/add-widget.tpl.html @@ -34,6 +34,7 @@
- {{ vm.isAdd ? 'Add' : 'Save' }} + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} - Cancel + {{ 'action.cancel' | translate }} diff --git a/ui/src/app/dashboard/states/manage-dashboard-states.controller.js b/ui/src/app/dashboard/states/manage-dashboard-states.controller.js index 3595f65705..1aa95b7b02 100644 --- a/ui/src/app/dashboard/states/manage-dashboard-states.controller.js +++ b/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(); }); - - } } diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js index 2a27c93806..9d3e1d16c6 100644 --- a/ui/src/app/layout/index.js +++ b/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 ]) diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 4710b9281f..63f20e5410 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/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",