thingsboard-memoizeit
Changes
ui/package.json 1(+1 -0)
ui/src/app/api/widget.service.js 31(+25 -6)
ui/src/app/common/types.constant.js 24(+24 -0)
ui/src/app/common/utils.service.js 41(+39 -2)
ui/src/app/dashboard/index.js 2(+1 -1)
ui/src/app/layout/index.js 2(+2 -0)
ui/src/app/locale/locale.constant.js 30(+29 -1)
Details
ui/package.json 1(+1 -0)
diff --git a/ui/package.json b/ui/package.json
index 73d7bcd..6884543 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",
ui/src/app/api/widget.service.js 31(+25 -6)
diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js
index f8f27ed..e23e916 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);
ui/src/app/common/types.constant.js 24(+24 -0)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 81ce0fa..1a51a1a 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"
ui/src/app/common/utils.service.js 41(+39 -2)
diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js
index a3433c2..e6d48ae 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 d1427b6..e61abcc 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 0000000..9092e2d
--- /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-icons-dialog.controller.js b/ui/src/app/components/material-icons-dialog.controller.js
new file mode 100644
index 0000000..94b18dc
--- /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 0000000..dbd8395
--- /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 0000000..8a2eb65
--- /dev/null
+++ b/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>
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 0000000..45ab2cd
--- /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 0000000..77ef87f
--- /dev/null
+++ b/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>
\ No newline at end of file
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 0000000..b273729
--- /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<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);
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.scss b/ui/src/app/components/widget/action/manage-widget-actions.scss
new file mode 100644
index 0000000..aee98ad
--- /dev/null
+++ b/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;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.tpl.html b/ui/src/app/components/widget/action/manage-widget-actions.tpl.html
new file mode 100644
index 0000000..b06959b
--- /dev/null
+++ b/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> </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> </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>
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 0000000..948308b
--- /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 0000000..ccb9d41
--- /dev/null
+++ b/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>
diff --git a/ui/src/app/dashboard/add-widget.tpl.html b/ui/src/app/dashboard/add-widget.tpl.html
index 894c23d..3920f91 100644
--- a/ui/src/app/dashboard/add-widget.tpl.html
+++ b/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"
diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js
index dcf05e2..5d32706 100644
--- a/ui/src/app/dashboard/edit-widget.directive.js
+++ b/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 = {};
diff --git a/ui/src/app/dashboard/edit-widget.tpl.html b/ui/src/app/dashboard/edit-widget.tpl.html
index 74f5755..c6bfd5a 100644
--- a/ui/src/app/dashboard/edit-widget.tpl.html
+++ b/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"
ui/src/app/dashboard/index.js 2(+1 -1)
diff --git a/ui/src/app/dashboard/index.js b/ui/src/app/dashboard/index.js
index d940f09..4089716 100644
--- a/ui/src/app/dashboard/index.js
+++ b/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';
diff --git a/ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html b/ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html
index 385de94..4cf0a3d 100644
--- a/ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html
+++ b/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>
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 3595f65..1aa95b7 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();
});
-
-
}
}
ui/src/app/layout/index.js 2(+2 -0)
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js
index 2a27c93..9d3e1d1 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
])
ui/src/app/locale/locale.constant.js 30(+29 -1)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 4710b92..63f20e5 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",