Browse Source

TB-64: Implement widget actions.

pull/177/head
Igor Kulikov 9 years ago
parent
commit
2fdb372c89
  1. 43
      ui/src/app/api/subscription.js
  2. 2
      ui/src/app/api/widget.service.js
  3. 3
      ui/src/app/common/types.constant.js
  4. 23
      ui/src/app/common/utils.service.js
  5. 24
      ui/src/app/components/dashboard-autocomplete.directive.js
  6. 10
      ui/src/app/components/dashboard.directive.js
  7. 10
      ui/src/app/components/dashboard.tpl.html
  8. 2
      ui/src/app/components/material-icon-select.directive.js
  9. 27
      ui/src/app/components/material-icon-select.scss
  10. 8
      ui/src/app/components/material-icon-select.tpl.html
  11. 41
      ui/src/app/components/widget/action/manage-widget-actions.directive.js
  12. 127
      ui/src/app/components/widget/action/widget-action-dialog.controller.js
  13. 56
      ui/src/app/components/widget/action/widget-action-dialog.tpl.html
  14. 1
      ui/src/app/components/widget/widget-config.directive.js
  15. 5
      ui/src/app/components/widget/widget-config.tpl.html
  16. 120
      ui/src/app/components/widget/widget.controller.js
  17. 21
      ui/src/app/dashboard/add-widget.controller.js
  18. 1
      ui/src/app/dashboard/add-widget.tpl.html
  19. 20
      ui/src/app/dashboard/edit-widget.directive.js
  20. 1
      ui/src/app/dashboard/edit-widget.tpl.html
  21. 15
      ui/src/app/dashboard/states/default-state-controller.js
  22. 16
      ui/src/app/dashboard/states/entity-state-controller.js
  23. 1
      ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
  24. 14
      ui/src/app/locale/locale.constant.js
  25. 19
      ui/src/app/widget/lib/alarms-table-widget.js
  26. 60
      ui/src/app/widget/lib/entities-table-widget.js
  27. 20
      ui/src/app/widget/lib/entities-table-widget.tpl.html

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 {

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

@ -626,7 +626,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr
}
for (var actionSourceId in types.widgetActionSources) {
result.actionSources[actionSourceId] = angular.copy(types.widgetActionSources[actionSourceId]);
result.actionSources[actionSourceId].name = $translate.instant(result.actionSources[actionSourceId].name);
result.actionSources[actionSourceId].name = $translate.instant(result.actionSources[actionSourceId].name) + '';
}
return result;

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

@ -400,8 +400,9 @@ export default angular.module('thingsboard.types', [])
}
},
widgetActionSources: {
'headerButton': {
headerButton: {
name: 'widget-action.header-button',
value: 'headerButton',
multiple: true
}
},

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

@ -38,7 +38,11 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
materialColors = [],
materialIcons = [];
var commonUsedMaterialIcons = [ 'more_horiz', 'close', 'play_arrow' ];
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));";
@ -148,7 +152,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
validateDatasources: validateDatasources,
createKey: createKey,
createLabelFromDatasource: createLabelFromDatasource,
insertVariable: insertVariable
insertVariable: insertVariable,
customTranslation: customTranslation
}
return service;
@ -188,7 +193,7 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
}
function getCommonMaterialIcons() {
return commonUsedMaterialIcons;
return commonMaterialIcons;
}
function genMaterialColor(str) {
@ -469,4 +474,16 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
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() {
});

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

@ -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"

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

@ -14,6 +14,8 @@
* limitations under the License.
*/
import './material-icon-select.scss';
import MaterialIconsDialogController from './material-icons-dialog.controller';
/* eslint-disable import/no-unresolved, import/default */

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;
}
}

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

@ -15,12 +15,10 @@
limitations under the License.
-->
<div layout="row">
<div class="tb-material-icon-select" layout="row">
<md-icon class="material-icons" ng-click="openIconDialog($event)">{{icon}}</md-icon>
<md-input-container flex>
<md-input-container class="md-block">
<label translate>icon.icon</label>
<input ng-click="openIconDialog($event)" ng-model="icon">
</md-input-container>
<label translate>icon.icon</label>
<input ng-mousedown="openIconDialog($event)" ng-model="icon">
</md-input-container>
</div>

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

@ -42,7 +42,8 @@ function ManageWidgetActions() {
scope: true,
bindToController: {
actionSources: '=',
widgetActions: '='
widgetActions: '=',
fetchDashboardStates: '&',
},
controller: ManageWidgetActionsController,
controllerAs: 'vm',
@ -55,7 +56,7 @@ function ManageWidgetActions() {
/*@ngInject*/
function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog, $q, $filter,
$translate, $timeout, types) {
$translate, $timeout, utils, types) {
let vm = this;
@ -165,12 +166,30 @@ function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog,
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, actionSources: vm.actionSources, action: angular.copy(action)},
locals: {isAdd: isAdd, fetchDashboardStates: vm.fetchDashboardStates,
actionSources: availableActionSources, widgetActions: vm.widgetActions,
action: angular.copy(action)},
skipHide: true,
fullscreen: true,
targetEvent: $event
@ -189,7 +208,8 @@ function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog,
}
function saveAction(action, prevActionId) {
action.actionSourceName = vm.actionSources[action.actionSourceId].name;
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);
@ -227,15 +247,10 @@ function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog,
var actionSourceActions = vm.widgetActions[actionSourceId];
for (var i=0;i<actionSourceActions.length;i++) {
var actionSourceAction = actionSourceActions[i];
var action = {
id: actionSourceAction.id,
actionSourceId: actionSourceId,
actionSourceName: actionSource.name,
name: actionSourceAction.name,
icon: actionSourceAction.icon,
type: actionSourceAction.type,
typeName: $translate.instant(types.widgetActionTypes[actionSourceAction.type].name)
};
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);
}
}

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

@ -15,14 +15,21 @@
*/
/*@ngInject*/
export default function WidgetActionDialogController($scope, $mdDialog, types, utils, isAdd, actionSources, action) {
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 = {
@ -32,15 +39,131 @@ export default function WidgetActionDialogController($scope, $mdDialog, types, u
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(vm.action);
$mdDialog.hide(cleanupAction(vm.action));
}
}

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

@ -31,12 +31,12 @@
<md-dialog-content>
<div class="md-dialog-content">
<md-content class="md-padding" layout="column">
<fieldset ng-disabled="loading">
<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">
{{actionSource.name}}
{{vm.actionSourceName(actionSource)}}
</md-option>
</md-select>
<div ng-messages="theForm.actionSource.$error">
@ -48,6 +48,7 @@
<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">
@ -63,6 +64,57 @@
<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>

1
ui/src/app/components/widget/widget-config.directive.js

@ -468,6 +468,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
aliasController: '=',
functionsOnly: '=',
fetchEntityKeys: '&',
fetchDashboardStates: '&',
onCreateEntityAlias: '&',
theForm: '='
},

5
ui/src/app/components/widget/widget-config.tpl.html

@ -277,7 +277,10 @@
</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">
<tb-manage-widget-actions
action-sources="actionSources"
widget-actions="actions"
fetch-dashboard-states="fetchDashboardStates({query: query})">
</tb-manage-widget-actions>
</md-content>
</md-tab>

120
ui/src/app/components/widget/widget.controller.js

@ -20,7 +20,7 @@ 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) ?

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();

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

@ -42,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>

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

@ -94,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();

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

@ -26,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>

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;
}

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(

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

@ -1104,11 +1104,16 @@ export default angular.module('thingsboard.locale', [])
"export": "Export widget"
},
"widget-action": {
"header-button": "Header button",
"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"
"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",
@ -1174,6 +1179,7 @@ export default angular.module('thingsboard.locale', [])
"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.",
@ -1205,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