Browse Source

Merge pull request #177 from thingsboard/feature/TB-64

TB-64: Widget actions
pull/180/head
Igor Kulikov 9 years ago
committed by GitHub
parent
commit
13a6c9a142
  1. 1
      ui/package.json
  2. 43
      ui/src/app/api/subscription.js
  3. 31
      ui/src/app/api/widget.service.js
  4. 25
      ui/src/app/common/types.constant.js
  5. 60
      ui/src/app/common/utils.service.js
  6. 24
      ui/src/app/components/dashboard-autocomplete.directive.js
  7. 12
      ui/src/app/components/dashboard.directive.js
  8. 10
      ui/src/app/components/dashboard.tpl.html
  9. 33
      ui/src/app/components/finish-render.directive.js
  10. 90
      ui/src/app/components/material-icon-select.directive.js
  11. 27
      ui/src/app/components/material-icon-select.scss
  12. 24
      ui/src/app/components/material-icon-select.tpl.html
  13. 59
      ui/src/app/components/material-icons-dialog.controller.js
  14. 31
      ui/src/app/components/material-icons-dialog.scss
  15. 62
      ui/src/app/components/material-icons-dialog.tpl.html
  16. 270
      ui/src/app/components/widget/action/manage-widget-actions.directive.js
  17. 33
      ui/src/app/components/widget/action/manage-widget-actions.scss
  18. 99
      ui/src/app/components/widget/action/manage-widget-actions.tpl.html
  19. 169
      ui/src/app/components/widget/action/widget-action-dialog.controller.js
  20. 133
      ui/src/app/components/widget/action/widget-action-dialog.tpl.html
  21. 35
      ui/src/app/components/widget/widget-config.directive.js
  22. 9
      ui/src/app/components/widget/widget-config.tpl.html
  23. 122
      ui/src/app/components/widget/widget.controller.js
  24. 6
      ui/src/app/components/widget/widget.directive.js
  25. 0
      ui/src/app/components/widget/widget.scss
  26. 21
      ui/src/app/dashboard/add-widget.controller.js
  27. 2
      ui/src/app/dashboard/add-widget.tpl.html
  28. 21
      ui/src/app/dashboard/edit-widget.directive.js
  29. 2
      ui/src/app/dashboard/edit-widget.tpl.html
  30. 2
      ui/src/app/dashboard/index.js
  31. 4
      ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html
  32. 15
      ui/src/app/dashboard/states/default-state-controller.js
  33. 16
      ui/src/app/dashboard/states/entity-state-controller.js
  34. 2
      ui/src/app/dashboard/states/manage-dashboard-states.controller.js
  35. 1
      ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
  36. 2
      ui/src/app/layout/index.js
  37. 40
      ui/src/app/locale/locale.constant.js
  38. 19
      ui/src/app/widget/lib/alarms-table-widget.js
  39. 60
      ui/src/app/widget/lib/entities-table-widget.js
  40. 20
      ui/src/app/widget/lib/entities-table-widget.tpl.html

1
ui/package.json

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

43
ui/src/app/api/subscription.js

@ -171,6 +171,48 @@ export default class Subscription {
return deferred.promise;
}
getFirstEntityInfo() {
var entityId;
var entityName;
if (this.type === this.ctx.types.widgetType.rpc.value) {
if (this.targetDeviceId) {
entityId = {
entityType: this.ctx.entityType.device,
id: this.targetDeviceId
}
entityName = this.targetDeviceName;
}
} else if (this.type == this.ctx.types.widgetType.alarm.value) {
if (this.alarmSource && this.alarmSource.entityType && this.alarmSource.entityId) {
entityId = {
entityType: this.alarmSource.entityType,
id: this.alarmSource.entityId
}
entityName = this.alarmSource.entityName;
}
} else {
for (var i=0;i<this.datasources.length;i++) {
var datasource = this.datasources[i];
if (datasource && datasource.entityType && datasource.entityId) {
entityId = {
entityType: datasource.entityType,
id: datasource.entityId
}
entityName = datasource.entityName;
break;
}
}
}
if (entityId) {
return {
entityId: entityId,
entityName: entityName
};
} else {
return null;
}
}
initAlarmSubscription() {
var deferred = this.ctx.$q.defer();
if (!this.ctx.aliasController) {
@ -342,6 +384,7 @@ export default class Subscription {
function success(aliasInfo) {
if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType == subscription.ctx.types.entityType.device) {
subscription.targetDeviceId = aliasInfo.currentEntity.id;
subscription.targetDeviceName = aliasInfo.currentEntity.name;
if (subscription.targetDeviceId) {
subscription.rpcEnabled = true;
} else {

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

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

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

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

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

@ -13,6 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import materialIconsCodepoints from 'raw-loader!material-design-icons/iconfont/codepoints';
/* eslint-enable import/no-unresolved, import/default */
import tinycolor from "tinycolor2";
import jsonSchemaDefaults from "json-schema-defaults";
import thingsboardTypes from "./types.constant";
@ -24,11 +31,18 @@ export default angular.module('thingsboard.utils', [thingsboardTypes])
const varsRegex = /\$\{([^\}]*)\}/g;
/*@ngInject*/
function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, types) {
var predefinedFunctions = {},
predefinedFunctionsList = [],
materialColors = [];
materialColors = [],
materialIcons = [];
var commonMaterialIcons = [ 'more_horiz', 'more_vert', 'open_in_new', 'visibility', 'play_arrow', 'arrow_back', 'arrow_downward',
'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people',
'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search',
'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export',
'share', 'add', 'edit', 'done' ];
predefinedFunctions['Sin'] = "return Math.round(1000*Math.sin(time/5000));";
predefinedFunctions['Cos'] = "return Math.round(1000*Math.cos(time/5000));";
@ -122,6 +136,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
getDefaultDatasourceJson: getDefaultDatasourceJson,
getDefaultAlarmDataKeys: getDefaultAlarmDataKeys,
getMaterialColor: getMaterialColor,
getMaterialIcons: getMaterialIcons,
getCommonMaterialIcons: getCommonMaterialIcons,
getPredefinedFunctionBody: getPredefinedFunctionBody,
getPredefinedFunctionsList: getPredefinedFunctionsList,
genMaterialColor: genMaterialColor,
@ -136,7 +152,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
validateDatasources: validateDatasources,
createKey: createKey,
createLabelFromDatasource: createLabelFromDatasource,
insertVariable: insertVariable
insertVariable: insertVariable,
customTranslation: customTranslation
}
return service;
@ -154,6 +171,31 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
return materialColors[colorIndex].value;
}
function getMaterialIcons() {
var deferred = $q.defer();
if (materialIcons.length) {
deferred.resolve(materialIcons);
} else {
$timeout(function() {
var codepointsArray = materialIconsCodepoints.split("\n");
codepointsArray.forEach(function (codepoint) {
if (codepoint && codepoint.length) {
var values = codepoint.split(' ');
if (values && values.length == 2) {
materialIcons.push(values[0]);
}
}
});
deferred.resolve(materialIcons);
});
}
return deferred.promise;
}
function getCommonMaterialIcons() {
return commonMaterialIcons;
}
function genMaterialColor(str) {
var hash = Math.abs(hashCode(str));
return getMaterialColor(hash);
@ -432,4 +474,16 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
return result;
}
function customTranslation(translationValue, defaultValue) {
var result = '';
var translationId = types.translate.customTranslationsPrefix + translationValue;
var translation = $translate.instant(translationId);
if (translation != translationId) {
result = translation + '';
} else {
result = defaultValue;
}
return result;
}
}

24
ui/src/app/components/dashboard-autocomplete.directive.js

@ -87,23 +87,32 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
dashboardService.getDashboardInfo(ngModelCtrl.$viewValue).then(
function success(dashboard) {
scope.dashboard = dashboard;
startWatchers();
},
function fail() {
scope.dashboard = null;
scope.updateView();
startWatchers();
}
);
} else {
scope.dashboard = null;
startWatchers();
}
}
scope.$watch('dashboard', function () {
scope.updateView();
});
scope.$watch('disabled', function () {
scope.updateView();
});
function startWatchers() {
scope.$watch('dashboard', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
scope.updateView();
}
});
scope.$watch('disabled', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
scope.updateView();
}
});
}
if (scope.selectFirstDashboard) {
var pageLink = {limit: 1, textSearch: ''};
@ -111,6 +120,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
var dashboards = result.data;
if (dashboards.length > 0) {
scope.dashboard = dashboards[0];
scope.updateView();
}
}, function fail() {
});

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

@ -20,7 +20,7 @@ import 'javascript-detect-element-resize/detect-element-resize';
import angularGridster from 'angular-gridster';
import thingsboardTypes from '../common/types.constant';
import thingsboardApiWidget from '../api/widget.service';
import thingsboardWidget from './widget.directive';
import thingsboardWidget from './widget/widget.directive';
import thingsboardToast from '../services/toast';
import thingsboardTimewindow from './timewindow.directive';
import thingsboardEvents from './tb-event-directives';
@ -187,6 +187,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
vm.showWidgetActions = showWidgetActions;
vm.widgetTitleStyle = widgetTitleStyle;
vm.widgetTitle = widgetTitle;
vm.customWidgetHeaderActions = customWidgetHeaderActions;
vm.widgetActions = widgetActions;
vm.dropWidgetShadow = dropWidgetShadow;
vm.enableWidgetFullscreen = enableWidgetFullscreen;
@ -875,6 +876,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
}
}
function customWidgetHeaderActions(widget) {
var ctx = widgetContext(widget);
if (ctx && ctx.customHeaderActions && ctx.customHeaderActions.length) {
return ctx.customHeaderActions;
} else {
return [];
}
}
function widgetActions(widget) {
var ctx = widgetContext(widget);
if (ctx && ctx.widgetActions && ctx.widgetActions.length) {

10
ui/src/app/components/dashboard.tpl.html

@ -52,6 +52,16 @@
<tb-timewindow aggregation="{{vm.hasAggregation(widget)}}" ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
</div>
<div class="tb-widget-actions" layout="row" layout-align="start center" ng-show="vm.showWidgetActions(widget)" tb-mousedown="$event.stopPropagation()">
<md-button ng-repeat="action in vm.customWidgetHeaderActions(widget)"
aria-label="{{action.displayName}}"
ng-show="!vm.isEdit"
ng-click="action.onAction($event)"
class="md-icon-button">
<md-tooltip md-direction="top">
{{action.displayName}}
</md-tooltip>
<ng-md-icon size="20" icon="{{action.icon}}"></ng-md-icon>
</md-button>
<md-button ng-repeat="action in vm.widgetActions(widget)"
aria-label="{{ action.name | translate }}"
ng-show="!vm.isEdit && action.show"

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

@ -0,0 +1,33 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default angular.module('thingsboard.directives.finishRender', [])
.directive('tbOnFinishRender', OnFinishRender)
.name;
/*@ngInject*/
function OnFinishRender($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit(attr.tbOnFinishRender);
});
}
}
};
}

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

@ -0,0 +1,90 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './material-icon-select.scss';
import MaterialIconsDialogController from './material-icons-dialog.controller';
/* eslint-disable import/no-unresolved, import/default */
import materialIconSelectTemplate from './material-icon-select.tpl.html';
import materialIconsDialogTemplate from './material-icons-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.materialIconSelect', [])
.controller('MaterialIconsDialogController', MaterialIconsDialogController)
.directive('tbMaterialIconSelect', MaterialIconSelect)
.name;
/*@ngInject*/
function MaterialIconSelect($compile, $templateCache, $document, $mdDialog) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(materialIconSelectTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.icon = null;
scope.updateView = function () {
ngModelCtrl.$setViewValue(scope.icon);
}
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
scope.icon = ngModelCtrl.$viewValue;
}
if (!scope.icon || !scope.icon.length) {
scope.icon = 'more_horiz';
}
}
scope.$watch('icon', function () {
scope.updateView();
});
scope.openIconDialog = function($event) {
if ($event) {
$event.stopPropagation();
}
$mdDialog.show({
controller: 'MaterialIconsDialogController',
controllerAs: 'vm',
templateUrl: materialIconsDialogTemplate,
parent: angular.element($document[0].body),
locals: {icon: scope.icon},
skipHide: true,
fullscreen: true,
targetEvent: $event
}).then(function (icon) {
scope.icon = icon;
});
}
$compile(element.contents())(scope);
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
tbRequired: '=?',
}
};
}

27
ui/src/app/components/material-icon-select.scss

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-material-icon-select {
md-icon {
padding: 4px;
margin: 8px 4px 4px;
cursor: pointer;
border: solid 1px rgba(0,0,0,0.27);
}
md-input-container {
margin-bottom: 0px;
}
}

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

@ -0,0 +1,24 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-material-icon-select" layout="row">
<md-icon class="material-icons" ng-click="openIconDialog($event)">{{icon}}</md-icon>
<md-input-container flex>
<label translate>icon.icon</label>
<input ng-mousedown="openIconDialog($event)" ng-model="icon">
</md-input-container>
</div>

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

@ -0,0 +1,59 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './material-icons-dialog.scss';
/*@ngInject*/
export default function MaterialIconsDialogController($scope, $mdDialog, $timeout, utils, icon) {
var vm = this;
vm.selectedIcon = icon;
vm.showAll = false;
vm.loadingIcons = false;
$scope.$watch('vm.showAll', function(showAll) {
if (showAll) {
vm.loadingIcons = true;
$timeout(function() {
utils.getMaterialIcons().then(
function success(icons) {
vm.icons = icons;
}
);
});
} else {
vm.icons = utils.getCommonMaterialIcons();
}
});
$scope.$on('iconsLoadFinished', function() {
vm.loadingIcons = false;
});
vm.cancel = cancel;
vm.selectIcon = selectIcon;
function cancel() {
$mdDialog.cancel();
}
function selectIcon($event, icon) {
vm.selectedIcon = icon;
$mdDialog.hide(vm.selectedIcon);
}
}

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

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-material-icons-dialog {
button.md-icon-button.tb-select-icon-button {
border: solid 1px orange;
border-radius: 0%;
padding: 16px;
height: 56px;
width: 56px;
margin: 10px;
}
.tb-icons-load {
top: 64px;
background: rgba(255,255,255,0.75);
z-index: 3;
}
}

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

@ -0,0 +1,62 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog class="tb-material-icons-dialog" aria-label="{{'icon.material-icons' | translate }}" style="min-width: 600px;">
<form>
<md-toolbar>
<div class="md-toolbar-tools">
<h2>{{ 'icon.select-icon' | translate }}</h2>
<span flex></span>
<section layout="row" layout-align="start center">
<md-switch ng-model="vm.showAll"
aria-label="{{ 'icon.show-all' | translate }}">
</md-switch>
<label translate>icon.show-all</label>
</section>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'action.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<div class="tb-absolute-fill tb-icons-load" ng-show="vm.loadingIcons" layout="column" layout-align="center center">
<md-progress-circular md-mode="indeterminate" ng-disabled="!vm.loadingIcons" class="md-accent" md-diameter="40"></md-progress-circular>
</div>
<md-dialog-content>
<div class="md-dialog-content">
<md-content class="md-padding" layout="column">
<fieldset ng-disabled="loading">
<md-button ng-class="{'md-primary md-raised': icon == vm.selectedIcon}" class="tb-select-icon-button md-icon-button"
ng-repeat="icon in vm.icons" ng-click="vm.selectIcon($event, icon)" tb-on-finish-render="iconsLoadFinished">
<md-icon class="material-icons">{{icon}}</md-icon>
<md-tooltip md-direction="bottom">
{{ icon }}
</md-tooltip>
</md-button>
</fieldset>
</md-content>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="loading" ng-click="vm.cancel()">
{{ 'action.cancel' | translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

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

@ -0,0 +1,270 @@
/**
* Created by igor on 6/20/17.
*/
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './manage-widget-actions.scss';
import thingsboardMaterialIconSelect from '../../material-icon-select.directive';
import WidgetActionDialogController from './widget-action-dialog.controller';
/* eslint-disable import/no-unresolved, import/default */
import manageWidgetActionsTemplate from './manage-widget-actions.tpl.html';
import widgetActionDialogTemplate from './widget-action-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.widgetActions', [thingsboardMaterialIconSelect])
.controller('WidgetActionDialogController', WidgetActionDialogController)
.directive('tbManageWidgetActions', ManageWidgetActions)
.name;
/*@ngInject*/
function ManageWidgetActions() {
return {
restrict: "E",
scope: true,
bindToController: {
actionSources: '=',
widgetActions: '=',
fetchDashboardStates: '&',
},
controller: ManageWidgetActionsController,
controllerAs: 'vm',
templateUrl: manageWidgetActionsTemplate
};
}
/* eslint-disable angular/angularelement */
/*@ngInject*/
function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog, $q, $filter,
$translate, $timeout, utils, types) {
let vm = this;
vm.allActions = [];
vm.actions = [];
vm.actionsCount = 0;
vm.query = {
order: 'actionSourceName',
limit: 10,
page: 1,
search: null
};
vm.enterFilterMode = enterFilterMode;
vm.exitFilterMode = exitFilterMode;
vm.onReorder = onReorder;
vm.onPaginate = onPaginate;
vm.addAction = addAction;
vm.editAction = editAction;
vm.deleteAction = deleteAction;
$timeout(function(){
$scope.manageWidgetActionsForm.querySearchInput.$pristine = false;
});
$scope.$watch('vm.widgetActions', function() {
if (vm.widgetActions) {
reloadActions();
}
});
$scope.$watch("vm.query.search", function(newVal, prevVal) {
if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
updateActions();
}
});
function enterFilterMode () {
vm.query.search = '';
}
function exitFilterMode () {
vm.query.search = null;
updateActions();
}
function onReorder () {
updateActions();
}
function onPaginate () {
updateActions();
}
function addAction($event) {
if ($event) {
$event.stopPropagation();
}
openWidgetActionDialog($event, null, true);
}
function editAction ($event, action) {
if ($event) {
$event.stopPropagation();
}
openWidgetActionDialog($event, action, false);
}
function deleteAction($event, action) {
if ($event) {
$event.stopPropagation();
}
if (action) {
var title = $translate.instant('widget-config.delete-action-title');
var content = $translate.instant('widget-config.delete-action-text', {actionName: action.name});
var confirm = $mdDialog.confirm()
.targetEvent($event)
.title(title)
.htmlContent(content)
.ariaLabel(title)
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
confirm._options.skipHide = true;
confirm._options.fullscreen = true;
$mdDialog.show(confirm).then(function () {
var index = getActionIndex(action.id, vm.allActions);
if (index > -1) {
vm.allActions.splice(index, 1);
}
var targetActions = vm.widgetActions[action.actionSourceId];
index = getActionIndex(action.id, targetActions);
if (index > -1) {
targetActions.splice(index, 1);
}
$scope.manageWidgetActionsForm.$setDirty();
updateActions();
});
}
}
function openWidgetActionDialog($event, action, isAdd) {
var prevActionId = null;
if (!isAdd) {
prevActionId = action.id;
}
var availableActionSources = {};
for (var id in vm.actionSources) {
var actionSource = vm.actionSources[id];
if (actionSource.multiple) {
availableActionSources[id] = actionSource;
} else {
if (!isAdd && action.actionSourceId == id) {
availableActionSources[id] = actionSource;
} else {
var result = $filter('filter')(vm.allActions, {actionSourceId: id});
if (!result || !result.length) {
availableActionSources[id] = actionSource;
}
}
}
}
$mdDialog.show({
controller: 'WidgetActionDialogController',
controllerAs: 'vm',
templateUrl: widgetActionDialogTemplate,
parent: angular.element($document[0].body),
locals: {isAdd: isAdd, fetchDashboardStates: vm.fetchDashboardStates,
actionSources: availableActionSources, widgetActions: vm.widgetActions,
action: angular.copy(action)},
skipHide: true,
fullscreen: true,
targetEvent: $event
}).then(function (action) {
saveAction(action, prevActionId);
updateActions();
});
}
function getActionIndex(id, actions) {
var result = $filter('filter')(actions, {id: id}, true);
if (result && result.length) {
return actions.indexOf(result[0]);
}
return -1;
}
function saveAction(action, prevActionId) {
var actionSourceName = vm.actionSources[action.actionSourceId].name;
action.actionSourceName = utils.customTranslation(actionSourceName, actionSourceName);
action.typeName = $translate.instant(types.widgetActionTypes[action.type].name);
var actionSourceId = action.actionSourceId;
var widgetAction = angular.copy(action);
delete widgetAction.actionSourceId;
delete widgetAction.actionSourceName;
delete widgetAction.typeName;
var targetActions = vm.widgetActions[actionSourceId];
if (!targetActions) {
targetActions = [];
vm.widgetActions[actionSourceId] = targetActions;
}
if (prevActionId) {
var index = getActionIndex(prevActionId, vm.allActions);
if (index > -1) {
vm.allActions[index] = action;
}
index = getActionIndex(prevActionId, targetActions);
if (index > -1) {
targetActions[index] = widgetAction;
}
} else {
vm.allActions.push(action);
targetActions.push(widgetAction);
}
$scope.manageWidgetActionsForm.$setDirty();
}
function reloadActions() {
vm.allActions = [];
vm.actions = [];
vm.actionsCount = 0;
for (var actionSourceId in vm.widgetActions) {
var actionSource = vm.actionSources[actionSourceId];
var actionSourceActions = vm.widgetActions[actionSourceId];
for (var i=0;i<actionSourceActions.length;i++) {
var actionSourceAction = actionSourceActions[i];
var action = angular.copy(actionSourceAction);
action.actionSourceId = actionSourceId;
action.actionSourceName = utils.customTranslation(actionSource.name, actionSource.name);
action.typeName = $translate.instant(types.widgetActionTypes[actionSourceAction.type].name);
vm.allActions.push(action);
}
}
updateActions ();
}
function updateActions () {
var result = $filter('orderBy')(vm.allActions, vm.query.order);
if (vm.query.search != null) {
result = $filter('filter')(result, {$: vm.query.search});
}
vm.actionsCount = result.length;
var startIndex = vm.query.limit * (vm.query.page - 1);
vm.actions = result.slice(startIndex, startIndex + vm.query.limit);
}
}

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

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-manage-widget-actions {
table.md-table {
tbody {
tr {
td {
&.tb-action-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 100px;
max-width: 100px;
width: 100px;
}
}
}
}
}
}

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

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

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

@ -0,0 +1,169 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*@ngInject*/
export default function WidgetActionDialogController($scope, $mdDialog, $filter, $q, dashboardService, dashboardUtils, types, utils,
isAdd, fetchDashboardStates, actionSources, widgetActions, action) {
var vm = this;
vm.types = types;
vm.isAdd = isAdd;
vm.fetchDashboardStates = fetchDashboardStates;
vm.actionSources = actionSources;
vm.widgetActions = widgetActions;
vm.targetDashboardStateSearchText = '';
vm.selectedDashboardStateIds = [];
if (vm.isAdd) {
vm.action = {
id: utils.guid()
};
} else {
vm.action = action;
}
vm.actionSourceName = actionSourceName;
vm.targetDashboardStateSearchTextChanged = function() {
}
vm.dashboardStateSearch = dashboardStateSearch;
vm.cancel = cancel;
vm.save = save;
$scope.$watch("vm.action.name", function(newVal, prevVal) {
if (!angular.equals(newVal, prevVal) && vm.action.name != null) {
checkActionName();
}
});
$scope.$watch("vm.action.actionSourceId", function(newVal, prevVal) {
if (!angular.equals(newVal, prevVal) && vm.action.actionSourceId != null) {
checkActionName();
}
});
$scope.$watch("vm.action.targetDashboardId", function() {
vm.selectedDashboardStateIds = [];
if (vm.action.targetDashboardId) {
dashboardService.getDashboard(vm.action.targetDashboardId).then(
function success(dashboard) {
dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard);
var states = dashboard.configuration.states;
vm.selectedDashboardStateIds = Object.keys(states);
}
);
}
});
$scope.$watch('vm.action.type', function(newType) {
if (newType) {
switch (newType) {
case vm.types.widgetActionTypes.openDashboardState.value:
case vm.types.widgetActionTypes.updateDashboardState.value:
case vm.types.widgetActionTypes.openDashboard.value:
if (angular.isUndefined(vm.action.setEntityId)) {
vm.action.setEntityId = true;
}
break;
}
}
});
function checkActionName() {
var actionNameIsUnique = true;
if (vm.action.actionSourceId && vm.action.name) {
var sourceActions = vm.widgetActions[vm.action.actionSourceId];
if (sourceActions) {
var result = $filter('filter')(sourceActions, {name: vm.action.name}, true);
if (result && result.length && result[0].id !== vm.action.id) {
actionNameIsUnique = false;
}
}
}
$scope.theForm.name.$setValidity('actionNameNotUnique', actionNameIsUnique);
}
function actionSourceName (actionSource) {
if (actionSource) {
return utils.customTranslation(actionSource.name, actionSource.name);
} else {
return '';
}
}
function dashboardStateSearch (query) {
if (vm.action.type == vm.types.widgetActionTypes.openDashboard.value) {
var deferred = $q.defer();
var result = query ? vm.selectedDashboardStateIds.filter(
createFilterForDashboardState(query)) : vm.selectedDashboardStateIds;
if (result && result.length) {
deferred.resolve(result);
} else {
deferred.resolve([query]);
}
return deferred.promise;
} else {
return vm.fetchDashboardStates({query: query});
}
}
function createFilterForDashboardState (query) {
var lowercaseQuery = angular.lowercase(query);
return function filterFn(stateId) {
return (angular.lowercase(stateId).indexOf(lowercaseQuery) === 0);
};
}
function cleanupAction(action) {
var result = {};
result.id = action.id;
result.actionSourceId = action.actionSourceId;
result.name = action.name;
result.icon = action.icon;
result.type = action.type;
switch (action.type) {
case vm.types.widgetActionTypes.openDashboardState.value:
case vm.types.widgetActionTypes.updateDashboardState.value:
result.targetDashboardStateId = action.targetDashboardStateId;
result.openRightLayout = action.openRightLayout;
result.setEntityId = action.setEntityId;
break;
case vm.types.widgetActionTypes.openDashboard.value:
result.targetDashboardId = action.targetDashboardId;
result.targetDashboardStateId = action.targetDashboardStateId;
result.setEntityId = action.setEntityId;
break;
case vm.types.widgetActionTypes.custom.value:
result.customFunction = action.customFunction;
break;
}
return result;
}
function cancel() {
$mdDialog.cancel();
}
function save() {
$scope.theForm.$setPristine();
$mdDialog.hide(cleanupAction(vm.action));
}
}

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

@ -0,0 +1,133 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog class="tb-widget-action-dialog" aria-label="{{'widget-config.action' | translate }}" style="min-width: 600px;">
<form name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2>{{ (vm.isAdd ? 'widget-config.add-action' : 'widget-config.edit-action') | translate }}</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'action.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<md-content class="md-padding" layout="column">
<fieldset ng-disabled="loading" layout="column">
<md-input-container class="md-block">
<label translate>widget-config.action-source</label>
<md-select name="actionSource" required aria-label="{{ 'widget-config.action-source' | translate }}" ng-model="vm.action.actionSourceId">
<md-option ng-repeat="(actionSourceId, actionSource) in vm.actionSources" ng-value="actionSourceId">
{{vm.actionSourceName(actionSource)}}
</md-option>
</md-select>
<div ng-messages="theForm.actionSource.$error">
<div ng-message="required" translate>widget-config.action-source-required</div>
</div>
</md-input-container>
<md-input-container class="md-block">
<label translate>widget-config.action-name</label>
<input name="name" required ng-model="vm.action.name">
<div ng-messages="theForm.name.$error">
<div ng-message="required" translate>widget-config.action-name-required</div>
<div ng-message="actionNameNotUnique" translate>widget-config.action-name-not-unique</div>
</div>
</md-input-container>
<tb-material-icon-select ng-model="vm.action.icon">
</tb-material-icon-select>
<md-input-container class="md-block">
<label translate>widget-config.action-type</label>
<md-select name="actionType" required aria-label="{{ 'widget-config.action-type' | translate }}" ng-model="vm.action.type">
<md-option ng-repeat="actionType in vm.types.widgetActionTypes" ng-value="actionType.value">
{{ actionType.name | translate }}
</md-option>
</md-select>
<div ng-messages="theForm.actionType.$error">
<div ng-message="required" translate>widget-config.action-type-required</div>
</div>
</md-input-container>
<div layout="column"
style="padding-bottom: 20px;"
ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboard.value">
<div class="md-caption tb-required"
style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>widget-action.target-dashboard</div>
<tb-dashboard-autocomplete the-form="theForm"
tb-required="true"
ng-model="vm.action.targetDashboardId"
select-first-dashboard="false">
</tb-dashboard-autocomplete>
</div>
<md-autocomplete ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value ||
vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value ||
vm.action.type == vm.types.widgetActionTypes.openDashboard.value"
ng-required="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value"
md-no-cache="true"
md-input-name="targetDashboardState"
ng-model="vm.action.targetDashboardStateId"
md-selected-item="vm.action.targetDashboardStateId"
md-search-text="vm.targetDashboardStateSearchText"
md-search-text-change="vm.targetDashboardStateSearchTextChanged()"
md-items="item in vm.dashboardStateSearch(vm.targetDashboardStateSearchText)"
md-item-text="item"
md-min-length="0"
md-floating-label="{{ 'widget-action.target-dashboard-state' | translate }}"
md-select-on-match="true">
<md-item-template>
<div>
<span md-highlight-text="vm.targetDashboardStateSearchText" md-highlight-flags="^i">{{item}}</span>
</div>
</md-item-template>
<div ng-messages="theForm.targetDashboardState.$error">
<div translate ng-message="required">widget-action.target-dashboard-state-required</div>
</div>
</md-autocomplete>
<md-checkbox ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value ||
vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value"
flex aria-label="{{ 'widget-action.open-right-layout' | translate }}"
ng-model="vm.action.openRightLayout">{{ 'widget-action.open-right-layout' | translate }}
</md-checkbox>
<md-checkbox ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value ||
vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value ||
vm.action.type == vm.types.widgetActionTypes.openDashboard.value"
flex aria-label="{{ 'widget-action.set-entity-from-widget' | translate }}"
ng-model="vm.action.setEntityId">{{ 'widget-action.set-entity-from-widget' | translate }}
</md-checkbox>
<tb-js-func ng-if="vm.action.type == vm.types.widgetActionTypes.custom.value"
ng-model="vm.action.customFunction"
function-args="{{ ['$event', 'widgetContext', 'entityId'] }}"
validation-args="{{ [] }}">
</tb-js-func>
</fieldset>
</md-content>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
class="md-raised md-primary">
{{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
</md-button>
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">
{{ 'action.cancel' | translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

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

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

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

@ -275,4 +275,13 @@
</ng-form>
</md-content>
</md-tab>
<md-tab label="{{ 'widget-config.actions' | translate }}">
<md-content class="md-padding" layout="column">
<tb-manage-widget-actions
action-sources="actionSources"
widget-actions="actions"
fetch-dashboard-states="fetchDashboardStates({query: query})">
</tb-manage-widget-actions>
</md-content>
</md-tab>
</md-tabs>

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

@ -15,12 +15,12 @@
*/
import $ from 'jquery';
import 'javascript-detect-element-resize/detect-element-resize';
import Subscription from '../api/subscription';
import Subscription from '../../api/subscription';
/* eslint-disable angular/angularelement */
/*@ngInject*/
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
export default function WidgetController($scope, $state, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
datasourceService, alarmService, entityService, deviceService, visibleRect, isEdit, isMobile, stDiff, dashboardTimewindow,
dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) {
@ -44,6 +44,20 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
var cafs = {};
var actionDescriptorsBySourceId = {};
if (widget.config.actions) {
for (var actionSourceId in widget.config.actions) {
var descriptors = widget.config.actions[actionSourceId];
var actionDescriptors = [];
descriptors.forEach(function(descriptor) {
var actionDescriptor = angular.copy(descriptor);
actionDescriptor.displayName = utils.customTranslation(descriptor.name, descriptor.name);
actionDescriptors.push(actionDescriptor);
});
actionDescriptorsBySourceId[actionSourceId] = actionDescriptors;
}
}
var widgetContext = {
inited: false,
$container: null,
@ -103,9 +117,32 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
utils: {
formatValue: formatValue
},
actionsApi: {
actionDescriptorsBySourceId: actionDescriptorsBySourceId,
getActionDescriptors: getActionDescriptors,
handleWidgetAction: handleWidgetAction
},
stateController: stateController
};
widgetContext.customHeaderActions = [];
var headerActionsDescriptors = getActionDescriptors(types.widgetActionSources.headerButton.value);
for (var i=0;i<headerActionsDescriptors.length;i++) {
var descriptor = headerActionsDescriptors[i];
var headerAction = {};
headerAction.name = descriptor.name;
headerAction.displayName = descriptor.displayName;
headerAction.icon = descriptor.icon;
headerAction.descriptor = descriptor;
headerAction.onAction = function($event) {
var entityInfo = getFirstEntityInfo();
var entityId = entityInfo ? entityInfo.entityId : null;
var entityName = entityInfo ? entityInfo.entityName : null;
handleWidgetAction($event, this.descriptor, entityId, entityName);
}
widgetContext.customHeaderActions.push(headerAction);
}
var subscriptionContext = {
$scope: $scope,
$q: $q,
@ -376,6 +413,87 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
return deferred.promise;
}
function getActionDescriptors(actionSourceId) {
var result = widgetContext.actionsApi.actionDescriptorsBySourceId[actionSourceId];
if (!result) {
result = [];
}
return result;
}
function handleWidgetAction($event, descriptor, entityId, entityName) {
var type = descriptor.type;
switch (type) {
case types.widgetActionTypes.openDashboardState.value:
case types.widgetActionTypes.updateDashboardState.value:
var targetDashboardStateId = descriptor.targetDashboardStateId;
var targetEntityId;
if (descriptor.setEntityId) {
targetEntityId = entityId;
}
var params = {};
if (targetEntityId) {
params.entityId = targetEntityId;
if (entityName) {
params.entityName = entityName;
}
}
if (type == types.widgetActionTypes.openDashboardState.value) {
widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout);
} else {
widgetContext.stateController.updateState(targetDashboardStateId, params, descriptor.openRightLayout);
}
break;
case types.widgetActionTypes.openDashboard.value:
var targetDashboardId = descriptor.targetDashboardId;
targetDashboardStateId = descriptor.targetDashboardStateId;
targetEntityId;
if (descriptor.setEntityId) {
targetEntityId = entityId;
}
var stateObject = {};
stateObject.params = {};
if (targetEntityId) {
stateObject.params.entityId = targetEntityId;
if (entityName) {
stateObject.params.entityName = entityName;
}
}
if (targetDashboardStateId) {
stateObject.id = targetDashboardStateId;
}
var stateParams = {
dashboardId: targetDashboardId,
state: angular.toJson([ stateObject ])
}
$state.go('home.dashboards.dashboard', stateParams);
break;
case types.widgetActionTypes.custom.value:
var customFunction = descriptor.customFunction;
if (angular.isDefined(customFunction) && customFunction.length > 0) {
try {
var customActionFunction = new Function('$event', 'widgetContext', 'entityId', 'entityName', customFunction);
customActionFunction($event, widgetContext, entityId, entityName);
} catch (e) {
//
}
}
break;
}
}
function getFirstEntityInfo() {
var entityInfo;
for (var id in widgetContext.subscriptions) {
var subscription = widgetContext.subscriptions[id];
entityInfo = subscription.getFirstEntityInfo();
if (entityInfo) {
break;
}
}
return entityInfo;
}
function configureWidgetElement() {
$scope.displayLegend = angular.isDefined(widget.config.showLegend) ?

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

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

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

21
ui/src/app/dashboard/add-widget.controller.js

@ -36,6 +36,7 @@ export default function AddWidgetController($scope, widgetService, entityService
vm.add = add;
vm.cancel = cancel;
vm.fetchEntityKeys = fetchEntityKeys;
vm.fetchDashboardStates = fetchDashboardStates;
vm.createEntityAlias = createEntityAlias;
vm.widgetConfig = {
@ -128,6 +129,26 @@ export default function AddWidgetController($scope, widgetService, entityService
return deferred.promise;
}
function fetchDashboardStates (query) {
var deferred = $q.defer();
var stateIds = Object.keys(vm.dashboard.configuration.states);
var result = query ? stateIds.filter(
createFilterForDashboardState(query)) : stateIds;
if (result && result.length) {
deferred.resolve(result);
} else {
deferred.resolve([query]);
}
return deferred.promise;
}
function createFilterForDashboardState (query) {
var lowercaseQuery = angular.lowercase(query);
return function filterFn(stateId) {
return (angular.lowercase(stateId).indexOf(lowercaseQuery) === 0);
};
}
function createEntityAlias (event, alias, allowedEntityTypes) {
var deferred = $q.defer();

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

@ -34,6 +34,7 @@
<fieldset ng-disabled="loading" style="position: relative; height: 600px;">
<tb-widget-config widget-type="vm.widget.type"
type-parameters="vm.widgetInfo.typeParameters"
action-sources="vm.widgetInfo.actionSources"
force-expand-datasources="true"
ng-model="vm.widgetConfig"
widget-settings-schema="vm.settingsSchema"
@ -41,6 +42,7 @@
alias-controller="vm.aliasController"
functions-only="vm.functionsOnly"
fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)"
fetch-dashboard-states="vm.fetchDashboardStates(query)"
on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)"
the-form="theForm"></tb-widget-config>
</fieldset>

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

@ -41,6 +41,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
scope.typeParameters = widgetInfo.typeParameters;
scope.actionSources = widgetInfo.actionSources;
scope.isDataEnabled = !widgetInfo.typeParameters.useCustomDatasources;
if (!settingsSchema || settingsSchema === '') {
scope.settingsSchema = {};
@ -93,6 +94,26 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
return deferred.promise;
};
scope.fetchDashboardStates = function(query) {
var deferred = $q.defer();
var stateIds = Object.keys(scope.dashboard.configuration.states);
var result = query ? stateIds.filter(
createFilterForDashboardState(query)) : stateIds;
if (result && result.length) {
deferred.resolve(result);
} else {
deferred.resolve([query]);
}
return deferred.promise;
}
function createFilterForDashboardState (query) {
var lowercaseQuery = angular.lowercase(query);
return function filterFn(stateId) {
return (angular.lowercase(stateId).indexOf(lowercaseQuery) === 0);
};
}
scope.createEntityAlias = function (event, alias, allowedEntityTypes) {
var deferred = $q.defer();

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

@ -18,6 +18,7 @@
<fieldset ng-disabled="loading">
<tb-widget-config widget-type="widget.type"
type-parameters="typeParameters"
action-sources="actionSources"
ng-model="widgetConfig"
is-data-enabled="isDataEnabled"
widget-settings-schema="settingsSchema"
@ -25,6 +26,7 @@
alias-controller="aliasController"
functions-only="widgetEditMode"
fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)"
fetch-dashboard-states="fetchDashboardStates(query)"
on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)"
the-form="theForm"></tb-widget-config>
</fieldset>

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

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

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

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

15
ui/src/app/dashboard/states/default-state-controller.js

@ -15,7 +15,7 @@
*/
/*@ngInject*/
export default function DefaultStateController($scope, $location, $state, $stateParams, $translate, types, dashboardUtils) {
export default function DefaultStateController($scope, $location, $state, $stateParams, utils, types, dashboardUtils) {
var vm = this;
@ -50,6 +50,9 @@ export default function DefaultStateController($scope, $location, $state, $state
}
function updateState(id, params, openRightLayout) {
if (!id) {
id = getStateId();
}
if (vm.states && vm.states[id]) {
if (!params) {
params = {};
@ -110,15 +113,7 @@ export default function DefaultStateController($scope, $location, $state, $state
}
function getStateName(id, state) {
var result = '';
var translationId = types.translate.customTranslationsPrefix + state.name;
var translation = $translate.instant(translationId);
if (translation != translationId) {
result = translation + '';
} else {
result = id;
}
return result;
return utils.customTranslation(state.name, id);
}
function parseState(stateJson) {

16
ui/src/app/dashboard/states/entity-state-controller.js

@ -55,6 +55,9 @@ export default function EntityStateController($scope, $location, $state, $stateP
}
function updateState(id, params, openRightLayout) {
if (!id) {
id = getStateId();
}
if (vm.states && vm.states[id]) {
resolveEntity(params).then(
function success(entityName) {
@ -121,17 +124,10 @@ export default function EntityStateController($scope, $location, $state, $stateP
var result = '';
if (vm.stateObject[index]) {
var stateName = vm.states[vm.stateObject[index].id].name;
var translationId = types.translate.customTranslationsPrefix + stateName;
var translation = $translate.instant(translationId);
if (translation != translationId) {
stateName = translation + '';
}
stateName = utils.customTranslation(stateName, stateName);
var params = vm.stateObject[index].params;
if (params && params.entityName) {
result = utils.insertVariable(stateName, 'entityName', params.entityName);
} else {
result = stateName;
}
var entityName = params && params.entityName ? params.entityName : '';
result = utils.insertVariable(stateName, 'entityName', entityName);
}
return result;
}

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

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

1
ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js

@ -102,6 +102,7 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
if (vm.addToDashboardType === 0) {
dashboardService.getDashboard(vm.dashboardId).then(
function success(dashboard) {
dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard);
selectTargetState($event, dashboard).then(
function(targetState) {
selectTargetLayout($event, dashboard, targetState).then(

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

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

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

@ -1103,6 +1103,18 @@ export default angular.module('thingsboard.locale', [])
"undo": "Undo widget changes",
"export": "Export widget"
},
"widget-action": {
"header-button": "Widget header button",
"open-dashboard-state": "Navigate to new dashboard state",
"update-dashboard-state": "Update current dashboard state",
"open-dashboard": "Navigate to other dashboard",
"custom": "Custom action",
"target-dashboard-state": "Target dashboard state",
"target-dashboard-state-required": "Target dashboard state is required",
"set-entity-from-widget": "Set entity from widget",
"target-dashboard": "Target dashboard",
"open-right-layout": "Open right dashboard layout (mobile view)"
},
"widgets-bundle": {
"current": "Current bundle",
"widgets-bundles": "Widgets Bundles",
@ -1158,7 +1170,23 @@ export default angular.module('thingsboard.locale', [])
"remove-datasource": "Remove datasource",
"add-datasource": "Add datasource",
"target-device": "Target device",
"alarm-source": "Alarm source"
"alarm-source": "Alarm source",
"actions": "Actions",
"action": "Action",
"add-action": "Add action",
"search-actions": "Search actions",
"action-source": "Action source",
"action-source-required": "Action source is required.",
"action-name": "Name",
"action-name-required": "Action name is required.",
"action-name-not-unique": "Another action with the same name already exists.<br/>Action name should be unique within the same action source.",
"action-icon": "Icon",
"action-type": "Type",
"action-type-required": "Action type is required.",
"edit-action": "Edit action",
"delete-action": "Delete action",
"delete-action-title": "Delete widget action",
"delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?"
},
"widget-type": {
"import": "Import widget type",
@ -1168,6 +1196,12 @@ export default angular.module('thingsboard.locale', [])
"widget-type-file": "Widget type file",
"invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure."
},
"icon": {
"icon": "Icon",
"select-icon": "Select icon",
"material-icons": "Material icons",
"show-all": "Show all icons"
},
"language": {
"language": "Language",
"en_US": "English",
@ -1177,6 +1211,10 @@ export default angular.module('thingsboard.locale', [])
"es_ES": "Spanish"
},
"custom": {
"widget-action": {
"action-cell-button": "Action cell button",
"row-click": "On row click"
}
}
}
}

19
ui/src/app/widget/lib/alarms-table-widget.js

@ -158,13 +158,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
vm.ctx.widgetActions = [ vm.searchAction ];
if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
var translationId = types.translate.customTranslationsPrefix + vm.settings.alarmsTitle;
var translation = $translate.instant(translationId);
if (translation != translationId) {
vm.alarmsTitle = translation + '';
} else {
vm.alarmsTitle = vm.settings.alarmsTitle;
}
vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle);
} else {
vm.alarmsTitle = $translate.instant('alarm.alarms');
}
@ -226,6 +220,9 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+
'border-color: ' + mdDarkSecondary + ';\n'+
'}\n'+
'table.md-table td.md-cell.tb-action-cell button.md-icon-button md-icon {\n'+
'color: ' + mdDarkSecondary + ';\n'+
'}\n'+
'table.md-table td.md-cell.md-placeholder {\n'+
'color: ' + mdDarkDisabled + ';\n'+
'}\n'+
@ -539,13 +536,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) {
var dataKey = vm.alarmSource.dataKeys[d];
var translationId = types.translate.customTranslationsPrefix + dataKey.label;
var translation = $translate.instant(translationId);
if (translation != translationId) {
dataKey.title = translation + '';
} else {
dataKey.title = dataKey.label;
}
dataKey.title = utils.customTranslation(dataKey.label, dataKey.label);
var keySettings = dataKey.settings;

60
ui/src/app/widget/lib/entities-table-widget.js

@ -65,8 +65,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
vm.currentEntity = null;
vm.displayEntityName = true;
vm.entityNameColumnTitle = '';
vm.displayEntityType = true;
vm.displayActions = false; //TODO: Widget actions
vm.actionCellDescriptors = [];
vm.displayPagination = true;
vm.defaultPageSize = 10;
vm.defaultSortOrder = 'entityName';
@ -92,6 +93,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
vm.onReorder = onReorder;
vm.onPaginate = onPaginate;
vm.onRowClick = onRowClick;
vm.onActionButtonClick = onActionButtonClick;
vm.isCurrent = isCurrent;
vm.cellStyle = cellStyle;
@ -141,14 +143,10 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
vm.ctx.widgetActions = [ vm.searchAction ];
vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton');
if (vm.settings.entitiesTitle && vm.settings.entitiesTitle.length) {
var translationId = types.translate.customTranslationsPrefix + vm.settings.entitiesTitle;
var translation = $translate.instant(translationId);
if (translation != translationId) {
vm.entitiesTitle = translation + '';
} else {
vm.entitiesTitle = vm.settings.entitiesTitle;
}
vm.entitiesTitle = utils.customTranslation(vm.settings.entitiesTitle, vm.settings.entitiesTitle);
} else {
vm.entitiesTitle = $translate.instant('entity.entities');
}
@ -157,6 +155,13 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true;
vm.displayEntityName = angular.isDefined(vm.settings.displayEntityName) ? vm.settings.displayEntityName : true;
if (vm.settings.entityNameColumnTitle && vm.settings.entityNameColumnTitle.length) {
vm.entityNameColumnTitle = utils.customTranslation(vm.settings.entityNameColumnTitle, vm.settings.entityNameColumnTitle);
} else {
vm.entityNameColumnTitle = $translate.instant('entity.entity-name');
}
vm.displayEntityType = angular.isDefined(vm.settings.displayEntityType) ? vm.settings.displayEntityType : true;
vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true;
@ -185,6 +190,8 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
//var mdDarkIcon = mdDarkSecondary;
var mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString();
//md-icon.md-default-theme, md-icon {
var cssString = 'table.md-table th.md-column {\n'+
'color: ' + mdDarkSecondary + ';\n'+
'}\n'+
@ -204,6 +211,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+
'border-color: ' + mdDarkSecondary + ';\n'+
'}\n'+
'table.md-table td.md-cell.tb-action-cell button.md-icon-button md-icon {\n'+
'color: ' + mdDarkSecondary + ';\n'+
'}\n'+
'table.md-table td.md-cell.md-placeholder {\n'+
'color: ' + mdDarkDisabled + ';\n'+
'}\n'+
@ -261,11 +271,37 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
}
function onRowClick($event, entity) {
if ($event) {
$event.stopPropagation();
}
if (vm.currentEntity != entity) {
vm.currentEntity = entity;
var descriptors = vm.ctx.actionsApi.getActionDescriptors('rowClick');
if (descriptors.length) {
var entityId;
var entityName;
if (vm.currentEntity) {
entityId = vm.currentEntity.id;
entityName = vm.currentEntity.entityName;
}
vm.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName);
}
}
}
function onActionButtonClick($event, entity, actionDescriptor) {
if ($event) {
$event.stopPropagation();
}
var entityId;
var entityName;
if (entity) {
entityId = entity.id;
entityName = entity.entityName;
}
vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName);
}
function isCurrent(entity) {
return (vm.currentEntity && entity && vm.currentEntity.id && entity.id) &&
(vm.currentEntity.id.id === entity.id.id);
@ -393,13 +429,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
}
vm.dataKeys.push(dataKey);
var translationId = types.translate.customTranslationsPrefix + dataKey.label;
var translation = $translate.instant(translationId);
if (translation != translationId) {
dataKey.title = translation + '';
} else {
dataKey.title = dataKey.label;
}
dataKey.title = utils.customTranslation(dataKey.label, dataKey.label);
var keySettings = dataKey.settings;

20
ui/src/app/widget/lib/entities-table-widget.tpl.html

@ -41,10 +41,10 @@
<table md-table>
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row>
<th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span translate>entity.entity-name</span></th>
<th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span>{{vm.entityNameColumnTitle}}</span></th>
<th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th>
<th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th>
<th md-column ng-if="vm.displayActions"><span>&nbsp</span></th>
<th md-column ng-if="vm.actionCellDescriptors.length"><span>&nbsp</span></th>
</tr>
</thead>
<tbody md-body>
@ -57,14 +57,18 @@
ng-style="vm.cellStyle(entity, key)"
ng-bind-html="vm.cellContent(entity, key)">
</td>
<td md-cell ng-if="vm.displayActions" class="tb-action-cell">
<!--md-button class="md-icon-button" aria-label="{{ 'entity.details' | translate }}"
ng-click="vm.openEntityDetails($event, entity)">
<md-icon aria-label="{{ 'entity.details' | translate }}" class="material-icons">more_horiz</md-icon>
<td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell"
ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
maxWidth: vm.actionCellDescriptors.length*36+'px',
width: vm.actionCellDescriptors.length*36+'px'}">
<md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors"
aria-label="{{ actionDescriptor.displayName }}"
ng-click="vm.onActionButtonClick($event, entity, actionDescriptor)">
<md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon>
<md-tooltip md-direction="top">
{{ 'entity.details' | translate }}
{{ actionDescriptor.displayName }}
</md-tooltip>
</md-button-->
</md-button>
</td>
</tr>
</tbody>

Loading…
Cancel
Save