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
@@ -111,6 +111,7 @@ | @@ -111,6 +111,7 @@ | ||
111 | "ngtemplate-loader": "^1.3.1", | 111 | "ngtemplate-loader": "^1.3.1", |
112 | "node-sass": "^3.9.3", | 112 | "node-sass": "^3.9.3", |
113 | "postcss-loader": "^0.13.0", | 113 | "postcss-loader": "^0.13.0", |
114 | + "raw-loader": "^0.5.1", | ||
114 | "react-hot-loader": "^3.0.0-beta.6", | 115 | "react-hot-loader": "^3.0.0-beta.6", |
115 | "sass-loader": "^4.0.2", | 116 | "sass-loader": "^4.0.2", |
116 | "style-loader": "^0.13.1", | 117 | "style-loader": "^0.13.1", |
@@ -171,6 +171,48 @@ export default class Subscription { | @@ -171,6 +171,48 @@ export default class Subscription { | ||
171 | return deferred.promise; | 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 | initAlarmSubscription() { | 216 | initAlarmSubscription() { |
175 | var deferred = this.ctx.$q.defer(); | 217 | var deferred = this.ctx.$q.defer(); |
176 | if (!this.ctx.aliasController) { | 218 | if (!this.ctx.aliasController) { |
@@ -342,6 +384,7 @@ export default class Subscription { | @@ -342,6 +384,7 @@ export default class Subscription { | ||
342 | function success(aliasInfo) { | 384 | function success(aliasInfo) { |
343 | if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType == subscription.ctx.types.entityType.device) { | 385 | if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType == subscription.ctx.types.entityType.device) { |
344 | subscription.targetDeviceId = aliasInfo.currentEntity.id; | 386 | subscription.targetDeviceId = aliasInfo.currentEntity.id; |
387 | + subscription.targetDeviceName = aliasInfo.currentEntity.name; | ||
345 | if (subscription.targetDeviceId) { | 388 | if (subscription.targetDeviceId) { |
346 | subscription.rpcEnabled = true; | 389 | subscription.rpcEnabled = true; |
347 | } else { | 390 | } else { |
@@ -40,7 +40,7 @@ export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsbo | @@ -40,7 +40,7 @@ export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsbo | ||
40 | .name; | 40 | .name; |
41 | 41 | ||
42 | /*@ngInject*/ | 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 | $window.$ = $; | 45 | $window.$ = $; |
46 | $window.jQuery = $; | 46 | $window.jQuery = $; |
@@ -548,13 +548,21 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ | @@ -548,13 +548,21 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ | ||
548 | ' }\n\n' + | 548 | ' }\n\n' + |
549 | 549 | ||
550 | ' self.typeParameters = function() {\n\n' + | 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 | ' }\n\n' + | 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 | ' self.onResize = function() {\n\n' + | 566 | ' self.onResize = function() {\n\n' + |
559 | 567 | ||
560 | ' }\n\n' + | 568 | ' }\n\n' + |
@@ -611,6 +619,16 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ | @@ -611,6 +619,16 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ | ||
611 | if (angular.isUndefined(result.typeParameters.maxDataKeys)) { | 619 | if (angular.isUndefined(result.typeParameters.maxDataKeys)) { |
612 | result.typeParameters.maxDataKeys = -1; | 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 | return result; | 632 | return result; |
615 | } catch (e) { | 633 | } catch (e) { |
616 | utils.processWidgetException(e); | 634 | utils.processWidgetException(e); |
@@ -650,6 +668,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ | @@ -650,6 +668,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ | ||
650 | widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema; | 668 | widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema; |
651 | } | 669 | } |
652 | widgetInfo.typeParameters = widgetType.typeParameters; | 670 | widgetInfo.typeParameters = widgetType.typeParameters; |
671 | + widgetInfo.actionSources = widgetType.actionSources; | ||
653 | putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem); | 672 | putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem); |
654 | putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem); | 673 | putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem); |
655 | deferred.resolve(widgetInfo); | 674 | deferred.resolve(widgetInfo); |
@@ -399,6 +399,31 @@ export default angular.module('thingsboard.types', []) | @@ -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 | systemBundleAlias: { | 427 | systemBundleAlias: { |
403 | charts: "charts", | 428 | charts: "charts", |
404 | cards: "cards" | 429 | cards: "cards" |
@@ -13,6 +13,13 @@ | @@ -13,6 +13,13 @@ | ||
13 | * See the License for the specific language governing permissions and | 13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. | 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 | import tinycolor from "tinycolor2"; | 23 | import tinycolor from "tinycolor2"; |
17 | import jsonSchemaDefaults from "json-schema-defaults"; | 24 | import jsonSchemaDefaults from "json-schema-defaults"; |
18 | import thingsboardTypes from "./types.constant"; | 25 | import thingsboardTypes from "./types.constant"; |
@@ -24,11 +31,18 @@ export default angular.module('thingsboard.utils', [thingsboardTypes]) | @@ -24,11 +31,18 @@ export default angular.module('thingsboard.utils', [thingsboardTypes]) | ||
24 | const varsRegex = /\$\{([^\}]*)\}/g; | 31 | const varsRegex = /\$\{([^\}]*)\}/g; |
25 | 32 | ||
26 | /*@ngInject*/ | 33 | /*@ngInject*/ |
27 | -function Utils($mdColorPalette, $rootScope, $window, $translate, types) { | 34 | +function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, types) { |
28 | 35 | ||
29 | var predefinedFunctions = {}, | 36 | var predefinedFunctions = {}, |
30 | predefinedFunctionsList = [], | 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 | predefinedFunctions['Sin'] = "return Math.round(1000*Math.sin(time/5000));"; | 47 | predefinedFunctions['Sin'] = "return Math.round(1000*Math.sin(time/5000));"; |
34 | predefinedFunctions['Cos'] = "return Math.round(1000*Math.cos(time/5000));"; | 48 | predefinedFunctions['Cos'] = "return Math.round(1000*Math.cos(time/5000));"; |
@@ -122,6 +136,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { | @@ -122,6 +136,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { | ||
122 | getDefaultDatasourceJson: getDefaultDatasourceJson, | 136 | getDefaultDatasourceJson: getDefaultDatasourceJson, |
123 | getDefaultAlarmDataKeys: getDefaultAlarmDataKeys, | 137 | getDefaultAlarmDataKeys: getDefaultAlarmDataKeys, |
124 | getMaterialColor: getMaterialColor, | 138 | getMaterialColor: getMaterialColor, |
139 | + getMaterialIcons: getMaterialIcons, | ||
140 | + getCommonMaterialIcons: getCommonMaterialIcons, | ||
125 | getPredefinedFunctionBody: getPredefinedFunctionBody, | 141 | getPredefinedFunctionBody: getPredefinedFunctionBody, |
126 | getPredefinedFunctionsList: getPredefinedFunctionsList, | 142 | getPredefinedFunctionsList: getPredefinedFunctionsList, |
127 | genMaterialColor: genMaterialColor, | 143 | genMaterialColor: genMaterialColor, |
@@ -136,7 +152,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { | @@ -136,7 +152,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { | ||
136 | validateDatasources: validateDatasources, | 152 | validateDatasources: validateDatasources, |
137 | createKey: createKey, | 153 | createKey: createKey, |
138 | createLabelFromDatasource: createLabelFromDatasource, | 154 | createLabelFromDatasource: createLabelFromDatasource, |
139 | - insertVariable: insertVariable | 155 | + insertVariable: insertVariable, |
156 | + customTranslation: customTranslation | ||
140 | } | 157 | } |
141 | 158 | ||
142 | return service; | 159 | return service; |
@@ -154,6 +171,31 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { | @@ -154,6 +171,31 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { | ||
154 | return materialColors[colorIndex].value; | 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 | function genMaterialColor(str) { | 199 | function genMaterialColor(str) { |
158 | var hash = Math.abs(hashCode(str)); | 200 | var hash = Math.abs(hashCode(str)); |
159 | return getMaterialColor(hash); | 201 | return getMaterialColor(hash); |
@@ -432,4 +474,16 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { | @@ -432,4 +474,16 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) { | ||
432 | return result; | 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,23 +87,32 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u | ||
87 | dashboardService.getDashboardInfo(ngModelCtrl.$viewValue).then( | 87 | dashboardService.getDashboardInfo(ngModelCtrl.$viewValue).then( |
88 | function success(dashboard) { | 88 | function success(dashboard) { |
89 | scope.dashboard = dashboard; | 89 | scope.dashboard = dashboard; |
90 | + startWatchers(); | ||
90 | }, | 91 | }, |
91 | function fail() { | 92 | function fail() { |
92 | scope.dashboard = null; | 93 | scope.dashboard = null; |
94 | + scope.updateView(); | ||
95 | + startWatchers(); | ||
93 | } | 96 | } |
94 | ); | 97 | ); |
95 | } else { | 98 | } else { |
96 | scope.dashboard = null; | 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 | if (scope.selectFirstDashboard) { | 117 | if (scope.selectFirstDashboard) { |
109 | var pageLink = {limit: 1, textSearch: ''}; | 118 | var pageLink = {limit: 1, textSearch: ''}; |
@@ -111,6 +120,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u | @@ -111,6 +120,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u | ||
111 | var dashboards = result.data; | 120 | var dashboards = result.data; |
112 | if (dashboards.length > 0) { | 121 | if (dashboards.length > 0) { |
113 | scope.dashboard = dashboards[0]; | 122 | scope.dashboard = dashboards[0]; |
123 | + scope.updateView(); | ||
114 | } | 124 | } |
115 | }, function fail() { | 125 | }, function fail() { |
116 | }); | 126 | }); |
@@ -20,7 +20,7 @@ import 'javascript-detect-element-resize/detect-element-resize'; | @@ -20,7 +20,7 @@ import 'javascript-detect-element-resize/detect-element-resize'; | ||
20 | import angularGridster from 'angular-gridster'; | 20 | import angularGridster from 'angular-gridster'; |
21 | import thingsboardTypes from '../common/types.constant'; | 21 | import thingsboardTypes from '../common/types.constant'; |
22 | import thingsboardApiWidget from '../api/widget.service'; | 22 | import thingsboardApiWidget from '../api/widget.service'; |
23 | -import thingsboardWidget from './widget.directive'; | 23 | +import thingsboardWidget from './widget/widget.directive'; |
24 | import thingsboardToast from '../services/toast'; | 24 | import thingsboardToast from '../services/toast'; |
25 | import thingsboardTimewindow from './timewindow.directive'; | 25 | import thingsboardTimewindow from './timewindow.directive'; |
26 | import thingsboardEvents from './tb-event-directives'; | 26 | import thingsboardEvents from './tb-event-directives'; |
@@ -187,6 +187,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -187,6 +187,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
187 | vm.showWidgetActions = showWidgetActions; | 187 | vm.showWidgetActions = showWidgetActions; |
188 | vm.widgetTitleStyle = widgetTitleStyle; | 188 | vm.widgetTitleStyle = widgetTitleStyle; |
189 | vm.widgetTitle = widgetTitle; | 189 | vm.widgetTitle = widgetTitle; |
190 | + vm.customWidgetHeaderActions = customWidgetHeaderActions; | ||
190 | vm.widgetActions = widgetActions; | 191 | vm.widgetActions = widgetActions; |
191 | vm.dropWidgetShadow = dropWidgetShadow; | 192 | vm.dropWidgetShadow = dropWidgetShadow; |
192 | vm.enableWidgetFullscreen = enableWidgetFullscreen; | 193 | vm.enableWidgetFullscreen = enableWidgetFullscreen; |
@@ -875,6 +876,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -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 | function widgetActions(widget) { | 888 | function widgetActions(widget) { |
879 | var ctx = widgetContext(widget); | 889 | var ctx = widgetContext(widget); |
880 | if (ctx && ctx.widgetActions && ctx.widgetActions.length) { | 890 | if (ctx && ctx.widgetActions && ctx.widgetActions.length) { |
@@ -52,6 +52,16 @@ | @@ -52,6 +52,16 @@ | ||
52 | <tb-timewindow aggregation="{{vm.hasAggregation(widget)}}" ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow> | 52 | <tb-timewindow aggregation="{{vm.hasAggregation(widget)}}" ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow> |
53 | </div> | 53 | </div> |
54 | <div class="tb-widget-actions" layout="row" layout-align="start center" ng-show="vm.showWidgetActions(widget)" tb-mousedown="$event.stopPropagation()"> | 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 | <md-button ng-repeat="action in vm.widgetActions(widget)" | 65 | <md-button ng-repeat="action in vm.widgetActions(widget)" |
56 | aria-label="{{ action.name | translate }}" | 66 | aria-label="{{ action.name | translate }}" |
57 | ng-show="!vm.isEdit && action.show" | 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 | +} |
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> |
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 | +} |
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 | +} |
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,13 +14,14 @@ | ||
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | import jsonSchemaDefaults from 'json-schema-defaults'; | 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 | import 'angular-ui-ace'; | 25 | import 'angular-ui-ace'; |
25 | 26 | ||
26 | /* eslint-disable import/no-unresolved, import/default */ | 27 | /* eslint-disable import/no-unresolved, import/default */ |
@@ -38,6 +39,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar | @@ -38,6 +39,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar | ||
38 | thingsboardDatasource, | 39 | thingsboardDatasource, |
39 | thingsboardTimewindow, | 40 | thingsboardTimewindow, |
40 | thingsboardLegendConfig, | 41 | thingsboardLegendConfig, |
42 | + thingsboardManageWidgetActions, | ||
41 | 'ui.ace']) | 43 | 'ui.ace']) |
42 | .directive('tbWidgetConfig', WidgetConfig) | 44 | .directive('tbWidgetConfig', WidgetConfig) |
43 | .name; | 45 | .name; |
@@ -117,6 +119,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout | @@ -117,6 +119,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout | ||
117 | scope.showLegend = angular.isDefined(config.showLegend) ? | 119 | scope.showLegend = angular.isDefined(config.showLegend) ? |
118 | config.showLegend : scope.widgetType === types.widgetType.timeseries.value; | 120 | config.showLegend : scope.widgetType === types.widgetType.timeseries.value; |
119 | scope.legendConfig = config.legendConfig; | 121 | scope.legendConfig = config.legendConfig; |
122 | + scope.actions = config.actions; | ||
123 | + if (!scope.actions) { | ||
124 | + scope.actions = {}; | ||
125 | + } | ||
120 | if (scope.widgetType !== types.widgetType.rpc.value && | 126 | if (scope.widgetType !== types.widgetType.rpc.value && |
121 | scope.widgetType !== types.widgetType.alarm.value && | 127 | scope.widgetType !== types.widgetType.alarm.value && |
122 | scope.widgetType !== types.widgetType.static.value | 128 | scope.widgetType !== types.widgetType.static.value |
@@ -324,6 +330,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout | @@ -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 | scope.addDatasource = function () { | 346 | scope.addDatasource = function () { |
328 | var newDatasource; | 347 | var newDatasource; |
329 | if (scope.functionsOnly) { | 348 | if (scope.functionsOnly) { |
@@ -443,11 +462,13 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout | @@ -443,11 +462,13 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout | ||
443 | isDataEnabled: '=?', | 462 | isDataEnabled: '=?', |
444 | widgetType: '=', | 463 | widgetType: '=', |
445 | typeParameters: '=', | 464 | typeParameters: '=', |
465 | + actionSources: '=', | ||
446 | widgetSettingsSchema: '=', | 466 | widgetSettingsSchema: '=', |
447 | datakeySettingsSchema: '=', | 467 | datakeySettingsSchema: '=', |
448 | aliasController: '=', | 468 | aliasController: '=', |
449 | functionsOnly: '=', | 469 | functionsOnly: '=', |
450 | fetchEntityKeys: '&', | 470 | fetchEntityKeys: '&', |
471 | + fetchDashboardStates: '&', | ||
451 | onCreateEntityAlias: '&', | 472 | onCreateEntityAlias: '&', |
452 | theForm: '=' | 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,4 +275,13 @@ | ||
275 | </ng-form> | 275 | </ng-form> |
276 | </md-content> | 276 | </md-content> |
277 | </md-tab> | 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 | </md-tabs> | 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,12 +15,12 @@ | ||
15 | */ | 15 | */ |
16 | import $ from 'jquery'; | 16 | import $ from 'jquery'; |
17 | import 'javascript-detect-element-resize/detect-element-resize'; | 17 | import 'javascript-detect-element-resize/detect-element-resize'; |
18 | -import Subscription from '../api/subscription'; | 18 | +import Subscription from '../../api/subscription'; |
19 | 19 | ||
20 | /* eslint-disable angular/angularelement */ | 20 | /* eslint-disable angular/angularelement */ |
21 | 21 | ||
22 | /*@ngInject*/ | 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 | datasourceService, alarmService, entityService, deviceService, visibleRect, isEdit, isMobile, stDiff, dashboardTimewindow, | 24 | datasourceService, alarmService, entityService, deviceService, visibleRect, isEdit, isMobile, stDiff, dashboardTimewindow, |
25 | dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) { | 25 | dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) { |
26 | 26 | ||
@@ -44,6 +44,20 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -44,6 +44,20 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
44 | 44 | ||
45 | var cafs = {}; | 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 | var widgetContext = { | 61 | var widgetContext = { |
48 | inited: false, | 62 | inited: false, |
49 | $container: null, | 63 | $container: null, |
@@ -103,9 +117,32 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -103,9 +117,32 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
103 | utils: { | 117 | utils: { |
104 | formatValue: formatValue | 118 | formatValue: formatValue |
105 | }, | 119 | }, |
120 | + actionsApi: { | ||
121 | + actionDescriptorsBySourceId: actionDescriptorsBySourceId, | ||
122 | + getActionDescriptors: getActionDescriptors, | ||
123 | + handleWidgetAction: handleWidgetAction | ||
124 | + }, | ||
106 | stateController: stateController | 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 | var subscriptionContext = { | 146 | var subscriptionContext = { |
110 | $scope: $scope, | 147 | $scope: $scope, |
111 | $q: $q, | 148 | $q: $q, |
@@ -376,6 +413,87 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -376,6 +413,87 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
376 | return deferred.promise; | 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 | function configureWidgetElement() { | 497 | function configureWidgetElement() { |
380 | 498 | ||
381 | $scope.displayLegend = angular.isDefined(widget.config.showLegend) ? | 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,9 +16,9 @@ | ||
16 | 16 | ||
17 | import './widget.scss'; | 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 | import WidgetController from './widget.controller'; | 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,6 +36,7 @@ export default function AddWidgetController($scope, widgetService, entityService | ||
36 | vm.add = add; | 36 | vm.add = add; |
37 | vm.cancel = cancel; | 37 | vm.cancel = cancel; |
38 | vm.fetchEntityKeys = fetchEntityKeys; | 38 | vm.fetchEntityKeys = fetchEntityKeys; |
39 | + vm.fetchDashboardStates = fetchDashboardStates; | ||
39 | vm.createEntityAlias = createEntityAlias; | 40 | vm.createEntityAlias = createEntityAlias; |
40 | 41 | ||
41 | vm.widgetConfig = { | 42 | vm.widgetConfig = { |
@@ -128,6 +129,26 @@ export default function AddWidgetController($scope, widgetService, entityService | @@ -128,6 +129,26 @@ export default function AddWidgetController($scope, widgetService, entityService | ||
128 | return deferred.promise; | 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 | function createEntityAlias (event, alias, allowedEntityTypes) { | 152 | function createEntityAlias (event, alias, allowedEntityTypes) { |
132 | 153 | ||
133 | var deferred = $q.defer(); | 154 | var deferred = $q.defer(); |
@@ -34,6 +34,7 @@ | @@ -34,6 +34,7 @@ | ||
34 | <fieldset ng-disabled="loading" style="position: relative; height: 600px;"> | 34 | <fieldset ng-disabled="loading" style="position: relative; height: 600px;"> |
35 | <tb-widget-config widget-type="vm.widget.type" | 35 | <tb-widget-config widget-type="vm.widget.type" |
36 | type-parameters="vm.widgetInfo.typeParameters" | 36 | type-parameters="vm.widgetInfo.typeParameters" |
37 | + action-sources="vm.widgetInfo.actionSources" | ||
37 | force-expand-datasources="true" | 38 | force-expand-datasources="true" |
38 | ng-model="vm.widgetConfig" | 39 | ng-model="vm.widgetConfig" |
39 | widget-settings-schema="vm.settingsSchema" | 40 | widget-settings-schema="vm.settingsSchema" |
@@ -41,6 +42,7 @@ | @@ -41,6 +42,7 @@ | ||
41 | alias-controller="vm.aliasController" | 42 | alias-controller="vm.aliasController" |
42 | functions-only="vm.functionsOnly" | 43 | functions-only="vm.functionsOnly" |
43 | fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)" | 44 | fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)" |
45 | + fetch-dashboard-states="vm.fetchDashboardStates(query)" | ||
44 | on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)" | 46 | on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)" |
45 | the-form="theForm"></tb-widget-config> | 47 | the-form="theForm"></tb-widget-config> |
46 | </fieldset> | 48 | </fieldset> |
@@ -41,6 +41,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid | @@ -41,6 +41,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid | ||
41 | var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; | 41 | var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; |
42 | var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; | 42 | var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; |
43 | scope.typeParameters = widgetInfo.typeParameters; | 43 | scope.typeParameters = widgetInfo.typeParameters; |
44 | + scope.actionSources = widgetInfo.actionSources; | ||
44 | scope.isDataEnabled = !widgetInfo.typeParameters.useCustomDatasources; | 45 | scope.isDataEnabled = !widgetInfo.typeParameters.useCustomDatasources; |
45 | if (!settingsSchema || settingsSchema === '') { | 46 | if (!settingsSchema || settingsSchema === '') { |
46 | scope.settingsSchema = {}; | 47 | scope.settingsSchema = {}; |
@@ -93,6 +94,26 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid | @@ -93,6 +94,26 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid | ||
93 | return deferred.promise; | 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 | scope.createEntityAlias = function (event, alias, allowedEntityTypes) { | 117 | scope.createEntityAlias = function (event, alias, allowedEntityTypes) { |
97 | 118 | ||
98 | var deferred = $q.defer(); | 119 | var deferred = $q.defer(); |
@@ -18,6 +18,7 @@ | @@ -18,6 +18,7 @@ | ||
18 | <fieldset ng-disabled="loading"> | 18 | <fieldset ng-disabled="loading"> |
19 | <tb-widget-config widget-type="widget.type" | 19 | <tb-widget-config widget-type="widget.type" |
20 | type-parameters="typeParameters" | 20 | type-parameters="typeParameters" |
21 | + action-sources="actionSources" | ||
21 | ng-model="widgetConfig" | 22 | ng-model="widgetConfig" |
22 | is-data-enabled="isDataEnabled" | 23 | is-data-enabled="isDataEnabled" |
23 | widget-settings-schema="settingsSchema" | 24 | widget-settings-schema="settingsSchema" |
@@ -25,6 +26,7 @@ | @@ -25,6 +26,7 @@ | ||
25 | alias-controller="aliasController" | 26 | alias-controller="aliasController" |
26 | functions-only="widgetEditMode" | 27 | functions-only="widgetEditMode" |
27 | fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)" | 28 | fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)" |
29 | + fetch-dashboard-states="fetchDashboardStates(query)" | ||
28 | on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)" | 30 | on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)" |
29 | the-form="theForm"></tb-widget-config> | 31 | the-form="theForm"></tb-widget-config> |
30 | </fieldset> | 32 | </fieldset> |
@@ -23,7 +23,7 @@ import thingsboardApiUser from '../api/user.service'; | @@ -23,7 +23,7 @@ import thingsboardApiUser from '../api/user.service'; | ||
23 | import thingsboardApiDashboard from '../api/dashboard.service'; | 23 | import thingsboardApiDashboard from '../api/dashboard.service'; |
24 | import thingsboardApiCustomer from '../api/customer.service'; | 24 | import thingsboardApiCustomer from '../api/customer.service'; |
25 | import thingsboardDetailsSidenav from '../components/details-sidenav.directive'; | 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 | import thingsboardDashboardSelect from '../components/dashboard-select.directive'; | 27 | import thingsboardDashboardSelect from '../components/dashboard-select.directive'; |
28 | import thingsboardRelatedEntityAutocomplete from '../components/related-entity-autocomplete.directive'; | 28 | import thingsboardRelatedEntityAutocomplete from '../components/related-entity-autocomplete.directive'; |
29 | import thingsboardDashboard from '../components/dashboard.directive'; | 29 | import thingsboardDashboard from '../components/dashboard.directive'; |
@@ -59,10 +59,10 @@ | @@ -59,10 +59,10 @@ | ||
59 | <span flex></span> | 59 | <span flex></span> |
60 | <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" | 60 | <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" |
61 | class="md-raised md-primary"> | 61 | class="md-raised md-primary"> |
62 | - {{ vm.isAdd ? 'Add' : 'Save' }} | 62 | + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} |
63 | </md-button> | 63 | </md-button> |
64 | <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;"> | 64 | <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;"> |
65 | - Cancel | 65 | + {{ 'action.cancel' | translate }} |
66 | </md-button> | 66 | </md-button> |
67 | </md-dialog-actions> | 67 | </md-dialog-actions> |
68 | </form> | 68 | </form> |
@@ -15,7 +15,7 @@ | @@ -15,7 +15,7 @@ | ||
15 | */ | 15 | */ |
16 | 16 | ||
17 | /*@ngInject*/ | 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 | var vm = this; | 20 | var vm = this; |
21 | 21 | ||
@@ -50,6 +50,9 @@ export default function DefaultStateController($scope, $location, $state, $state | @@ -50,6 +50,9 @@ export default function DefaultStateController($scope, $location, $state, $state | ||
50 | } | 50 | } |
51 | 51 | ||
52 | function updateState(id, params, openRightLayout) { | 52 | function updateState(id, params, openRightLayout) { |
53 | + if (!id) { | ||
54 | + id = getStateId(); | ||
55 | + } | ||
53 | if (vm.states && vm.states[id]) { | 56 | if (vm.states && vm.states[id]) { |
54 | if (!params) { | 57 | if (!params) { |
55 | params = {}; | 58 | params = {}; |
@@ -110,15 +113,7 @@ export default function DefaultStateController($scope, $location, $state, $state | @@ -110,15 +113,7 @@ export default function DefaultStateController($scope, $location, $state, $state | ||
110 | } | 113 | } |
111 | 114 | ||
112 | function getStateName(id, state) { | 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 | function parseState(stateJson) { | 119 | function parseState(stateJson) { |
@@ -55,6 +55,9 @@ export default function EntityStateController($scope, $location, $state, $stateP | @@ -55,6 +55,9 @@ export default function EntityStateController($scope, $location, $state, $stateP | ||
55 | } | 55 | } |
56 | 56 | ||
57 | function updateState(id, params, openRightLayout) { | 57 | function updateState(id, params, openRightLayout) { |
58 | + if (!id) { | ||
59 | + id = getStateId(); | ||
60 | + } | ||
58 | if (vm.states && vm.states[id]) { | 61 | if (vm.states && vm.states[id]) { |
59 | resolveEntity(params).then( | 62 | resolveEntity(params).then( |
60 | function success(entityName) { | 63 | function success(entityName) { |
@@ -121,17 +124,10 @@ export default function EntityStateController($scope, $location, $state, $stateP | @@ -121,17 +124,10 @@ export default function EntityStateController($scope, $location, $state, $stateP | ||
121 | var result = ''; | 124 | var result = ''; |
122 | if (vm.stateObject[index]) { | 125 | if (vm.stateObject[index]) { |
123 | var stateName = vm.states[vm.stateObject[index].id].name; | 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 | var params = vm.stateObject[index].params; | 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 | return result; | 132 | return result; |
137 | } | 133 | } |
@@ -175,8 +175,6 @@ export default function ManageDashboardStatesController($scope, $mdDialog, $filt | @@ -175,8 +175,6 @@ export default function ManageDashboardStatesController($scope, $mdDialog, $filt | ||
175 | $scope.theForm.$setDirty(); | 175 | $scope.theForm.$setDirty(); |
176 | updateStates(); | 176 | updateStates(); |
177 | }); | 177 | }); |
178 | - | ||
179 | - | ||
180 | } | 178 | } |
181 | } | 179 | } |
182 | 180 |
@@ -102,6 +102,7 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, | @@ -102,6 +102,7 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, | ||
102 | if (vm.addToDashboardType === 0) { | 102 | if (vm.addToDashboardType === 0) { |
103 | dashboardService.getDashboard(vm.dashboardId).then( | 103 | dashboardService.getDashboard(vm.dashboardId).then( |
104 | function success(dashboard) { | 104 | function success(dashboard) { |
105 | + dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard); | ||
105 | selectTargetState($event, dashboard).then( | 106 | selectTargetState($event, dashboard).then( |
106 | function(targetState) { | 107 | function(targetState) { |
107 | selectTargetLayout($event, dashboard, targetState).then( | 108 | selectTargetLayout($event, dashboard, targetState).then( |
@@ -26,6 +26,7 @@ import thingsboardApiLogin from '../api/login.service'; | @@ -26,6 +26,7 @@ import thingsboardApiLogin from '../api/login.service'; | ||
26 | import thingsboardApiUser from '../api/user.service'; | 26 | import thingsboardApiUser from '../api/user.service'; |
27 | 27 | ||
28 | import thingsboardNoAnimate from '../components/no-animate.directive'; | 28 | import thingsboardNoAnimate from '../components/no-animate.directive'; |
29 | +import thingsboardOnFinishRender from '../components/finish-render.directive'; | ||
29 | import thingsboardSideMenu from '../components/side-menu.directive'; | 30 | import thingsboardSideMenu from '../components/side-menu.directive'; |
30 | import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive'; | 31 | import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive'; |
31 | 32 | ||
@@ -81,6 +82,7 @@ export default angular.module('thingsboard.home', [ | @@ -81,6 +82,7 @@ export default angular.module('thingsboard.home', [ | ||
81 | thingsboardApiLogin, | 82 | thingsboardApiLogin, |
82 | thingsboardApiUser, | 83 | thingsboardApiUser, |
83 | thingsboardNoAnimate, | 84 | thingsboardNoAnimate, |
85 | + thingsboardOnFinishRender, | ||
84 | thingsboardSideMenu, | 86 | thingsboardSideMenu, |
85 | thingsboardDashboardAutocomplete | 87 | thingsboardDashboardAutocomplete |
86 | ]) | 88 | ]) |
@@ -1103,6 +1103,18 @@ export default angular.module('thingsboard.locale', []) | @@ -1103,6 +1103,18 @@ export default angular.module('thingsboard.locale', []) | ||
1103 | "undo": "Undo widget changes", | 1103 | "undo": "Undo widget changes", |
1104 | "export": "Export widget" | 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 | "widgets-bundle": { | 1118 | "widgets-bundle": { |
1107 | "current": "Current bundle", | 1119 | "current": "Current bundle", |
1108 | "widgets-bundles": "Widgets Bundles", | 1120 | "widgets-bundles": "Widgets Bundles", |
@@ -1158,7 +1170,23 @@ export default angular.module('thingsboard.locale', []) | @@ -1158,7 +1170,23 @@ export default angular.module('thingsboard.locale', []) | ||
1158 | "remove-datasource": "Remove datasource", | 1170 | "remove-datasource": "Remove datasource", |
1159 | "add-datasource": "Add datasource", | 1171 | "add-datasource": "Add datasource", |
1160 | "target-device": "Target device", | 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 | "widget-type": { | 1191 | "widget-type": { |
1164 | "import": "Import widget type", | 1192 | "import": "Import widget type", |
@@ -1168,6 +1196,12 @@ export default angular.module('thingsboard.locale', []) | @@ -1168,6 +1196,12 @@ export default angular.module('thingsboard.locale', []) | ||
1168 | "widget-type-file": "Widget type file", | 1196 | "widget-type-file": "Widget type file", |
1169 | "invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure." | 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 | "language": { | 1205 | "language": { |
1172 | "language": "Language", | 1206 | "language": "Language", |
1173 | "en_US": "English", | 1207 | "en_US": "English", |
@@ -1177,6 +1211,10 @@ export default angular.module('thingsboard.locale', []) | @@ -1177,6 +1211,10 @@ export default angular.module('thingsboard.locale', []) | ||
1177 | "es_ES": "Spanish" | 1211 | "es_ES": "Spanish" |
1178 | }, | 1212 | }, |
1179 | "custom": { | 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,13 +158,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia | ||
158 | vm.ctx.widgetActions = [ vm.searchAction ]; | 158 | vm.ctx.widgetActions = [ vm.searchAction ]; |
159 | 159 | ||
160 | if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) { | 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 | } else { | 162 | } else { |
169 | vm.alarmsTitle = $translate.instant('alarm.alarms'); | 163 | vm.alarmsTitle = $translate.instant('alarm.alarms'); |
170 | } | 164 | } |
@@ -226,6 +220,9 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia | @@ -226,6 +220,9 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia | ||
226 | 'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+ | 220 | 'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+ |
227 | 'border-color: ' + mdDarkSecondary + ';\n'+ | 221 | 'border-color: ' + mdDarkSecondary + ';\n'+ |
228 | '}\n'+ | 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 | 'table.md-table td.md-cell.md-placeholder {\n'+ | 226 | 'table.md-table td.md-cell.md-placeholder {\n'+ |
230 | 'color: ' + mdDarkDisabled + ';\n'+ | 227 | 'color: ' + mdDarkDisabled + ';\n'+ |
231 | '}\n'+ | 228 | '}\n'+ |
@@ -539,13 +536,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia | @@ -539,13 +536,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia | ||
539 | for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) { | 536 | for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) { |
540 | var dataKey = vm.alarmSource.dataKeys[d]; | 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 | var keySettings = dataKey.settings; | 541 | var keySettings = dataKey.settings; |
551 | 542 |
@@ -65,8 +65,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | @@ -65,8 +65,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | ||
65 | vm.currentEntity = null; | 65 | vm.currentEntity = null; |
66 | 66 | ||
67 | vm.displayEntityName = true; | 67 | vm.displayEntityName = true; |
68 | + vm.entityNameColumnTitle = ''; | ||
68 | vm.displayEntityType = true; | 69 | vm.displayEntityType = true; |
69 | - vm.displayActions = false; //TODO: Widget actions | 70 | + vm.actionCellDescriptors = []; |
70 | vm.displayPagination = true; | 71 | vm.displayPagination = true; |
71 | vm.defaultPageSize = 10; | 72 | vm.defaultPageSize = 10; |
72 | vm.defaultSortOrder = 'entityName'; | 73 | vm.defaultSortOrder = 'entityName'; |
@@ -92,6 +93,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | @@ -92,6 +93,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | ||
92 | vm.onReorder = onReorder; | 93 | vm.onReorder = onReorder; |
93 | vm.onPaginate = onPaginate; | 94 | vm.onPaginate = onPaginate; |
94 | vm.onRowClick = onRowClick; | 95 | vm.onRowClick = onRowClick; |
96 | + vm.onActionButtonClick = onActionButtonClick; | ||
95 | vm.isCurrent = isCurrent; | 97 | vm.isCurrent = isCurrent; |
96 | 98 | ||
97 | vm.cellStyle = cellStyle; | 99 | vm.cellStyle = cellStyle; |
@@ -141,14 +143,10 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | @@ -141,14 +143,10 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | ||
141 | 143 | ||
142 | vm.ctx.widgetActions = [ vm.searchAction ]; | 144 | vm.ctx.widgetActions = [ vm.searchAction ]; |
143 | 145 | ||
146 | + vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton'); | ||
147 | + | ||
144 | if (vm.settings.entitiesTitle && vm.settings.entitiesTitle.length) { | 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 | } else { | 150 | } else { |
153 | vm.entitiesTitle = $translate.instant('entity.entities'); | 151 | vm.entitiesTitle = $translate.instant('entity.entities'); |
154 | } | 152 | } |
@@ -157,6 +155,13 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | @@ -157,6 +155,13 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | ||
157 | 155 | ||
158 | vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true; | 156 | vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true; |
159 | vm.displayEntityName = angular.isDefined(vm.settings.displayEntityName) ? vm.settings.displayEntityName : true; | 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 | vm.displayEntityType = angular.isDefined(vm.settings.displayEntityType) ? vm.settings.displayEntityType : true; | 165 | vm.displayEntityType = angular.isDefined(vm.settings.displayEntityType) ? vm.settings.displayEntityType : true; |
161 | vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true; | 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,6 +190,8 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | ||
185 | //var mdDarkIcon = mdDarkSecondary; | 190 | //var mdDarkIcon = mdDarkSecondary; |
186 | var mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); | 191 | var mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); |
187 | 192 | ||
193 | + //md-icon.md-default-theme, md-icon { | ||
194 | + | ||
188 | var cssString = 'table.md-table th.md-column {\n'+ | 195 | var cssString = 'table.md-table th.md-column {\n'+ |
189 | 'color: ' + mdDarkSecondary + ';\n'+ | 196 | 'color: ' + mdDarkSecondary + ';\n'+ |
190 | '}\n'+ | 197 | '}\n'+ |
@@ -204,6 +211,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | @@ -204,6 +211,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | ||
204 | 'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+ | 211 | 'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+ |
205 | 'border-color: ' + mdDarkSecondary + ';\n'+ | 212 | 'border-color: ' + mdDarkSecondary + ';\n'+ |
206 | '}\n'+ | 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 | 'table.md-table td.md-cell.md-placeholder {\n'+ | 217 | 'table.md-table td.md-cell.md-placeholder {\n'+ |
208 | 'color: ' + mdDarkDisabled + ';\n'+ | 218 | 'color: ' + mdDarkDisabled + ';\n'+ |
209 | '}\n'+ | 219 | '}\n'+ |
@@ -261,11 +271,37 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | @@ -261,11 +271,37 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | ||
261 | } | 271 | } |
262 | 272 | ||
263 | function onRowClick($event, entity) { | 273 | function onRowClick($event, entity) { |
274 | + if ($event) { | ||
275 | + $event.stopPropagation(); | ||
276 | + } | ||
264 | if (vm.currentEntity != entity) { | 277 | if (vm.currentEntity != entity) { |
265 | vm.currentEntity = entity; | 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 | function isCurrent(entity) { | 305 | function isCurrent(entity) { |
270 | return (vm.currentEntity && entity && vm.currentEntity.id && entity.id) && | 306 | return (vm.currentEntity && entity && vm.currentEntity.id && entity.id) && |
271 | (vm.currentEntity.id.id === entity.id.id); | 307 | (vm.currentEntity.id.id === entity.id.id); |
@@ -393,13 +429,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | @@ -393,13 +429,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra | ||
393 | } | 429 | } |
394 | vm.dataKeys.push(dataKey); | 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 | var keySettings = dataKey.settings; | 434 | var keySettings = dataKey.settings; |
405 | 435 |
@@ -41,10 +41,10 @@ | @@ -41,10 +41,10 @@ | ||
41 | <table md-table> | 41 | <table md-table> |
42 | <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> | 42 | <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> |
43 | <tr md-row> | 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 | <th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th> | 45 | <th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th> |
46 | <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th> | 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 | </tr> | 48 | </tr> |
49 | </thead> | 49 | </thead> |
50 | <tbody md-body> | 50 | <tbody md-body> |
@@ -57,14 +57,18 @@ | @@ -57,14 +57,18 @@ | ||
57 | ng-style="vm.cellStyle(entity, key)" | 57 | ng-style="vm.cellStyle(entity, key)" |
58 | ng-bind-html="vm.cellContent(entity, key)"> | 58 | ng-bind-html="vm.cellContent(entity, key)"> |
59 | </td> | 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 | <md-tooltip md-direction="top"> | 68 | <md-tooltip md-direction="top"> |
65 | - {{ 'entity.details' | translate }} | 69 | + {{ actionDescriptor.displayName }} |
66 | </md-tooltip> | 70 | </md-tooltip> |
67 | - </md-button--> | 71 | + </md-button> |
68 | </td> | 72 | </td> |
69 | </tr> | 73 | </tr> |
70 | </tbody> | 74 | </tbody> |