thingsboard-memoizeit
Changes
ui/package.json 1(+1 -0)
ui/src/app/api/subscription.js 43(+43 -0)
ui/src/app/api/widget.service.js 31(+25 -6)
ui/src/app/common/types.constant.js 25(+25 -0)
ui/src/app/common/utils.service.js 60(+57 -3)
ui/src/app/components/dashboard.directive.js 12(+11 -1)
ui/src/app/components/dashboard.tpl.html 10(+10 -0)
ui/src/app/components/widget/widget.controller.js 122(+120 -2)
ui/src/app/dashboard/index.js 2(+1 -1)
ui/src/app/layout/index.js 2(+2 -0)
ui/src/app/locale/locale.constant.js 40(+39 -1)
ui/src/app/widget/lib/alarms-table-widget.js 19(+5 -14)
ui/src/app/widget/lib/entities-table-widget.js 60(+45 -15)
Details
ui/package.json 1(+1 -0)
diff --git a/ui/package.json b/ui/package.json
index 73d7bcd..6884543 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -111,6 +111,7 @@
"ngtemplate-loader": "^1.3.1",
"node-sass": "^3.9.3",
"postcss-loader": "^0.13.0",
+ "raw-loader": "^0.5.1",
"react-hot-loader": "^3.0.0-beta.6",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.1",
ui/src/app/api/subscription.js 43(+43 -0)
diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js
index 215da0f..dd93626 100644
--- a/ui/src/app/api/subscription.js
+++ b/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 {
ui/src/app/api/widget.service.js 31(+25 -6)
diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js
index f8f27ed..a58c307 100644
--- a/ui/src/app/api/widget.service.js
+++ b/ui/src/app/api/widget.service.js
@@ -40,7 +40,7 @@ export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsbo
.name;
/*@ngInject*/
-function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, types, utils) {
+function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $translate, types, utils) {
$window.$ = $;
$window.jQuery = $;
@@ -548,13 +548,21 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
' }\n\n' +
' self.typeParameters = function() {\n\n' +
- {
- useCustomDatasources: false,
- maxDatasources: -1 //unlimited
- maxDataKeys: -1 //unlimited
- }
+ return {
+ useCustomDatasources: false,
+ maxDatasources: -1 //unlimited
+ maxDataKeys: -1 //unlimited
+ };
' }\n\n' +
+ ' self.actionSources = function() {\n\n' +
+ return {
+ 'headerButton': {
+ name: 'Header button',
+ multiple: true
+ }
+ };
+ }\n\n' +
' self.onResize = function() {\n\n' +
' }\n\n' +
@@ -611,6 +619,16 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
if (angular.isUndefined(result.typeParameters.maxDataKeys)) {
result.typeParameters.maxDataKeys = -1;
}
+ if (angular.isFunction(widgetTypeInstance.actionSources)) {
+ result.actionSources = widgetTypeInstance.actionSources();
+ } else {
+ result.actionSources = {};
+ }
+ for (var actionSourceId in types.widgetActionSources) {
+ result.actionSources[actionSourceId] = angular.copy(types.widgetActionSources[actionSourceId]);
+ result.actionSources[actionSourceId].name = $translate.instant(result.actionSources[actionSourceId].name) + '';
+ }
+
return result;
} catch (e) {
utils.processWidgetException(e);
@@ -650,6 +668,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema;
}
widgetInfo.typeParameters = widgetType.typeParameters;
+ widgetInfo.actionSources = widgetType.actionSources;
putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem);
deferred.resolve(widgetInfo);
ui/src/app/common/types.constant.js 25(+25 -0)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index ad36e84..61fc120 100644
--- a/ui/src/app/common/types.constant.js
+++ b/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"
ui/src/app/common/utils.service.js 60(+57 -3)
diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js
index 0939971..7c9f97d 100644
--- a/ui/src/app/common/utils.service.js
+++ b/ui/src/app/common/utils.service.js
@@ -13,6 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import materialIconsCodepoints from 'raw-loader!material-design-icons/iconfont/codepoints';
+
+/* eslint-enable import/no-unresolved, import/default */
+
import tinycolor from "tinycolor2";
import jsonSchemaDefaults from "json-schema-defaults";
import thingsboardTypes from "./types.constant";
@@ -24,11 +31,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;
+ }
+
}
ui/src/app/components/dashboard.directive.js 12(+11 -1)
diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js
index d1427b6..1a4cdba 100644
--- a/ui/src/app/components/dashboard.directive.js
+++ b/ui/src/app/components/dashboard.directive.js
@@ -20,7 +20,7 @@ import 'javascript-detect-element-resize/detect-element-resize';
import angularGridster from 'angular-gridster';
import thingsboardTypes from '../common/types.constant';
import thingsboardApiWidget from '../api/widget.service';
-import thingsboardWidget from './widget.directive';
+import thingsboardWidget from './widget/widget.directive';
import thingsboardToast from '../services/toast';
import thingsboardTimewindow from './timewindow.directive';
import thingsboardEvents from './tb-event-directives';
@@ -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) {
ui/src/app/components/dashboard.tpl.html 10(+10 -0)
diff --git a/ui/src/app/components/dashboard.tpl.html b/ui/src/app/components/dashboard.tpl.html
index aea3dfc..5d46fea 100644
--- a/ui/src/app/components/dashboard.tpl.html
+++ b/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"
diff --git a/ui/src/app/components/dashboard-autocomplete.directive.js b/ui/src/app/components/dashboard-autocomplete.directive.js
index 77e3c1c..59a56e1 100644
--- a/ui/src/app/components/dashboard-autocomplete.directive.js
+++ b/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() {
});
diff --git a/ui/src/app/components/finish-render.directive.js b/ui/src/app/components/finish-render.directive.js
new file mode 100644
index 0000000..9092e2d
--- /dev/null
+++ b/ui/src/app/components/finish-render.directive.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export default angular.module('thingsboard.directives.finishRender', [])
+ .directive('tbOnFinishRender', OnFinishRender)
+ .name;
+
+/*@ngInject*/
+function OnFinishRender($timeout) {
+ return {
+ restrict: 'A',
+ link: function (scope, element, attr) {
+ if (scope.$last === true) {
+ $timeout(function () {
+ scope.$emit(attr.tbOnFinishRender);
+ });
+ }
+ }
+ };
+}
diff --git a/ui/src/app/components/material-icons-dialog.controller.js b/ui/src/app/components/material-icons-dialog.controller.js
new file mode 100644
index 0000000..94b18dc
--- /dev/null
+++ b/ui/src/app/components/material-icons-dialog.controller.js
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './material-icons-dialog.scss';
+
+/*@ngInject*/
+export default function MaterialIconsDialogController($scope, $mdDialog, $timeout, utils, icon) {
+
+ var vm = this;
+
+ vm.selectedIcon = icon;
+
+ vm.showAll = false;
+ vm.loadingIcons = false;
+
+ $scope.$watch('vm.showAll', function(showAll) {
+ if (showAll) {
+ vm.loadingIcons = true;
+ $timeout(function() {
+ utils.getMaterialIcons().then(
+ function success(icons) {
+ vm.icons = icons;
+ }
+ );
+ });
+ } else {
+ vm.icons = utils.getCommonMaterialIcons();
+ }
+ });
+
+ $scope.$on('iconsLoadFinished', function() {
+ vm.loadingIcons = false;
+ });
+
+ vm.cancel = cancel;
+ vm.selectIcon = selectIcon;
+
+ function cancel() {
+ $mdDialog.cancel();
+ }
+
+ function selectIcon($event, icon) {
+ vm.selectedIcon = icon;
+ $mdDialog.hide(vm.selectedIcon);
+ }
+}
diff --git a/ui/src/app/components/material-icons-dialog.scss b/ui/src/app/components/material-icons-dialog.scss
new file mode 100644
index 0000000..dbd8395
--- /dev/null
+++ b/ui/src/app/components/material-icons-dialog.scss
@@ -0,0 +1,31 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-material-icons-dialog {
+ button.md-icon-button.tb-select-icon-button {
+ border: solid 1px orange;
+ border-radius: 0%;
+ padding: 16px;
+ height: 56px;
+ width: 56px;
+ margin: 10px;
+ }
+ .tb-icons-load {
+ top: 64px;
+ background: rgba(255,255,255,0.75);
+ z-index: 3;
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/components/material-icons-dialog.tpl.html b/ui/src/app/components/material-icons-dialog.tpl.html
new file mode 100644
index 0000000..8a2eb65
--- /dev/null
+++ b/ui/src/app/components/material-icons-dialog.tpl.html
@@ -0,0 +1,62 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-dialog class="tb-material-icons-dialog" aria-label="{{'icon.material-icons' | translate }}" style="min-width: 600px;">
+ <form>
+ <md-toolbar>
+ <div class="md-toolbar-tools">
+ <h2>{{ 'icon.select-icon' | translate }}</h2>
+ <span flex></span>
+ <section layout="row" layout-align="start center">
+ <md-switch ng-model="vm.showAll"
+ aria-label="{{ 'icon.show-all' | translate }}">
+ </md-switch>
+ <label translate>icon.show-all</label>
+ </section>
+ <md-button class="md-icon-button" ng-click="vm.cancel()">
+ <ng-md-icon icon="close" aria-label="{{ 'action.close' | translate }}"></ng-md-icon>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
+ <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+ <div class="tb-absolute-fill tb-icons-load" ng-show="vm.loadingIcons" layout="column" layout-align="center center">
+ <md-progress-circular md-mode="indeterminate" ng-disabled="!vm.loadingIcons" class="md-accent" md-diameter="40"></md-progress-circular>
+ </div>
+ <md-dialog-content>
+ <div class="md-dialog-content">
+ <md-content class="md-padding" layout="column">
+ <fieldset ng-disabled="loading">
+ <md-button ng-class="{'md-primary md-raised': icon == vm.selectedIcon}" class="tb-select-icon-button md-icon-button"
+ ng-repeat="icon in vm.icons" ng-click="vm.selectIcon($event, icon)" tb-on-finish-render="iconsLoadFinished">
+ <md-icon class="material-icons">{{icon}}</md-icon>
+ <md-tooltip md-direction="bottom">
+ {{ icon }}
+ </md-tooltip>
+ </md-button>
+ </fieldset>
+ </md-content>
+ </div>
+ </md-dialog-content>
+ <md-dialog-actions layout="row">
+ <span flex></span>
+ <md-button ng-disabled="loading" ng-click="vm.cancel()">
+ {{ 'action.cancel' | translate }}
+ </md-button>
+ </md-dialog-actions>
+ </form>
+</md-dialog>
diff --git a/ui/src/app/components/material-icon-select.directive.js b/ui/src/app/components/material-icon-select.directive.js
new file mode 100644
index 0000000..f40d1c8
--- /dev/null
+++ b/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: '=?',
+ }
+ };
+}
diff --git a/ui/src/app/components/material-icon-select.scss b/ui/src/app/components/material-icon-select.scss
new file mode 100644
index 0000000..5196e11
--- /dev/null
+++ b/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;
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/components/material-icon-select.tpl.html b/ui/src/app/components/material-icon-select.tpl.html
new file mode 100644
index 0000000..c2bee3f
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.directive.js b/ui/src/app/components/widget/action/manage-widget-actions.directive.js
new file mode 100644
index 0000000..93ca06f
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.scss b/ui/src/app/components/widget/action/manage-widget-actions.scss
new file mode 100644
index 0000000..aee98ad
--- /dev/null
+++ b/ui/src/app/components/widget/action/manage-widget-actions.scss
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+.tb-manage-widget-actions {
+ table.md-table {
+ tbody {
+ tr {
+ td {
+ &.tb-action-cell {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ min-width: 100px;
+ max-width: 100px;
+ width: 100px;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.tpl.html b/ui/src/app/components/widget/action/manage-widget-actions.tpl.html
new file mode 100644
index 0000000..b06959b
--- /dev/null
+++ b/ui/src/app/components/widget/action/manage-widget-actions.tpl.html
@@ -0,0 +1,99 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<div ng-form="manageWidgetActionsForm" class="tb-manage-widget-actions md-whiteframe-z1" layout="column">
+ <md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search === null">
+ <div class="md-toolbar-tools">
+ <span translate>widget-config.actions</span>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.addAction($event)">
+ <md-icon>add</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'widget-config.add-action' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
+ <md-icon>search</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.search' | translate }}
+ </md-tooltip>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search != null">
+ <div class="md-toolbar-tools">
+ <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
+ <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'widget-config.search-actions' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-input-container flex>
+ <label> </label>
+ <input ng-model="vm.query.search" name="querySearchInput" placeholder="{{ 'widget-config.search-actions' | translate }}"/>
+ </md-input-container>
+ <md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
+ <md-icon aria-label="Close" class="material-icons">close</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.close' | translate }}
+ </md-tooltip>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-table-container>
+ <table md-table>
+ <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
+ <tr md-row>
+ <th md-column md-order-by="actionSourceName"><span translate>widget-config.action-source</span></th>
+ <th md-column md-order-by="name"><span translate>widget-config.action-name</span></th>
+ <th md-column md-order-by="icon"><span translate>widget-config.action-icon</span></th>
+ <th md-column md-order-by="typeName"><span translate>widget-config.action-type</span></th>
+ <th md-column><span> </span></th>
+ </tr>
+ </thead>
+ <tbody md-body>
+ <tr md-row ng-repeat="action in vm.actions">
+ <td md-cell>{{action.actionSourceName}}</td>
+ <td md-cell>{{action.name}}</td>
+ <td md-cell>
+ <md-icon aria-label="{{ 'widget-config.action-icon' | translate }}" class="material-icons">{{action.icon}}</md-icon>
+ </td>
+ <td md-cell>{{action.typeName}}</td>
+ <td md-cell class="tb-action-cell">
+ <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}"
+ ng-click="vm.editAction($event, action)">
+ <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'widget-config.edit-action' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-button class="md-icon-button" aria-label="{{'action.delete' | translate}}" ng-click="vm.deleteAction($event, action)">
+ <md-icon aria-label="Delete" class="material-icons">delete</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'widget-config.delete-action' | translate }}
+ </md-tooltip>
+ </md-button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </md-table-container>
+ <md-table-pagination md-limit="vm.query.limit" md-limit-options="[10, 15, 20]"
+ md-page="vm.query.page" md-total="{{vm.actionsCount}}"
+ md-on-paginate="vm.onPaginate" md-page-select>
+ </md-table-pagination>
+</div>
diff --git a/ui/src/app/components/widget/action/widget-action-dialog.controller.js b/ui/src/app/components/widget/action/widget-action-dialog.controller.js
new file mode 100644
index 0000000..99a2f0b
--- /dev/null
+++ b/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));
+ }
+}
diff --git a/ui/src/app/components/widget/action/widget-action-dialog.tpl.html b/ui/src/app/components/widget/action/widget-action-dialog.tpl.html
new file mode 100644
index 0000000..7a20e74
--- /dev/null
+++ b/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>
diff --git a/ui/src/app/dashboard/add-widget.controller.js b/ui/src/app/dashboard/add-widget.controller.js
index fd2ada7..c15492d 100644
--- a/ui/src/app/dashboard/add-widget.controller.js
+++ b/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();
diff --git a/ui/src/app/dashboard/add-widget.tpl.html b/ui/src/app/dashboard/add-widget.tpl.html
index 894c23d..4ca29f0 100644
--- a/ui/src/app/dashboard/add-widget.tpl.html
+++ b/ui/src/app/dashboard/add-widget.tpl.html
@@ -34,6 +34,7 @@
<fieldset ng-disabled="loading" style="position: relative; height: 600px;">
<tb-widget-config widget-type="vm.widget.type"
type-parameters="vm.widgetInfo.typeParameters"
+ action-sources="vm.widgetInfo.actionSources"
force-expand-datasources="true"
ng-model="vm.widgetConfig"
widget-settings-schema="vm.settingsSchema"
@@ -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>
diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js
index dcf05e2..9bf1548 100644
--- a/ui/src/app/dashboard/edit-widget.directive.js
+++ b/ui/src/app/dashboard/edit-widget.directive.js
@@ -41,6 +41,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
scope.typeParameters = widgetInfo.typeParameters;
+ scope.actionSources = widgetInfo.actionSources;
scope.isDataEnabled = !widgetInfo.typeParameters.useCustomDatasources;
if (!settingsSchema || settingsSchema === '') {
scope.settingsSchema = {};
@@ -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();
diff --git a/ui/src/app/dashboard/edit-widget.tpl.html b/ui/src/app/dashboard/edit-widget.tpl.html
index 74f5755..1f724a8 100644
--- a/ui/src/app/dashboard/edit-widget.tpl.html
+++ b/ui/src/app/dashboard/edit-widget.tpl.html
@@ -18,6 +18,7 @@
<fieldset ng-disabled="loading">
<tb-widget-config widget-type="widget.type"
type-parameters="typeParameters"
+ action-sources="actionSources"
ng-model="widgetConfig"
is-data-enabled="isDataEnabled"
widget-settings-schema="settingsSchema"
@@ -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>
ui/src/app/dashboard/index.js 2(+1 -1)
diff --git a/ui/src/app/dashboard/index.js b/ui/src/app/dashboard/index.js
index d940f09..4089716 100644
--- a/ui/src/app/dashboard/index.js
+++ b/ui/src/app/dashboard/index.js
@@ -23,7 +23,7 @@ import thingsboardApiUser from '../api/user.service';
import thingsboardApiDashboard from '../api/dashboard.service';
import thingsboardApiCustomer from '../api/customer.service';
import thingsboardDetailsSidenav from '../components/details-sidenav.directive';
-import thingsboardWidgetConfig from '../components/widget-config.directive';
+import thingsboardWidgetConfig from '../components/widget/widget-config.directive';
import thingsboardDashboardSelect from '../components/dashboard-select.directive';
import thingsboardRelatedEntityAutocomplete from '../components/related-entity-autocomplete.directive';
import thingsboardDashboard from '../components/dashboard.directive';
diff --git a/ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html b/ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html
index 385de94..4cf0a3d 100644
--- a/ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html
+++ b/ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html
@@ -59,10 +59,10 @@
<span flex></span>
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
class="md-raised md-primary">
- {{ vm.isAdd ? 'Add' : 'Save' }}
+ {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
</md-button>
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">
- Cancel
+ {{ 'action.cancel' | translate }}
</md-button>
</md-dialog-actions>
</form>
diff --git a/ui/src/app/dashboard/states/default-state-controller.js b/ui/src/app/dashboard/states/default-state-controller.js
index 716d333..d39478f 100644
--- a/ui/src/app/dashboard/states/default-state-controller.js
+++ b/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) {
diff --git a/ui/src/app/dashboard/states/entity-state-controller.js b/ui/src/app/dashboard/states/entity-state-controller.js
index dd295cc..4510449 100644
--- a/ui/src/app/dashboard/states/entity-state-controller.js
+++ b/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;
}
diff --git a/ui/src/app/dashboard/states/manage-dashboard-states.controller.js b/ui/src/app/dashboard/states/manage-dashboard-states.controller.js
index 3595f65..1aa95b7 100644
--- a/ui/src/app/dashboard/states/manage-dashboard-states.controller.js
+++ b/ui/src/app/dashboard/states/manage-dashboard-states.controller.js
@@ -175,8 +175,6 @@ export default function ManageDashboardStatesController($scope, $mdDialog, $filt
$scope.theForm.$setDirty();
updateStates();
});
-
-
}
}
diff --git a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
index bcf37dd..a133829 100644
--- a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
+++ b/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(
ui/src/app/layout/index.js 2(+2 -0)
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js
index 2a27c93..9d3e1d1 100644
--- a/ui/src/app/layout/index.js
+++ b/ui/src/app/layout/index.js
@@ -26,6 +26,7 @@ import thingsboardApiLogin from '../api/login.service';
import thingsboardApiUser from '../api/user.service';
import thingsboardNoAnimate from '../components/no-animate.directive';
+import thingsboardOnFinishRender from '../components/finish-render.directive';
import thingsboardSideMenu from '../components/side-menu.directive';
import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive';
@@ -81,6 +82,7 @@ export default angular.module('thingsboard.home', [
thingsboardApiLogin,
thingsboardApiUser,
thingsboardNoAnimate,
+ thingsboardOnFinishRender,
thingsboardSideMenu,
thingsboardDashboardAutocomplete
])
ui/src/app/locale/locale.constant.js 40(+39 -1)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 4710b92..4d4ebe2 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/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"
+ }
}
}
}
ui/src/app/widget/lib/alarms-table-widget.js 19(+5 -14)
diff --git a/ui/src/app/widget/lib/alarms-table-widget.js b/ui/src/app/widget/lib/alarms-table-widget.js
index 5a65af2..7618e6a 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.js
+++ b/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;
ui/src/app/widget/lib/entities-table-widget.js 60(+45 -15)
diff --git a/ui/src/app/widget/lib/entities-table-widget.js b/ui/src/app/widget/lib/entities-table-widget.js
index 4b8aa6e..733e7cb 100644
--- a/ui/src/app/widget/lib/entities-table-widget.js
+++ b/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;
diff --git a/ui/src/app/widget/lib/entities-table-widget.tpl.html b/ui/src/app/widget/lib/entities-table-widget.tpl.html
index 247d479..162e5f7 100644
--- a/ui/src/app/widget/lib/entities-table-widget.tpl.html
+++ b/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> </span></th>
+ <th md-column ng-if="vm.actionCellDescriptors.length"><span> </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>