Commit 13a6c9a14210b5f5b814bea191fae5411befbc7c
Committed by
GitHub
Merge pull request #177 from thingsboard/feature/TB-64
TB-64: Widget actions
Showing
40 changed files
with
1543 additions
and
92 deletions
... | ... | @@ -171,6 +171,48 @@ export default class Subscription { |
171 | 171 | return deferred.promise; |
172 | 172 | } |
173 | 173 | |
174 | + getFirstEntityInfo() { | |
175 | + var entityId; | |
176 | + var entityName; | |
177 | + if (this.type === this.ctx.types.widgetType.rpc.value) { | |
178 | + if (this.targetDeviceId) { | |
179 | + entityId = { | |
180 | + entityType: this.ctx.entityType.device, | |
181 | + id: this.targetDeviceId | |
182 | + } | |
183 | + entityName = this.targetDeviceName; | |
184 | + } | |
185 | + } else if (this.type == this.ctx.types.widgetType.alarm.value) { | |
186 | + if (this.alarmSource && this.alarmSource.entityType && this.alarmSource.entityId) { | |
187 | + entityId = { | |
188 | + entityType: this.alarmSource.entityType, | |
189 | + id: this.alarmSource.entityId | |
190 | + } | |
191 | + entityName = this.alarmSource.entityName; | |
192 | + } | |
193 | + } else { | |
194 | + for (var i=0;i<this.datasources.length;i++) { | |
195 | + var datasource = this.datasources[i]; | |
196 | + if (datasource && datasource.entityType && datasource.entityId) { | |
197 | + entityId = { | |
198 | + entityType: datasource.entityType, | |
199 | + id: datasource.entityId | |
200 | + } | |
201 | + entityName = datasource.entityName; | |
202 | + break; | |
203 | + } | |
204 | + } | |
205 | + } | |
206 | + if (entityId) { | |
207 | + return { | |
208 | + entityId: entityId, | |
209 | + entityName: entityName | |
210 | + }; | |
211 | + } else { | |
212 | + return null; | |
213 | + } | |
214 | + } | |
215 | + | |
174 | 216 | initAlarmSubscription() { |
175 | 217 | var deferred = this.ctx.$q.defer(); |
176 | 218 | if (!this.ctx.aliasController) { |
... | ... | @@ -342,6 +384,7 @@ export default class Subscription { |
342 | 384 | function success(aliasInfo) { |
343 | 385 | if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType == subscription.ctx.types.entityType.device) { |
344 | 386 | subscription.targetDeviceId = aliasInfo.currentEntity.id; |
387 | + subscription.targetDeviceName = aliasInfo.currentEntity.name; | |
345 | 388 | if (subscription.targetDeviceId) { |
346 | 389 | subscription.rpcEnabled = true; |
347 | 390 | } else { | ... | ... |
... | ... | @@ -40,7 +40,7 @@ export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsbo |
40 | 40 | .name; |
41 | 41 | |
42 | 42 | /*@ngInject*/ |
43 | -function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, types, utils) { | |
43 | +function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $translate, types, utils) { | |
44 | 44 | |
45 | 45 | $window.$ = $; |
46 | 46 | $window.jQuery = $; |
... | ... | @@ -548,13 +548,21 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ |
548 | 548 | ' }\n\n' + |
549 | 549 | |
550 | 550 | ' self.typeParameters = function() {\n\n' + |
551 | - { | |
552 | - useCustomDatasources: false, | |
553 | - maxDatasources: -1 //unlimited | |
554 | - maxDataKeys: -1 //unlimited | |
555 | - } | |
551 | + return { | |
552 | + useCustomDatasources: false, | |
553 | + maxDatasources: -1 //unlimited | |
554 | + maxDataKeys: -1 //unlimited | |
555 | + }; | |
556 | 556 | ' }\n\n' + |
557 | 557 | |
558 | + ' self.actionSources = function() {\n\n' + | |
559 | + return { | |
560 | + 'headerButton': { | |
561 | + name: 'Header button', | |
562 | + multiple: true | |
563 | + } | |
564 | + }; | |
565 | + }\n\n' + | |
558 | 566 | ' self.onResize = function() {\n\n' + |
559 | 567 | |
560 | 568 | ' }\n\n' + |
... | ... | @@ -611,6 +619,16 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ |
611 | 619 | if (angular.isUndefined(result.typeParameters.maxDataKeys)) { |
612 | 620 | result.typeParameters.maxDataKeys = -1; |
613 | 621 | } |
622 | + if (angular.isFunction(widgetTypeInstance.actionSources)) { | |
623 | + result.actionSources = widgetTypeInstance.actionSources(); | |
624 | + } else { | |
625 | + result.actionSources = {}; | |
626 | + } | |
627 | + for (var actionSourceId in types.widgetActionSources) { | |
628 | + result.actionSources[actionSourceId] = angular.copy(types.widgetActionSources[actionSourceId]); | |
629 | + result.actionSources[actionSourceId].name = $translate.instant(result.actionSources[actionSourceId].name) + ''; | |
630 | + } | |
631 | + | |
614 | 632 | return result; |
615 | 633 | } catch (e) { |
616 | 634 | utils.processWidgetException(e); |
... | ... | @@ -650,6 +668,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ |
650 | 668 | widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema; |
651 | 669 | } |
652 | 670 | widgetInfo.typeParameters = widgetType.typeParameters; |
671 | + widgetInfo.actionSources = widgetType.actionSources; | |
653 | 672 | putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem); |
654 | 673 | putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem); |
655 | 674 | deferred.resolve(widgetInfo); | ... | ... |
... | ... | @@ -399,6 +399,31 @@ export default angular.module('thingsboard.types', []) |
399 | 399 | } |
400 | 400 | } |
401 | 401 | }, |
402 | + widgetActionSources: { | |
403 | + headerButton: { | |
404 | + name: 'widget-action.header-button', | |
405 | + value: 'headerButton', | |
406 | + multiple: true | |
407 | + } | |
408 | + }, | |
409 | + widgetActionTypes: { | |
410 | + openDashboardState: { | |
411 | + name: 'widget-action.open-dashboard-state', | |
412 | + value: 'openDashboardState' | |
413 | + }, | |
414 | + updateDashboardState: { | |
415 | + name: 'widget-action.update-dashboard-state', | |
416 | + value: 'updateDashboardState' | |
417 | + }, | |
418 | + openDashboard: { | |
419 | + name: 'widget-action.open-dashboard', | |
420 | + value: 'openDashboard' | |
421 | + }, | |
422 | + custom: { | |
423 | + name: 'widget-action.custom', | |
424 | + value: 'custom' | |
425 | + } | |
426 | + }, | |
402 | 427 | systemBundleAlias: { |
403 | 428 | charts: "charts", |
404 | 429 | cards: "cards" | ... | ... |
... | ... | @@ -13,6 +13,13 @@ |
13 | 13 | * See the License for the specific language governing permissions and |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | + | |
17 | +/* eslint-disable import/no-unresolved, import/default */ | |
18 | + | |
19 | +import materialIconsCodepoints from 'raw-loader!material-design-icons/iconfont/codepoints'; | |
20 | + | |
21 | +/* eslint-enable import/no-unresolved, import/default */ | |
22 | + | |
16 | 23 | import tinycolor from "tinycolor2"; |
17 | 24 | import jsonSchemaDefaults from "json-schema-defaults"; |
18 | 25 | import thingsboardTypes from "./types.constant"; |
... | ... | @@ -24,11 +31,18 @@ export default angular.module('thingsboard.utils', [thingsboardTypes]) |
24 | 31 | const varsRegex = /\$\{([^\}]*)\}/g; |
25 | 32 | |
26 | 33 | /*@ngInject*/ |
27 | -function Utils($mdColorPalette, $rootScope, $window, $translate, types) { | |
34 | +function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, types) { | |
28 | 35 | |
29 | 36 | var predefinedFunctions = {}, |
30 | 37 | predefinedFunctionsList = [], |
31 | - materialColors = []; | |
38 | + materialColors = [], | |
39 | + materialIcons = []; | |
40 | + | |
41 | + var commonMaterialIcons = [ 'more_horiz', 'more_vert', 'open_in_new', 'visibility', 'play_arrow', 'arrow_back', 'arrow_downward', | |
42 | + 'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people', | |
43 | + 'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search', | |
44 | + 'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export', | |
45 | + 'share', 'add', 'edit', 'done' ]; | |
32 | 46 | |
33 | 47 | predefinedFunctions['Sin'] = "return Math.round(1000*Math.sin(time/5000));"; |
34 | 48 | predefinedFunctions['Cos'] = "return Math.round(1000*Math.cos(time/5000));"; |
... | ... | @@ -122,6 +136,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { |
122 | 136 | getDefaultDatasourceJson: getDefaultDatasourceJson, |
123 | 137 | getDefaultAlarmDataKeys: getDefaultAlarmDataKeys, |
124 | 138 | getMaterialColor: getMaterialColor, |
139 | + getMaterialIcons: getMaterialIcons, | |
140 | + getCommonMaterialIcons: getCommonMaterialIcons, | |
125 | 141 | getPredefinedFunctionBody: getPredefinedFunctionBody, |
126 | 142 | getPredefinedFunctionsList: getPredefinedFunctionsList, |
127 | 143 | genMaterialColor: genMaterialColor, |
... | ... | @@ -136,7 +152,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { |
136 | 152 | validateDatasources: validateDatasources, |
137 | 153 | createKey: createKey, |
138 | 154 | createLabelFromDatasource: createLabelFromDatasource, |
139 | - insertVariable: insertVariable | |
155 | + insertVariable: insertVariable, | |
156 | + customTranslation: customTranslation | |
140 | 157 | } |
141 | 158 | |
142 | 159 | return service; |
... | ... | @@ -154,6 +171,31 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { |
154 | 171 | return materialColors[colorIndex].value; |
155 | 172 | } |
156 | 173 | |
174 | + function getMaterialIcons() { | |
175 | + var deferred = $q.defer(); | |
176 | + if (materialIcons.length) { | |
177 | + deferred.resolve(materialIcons); | |
178 | + } else { | |
179 | + $timeout(function() { | |
180 | + var codepointsArray = materialIconsCodepoints.split("\n"); | |
181 | + codepointsArray.forEach(function (codepoint) { | |
182 | + if (codepoint && codepoint.length) { | |
183 | + var values = codepoint.split(' '); | |
184 | + if (values && values.length == 2) { | |
185 | + materialIcons.push(values[0]); | |
186 | + } | |
187 | + } | |
188 | + }); | |
189 | + deferred.resolve(materialIcons); | |
190 | + }); | |
191 | + } | |
192 | + return deferred.promise; | |
193 | + } | |
194 | + | |
195 | + function getCommonMaterialIcons() { | |
196 | + return commonMaterialIcons; | |
197 | + } | |
198 | + | |
157 | 199 | function genMaterialColor(str) { |
158 | 200 | var hash = Math.abs(hashCode(str)); |
159 | 201 | return getMaterialColor(hash); |
... | ... | @@ -432,4 +474,16 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { |
432 | 474 | return result; |
433 | 475 | } |
434 | 476 | |
477 | + function customTranslation(translationValue, defaultValue) { | |
478 | + var result = ''; | |
479 | + var translationId = types.translate.customTranslationsPrefix + translationValue; | |
480 | + var translation = $translate.instant(translationId); | |
481 | + if (translation != translationId) { | |
482 | + result = translation + ''; | |
483 | + } else { | |
484 | + result = defaultValue; | |
485 | + } | |
486 | + return result; | |
487 | + } | |
488 | + | |
435 | 489 | } | ... | ... |
... | ... | @@ -87,23 +87,32 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u |
87 | 87 | dashboardService.getDashboardInfo(ngModelCtrl.$viewValue).then( |
88 | 88 | function success(dashboard) { |
89 | 89 | scope.dashboard = dashboard; |
90 | + startWatchers(); | |
90 | 91 | }, |
91 | 92 | function fail() { |
92 | 93 | scope.dashboard = null; |
94 | + scope.updateView(); | |
95 | + startWatchers(); | |
93 | 96 | } |
94 | 97 | ); |
95 | 98 | } else { |
96 | 99 | scope.dashboard = null; |
100 | + startWatchers(); | |
97 | 101 | } |
98 | 102 | } |
99 | 103 | |
100 | - scope.$watch('dashboard', function () { | |
101 | - scope.updateView(); | |
102 | - }); | |
103 | - | |
104 | - scope.$watch('disabled', function () { | |
105 | - scope.updateView(); | |
106 | - }); | |
104 | + function startWatchers() { | |
105 | + scope.$watch('dashboard', function (newVal, prevVal) { | |
106 | + if (!angular.equals(newVal, prevVal)) { | |
107 | + scope.updateView(); | |
108 | + } | |
109 | + }); | |
110 | + scope.$watch('disabled', function (newVal, prevVal) { | |
111 | + if (!angular.equals(newVal, prevVal)) { | |
112 | + scope.updateView(); | |
113 | + } | |
114 | + }); | |
115 | + } | |
107 | 116 | |
108 | 117 | if (scope.selectFirstDashboard) { |
109 | 118 | var pageLink = {limit: 1, textSearch: ''}; |
... | ... | @@ -111,6 +120,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u |
111 | 120 | var dashboards = result.data; |
112 | 121 | if (dashboards.length > 0) { |
113 | 122 | scope.dashboard = dashboards[0]; |
123 | + scope.updateView(); | |
114 | 124 | } |
115 | 125 | }, function fail() { |
116 | 126 | }); | ... | ... |
... | ... | @@ -20,7 +20,7 @@ import 'javascript-detect-element-resize/detect-element-resize'; |
20 | 20 | import angularGridster from 'angular-gridster'; |
21 | 21 | import thingsboardTypes from '../common/types.constant'; |
22 | 22 | import thingsboardApiWidget from '../api/widget.service'; |
23 | -import thingsboardWidget from './widget.directive'; | |
23 | +import thingsboardWidget from './widget/widget.directive'; | |
24 | 24 | import thingsboardToast from '../services/toast'; |
25 | 25 | import thingsboardTimewindow from './timewindow.directive'; |
26 | 26 | import thingsboardEvents from './tb-event-directives'; |
... | ... | @@ -187,6 +187,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ |
187 | 187 | vm.showWidgetActions = showWidgetActions; |
188 | 188 | vm.widgetTitleStyle = widgetTitleStyle; |
189 | 189 | vm.widgetTitle = widgetTitle; |
190 | + vm.customWidgetHeaderActions = customWidgetHeaderActions; | |
190 | 191 | vm.widgetActions = widgetActions; |
191 | 192 | vm.dropWidgetShadow = dropWidgetShadow; |
192 | 193 | vm.enableWidgetFullscreen = enableWidgetFullscreen; |
... | ... | @@ -875,6 +876,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ |
875 | 876 | } |
876 | 877 | } |
877 | 878 | |
879 | + function customWidgetHeaderActions(widget) { | |
880 | + var ctx = widgetContext(widget); | |
881 | + if (ctx && ctx.customHeaderActions && ctx.customHeaderActions.length) { | |
882 | + return ctx.customHeaderActions; | |
883 | + } else { | |
884 | + return []; | |
885 | + } | |
886 | + } | |
887 | + | |
878 | 888 | function widgetActions(widget) { |
879 | 889 | var ctx = widgetContext(widget); |
880 | 890 | if (ctx && ctx.widgetActions && ctx.widgetActions.length) { | ... | ... |
... | ... | @@ -52,6 +52,16 @@ |
52 | 52 | <tb-timewindow aggregation="{{vm.hasAggregation(widget)}}" ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow> |
53 | 53 | </div> |
54 | 54 | <div class="tb-widget-actions" layout="row" layout-align="start center" ng-show="vm.showWidgetActions(widget)" tb-mousedown="$event.stopPropagation()"> |
55 | + <md-button ng-repeat="action in vm.customWidgetHeaderActions(widget)" | |
56 | + aria-label="{{action.displayName}}" | |
57 | + ng-show="!vm.isEdit" | |
58 | + ng-click="action.onAction($event)" | |
59 | + class="md-icon-button"> | |
60 | + <md-tooltip md-direction="top"> | |
61 | + {{action.displayName}} | |
62 | + </md-tooltip> | |
63 | + <ng-md-icon size="20" icon="{{action.icon}}"></ng-md-icon> | |
64 | + </md-button> | |
55 | 65 | <md-button ng-repeat="action in vm.widgetActions(widget)" |
56 | 66 | aria-label="{{ action.name | translate }}" |
57 | 67 | ng-show="!vm.isEdit && action.show" | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +export default angular.module('thingsboard.directives.finishRender', []) | |
18 | + .directive('tbOnFinishRender', OnFinishRender) | |
19 | + .name; | |
20 | + | |
21 | +/*@ngInject*/ | |
22 | +function OnFinishRender($timeout) { | |
23 | + return { | |
24 | + restrict: 'A', | |
25 | + link: function (scope, element, attr) { | |
26 | + if (scope.$last === true) { | |
27 | + $timeout(function () { | |
28 | + scope.$emit(attr.tbOnFinishRender); | |
29 | + }); | |
30 | + } | |
31 | + } | |
32 | + }; | |
33 | +} | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +import './material-icon-select.scss'; | |
18 | + | |
19 | +import MaterialIconsDialogController from './material-icons-dialog.controller'; | |
20 | + | |
21 | +/* eslint-disable import/no-unresolved, import/default */ | |
22 | + | |
23 | +import materialIconSelectTemplate from './material-icon-select.tpl.html'; | |
24 | +import materialIconsDialogTemplate from './material-icons-dialog.tpl.html'; | |
25 | + | |
26 | +/* eslint-enable import/no-unresolved, import/default */ | |
27 | + | |
28 | + | |
29 | +export default angular.module('thingsboard.directives.materialIconSelect', []) | |
30 | + .controller('MaterialIconsDialogController', MaterialIconsDialogController) | |
31 | + .directive('tbMaterialIconSelect', MaterialIconSelect) | |
32 | + .name; | |
33 | + | |
34 | +/*@ngInject*/ | |
35 | +function MaterialIconSelect($compile, $templateCache, $document, $mdDialog) { | |
36 | + | |
37 | + var linker = function (scope, element, attrs, ngModelCtrl) { | |
38 | + var template = $templateCache.get(materialIconSelectTemplate); | |
39 | + element.html(template); | |
40 | + | |
41 | + scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; | |
42 | + scope.icon = null; | |
43 | + | |
44 | + scope.updateView = function () { | |
45 | + ngModelCtrl.$setViewValue(scope.icon); | |
46 | + } | |
47 | + | |
48 | + ngModelCtrl.$render = function () { | |
49 | + if (ngModelCtrl.$viewValue) { | |
50 | + scope.icon = ngModelCtrl.$viewValue; | |
51 | + } | |
52 | + if (!scope.icon || !scope.icon.length) { | |
53 | + scope.icon = 'more_horiz'; | |
54 | + } | |
55 | + } | |
56 | + | |
57 | + scope.$watch('icon', function () { | |
58 | + scope.updateView(); | |
59 | + }); | |
60 | + | |
61 | + scope.openIconDialog = function($event) { | |
62 | + if ($event) { | |
63 | + $event.stopPropagation(); | |
64 | + } | |
65 | + $mdDialog.show({ | |
66 | + controller: 'MaterialIconsDialogController', | |
67 | + controllerAs: 'vm', | |
68 | + templateUrl: materialIconsDialogTemplate, | |
69 | + parent: angular.element($document[0].body), | |
70 | + locals: {icon: scope.icon}, | |
71 | + skipHide: true, | |
72 | + fullscreen: true, | |
73 | + targetEvent: $event | |
74 | + }).then(function (icon) { | |
75 | + scope.icon = icon; | |
76 | + }); | |
77 | + } | |
78 | + | |
79 | + $compile(element.contents())(scope); | |
80 | + } | |
81 | + | |
82 | + return { | |
83 | + restrict: "E", | |
84 | + require: "^ngModel", | |
85 | + link: linker, | |
86 | + scope: { | |
87 | + tbRequired: '=?', | |
88 | + } | |
89 | + }; | |
90 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +.tb-material-icon-select { | |
18 | + md-icon { | |
19 | + padding: 4px; | |
20 | + margin: 8px 4px 4px; | |
21 | + cursor: pointer; | |
22 | + border: solid 1px rgba(0,0,0,0.27); | |
23 | + } | |
24 | + md-input-container { | |
25 | + margin-bottom: 0px; | |
26 | + } | |
27 | +} | |
\ No newline at end of file | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<div class="tb-material-icon-select" layout="row"> | |
19 | + <md-icon class="material-icons" ng-click="openIconDialog($event)">{{icon}}</md-icon> | |
20 | + <md-input-container flex> | |
21 | + <label translate>icon.icon</label> | |
22 | + <input ng-mousedown="openIconDialog($event)" ng-model="icon"> | |
23 | + </md-input-container> | |
24 | +</div> | |
\ No newline at end of file | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +import './material-icons-dialog.scss'; | |
18 | + | |
19 | +/*@ngInject*/ | |
20 | +export default function MaterialIconsDialogController($scope, $mdDialog, $timeout, utils, icon) { | |
21 | + | |
22 | + var vm = this; | |
23 | + | |
24 | + vm.selectedIcon = icon; | |
25 | + | |
26 | + vm.showAll = false; | |
27 | + vm.loadingIcons = false; | |
28 | + | |
29 | + $scope.$watch('vm.showAll', function(showAll) { | |
30 | + if (showAll) { | |
31 | + vm.loadingIcons = true; | |
32 | + $timeout(function() { | |
33 | + utils.getMaterialIcons().then( | |
34 | + function success(icons) { | |
35 | + vm.icons = icons; | |
36 | + } | |
37 | + ); | |
38 | + }); | |
39 | + } else { | |
40 | + vm.icons = utils.getCommonMaterialIcons(); | |
41 | + } | |
42 | + }); | |
43 | + | |
44 | + $scope.$on('iconsLoadFinished', function() { | |
45 | + vm.loadingIcons = false; | |
46 | + }); | |
47 | + | |
48 | + vm.cancel = cancel; | |
49 | + vm.selectIcon = selectIcon; | |
50 | + | |
51 | + function cancel() { | |
52 | + $mdDialog.cancel(); | |
53 | + } | |
54 | + | |
55 | + function selectIcon($event, icon) { | |
56 | + vm.selectedIcon = icon; | |
57 | + $mdDialog.hide(vm.selectedIcon); | |
58 | + } | |
59 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +.tb-material-icons-dialog { | |
18 | + button.md-icon-button.tb-select-icon-button { | |
19 | + border: solid 1px orange; | |
20 | + border-radius: 0%; | |
21 | + padding: 16px; | |
22 | + height: 56px; | |
23 | + width: 56px; | |
24 | + margin: 10px; | |
25 | + } | |
26 | + .tb-icons-load { | |
27 | + top: 64px; | |
28 | + background: rgba(255,255,255,0.75); | |
29 | + z-index: 3; | |
30 | + } | |
31 | +} | |
\ No newline at end of file | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<md-dialog class="tb-material-icons-dialog" aria-label="{{'icon.material-icons' | translate }}" style="min-width: 600px;"> | |
19 | + <form> | |
20 | + <md-toolbar> | |
21 | + <div class="md-toolbar-tools"> | |
22 | + <h2>{{ 'icon.select-icon' | translate }}</h2> | |
23 | + <span flex></span> | |
24 | + <section layout="row" layout-align="start center"> | |
25 | + <md-switch ng-model="vm.showAll" | |
26 | + aria-label="{{ 'icon.show-all' | translate }}"> | |
27 | + </md-switch> | |
28 | + <label translate>icon.show-all</label> | |
29 | + </section> | |
30 | + <md-button class="md-icon-button" ng-click="vm.cancel()"> | |
31 | + <ng-md-icon icon="close" aria-label="{{ 'action.close' | translate }}"></ng-md-icon> | |
32 | + </md-button> | |
33 | + </div> | |
34 | + </md-toolbar> | |
35 | + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> | |
36 | + <span style="min-height: 5px;" flex="" ng-show="!loading"></span> | |
37 | + <div class="tb-absolute-fill tb-icons-load" ng-show="vm.loadingIcons" layout="column" layout-align="center center"> | |
38 | + <md-progress-circular md-mode="indeterminate" ng-disabled="!vm.loadingIcons" class="md-accent" md-diameter="40"></md-progress-circular> | |
39 | + </div> | |
40 | + <md-dialog-content> | |
41 | + <div class="md-dialog-content"> | |
42 | + <md-content class="md-padding" layout="column"> | |
43 | + <fieldset ng-disabled="loading"> | |
44 | + <md-button ng-class="{'md-primary md-raised': icon == vm.selectedIcon}" class="tb-select-icon-button md-icon-button" | |
45 | + ng-repeat="icon in vm.icons" ng-click="vm.selectIcon($event, icon)" tb-on-finish-render="iconsLoadFinished"> | |
46 | + <md-icon class="material-icons">{{icon}}</md-icon> | |
47 | + <md-tooltip md-direction="bottom"> | |
48 | + {{ icon }} | |
49 | + </md-tooltip> | |
50 | + </md-button> | |
51 | + </fieldset> | |
52 | + </md-content> | |
53 | + </div> | |
54 | + </md-dialog-content> | |
55 | + <md-dialog-actions layout="row"> | |
56 | + <span flex></span> | |
57 | + <md-button ng-disabled="loading" ng-click="vm.cancel()"> | |
58 | + {{ 'action.cancel' | translate }} | |
59 | + </md-button> | |
60 | + </md-dialog-actions> | |
61 | + </form> | |
62 | +</md-dialog> | ... | ... |
1 | +/** | |
2 | + * Created by igor on 6/20/17. | |
3 | + */ | |
4 | +/* | |
5 | + * Copyright © 2016-2017 The Thingsboard Authors | |
6 | + * | |
7 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
8 | + * you may not use this file except in compliance with the License. | |
9 | + * You may obtain a copy of the License at | |
10 | + * | |
11 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
12 | + * | |
13 | + * Unless required by applicable law or agreed to in writing, software | |
14 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
15 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
16 | + * See the License for the specific language governing permissions and | |
17 | + * limitations under the License. | |
18 | + */ | |
19 | + | |
20 | +import './manage-widget-actions.scss'; | |
21 | + | |
22 | +import thingsboardMaterialIconSelect from '../../material-icon-select.directive'; | |
23 | + | |
24 | +import WidgetActionDialogController from './widget-action-dialog.controller'; | |
25 | + | |
26 | +/* eslint-disable import/no-unresolved, import/default */ | |
27 | + | |
28 | +import manageWidgetActionsTemplate from './manage-widget-actions.tpl.html'; | |
29 | +import widgetActionDialogTemplate from './widget-action-dialog.tpl.html'; | |
30 | + | |
31 | +/* eslint-enable import/no-unresolved, import/default */ | |
32 | + | |
33 | +export default angular.module('thingsboard.directives.widgetActions', [thingsboardMaterialIconSelect]) | |
34 | + .controller('WidgetActionDialogController', WidgetActionDialogController) | |
35 | + .directive('tbManageWidgetActions', ManageWidgetActions) | |
36 | + .name; | |
37 | + | |
38 | +/*@ngInject*/ | |
39 | +function ManageWidgetActions() { | |
40 | + return { | |
41 | + restrict: "E", | |
42 | + scope: true, | |
43 | + bindToController: { | |
44 | + actionSources: '=', | |
45 | + widgetActions: '=', | |
46 | + fetchDashboardStates: '&', | |
47 | + }, | |
48 | + controller: ManageWidgetActionsController, | |
49 | + controllerAs: 'vm', | |
50 | + templateUrl: manageWidgetActionsTemplate | |
51 | + }; | |
52 | +} | |
53 | + | |
54 | +/* eslint-disable angular/angularelement */ | |
55 | + | |
56 | + | |
57 | +/*@ngInject*/ | |
58 | +function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog, $q, $filter, | |
59 | + $translate, $timeout, utils, types) { | |
60 | + | |
61 | + let vm = this; | |
62 | + | |
63 | + vm.allActions = []; | |
64 | + | |
65 | + vm.actions = []; | |
66 | + vm.actionsCount = 0; | |
67 | + | |
68 | + vm.query = { | |
69 | + order: 'actionSourceName', | |
70 | + limit: 10, | |
71 | + page: 1, | |
72 | + search: null | |
73 | + }; | |
74 | + | |
75 | + vm.enterFilterMode = enterFilterMode; | |
76 | + vm.exitFilterMode = exitFilterMode; | |
77 | + vm.onReorder = onReorder; | |
78 | + vm.onPaginate = onPaginate; | |
79 | + vm.addAction = addAction; | |
80 | + vm.editAction = editAction; | |
81 | + vm.deleteAction = deleteAction; | |
82 | + | |
83 | + $timeout(function(){ | |
84 | + $scope.manageWidgetActionsForm.querySearchInput.$pristine = false; | |
85 | + }); | |
86 | + | |
87 | + $scope.$watch('vm.widgetActions', function() { | |
88 | + if (vm.widgetActions) { | |
89 | + reloadActions(); | |
90 | + } | |
91 | + }); | |
92 | + | |
93 | + $scope.$watch("vm.query.search", function(newVal, prevVal) { | |
94 | + if (!angular.equals(newVal, prevVal) && vm.query.search != null) { | |
95 | + updateActions(); | |
96 | + } | |
97 | + }); | |
98 | + | |
99 | + function enterFilterMode () { | |
100 | + vm.query.search = ''; | |
101 | + } | |
102 | + | |
103 | + function exitFilterMode () { | |
104 | + vm.query.search = null; | |
105 | + updateActions(); | |
106 | + } | |
107 | + | |
108 | + function onReorder () { | |
109 | + updateActions(); | |
110 | + } | |
111 | + | |
112 | + function onPaginate () { | |
113 | + updateActions(); | |
114 | + } | |
115 | + | |
116 | + function addAction($event) { | |
117 | + if ($event) { | |
118 | + $event.stopPropagation(); | |
119 | + } | |
120 | + openWidgetActionDialog($event, null, true); | |
121 | + } | |
122 | + | |
123 | + function editAction ($event, action) { | |
124 | + if ($event) { | |
125 | + $event.stopPropagation(); | |
126 | + } | |
127 | + openWidgetActionDialog($event, action, false); | |
128 | + } | |
129 | + | |
130 | + function deleteAction($event, action) { | |
131 | + if ($event) { | |
132 | + $event.stopPropagation(); | |
133 | + } | |
134 | + if (action) { | |
135 | + var title = $translate.instant('widget-config.delete-action-title'); | |
136 | + var content = $translate.instant('widget-config.delete-action-text', {actionName: action.name}); | |
137 | + var confirm = $mdDialog.confirm() | |
138 | + .targetEvent($event) | |
139 | + .title(title) | |
140 | + .htmlContent(content) | |
141 | + .ariaLabel(title) | |
142 | + .cancel($translate.instant('action.no')) | |
143 | + .ok($translate.instant('action.yes')); | |
144 | + | |
145 | + confirm._options.skipHide = true; | |
146 | + confirm._options.fullscreen = true; | |
147 | + | |
148 | + $mdDialog.show(confirm).then(function () { | |
149 | + var index = getActionIndex(action.id, vm.allActions); | |
150 | + if (index > -1) { | |
151 | + vm.allActions.splice(index, 1); | |
152 | + } | |
153 | + var targetActions = vm.widgetActions[action.actionSourceId]; | |
154 | + index = getActionIndex(action.id, targetActions); | |
155 | + if (index > -1) { | |
156 | + targetActions.splice(index, 1); | |
157 | + } | |
158 | + $scope.manageWidgetActionsForm.$setDirty(); | |
159 | + updateActions(); | |
160 | + }); | |
161 | + } | |
162 | + } | |
163 | + | |
164 | + function openWidgetActionDialog($event, action, isAdd) { | |
165 | + var prevActionId = null; | |
166 | + if (!isAdd) { | |
167 | + prevActionId = action.id; | |
168 | + } | |
169 | + var availableActionSources = {}; | |
170 | + for (var id in vm.actionSources) { | |
171 | + var actionSource = vm.actionSources[id]; | |
172 | + if (actionSource.multiple) { | |
173 | + availableActionSources[id] = actionSource; | |
174 | + } else { | |
175 | + if (!isAdd && action.actionSourceId == id) { | |
176 | + availableActionSources[id] = actionSource; | |
177 | + } else { | |
178 | + var result = $filter('filter')(vm.allActions, {actionSourceId: id}); | |
179 | + if (!result || !result.length) { | |
180 | + availableActionSources[id] = actionSource; | |
181 | + } | |
182 | + } | |
183 | + } | |
184 | + } | |
185 | + $mdDialog.show({ | |
186 | + controller: 'WidgetActionDialogController', | |
187 | + controllerAs: 'vm', | |
188 | + templateUrl: widgetActionDialogTemplate, | |
189 | + parent: angular.element($document[0].body), | |
190 | + locals: {isAdd: isAdd, fetchDashboardStates: vm.fetchDashboardStates, | |
191 | + actionSources: availableActionSources, widgetActions: vm.widgetActions, | |
192 | + action: angular.copy(action)}, | |
193 | + skipHide: true, | |
194 | + fullscreen: true, | |
195 | + targetEvent: $event | |
196 | + }).then(function (action) { | |
197 | + saveAction(action, prevActionId); | |
198 | + updateActions(); | |
199 | + }); | |
200 | + } | |
201 | + | |
202 | + function getActionIndex(id, actions) { | |
203 | + var result = $filter('filter')(actions, {id: id}, true); | |
204 | + if (result && result.length) { | |
205 | + return actions.indexOf(result[0]); | |
206 | + } | |
207 | + return -1; | |
208 | + } | |
209 | + | |
210 | + function saveAction(action, prevActionId) { | |
211 | + var actionSourceName = vm.actionSources[action.actionSourceId].name; | |
212 | + action.actionSourceName = utils.customTranslation(actionSourceName, actionSourceName); | |
213 | + action.typeName = $translate.instant(types.widgetActionTypes[action.type].name); | |
214 | + var actionSourceId = action.actionSourceId; | |
215 | + var widgetAction = angular.copy(action); | |
216 | + delete widgetAction.actionSourceId; | |
217 | + delete widgetAction.actionSourceName; | |
218 | + delete widgetAction.typeName; | |
219 | + var targetActions = vm.widgetActions[actionSourceId]; | |
220 | + if (!targetActions) { | |
221 | + targetActions = []; | |
222 | + vm.widgetActions[actionSourceId] = targetActions; | |
223 | + } | |
224 | + if (prevActionId) { | |
225 | + var index = getActionIndex(prevActionId, vm.allActions); | |
226 | + if (index > -1) { | |
227 | + vm.allActions[index] = action; | |
228 | + } | |
229 | + index = getActionIndex(prevActionId, targetActions); | |
230 | + if (index > -1) { | |
231 | + targetActions[index] = widgetAction; | |
232 | + } | |
233 | + } else { | |
234 | + vm.allActions.push(action); | |
235 | + targetActions.push(widgetAction); | |
236 | + } | |
237 | + $scope.manageWidgetActionsForm.$setDirty(); | |
238 | + } | |
239 | + | |
240 | + function reloadActions() { | |
241 | + vm.allActions = []; | |
242 | + vm.actions = []; | |
243 | + vm.actionsCount = 0; | |
244 | + | |
245 | + for (var actionSourceId in vm.widgetActions) { | |
246 | + var actionSource = vm.actionSources[actionSourceId]; | |
247 | + var actionSourceActions = vm.widgetActions[actionSourceId]; | |
248 | + for (var i=0;i<actionSourceActions.length;i++) { | |
249 | + var actionSourceAction = actionSourceActions[i]; | |
250 | + var action = angular.copy(actionSourceAction); | |
251 | + action.actionSourceId = actionSourceId; | |
252 | + action.actionSourceName = utils.customTranslation(actionSource.name, actionSource.name); | |
253 | + action.typeName = $translate.instant(types.widgetActionTypes[actionSourceAction.type].name); | |
254 | + vm.allActions.push(action); | |
255 | + } | |
256 | + } | |
257 | + | |
258 | + updateActions (); | |
259 | + } | |
260 | + | |
261 | + function updateActions () { | |
262 | + var result = $filter('orderBy')(vm.allActions, vm.query.order); | |
263 | + if (vm.query.search != null) { | |
264 | + result = $filter('filter')(result, {$: vm.query.search}); | |
265 | + } | |
266 | + vm.actionsCount = result.length; | |
267 | + var startIndex = vm.query.limit * (vm.query.page - 1); | |
268 | + vm.actions = result.slice(startIndex, startIndex + vm.query.limit); | |
269 | + } | |
270 | +} | |
\ No newline at end of file | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +.tb-manage-widget-actions { | |
17 | + table.md-table { | |
18 | + tbody { | |
19 | + tr { | |
20 | + td { | |
21 | + &.tb-action-cell { | |
22 | + overflow: hidden; | |
23 | + text-overflow: ellipsis; | |
24 | + white-space: nowrap; | |
25 | + min-width: 100px; | |
26 | + max-width: 100px; | |
27 | + width: 100px; | |
28 | + } | |
29 | + } | |
30 | + } | |
31 | + } | |
32 | + } | |
33 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<div ng-form="manageWidgetActionsForm" class="tb-manage-widget-actions md-whiteframe-z1" layout="column"> | |
19 | + <md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search === null"> | |
20 | + <div class="md-toolbar-tools"> | |
21 | + <span translate>widget-config.actions</span> | |
22 | + <span flex></span> | |
23 | + <md-button class="md-icon-button" ng-click="vm.addAction($event)"> | |
24 | + <md-icon>add</md-icon> | |
25 | + <md-tooltip md-direction="top"> | |
26 | + {{ 'widget-config.add-action' | translate }} | |
27 | + </md-tooltip> | |
28 | + </md-button> | |
29 | + <md-button class="md-icon-button" ng-click="vm.enterFilterMode()"> | |
30 | + <md-icon>search</md-icon> | |
31 | + <md-tooltip md-direction="top"> | |
32 | + {{ 'action.search' | translate }} | |
33 | + </md-tooltip> | |
34 | + </md-button> | |
35 | + </div> | |
36 | + </md-toolbar> | |
37 | + <md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search != null"> | |
38 | + <div class="md-toolbar-tools"> | |
39 | + <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}"> | |
40 | + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> | |
41 | + <md-tooltip md-direction="top"> | |
42 | + {{ 'widget-config.search-actions' | translate }} | |
43 | + </md-tooltip> | |
44 | + </md-button> | |
45 | + <md-input-container flex> | |
46 | + <label> </label> | |
47 | + <input ng-model="vm.query.search" name="querySearchInput" placeholder="{{ 'widget-config.search-actions' | translate }}"/> | |
48 | + </md-input-container> | |
49 | + <md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()"> | |
50 | + <md-icon aria-label="Close" class="material-icons">close</md-icon> | |
51 | + <md-tooltip md-direction="top"> | |
52 | + {{ 'action.close' | translate }} | |
53 | + </md-tooltip> | |
54 | + </md-button> | |
55 | + </div> | |
56 | + </md-toolbar> | |
57 | + <md-table-container> | |
58 | + <table md-table> | |
59 | + <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> | |
60 | + <tr md-row> | |
61 | + <th md-column md-order-by="actionSourceName"><span translate>widget-config.action-source</span></th> | |
62 | + <th md-column md-order-by="name"><span translate>widget-config.action-name</span></th> | |
63 | + <th md-column md-order-by="icon"><span translate>widget-config.action-icon</span></th> | |
64 | + <th md-column md-order-by="typeName"><span translate>widget-config.action-type</span></th> | |
65 | + <th md-column><span> </span></th> | |
66 | + </tr> | |
67 | + </thead> | |
68 | + <tbody md-body> | |
69 | + <tr md-row ng-repeat="action in vm.actions"> | |
70 | + <td md-cell>{{action.actionSourceName}}</td> | |
71 | + <td md-cell>{{action.name}}</td> | |
72 | + <td md-cell> | |
73 | + <md-icon aria-label="{{ 'widget-config.action-icon' | translate }}" class="material-icons">{{action.icon}}</md-icon> | |
74 | + </td> | |
75 | + <td md-cell>{{action.typeName}}</td> | |
76 | + <td md-cell class="tb-action-cell"> | |
77 | + <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" | |
78 | + ng-click="vm.editAction($event, action)"> | |
79 | + <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon> | |
80 | + <md-tooltip md-direction="top"> | |
81 | + {{ 'widget-config.edit-action' | translate }} | |
82 | + </md-tooltip> | |
83 | + </md-button> | |
84 | + <md-button class="md-icon-button" aria-label="{{'action.delete' | translate}}" ng-click="vm.deleteAction($event, action)"> | |
85 | + <md-icon aria-label="Delete" class="material-icons">delete</md-icon> | |
86 | + <md-tooltip md-direction="top"> | |
87 | + {{ 'widget-config.delete-action' | translate }} | |
88 | + </md-tooltip> | |
89 | + </md-button> | |
90 | + </td> | |
91 | + </tr> | |
92 | + </tbody> | |
93 | + </table> | |
94 | + </md-table-container> | |
95 | + <md-table-pagination md-limit="vm.query.limit" md-limit-options="[10, 15, 20]" | |
96 | + md-page="vm.query.page" md-total="{{vm.actionsCount}}" | |
97 | + md-on-paginate="vm.onPaginate" md-page-select> | |
98 | + </md-table-pagination> | |
99 | +</div> | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +/*@ngInject*/ | |
18 | +export default function WidgetActionDialogController($scope, $mdDialog, $filter, $q, dashboardService, dashboardUtils, types, utils, | |
19 | + isAdd, fetchDashboardStates, actionSources, widgetActions, action) { | |
20 | + | |
21 | + var vm = this; | |
22 | + | |
23 | + vm.types = types; | |
24 | + | |
25 | + vm.isAdd = isAdd; | |
26 | + vm.fetchDashboardStates = fetchDashboardStates; | |
27 | + vm.actionSources = actionSources; | |
28 | + vm.widgetActions = widgetActions; | |
29 | + | |
30 | + vm.targetDashboardStateSearchText = ''; | |
31 | + | |
32 | + vm.selectedDashboardStateIds = []; | |
33 | + | |
34 | + if (vm.isAdd) { | |
35 | + vm.action = { | |
36 | + id: utils.guid() | |
37 | + }; | |
38 | + } else { | |
39 | + vm.action = action; | |
40 | + } | |
41 | + | |
42 | + vm.actionSourceName = actionSourceName; | |
43 | + | |
44 | + vm.targetDashboardStateSearchTextChanged = function() { | |
45 | + } | |
46 | + | |
47 | + vm.dashboardStateSearch = dashboardStateSearch; | |
48 | + vm.cancel = cancel; | |
49 | + vm.save = save; | |
50 | + | |
51 | + $scope.$watch("vm.action.name", function(newVal, prevVal) { | |
52 | + if (!angular.equals(newVal, prevVal) && vm.action.name != null) { | |
53 | + checkActionName(); | |
54 | + } | |
55 | + }); | |
56 | + | |
57 | + $scope.$watch("vm.action.actionSourceId", function(newVal, prevVal) { | |
58 | + if (!angular.equals(newVal, prevVal) && vm.action.actionSourceId != null) { | |
59 | + checkActionName(); | |
60 | + } | |
61 | + }); | |
62 | + | |
63 | + $scope.$watch("vm.action.targetDashboardId", function() { | |
64 | + vm.selectedDashboardStateIds = []; | |
65 | + if (vm.action.targetDashboardId) { | |
66 | + dashboardService.getDashboard(vm.action.targetDashboardId).then( | |
67 | + function success(dashboard) { | |
68 | + dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard); | |
69 | + var states = dashboard.configuration.states; | |
70 | + vm.selectedDashboardStateIds = Object.keys(states); | |
71 | + } | |
72 | + ); | |
73 | + } | |
74 | + }); | |
75 | + | |
76 | + $scope.$watch('vm.action.type', function(newType) { | |
77 | + if (newType) { | |
78 | + switch (newType) { | |
79 | + case vm.types.widgetActionTypes.openDashboardState.value: | |
80 | + case vm.types.widgetActionTypes.updateDashboardState.value: | |
81 | + case vm.types.widgetActionTypes.openDashboard.value: | |
82 | + if (angular.isUndefined(vm.action.setEntityId)) { | |
83 | + vm.action.setEntityId = true; | |
84 | + } | |
85 | + break; | |
86 | + } | |
87 | + } | |
88 | + }); | |
89 | + | |
90 | + function checkActionName() { | |
91 | + var actionNameIsUnique = true; | |
92 | + if (vm.action.actionSourceId && vm.action.name) { | |
93 | + var sourceActions = vm.widgetActions[vm.action.actionSourceId]; | |
94 | + if (sourceActions) { | |
95 | + var result = $filter('filter')(sourceActions, {name: vm.action.name}, true); | |
96 | + if (result && result.length && result[0].id !== vm.action.id) { | |
97 | + actionNameIsUnique = false; | |
98 | + } | |
99 | + } | |
100 | + } | |
101 | + $scope.theForm.name.$setValidity('actionNameNotUnique', actionNameIsUnique); | |
102 | + } | |
103 | + | |
104 | + function actionSourceName (actionSource) { | |
105 | + if (actionSource) { | |
106 | + return utils.customTranslation(actionSource.name, actionSource.name); | |
107 | + } else { | |
108 | + return ''; | |
109 | + } | |
110 | + } | |
111 | + | |
112 | + function dashboardStateSearch (query) { | |
113 | + if (vm.action.type == vm.types.widgetActionTypes.openDashboard.value) { | |
114 | + var deferred = $q.defer(); | |
115 | + var result = query ? vm.selectedDashboardStateIds.filter( | |
116 | + createFilterForDashboardState(query)) : vm.selectedDashboardStateIds; | |
117 | + if (result && result.length) { | |
118 | + deferred.resolve(result); | |
119 | + } else { | |
120 | + deferred.resolve([query]); | |
121 | + } | |
122 | + return deferred.promise; | |
123 | + } else { | |
124 | + return vm.fetchDashboardStates({query: query}); | |
125 | + } | |
126 | + } | |
127 | + | |
128 | + function createFilterForDashboardState (query) { | |
129 | + var lowercaseQuery = angular.lowercase(query); | |
130 | + return function filterFn(stateId) { | |
131 | + return (angular.lowercase(stateId).indexOf(lowercaseQuery) === 0); | |
132 | + }; | |
133 | + } | |
134 | + | |
135 | + function cleanupAction(action) { | |
136 | + var result = {}; | |
137 | + result.id = action.id; | |
138 | + result.actionSourceId = action.actionSourceId; | |
139 | + result.name = action.name; | |
140 | + result.icon = action.icon; | |
141 | + result.type = action.type; | |
142 | + switch (action.type) { | |
143 | + case vm.types.widgetActionTypes.openDashboardState.value: | |
144 | + case vm.types.widgetActionTypes.updateDashboardState.value: | |
145 | + result.targetDashboardStateId = action.targetDashboardStateId; | |
146 | + result.openRightLayout = action.openRightLayout; | |
147 | + result.setEntityId = action.setEntityId; | |
148 | + break; | |
149 | + case vm.types.widgetActionTypes.openDashboard.value: | |
150 | + result.targetDashboardId = action.targetDashboardId; | |
151 | + result.targetDashboardStateId = action.targetDashboardStateId; | |
152 | + result.setEntityId = action.setEntityId; | |
153 | + break; | |
154 | + case vm.types.widgetActionTypes.custom.value: | |
155 | + result.customFunction = action.customFunction; | |
156 | + break; | |
157 | + } | |
158 | + return result; | |
159 | + } | |
160 | + | |
161 | + function cancel() { | |
162 | + $mdDialog.cancel(); | |
163 | + } | |
164 | + | |
165 | + function save() { | |
166 | + $scope.theForm.$setPristine(); | |
167 | + $mdDialog.hide(cleanupAction(vm.action)); | |
168 | + } | |
169 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<md-dialog class="tb-widget-action-dialog" aria-label="{{'widget-config.action' | translate }}" style="min-width: 600px;"> | |
19 | + <form name="theForm" ng-submit="vm.save()"> | |
20 | + <md-toolbar> | |
21 | + <div class="md-toolbar-tools"> | |
22 | + <h2>{{ (vm.isAdd ? 'widget-config.add-action' : 'widget-config.edit-action') | translate }}</h2> | |
23 | + <span flex></span> | |
24 | + <md-button class="md-icon-button" ng-click="vm.cancel()"> | |
25 | + <ng-md-icon icon="close" aria-label="{{ 'action.close' | translate }}"></ng-md-icon> | |
26 | + </md-button> | |
27 | + </div> | |
28 | + </md-toolbar> | |
29 | + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> | |
30 | + <span style="min-height: 5px;" flex="" ng-show="!loading"></span> | |
31 | + <md-dialog-content> | |
32 | + <div class="md-dialog-content"> | |
33 | + <md-content class="md-padding" layout="column"> | |
34 | + <fieldset ng-disabled="loading" layout="column"> | |
35 | + <md-input-container class="md-block"> | |
36 | + <label translate>widget-config.action-source</label> | |
37 | + <md-select name="actionSource" required aria-label="{{ 'widget-config.action-source' | translate }}" ng-model="vm.action.actionSourceId"> | |
38 | + <md-option ng-repeat="(actionSourceId, actionSource) in vm.actionSources" ng-value="actionSourceId"> | |
39 | + {{vm.actionSourceName(actionSource)}} | |
40 | + </md-option> | |
41 | + </md-select> | |
42 | + <div ng-messages="theForm.actionSource.$error"> | |
43 | + <div ng-message="required" translate>widget-config.action-source-required</div> | |
44 | + </div> | |
45 | + </md-input-container> | |
46 | + <md-input-container class="md-block"> | |
47 | + <label translate>widget-config.action-name</label> | |
48 | + <input name="name" required ng-model="vm.action.name"> | |
49 | + <div ng-messages="theForm.name.$error"> | |
50 | + <div ng-message="required" translate>widget-config.action-name-required</div> | |
51 | + <div ng-message="actionNameNotUnique" translate>widget-config.action-name-not-unique</div> | |
52 | + </div> | |
53 | + </md-input-container> | |
54 | + <tb-material-icon-select ng-model="vm.action.icon"> | |
55 | + </tb-material-icon-select> | |
56 | + <md-input-container class="md-block"> | |
57 | + <label translate>widget-config.action-type</label> | |
58 | + <md-select name="actionType" required aria-label="{{ 'widget-config.action-type' | translate }}" ng-model="vm.action.type"> | |
59 | + <md-option ng-repeat="actionType in vm.types.widgetActionTypes" ng-value="actionType.value"> | |
60 | + {{ actionType.name | translate }} | |
61 | + </md-option> | |
62 | + </md-select> | |
63 | + <div ng-messages="theForm.actionType.$error"> | |
64 | + <div ng-message="required" translate>widget-config.action-type-required</div> | |
65 | + </div> | |
66 | + </md-input-container> | |
67 | + <div layout="column" | |
68 | + style="padding-bottom: 20px;" | |
69 | + ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboard.value"> | |
70 | + <div class="md-caption tb-required" | |
71 | + style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>widget-action.target-dashboard</div> | |
72 | + <tb-dashboard-autocomplete the-form="theForm" | |
73 | + tb-required="true" | |
74 | + ng-model="vm.action.targetDashboardId" | |
75 | + select-first-dashboard="false"> | |
76 | + </tb-dashboard-autocomplete> | |
77 | + </div> | |
78 | + <md-autocomplete ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value || | |
79 | + vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value || | |
80 | + vm.action.type == vm.types.widgetActionTypes.openDashboard.value" | |
81 | + ng-required="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value" | |
82 | + md-no-cache="true" | |
83 | + md-input-name="targetDashboardState" | |
84 | + ng-model="vm.action.targetDashboardStateId" | |
85 | + md-selected-item="vm.action.targetDashboardStateId" | |
86 | + md-search-text="vm.targetDashboardStateSearchText" | |
87 | + md-search-text-change="vm.targetDashboardStateSearchTextChanged()" | |
88 | + md-items="item in vm.dashboardStateSearch(vm.targetDashboardStateSearchText)" | |
89 | + md-item-text="item" | |
90 | + md-min-length="0" | |
91 | + md-floating-label="{{ 'widget-action.target-dashboard-state' | translate }}" | |
92 | + md-select-on-match="true"> | |
93 | + <md-item-template> | |
94 | + <div> | |
95 | + <span md-highlight-text="vm.targetDashboardStateSearchText" md-highlight-flags="^i">{{item}}</span> | |
96 | + </div> | |
97 | + </md-item-template> | |
98 | + <div ng-messages="theForm.targetDashboardState.$error"> | |
99 | + <div translate ng-message="required">widget-action.target-dashboard-state-required</div> | |
100 | + </div> | |
101 | + </md-autocomplete> | |
102 | + <md-checkbox ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value || | |
103 | + vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value" | |
104 | + flex aria-label="{{ 'widget-action.open-right-layout' | translate }}" | |
105 | + ng-model="vm.action.openRightLayout">{{ 'widget-action.open-right-layout' | translate }} | |
106 | + </md-checkbox> | |
107 | + <md-checkbox ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value || | |
108 | + vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value || | |
109 | + vm.action.type == vm.types.widgetActionTypes.openDashboard.value" | |
110 | + flex aria-label="{{ 'widget-action.set-entity-from-widget' | translate }}" | |
111 | + ng-model="vm.action.setEntityId">{{ 'widget-action.set-entity-from-widget' | translate }} | |
112 | + </md-checkbox> | |
113 | + <tb-js-func ng-if="vm.action.type == vm.types.widgetActionTypes.custom.value" | |
114 | + ng-model="vm.action.customFunction" | |
115 | + function-args="{{ ['$event', 'widgetContext', 'entityId'] }}" | |
116 | + validation-args="{{ [] }}"> | |
117 | + </tb-js-func> | |
118 | + </fieldset> | |
119 | + </md-content> | |
120 | + </div> | |
121 | + </md-dialog-content> | |
122 | + <md-dialog-actions layout="row"> | |
123 | + <span flex></span> | |
124 | + <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" | |
125 | + class="md-raised md-primary"> | |
126 | + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} | |
127 | + </md-button> | |
128 | + <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;"> | |
129 | + {{ 'action.cancel' | translate }} | |
130 | + </md-button> | |
131 | + </md-dialog-actions> | |
132 | + </form> | |
133 | +</md-dialog> | ... | ... |
ui/src/app/components/widget/widget-config.directive.js
renamed from
ui/src/app/components/widget-config.directive.js
... | ... | @@ -14,13 +14,14 @@ |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 | import jsonSchemaDefaults from 'json-schema-defaults'; |
17 | -import thingsboardTypes from '../common/types.constant'; | |
18 | -import thingsboardUtils from '../common/utils.service'; | |
19 | -import thingsboardEntityAliasSelect from './entity-alias-select.directive'; | |
20 | -import thingsboardDatasource from './datasource.directive'; | |
21 | -import thingsboardTimewindow from './timewindow.directive'; | |
22 | -import thingsboardLegendConfig from './legend-config.directive'; | |
23 | -import thingsboardJsonForm from "./json-form.directive"; | |
17 | +import thingsboardTypes from '../../common/types.constant'; | |
18 | +import thingsboardUtils from '../../common/utils.service'; | |
19 | +import thingsboardEntityAliasSelect from '../entity-alias-select.directive'; | |
20 | +import thingsboardDatasource from '../datasource.directive'; | |
21 | +import thingsboardTimewindow from '../timewindow.directive'; | |
22 | +import thingsboardLegendConfig from '../legend-config.directive'; | |
23 | +import thingsboardJsonForm from '../json-form.directive'; | |
24 | +import thingsboardManageWidgetActions from './action/manage-widget-actions.directive'; | |
24 | 25 | import 'angular-ui-ace'; |
25 | 26 | |
26 | 27 | /* eslint-disable import/no-unresolved, import/default */ |
... | ... | @@ -38,6 +39,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar |
38 | 39 | thingsboardDatasource, |
39 | 40 | thingsboardTimewindow, |
40 | 41 | thingsboardLegendConfig, |
42 | + thingsboardManageWidgetActions, | |
41 | 43 | 'ui.ace']) |
42 | 44 | .directive('tbWidgetConfig', WidgetConfig) |
43 | 45 | .name; |
... | ... | @@ -117,6 +119,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout |
117 | 119 | scope.showLegend = angular.isDefined(config.showLegend) ? |
118 | 120 | config.showLegend : scope.widgetType === types.widgetType.timeseries.value; |
119 | 121 | scope.legendConfig = config.legendConfig; |
122 | + scope.actions = config.actions; | |
123 | + if (!scope.actions) { | |
124 | + scope.actions = {}; | |
125 | + } | |
120 | 126 | if (scope.widgetType !== types.widgetType.rpc.value && |
121 | 127 | scope.widgetType !== types.widgetType.alarm.value && |
122 | 128 | scope.widgetType !== types.widgetType.static.value |
... | ... | @@ -324,6 +330,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout |
324 | 330 | } |
325 | 331 | }); |
326 | 332 | |
333 | + scope.$watch('actions', function () { | |
334 | + if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config) { | |
335 | + var value = ngModelCtrl.$viewValue; | |
336 | + var config = value.config; | |
337 | + config.actions = scope.actions; | |
338 | + ngModelCtrl.$setViewValue(value); | |
339 | + scope.updateValidity(); | |
340 | + /*if (scope.theForm) { | |
341 | + scope.theForm.$setDirty(); | |
342 | + }*/ | |
343 | + } | |
344 | + }, true); | |
345 | + | |
327 | 346 | scope.addDatasource = function () { |
328 | 347 | var newDatasource; |
329 | 348 | if (scope.functionsOnly) { |
... | ... | @@ -443,11 +462,13 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout |
443 | 462 | isDataEnabled: '=?', |
444 | 463 | widgetType: '=', |
445 | 464 | typeParameters: '=', |
465 | + actionSources: '=', | |
446 | 466 | widgetSettingsSchema: '=', |
447 | 467 | datakeySettingsSchema: '=', |
448 | 468 | aliasController: '=', |
449 | 469 | functionsOnly: '=', |
450 | 470 | fetchEntityKeys: '&', |
471 | + fetchDashboardStates: '&', | |
451 | 472 | onCreateEntityAlias: '&', |
452 | 473 | theForm: '=' |
453 | 474 | }, | ... | ... |
ui/src/app/components/widget/widget-config.tpl.html
renamed from
ui/src/app/components/widget-config.tpl.html
... | ... | @@ -275,4 +275,13 @@ |
275 | 275 | </ng-form> |
276 | 276 | </md-content> |
277 | 277 | </md-tab> |
278 | + <md-tab label="{{ 'widget-config.actions' | translate }}"> | |
279 | + <md-content class="md-padding" layout="column"> | |
280 | + <tb-manage-widget-actions | |
281 | + action-sources="actionSources" | |
282 | + widget-actions="actions" | |
283 | + fetch-dashboard-states="fetchDashboardStates({query: query})"> | |
284 | + </tb-manage-widget-actions> | |
285 | + </md-content> | |
286 | + </md-tab> | |
278 | 287 | </md-tabs> | ... | ... |
ui/src/app/components/widget/widget.controller.js
renamed from
ui/src/app/components/widget.controller.js
... | ... | @@ -15,12 +15,12 @@ |
15 | 15 | */ |
16 | 16 | import $ from 'jquery'; |
17 | 17 | import 'javascript-detect-element-resize/detect-element-resize'; |
18 | -import Subscription from '../api/subscription'; | |
18 | +import Subscription from '../../api/subscription'; | |
19 | 19 | |
20 | 20 | /* eslint-disable angular/angularelement */ |
21 | 21 | |
22 | 22 | /*@ngInject*/ |
23 | -export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService, | |
23 | +export default function WidgetController($scope, $state, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService, | |
24 | 24 | datasourceService, alarmService, entityService, deviceService, visibleRect, isEdit, isMobile, stDiff, dashboardTimewindow, |
25 | 25 | dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) { |
26 | 26 | |
... | ... | @@ -44,6 +44,20 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
44 | 44 | |
45 | 45 | var cafs = {}; |
46 | 46 | |
47 | + var actionDescriptorsBySourceId = {}; | |
48 | + if (widget.config.actions) { | |
49 | + for (var actionSourceId in widget.config.actions) { | |
50 | + var descriptors = widget.config.actions[actionSourceId]; | |
51 | + var actionDescriptors = []; | |
52 | + descriptors.forEach(function(descriptor) { | |
53 | + var actionDescriptor = angular.copy(descriptor); | |
54 | + actionDescriptor.displayName = utils.customTranslation(descriptor.name, descriptor.name); | |
55 | + actionDescriptors.push(actionDescriptor); | |
56 | + }); | |
57 | + actionDescriptorsBySourceId[actionSourceId] = actionDescriptors; | |
58 | + } | |
59 | + } | |
60 | + | |
47 | 61 | var widgetContext = { |
48 | 62 | inited: false, |
49 | 63 | $container: null, |
... | ... | @@ -103,9 +117,32 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
103 | 117 | utils: { |
104 | 118 | formatValue: formatValue |
105 | 119 | }, |
120 | + actionsApi: { | |
121 | + actionDescriptorsBySourceId: actionDescriptorsBySourceId, | |
122 | + getActionDescriptors: getActionDescriptors, | |
123 | + handleWidgetAction: handleWidgetAction | |
124 | + }, | |
106 | 125 | stateController: stateController |
107 | 126 | }; |
108 | 127 | |
128 | + widgetContext.customHeaderActions = []; | |
129 | + var headerActionsDescriptors = getActionDescriptors(types.widgetActionSources.headerButton.value); | |
130 | + for (var i=0;i<headerActionsDescriptors.length;i++) { | |
131 | + var descriptor = headerActionsDescriptors[i]; | |
132 | + var headerAction = {}; | |
133 | + headerAction.name = descriptor.name; | |
134 | + headerAction.displayName = descriptor.displayName; | |
135 | + headerAction.icon = descriptor.icon; | |
136 | + headerAction.descriptor = descriptor; | |
137 | + headerAction.onAction = function($event) { | |
138 | + var entityInfo = getFirstEntityInfo(); | |
139 | + var entityId = entityInfo ? entityInfo.entityId : null; | |
140 | + var entityName = entityInfo ? entityInfo.entityName : null; | |
141 | + handleWidgetAction($event, this.descriptor, entityId, entityName); | |
142 | + } | |
143 | + widgetContext.customHeaderActions.push(headerAction); | |
144 | + } | |
145 | + | |
109 | 146 | var subscriptionContext = { |
110 | 147 | $scope: $scope, |
111 | 148 | $q: $q, |
... | ... | @@ -376,6 +413,87 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
376 | 413 | return deferred.promise; |
377 | 414 | } |
378 | 415 | |
416 | + function getActionDescriptors(actionSourceId) { | |
417 | + var result = widgetContext.actionsApi.actionDescriptorsBySourceId[actionSourceId]; | |
418 | + if (!result) { | |
419 | + result = []; | |
420 | + } | |
421 | + return result; | |
422 | + } | |
423 | + | |
424 | + function handleWidgetAction($event, descriptor, entityId, entityName) { | |
425 | + var type = descriptor.type; | |
426 | + switch (type) { | |
427 | + case types.widgetActionTypes.openDashboardState.value: | |
428 | + case types.widgetActionTypes.updateDashboardState.value: | |
429 | + var targetDashboardStateId = descriptor.targetDashboardStateId; | |
430 | + var targetEntityId; | |
431 | + if (descriptor.setEntityId) { | |
432 | + targetEntityId = entityId; | |
433 | + } | |
434 | + var params = {}; | |
435 | + if (targetEntityId) { | |
436 | + params.entityId = targetEntityId; | |
437 | + if (entityName) { | |
438 | + params.entityName = entityName; | |
439 | + } | |
440 | + } | |
441 | + if (type == types.widgetActionTypes.openDashboardState.value) { | |
442 | + widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout); | |
443 | + } else { | |
444 | + widgetContext.stateController.updateState(targetDashboardStateId, params, descriptor.openRightLayout); | |
445 | + } | |
446 | + break; | |
447 | + case types.widgetActionTypes.openDashboard.value: | |
448 | + var targetDashboardId = descriptor.targetDashboardId; | |
449 | + targetDashboardStateId = descriptor.targetDashboardStateId; | |
450 | + targetEntityId; | |
451 | + if (descriptor.setEntityId) { | |
452 | + targetEntityId = entityId; | |
453 | + } | |
454 | + var stateObject = {}; | |
455 | + stateObject.params = {}; | |
456 | + if (targetEntityId) { | |
457 | + stateObject.params.entityId = targetEntityId; | |
458 | + if (entityName) { | |
459 | + stateObject.params.entityName = entityName; | |
460 | + } | |
461 | + } | |
462 | + if (targetDashboardStateId) { | |
463 | + stateObject.id = targetDashboardStateId; | |
464 | + } | |
465 | + var stateParams = { | |
466 | + dashboardId: targetDashboardId, | |
467 | + state: angular.toJson([ stateObject ]) | |
468 | + } | |
469 | + $state.go('home.dashboards.dashboard', stateParams); | |
470 | + break; | |
471 | + case types.widgetActionTypes.custom.value: | |
472 | + var customFunction = descriptor.customFunction; | |
473 | + if (angular.isDefined(customFunction) && customFunction.length > 0) { | |
474 | + try { | |
475 | + var customActionFunction = new Function('$event', 'widgetContext', 'entityId', 'entityName', customFunction); | |
476 | + customActionFunction($event, widgetContext, entityId, entityName); | |
477 | + } catch (e) { | |
478 | + // | |
479 | + } | |
480 | + } | |
481 | + break; | |
482 | + } | |
483 | + } | |
484 | + | |
485 | + function getFirstEntityInfo() { | |
486 | + var entityInfo; | |
487 | + for (var id in widgetContext.subscriptions) { | |
488 | + var subscription = widgetContext.subscriptions[id]; | |
489 | + entityInfo = subscription.getFirstEntityInfo(); | |
490 | + if (entityInfo) { | |
491 | + break; | |
492 | + } | |
493 | + } | |
494 | + return entityInfo; | |
495 | + } | |
496 | + | |
379 | 497 | function configureWidgetElement() { |
380 | 498 | |
381 | 499 | $scope.displayLegend = angular.isDefined(widget.config.showLegend) ? | ... | ... |
ui/src/app/components/widget/widget.directive.js
renamed from
ui/src/app/components/widget.directive.js
... | ... | @@ -16,9 +16,9 @@ |
16 | 16 | |
17 | 17 | import './widget.scss'; |
18 | 18 | |
19 | -import thingsboardLegend from './legend.directive'; | |
20 | -import thingsboardTypes from '../common/types.constant'; | |
21 | -import thingsboardApiDatasource from '../api/datasource.service'; | |
19 | +import thingsboardLegend from '../legend.directive'; | |
20 | +import thingsboardTypes from '../../common/types.constant'; | |
21 | +import thingsboardApiDatasource from '../../api/datasource.service'; | |
22 | 22 | |
23 | 23 | import WidgetController from './widget.controller'; |
24 | 24 | ... | ... |
ui/src/app/components/widget/widget.scss
renamed from
ui/src/app/components/widget.scss
... | ... | @@ -36,6 +36,7 @@ export default function AddWidgetController($scope, widgetService, entityService |
36 | 36 | vm.add = add; |
37 | 37 | vm.cancel = cancel; |
38 | 38 | vm.fetchEntityKeys = fetchEntityKeys; |
39 | + vm.fetchDashboardStates = fetchDashboardStates; | |
39 | 40 | vm.createEntityAlias = createEntityAlias; |
40 | 41 | |
41 | 42 | vm.widgetConfig = { |
... | ... | @@ -128,6 +129,26 @@ export default function AddWidgetController($scope, widgetService, entityService |
128 | 129 | return deferred.promise; |
129 | 130 | } |
130 | 131 | |
132 | + function fetchDashboardStates (query) { | |
133 | + var deferred = $q.defer(); | |
134 | + var stateIds = Object.keys(vm.dashboard.configuration.states); | |
135 | + var result = query ? stateIds.filter( | |
136 | + createFilterForDashboardState(query)) : stateIds; | |
137 | + if (result && result.length) { | |
138 | + deferred.resolve(result); | |
139 | + } else { | |
140 | + deferred.resolve([query]); | |
141 | + } | |
142 | + return deferred.promise; | |
143 | + } | |
144 | + | |
145 | + function createFilterForDashboardState (query) { | |
146 | + var lowercaseQuery = angular.lowercase(query); | |
147 | + return function filterFn(stateId) { | |
148 | + return (angular.lowercase(stateId).indexOf(lowercaseQuery) === 0); | |
149 | + }; | |
150 | + } | |
151 | + | |
131 | 152 | function createEntityAlias (event, alias, allowedEntityTypes) { |
132 | 153 | |
133 | 154 | var deferred = $q.defer(); | ... | ... |
... | ... | @@ -34,6 +34,7 @@ |
34 | 34 | <fieldset ng-disabled="loading" style="position: relative; height: 600px;"> |
35 | 35 | <tb-widget-config widget-type="vm.widget.type" |
36 | 36 | type-parameters="vm.widgetInfo.typeParameters" |
37 | + action-sources="vm.widgetInfo.actionSources" | |
37 | 38 | force-expand-datasources="true" |
38 | 39 | ng-model="vm.widgetConfig" |
39 | 40 | widget-settings-schema="vm.settingsSchema" |
... | ... | @@ -41,6 +42,7 @@ |
41 | 42 | alias-controller="vm.aliasController" |
42 | 43 | functions-only="vm.functionsOnly" |
43 | 44 | fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)" |
45 | + fetch-dashboard-states="vm.fetchDashboardStates(query)" | |
44 | 46 | on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)" |
45 | 47 | the-form="theForm"></tb-widget-config> |
46 | 48 | </fieldset> | ... | ... |
... | ... | @@ -41,6 +41,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid |
41 | 41 | var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; |
42 | 42 | var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; |
43 | 43 | scope.typeParameters = widgetInfo.typeParameters; |
44 | + scope.actionSources = widgetInfo.actionSources; | |
44 | 45 | scope.isDataEnabled = !widgetInfo.typeParameters.useCustomDatasources; |
45 | 46 | if (!settingsSchema || settingsSchema === '') { |
46 | 47 | scope.settingsSchema = {}; |
... | ... | @@ -93,6 +94,26 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid |
93 | 94 | return deferred.promise; |
94 | 95 | }; |
95 | 96 | |
97 | + scope.fetchDashboardStates = function(query) { | |
98 | + var deferred = $q.defer(); | |
99 | + var stateIds = Object.keys(scope.dashboard.configuration.states); | |
100 | + var result = query ? stateIds.filter( | |
101 | + createFilterForDashboardState(query)) : stateIds; | |
102 | + if (result && result.length) { | |
103 | + deferred.resolve(result); | |
104 | + } else { | |
105 | + deferred.resolve([query]); | |
106 | + } | |
107 | + return deferred.promise; | |
108 | + } | |
109 | + | |
110 | + function createFilterForDashboardState (query) { | |
111 | + var lowercaseQuery = angular.lowercase(query); | |
112 | + return function filterFn(stateId) { | |
113 | + return (angular.lowercase(stateId).indexOf(lowercaseQuery) === 0); | |
114 | + }; | |
115 | + } | |
116 | + | |
96 | 117 | scope.createEntityAlias = function (event, alias, allowedEntityTypes) { |
97 | 118 | |
98 | 119 | var deferred = $q.defer(); | ... | ... |
... | ... | @@ -18,6 +18,7 @@ |
18 | 18 | <fieldset ng-disabled="loading"> |
19 | 19 | <tb-widget-config widget-type="widget.type" |
20 | 20 | type-parameters="typeParameters" |
21 | + action-sources="actionSources" | |
21 | 22 | ng-model="widgetConfig" |
22 | 23 | is-data-enabled="isDataEnabled" |
23 | 24 | widget-settings-schema="settingsSchema" |
... | ... | @@ -25,6 +26,7 @@ |
25 | 26 | alias-controller="aliasController" |
26 | 27 | functions-only="widgetEditMode" |
27 | 28 | fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)" |
29 | + fetch-dashboard-states="fetchDashboardStates(query)" | |
28 | 30 | on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)" |
29 | 31 | the-form="theForm"></tb-widget-config> |
30 | 32 | </fieldset> | ... | ... |
... | ... | @@ -23,7 +23,7 @@ import thingsboardApiUser from '../api/user.service'; |
23 | 23 | import thingsboardApiDashboard from '../api/dashboard.service'; |
24 | 24 | import thingsboardApiCustomer from '../api/customer.service'; |
25 | 25 | import thingsboardDetailsSidenav from '../components/details-sidenav.directive'; |
26 | -import thingsboardWidgetConfig from '../components/widget-config.directive'; | |
26 | +import thingsboardWidgetConfig from '../components/widget/widget-config.directive'; | |
27 | 27 | import thingsboardDashboardSelect from '../components/dashboard-select.directive'; |
28 | 28 | import thingsboardRelatedEntityAutocomplete from '../components/related-entity-autocomplete.directive'; |
29 | 29 | import thingsboardDashboard from '../components/dashboard.directive'; | ... | ... |
... | ... | @@ -59,10 +59,10 @@ |
59 | 59 | <span flex></span> |
60 | 60 | <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" |
61 | 61 | class="md-raised md-primary"> |
62 | - {{ vm.isAdd ? 'Add' : 'Save' }} | |
62 | + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} | |
63 | 63 | </md-button> |
64 | 64 | <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;"> |
65 | - Cancel | |
65 | + {{ 'action.cancel' | translate }} | |
66 | 66 | </md-button> |
67 | 67 | </md-dialog-actions> |
68 | 68 | </form> | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | */ |
16 | 16 | |
17 | 17 | /*@ngInject*/ |
18 | -export default function DefaultStateController($scope, $location, $state, $stateParams, $translate, types, dashboardUtils) { | |
18 | +export default function DefaultStateController($scope, $location, $state, $stateParams, utils, types, dashboardUtils) { | |
19 | 19 | |
20 | 20 | var vm = this; |
21 | 21 | |
... | ... | @@ -50,6 +50,9 @@ export default function DefaultStateController($scope, $location, $state, $state |
50 | 50 | } |
51 | 51 | |
52 | 52 | function updateState(id, params, openRightLayout) { |
53 | + if (!id) { | |
54 | + id = getStateId(); | |
55 | + } | |
53 | 56 | if (vm.states && vm.states[id]) { |
54 | 57 | if (!params) { |
55 | 58 | params = {}; |
... | ... | @@ -110,15 +113,7 @@ export default function DefaultStateController($scope, $location, $state, $state |
110 | 113 | } |
111 | 114 | |
112 | 115 | function getStateName(id, state) { |
113 | - var result = ''; | |
114 | - var translationId = types.translate.customTranslationsPrefix + state.name; | |
115 | - var translation = $translate.instant(translationId); | |
116 | - if (translation != translationId) { | |
117 | - result = translation + ''; | |
118 | - } else { | |
119 | - result = id; | |
120 | - } | |
121 | - return result; | |
116 | + return utils.customTranslation(state.name, id); | |
122 | 117 | } |
123 | 118 | |
124 | 119 | function parseState(stateJson) { | ... | ... |
... | ... | @@ -55,6 +55,9 @@ export default function EntityStateController($scope, $location, $state, $stateP |
55 | 55 | } |
56 | 56 | |
57 | 57 | function updateState(id, params, openRightLayout) { |
58 | + if (!id) { | |
59 | + id = getStateId(); | |
60 | + } | |
58 | 61 | if (vm.states && vm.states[id]) { |
59 | 62 | resolveEntity(params).then( |
60 | 63 | function success(entityName) { |
... | ... | @@ -121,17 +124,10 @@ export default function EntityStateController($scope, $location, $state, $stateP |
121 | 124 | var result = ''; |
122 | 125 | if (vm.stateObject[index]) { |
123 | 126 | var stateName = vm.states[vm.stateObject[index].id].name; |
124 | - var translationId = types.translate.customTranslationsPrefix + stateName; | |
125 | - var translation = $translate.instant(translationId); | |
126 | - if (translation != translationId) { | |
127 | - stateName = translation + ''; | |
128 | - } | |
127 | + stateName = utils.customTranslation(stateName, stateName); | |
129 | 128 | var params = vm.stateObject[index].params; |
130 | - if (params && params.entityName) { | |
131 | - result = utils.insertVariable(stateName, 'entityName', params.entityName); | |
132 | - } else { | |
133 | - result = stateName; | |
134 | - } | |
129 | + var entityName = params && params.entityName ? params.entityName : ''; | |
130 | + result = utils.insertVariable(stateName, 'entityName', entityName); | |
135 | 131 | } |
136 | 132 | return result; |
137 | 133 | } | ... | ... |
... | ... | @@ -102,6 +102,7 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, |
102 | 102 | if (vm.addToDashboardType === 0) { |
103 | 103 | dashboardService.getDashboard(vm.dashboardId).then( |
104 | 104 | function success(dashboard) { |
105 | + dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard); | |
105 | 106 | selectTargetState($event, dashboard).then( |
106 | 107 | function(targetState) { |
107 | 108 | selectTargetLayout($event, dashboard, targetState).then( | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import thingsboardApiLogin from '../api/login.service'; |
26 | 26 | import thingsboardApiUser from '../api/user.service'; |
27 | 27 | |
28 | 28 | import thingsboardNoAnimate from '../components/no-animate.directive'; |
29 | +import thingsboardOnFinishRender from '../components/finish-render.directive'; | |
29 | 30 | import thingsboardSideMenu from '../components/side-menu.directive'; |
30 | 31 | import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive'; |
31 | 32 | |
... | ... | @@ -81,6 +82,7 @@ export default angular.module('thingsboard.home', [ |
81 | 82 | thingsboardApiLogin, |
82 | 83 | thingsboardApiUser, |
83 | 84 | thingsboardNoAnimate, |
85 | + thingsboardOnFinishRender, | |
84 | 86 | thingsboardSideMenu, |
85 | 87 | thingsboardDashboardAutocomplete |
86 | 88 | ]) | ... | ... |
... | ... | @@ -1103,6 +1103,18 @@ export default angular.module('thingsboard.locale', []) |
1103 | 1103 | "undo": "Undo widget changes", |
1104 | 1104 | "export": "Export widget" |
1105 | 1105 | }, |
1106 | + "widget-action": { | |
1107 | + "header-button": "Widget header button", | |
1108 | + "open-dashboard-state": "Navigate to new dashboard state", | |
1109 | + "update-dashboard-state": "Update current dashboard state", | |
1110 | + "open-dashboard": "Navigate to other dashboard", | |
1111 | + "custom": "Custom action", | |
1112 | + "target-dashboard-state": "Target dashboard state", | |
1113 | + "target-dashboard-state-required": "Target dashboard state is required", | |
1114 | + "set-entity-from-widget": "Set entity from widget", | |
1115 | + "target-dashboard": "Target dashboard", | |
1116 | + "open-right-layout": "Open right dashboard layout (mobile view)" | |
1117 | + }, | |
1106 | 1118 | "widgets-bundle": { |
1107 | 1119 | "current": "Current bundle", |
1108 | 1120 | "widgets-bundles": "Widgets Bundles", |
... | ... | @@ -1158,7 +1170,23 @@ export default angular.module('thingsboard.locale', []) |
1158 | 1170 | "remove-datasource": "Remove datasource", |
1159 | 1171 | "add-datasource": "Add datasource", |
1160 | 1172 | "target-device": "Target device", |
1161 | - "alarm-source": "Alarm source" | |
1173 | + "alarm-source": "Alarm source", | |
1174 | + "actions": "Actions", | |
1175 | + "action": "Action", | |
1176 | + "add-action": "Add action", | |
1177 | + "search-actions": "Search actions", | |
1178 | + "action-source": "Action source", | |
1179 | + "action-source-required": "Action source is required.", | |
1180 | + "action-name": "Name", | |
1181 | + "action-name-required": "Action name is required.", | |
1182 | + "action-name-not-unique": "Another action with the same name already exists.<br/>Action name should be unique within the same action source.", | |
1183 | + "action-icon": "Icon", | |
1184 | + "action-type": "Type", | |
1185 | + "action-type-required": "Action type is required.", | |
1186 | + "edit-action": "Edit action", | |
1187 | + "delete-action": "Delete action", | |
1188 | + "delete-action-title": "Delete widget action", | |
1189 | + "delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?" | |
1162 | 1190 | }, |
1163 | 1191 | "widget-type": { |
1164 | 1192 | "import": "Import widget type", |
... | ... | @@ -1168,6 +1196,12 @@ export default angular.module('thingsboard.locale', []) |
1168 | 1196 | "widget-type-file": "Widget type file", |
1169 | 1197 | "invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure." |
1170 | 1198 | }, |
1199 | + "icon": { | |
1200 | + "icon": "Icon", | |
1201 | + "select-icon": "Select icon", | |
1202 | + "material-icons": "Material icons", | |
1203 | + "show-all": "Show all icons" | |
1204 | + }, | |
1171 | 1205 | "language": { |
1172 | 1206 | "language": "Language", |
1173 | 1207 | "en_US": "English", |
... | ... | @@ -1177,6 +1211,10 @@ export default angular.module('thingsboard.locale', []) |
1177 | 1211 | "es_ES": "Spanish" |
1178 | 1212 | }, |
1179 | 1213 | "custom": { |
1214 | + "widget-action": { | |
1215 | + "action-cell-button": "Action cell button", | |
1216 | + "row-click": "On row click" | |
1217 | + } | |
1180 | 1218 | } |
1181 | 1219 | } |
1182 | 1220 | } | ... | ... |
... | ... | @@ -158,13 +158,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
158 | 158 | vm.ctx.widgetActions = [ vm.searchAction ]; |
159 | 159 | |
160 | 160 | if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) { |
161 | - var translationId = types.translate.customTranslationsPrefix + vm.settings.alarmsTitle; | |
162 | - var translation = $translate.instant(translationId); | |
163 | - if (translation != translationId) { | |
164 | - vm.alarmsTitle = translation + ''; | |
165 | - } else { | |
166 | - vm.alarmsTitle = vm.settings.alarmsTitle; | |
167 | - } | |
161 | + vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle); | |
168 | 162 | } else { |
169 | 163 | vm.alarmsTitle = $translate.instant('alarm.alarms'); |
170 | 164 | } |
... | ... | @@ -226,6 +220,9 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
226 | 220 | 'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+ |
227 | 221 | 'border-color: ' + mdDarkSecondary + ';\n'+ |
228 | 222 | '}\n'+ |
223 | + 'table.md-table td.md-cell.tb-action-cell button.md-icon-button md-icon {\n'+ | |
224 | + 'color: ' + mdDarkSecondary + ';\n'+ | |
225 | + '}\n'+ | |
229 | 226 | 'table.md-table td.md-cell.md-placeholder {\n'+ |
230 | 227 | 'color: ' + mdDarkDisabled + ';\n'+ |
231 | 228 | '}\n'+ |
... | ... | @@ -539,13 +536,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia |
539 | 536 | for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) { |
540 | 537 | var dataKey = vm.alarmSource.dataKeys[d]; |
541 | 538 | |
542 | - var translationId = types.translate.customTranslationsPrefix + dataKey.label; | |
543 | - var translation = $translate.instant(translationId); | |
544 | - if (translation != translationId) { | |
545 | - dataKey.title = translation + ''; | |
546 | - } else { | |
547 | - dataKey.title = dataKey.label; | |
548 | - } | |
539 | + dataKey.title = utils.customTranslation(dataKey.label, dataKey.label); | |
549 | 540 | |
550 | 541 | var keySettings = dataKey.settings; |
551 | 542 | ... | ... |
... | ... | @@ -65,8 +65,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
65 | 65 | vm.currentEntity = null; |
66 | 66 | |
67 | 67 | vm.displayEntityName = true; |
68 | + vm.entityNameColumnTitle = ''; | |
68 | 69 | vm.displayEntityType = true; |
69 | - vm.displayActions = false; //TODO: Widget actions | |
70 | + vm.actionCellDescriptors = []; | |
70 | 71 | vm.displayPagination = true; |
71 | 72 | vm.defaultPageSize = 10; |
72 | 73 | vm.defaultSortOrder = 'entityName'; |
... | ... | @@ -92,6 +93,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
92 | 93 | vm.onReorder = onReorder; |
93 | 94 | vm.onPaginate = onPaginate; |
94 | 95 | vm.onRowClick = onRowClick; |
96 | + vm.onActionButtonClick = onActionButtonClick; | |
95 | 97 | vm.isCurrent = isCurrent; |
96 | 98 | |
97 | 99 | vm.cellStyle = cellStyle; |
... | ... | @@ -141,14 +143,10 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
141 | 143 | |
142 | 144 | vm.ctx.widgetActions = [ vm.searchAction ]; |
143 | 145 | |
146 | + vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton'); | |
147 | + | |
144 | 148 | if (vm.settings.entitiesTitle && vm.settings.entitiesTitle.length) { |
145 | - var translationId = types.translate.customTranslationsPrefix + vm.settings.entitiesTitle; | |
146 | - var translation = $translate.instant(translationId); | |
147 | - if (translation != translationId) { | |
148 | - vm.entitiesTitle = translation + ''; | |
149 | - } else { | |
150 | - vm.entitiesTitle = vm.settings.entitiesTitle; | |
151 | - } | |
149 | + vm.entitiesTitle = utils.customTranslation(vm.settings.entitiesTitle, vm.settings.entitiesTitle); | |
152 | 150 | } else { |
153 | 151 | vm.entitiesTitle = $translate.instant('entity.entities'); |
154 | 152 | } |
... | ... | @@ -157,6 +155,13 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
157 | 155 | |
158 | 156 | vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true; |
159 | 157 | vm.displayEntityName = angular.isDefined(vm.settings.displayEntityName) ? vm.settings.displayEntityName : true; |
158 | + | |
159 | + if (vm.settings.entityNameColumnTitle && vm.settings.entityNameColumnTitle.length) { | |
160 | + vm.entityNameColumnTitle = utils.customTranslation(vm.settings.entityNameColumnTitle, vm.settings.entityNameColumnTitle); | |
161 | + } else { | |
162 | + vm.entityNameColumnTitle = $translate.instant('entity.entity-name'); | |
163 | + } | |
164 | + | |
160 | 165 | vm.displayEntityType = angular.isDefined(vm.settings.displayEntityType) ? vm.settings.displayEntityType : true; |
161 | 166 | vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true; |
162 | 167 | |
... | ... | @@ -185,6 +190,8 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
185 | 190 | //var mdDarkIcon = mdDarkSecondary; |
186 | 191 | var mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); |
187 | 192 | |
193 | + //md-icon.md-default-theme, md-icon { | |
194 | + | |
188 | 195 | var cssString = 'table.md-table th.md-column {\n'+ |
189 | 196 | 'color: ' + mdDarkSecondary + ';\n'+ |
190 | 197 | '}\n'+ |
... | ... | @@ -204,6 +211,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
204 | 211 | 'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+ |
205 | 212 | 'border-color: ' + mdDarkSecondary + ';\n'+ |
206 | 213 | '}\n'+ |
214 | + 'table.md-table td.md-cell.tb-action-cell button.md-icon-button md-icon {\n'+ | |
215 | + 'color: ' + mdDarkSecondary + ';\n'+ | |
216 | + '}\n'+ | |
207 | 217 | 'table.md-table td.md-cell.md-placeholder {\n'+ |
208 | 218 | 'color: ' + mdDarkDisabled + ';\n'+ |
209 | 219 | '}\n'+ |
... | ... | @@ -261,11 +271,37 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
261 | 271 | } |
262 | 272 | |
263 | 273 | function onRowClick($event, entity) { |
274 | + if ($event) { | |
275 | + $event.stopPropagation(); | |
276 | + } | |
264 | 277 | if (vm.currentEntity != entity) { |
265 | 278 | vm.currentEntity = entity; |
279 | + var descriptors = vm.ctx.actionsApi.getActionDescriptors('rowClick'); | |
280 | + if (descriptors.length) { | |
281 | + var entityId; | |
282 | + var entityName; | |
283 | + if (vm.currentEntity) { | |
284 | + entityId = vm.currentEntity.id; | |
285 | + entityName = vm.currentEntity.entityName; | |
286 | + } | |
287 | + vm.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName); | |
288 | + } | |
266 | 289 | } |
267 | 290 | } |
268 | 291 | |
292 | + function onActionButtonClick($event, entity, actionDescriptor) { | |
293 | + if ($event) { | |
294 | + $event.stopPropagation(); | |
295 | + } | |
296 | + var entityId; | |
297 | + var entityName; | |
298 | + if (entity) { | |
299 | + entityId = entity.id; | |
300 | + entityName = entity.entityName; | |
301 | + } | |
302 | + vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName); | |
303 | + } | |
304 | + | |
269 | 305 | function isCurrent(entity) { |
270 | 306 | return (vm.currentEntity && entity && vm.currentEntity.id && entity.id) && |
271 | 307 | (vm.currentEntity.id.id === entity.id.id); |
... | ... | @@ -393,13 +429,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra |
393 | 429 | } |
394 | 430 | vm.dataKeys.push(dataKey); |
395 | 431 | |
396 | - var translationId = types.translate.customTranslationsPrefix + dataKey.label; | |
397 | - var translation = $translate.instant(translationId); | |
398 | - if (translation != translationId) { | |
399 | - dataKey.title = translation + ''; | |
400 | - } else { | |
401 | - dataKey.title = dataKey.label; | |
402 | - } | |
432 | + dataKey.title = utils.customTranslation(dataKey.label, dataKey.label); | |
403 | 433 | |
404 | 434 | var keySettings = dataKey.settings; |
405 | 435 | ... | ... |
... | ... | @@ -41,10 +41,10 @@ |
41 | 41 | <table md-table> |
42 | 42 | <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> |
43 | 43 | <tr md-row> |
44 | - <th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span translate>entity.entity-name</span></th> | |
44 | + <th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span>{{vm.entityNameColumnTitle}}</span></th> | |
45 | 45 | <th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th> |
46 | 46 | <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th> |
47 | - <th md-column ng-if="vm.displayActions"><span> </span></th> | |
47 | + <th md-column ng-if="vm.actionCellDescriptors.length"><span> </span></th> | |
48 | 48 | </tr> |
49 | 49 | </thead> |
50 | 50 | <tbody md-body> |
... | ... | @@ -57,14 +57,18 @@ |
57 | 57 | ng-style="vm.cellStyle(entity, key)" |
58 | 58 | ng-bind-html="vm.cellContent(entity, key)"> |
59 | 59 | </td> |
60 | - <td md-cell ng-if="vm.displayActions" class="tb-action-cell"> | |
61 | - <!--md-button class="md-icon-button" aria-label="{{ 'entity.details' | translate }}" | |
62 | - ng-click="vm.openEntityDetails($event, entity)"> | |
63 | - <md-icon aria-label="{{ 'entity.details' | translate }}" class="material-icons">more_horiz</md-icon> | |
60 | + <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell" | |
61 | + ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px', | |
62 | + maxWidth: vm.actionCellDescriptors.length*36+'px', | |
63 | + width: vm.actionCellDescriptors.length*36+'px'}"> | |
64 | + <md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors" | |
65 | + aria-label="{{ actionDescriptor.displayName }}" | |
66 | + ng-click="vm.onActionButtonClick($event, entity, actionDescriptor)"> | |
67 | + <md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon> | |
64 | 68 | <md-tooltip md-direction="top"> |
65 | - {{ 'entity.details' | translate }} | |
69 | + {{ actionDescriptor.displayName }} | |
66 | 70 | </md-tooltip> |
67 | - </md-button--> | |
71 | + </md-button> | |
68 | 72 | </td> |
69 | 73 | </tr> |
70 | 74 | </tbody> | ... | ... |