Custom action with additional resources

This commit is contained in:
Chantsova Ekaterina 2019-07-19 13:36:25 +03:00 committed by Igor Kulikov
parent d324a89f38
commit 75702b6474
11 changed files with 1391 additions and 5 deletions

View File

@ -828,6 +828,10 @@ export default angular.module('thingsboard.types', [])
custom: {
name: 'widget-action.custom',
value: 'custom'
},
customPretty: {
name: 'widget-action.custom-pretty',
value: 'customPretty'
}
},
systemBundleAlias: {

View File

@ -0,0 +1,993 @@
/*
* Copyright © 2016-2019 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 './custom-action-pretty-editor.scss';
import customActionPrettyEditorTemplate from './custom-action-pretty-editor.tpl.html';
import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/html';
import 'brace/mode/css';
import 'brace/snippets/text';
import 'brace/snippets/html';
import 'brace/snippets/css';
import beautify from 'js-beautify';
import Split from "split.js";
const html_beautify = beautify.html;
const css_beautify = beautify.css;
export default angular.module('thingsboard.directives.customActionPrettyEditor', [])
.directive('tbCustomActionPrettyEditor', CustomActionPrettyEditor)
.name;
/*@ngInject*/
function CustomActionPrettyEditor($compile, $templateCache, $window, $timeout) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(customActionPrettyEditorTemplate);
element.html(template);
var ace_editors = [];
scope.fullscreen = false;
scope.htmlEditorOptions = {
useWrapMode: true,
mode: 'html',
advanced: {
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
},
onLoad: function (_ace) {
ace_editors.push(_ace);
}
};
scope.cssEditorOptions = {
useWrapMode: true,
mode: 'css',
advanced: {
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
},
onLoad: function (_ace) {
ace_editors.push(_ace);
}
};
scope.addResource = addResource;
scope.beautifyCss = beautifyCss;
scope.beautifyHtml = beautifyHtml;
scope.removeResource = removeResource;
scope.toggleFullscreen = toggleFullscreen;
var sampleJsFunction = "/* There are three examples: for delete, edit and add entity */\n" +
"/* Delete entity example */\n" +
"//\n" +
"//var $injector = widgetContext.$scope.$injector;\n" +
"//var $mdDialog = $injector.get('$mdDialog'),\n" +
"// $document = $injector.get('$document'),\n" +
"// types = $injector.get('types'),\n" +
"// assetService = $injector.get('assetService'),\n" +
"// deviceService = $injector.get('deviceService')\n" +
"// $rootScope = $injector.get('$rootScope'),\n" +
"// $q = $injector.get('$q');\n" +
"//\n" +
"//openDeleteEntityDialog();\n" +
"//\n" +
"//function openDeleteEntityDialog() {\n" +
"// var title = 'Delete ' + entityId.entityType\n" +
"// .toLowerCase() + ' ' +\n" +
"// entityName;\n" +
"// var content = 'Are you sure you want to delete the ' +\n" +
"// entityId.entityType.toLowerCase() + ' ' +\n" +
"// entityName + '?';\n" +
"// var confirm = $mdDialog.confirm()\n" +
"// .targetEvent($event)\n" +
"// .title(title)\n" +
"// .htmlContent(content)\n" +
"// .ariaLabel(title)\n" +
"// .cancel('Cancel')\n" +
"// .ok('Delete');\n" +
"// $mdDialog.show(confirm).then(function() {\n" +
"// deleteEntity();\n" +
"// })\n" +
"//}\n" +
"//\n" +
"//function deleteEntity() {\n" +
"// deleteEntityPromise(entityId).then(\n" +
"// function success() {\n" +
"// updateAliasData();\n" +
"// },\n" +
"// function fail() {\n" +
"// showErrorDialog();\n" +
"// }\n" +
"// );\n" +
"//}\n" +
"//\n" +
"//function deleteEntityPromise(entityId) {\n" +
"// if (entityId.entityType == types.entityType.asset) {\n" +
"// return assetService.deleteAsset(entityId.id);\n" +
"// } else if (entityId.entityType == types.entityType.device) {\n" +
"// return deviceService.deleteDevice(entityId.id);\n" +
"// }\n" +
"//}\n" +
"//\n" +
"//function updateAliasData() {\n" +
"// var aliasIds = [];\n" +
"// for (var id in widgetContext.aliasController.resolvedAliases) {\n" +
"// aliasIds.push(id);\n" +
"// }\n" +
"// var tasks = [];\n" +
"// aliasIds.forEach(function(aliasId) {\n" +
"// widgetContext.aliasController.setAliasUnresolved(aliasId);\n" +
"// tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n" +
"// });\n" +
"// $q.all(tasks).then(function() {\n" +
"// $rootScope.$broadcast('entityAliasesChanged', aliasIds);\n" +
"// });\n" +
"//}\n" +
"//\n" +
"//function showErrorDialog() {\n" +
"// var title = 'Error';\n" +
"// var content = 'An error occurred while deleting the entity. Please try again.';\n" +
"// var alert = $mdDialog.alert()\n" +
"// .title(title)\n" +
"// .htmlContent(content)\n" +
"// .ariaLabel(title)\n" +
"// .parent(angular.element($document[0].body))\n" +
"// .targetEvent($event)\n" +
"// .multiple(true)\n" +
"// .clickOutsideToClose(true)\n" +
"// .ok('CLOSE');\n" +
"// $mdDialog.show(alert);\n" +
"//}\n" +
"//\n" +
"/* Edit entity example */\n" +
"//\n" +
"//var $injector = widgetContext.$scope.$injector;\n" +
"//var $mdDialog = $injector.get('$mdDialog'),\n" +
"// $document = $injector.get('$document'),\n" +
"// $q = $injector.get('$q'),\n" +
"// types = $injector.get('types'),\n" +
"// $rootScope = $injector.get('$rootScope'),\n" +
"// entityService = $injector.get('entityService'),\n" +
"// attributeService = $injector.get('attributeService'),\n" +
"// entityRelationService = $injector.get('entityRelationService');\n" +
"//\n" +
"//openEditEntityDialog();\n" +
"//\n" +
"//function openEditEntityDialog() {\n" +
"// $mdDialog.show({\n" +
"// controller: ['$scope','$mdDialog', EditEntityDialogController],\n" +
"// controllerAs: 'vm',\n" +
"// template: htmlTemplate,\n" +
"// locals: {\n" +
"// entityId: entityId\n" +
"// },\n" +
"// parent: angular.element($document[0].body),\n" +
"// targetEvent: $event,\n" +
"// multiple: true,\n" +
"// clickOutsideToClose: false\n" +
"// });\n" +
"//}\n" +
"//\n" +
"//function EditEntityDialogController($scope,$mdDialog) {\n" +
"// var vm = this;\n" +
"// vm.entityId = entityId;\n" +
"// vm.entityName = entityName;\n" +
"// vm.entityType = entityId.entityType;\n" +
"// vm.allowedEntityTypes = [types.entityType.asset, types.entityType.device];\n" +
"// vm.allowedRelatedEntityTypes = [];\n" +
"// vm.entitySearchDirection = types.entitySearchDirection;\n" +
"// vm.attributes = {};\n" +
"// vm.serverAttributes = {};\n" +
"// vm.relations = [];\n" +
"// vm.newRelations = [];\n" +
"// vm.relationsToDelete = [];\n" +
"// getEntityInfo();\n" +
"// \n" +
"// vm.addRelation = function() {\n" +
"// var relation = {\n" +
"// direction: null,\n" +
"// relationType: null,\n" +
"// relatedEntity: null\n" +
"// };\n" +
"// vm.newRelations.push(relation);\n" +
"// $scope.editEntityForm.$setDirty();\n" +
"// };\n" +
"// vm.removeRelation = function(index) {\n" +
"// if (index > -1) {\n" +
"// vm.newRelations.splice(index, 1);\n" +
"// $scope.editEntityForm.$setDirty();\n" +
"// }\n" +
"// };\n" +
"// vm.removeOldRelation = function(index, relation) {\n" +
"// if (index > -1) {\n" +
"// vm.relations.splice(index, 1);\n" +
"// vm.relationsToDelete.push(relation);\n" +
"// $scope.editEntityForm.$setDirty();\n" +
"// }\n" +
"// };\n" +
"// vm.save = function() {\n" +
"// saveAttributes();\n" +
"// saveRelations();\n" +
"// $scope.editEntityForm.$setPristine();\n" +
"// };\n" +
"// vm.cancel = function() {\n" +
"// $mdDialog.hide();\n" +
"// };\n" +
"// \n" +
"// function getEntityAttributes(attributes) {\n" +
"// for (var i = 0; i < attributes.length; i++) {\n" +
"// vm.attributes[attributes[i].key] = attributes[i].value; \n" +
"// }\n" +
"// vm.serverAttributes = angular.copy(vm.attributes);\n" +
"// }\n" +
"// \n" +
"// function getEntityRelations(relations) {\n" +
"// var relationsFrom = relations[0];\n" +
"// var relationsTo = relations[1];\n" +
"// for (var i=0; i < relationsFrom.length; i++) {\n" +
"// var relation = {\n" +
"// direction: types.entitySearchDirection.from,\n" +
"// relationType: relationsFrom[i].type,\n" +
"// relatedEntity: relationsFrom[i].to\n" +
"// };\n" +
"// vm.relations.push(relation);\n" +
"// }\n" +
"// for (var i=0; i < relationsTo.length; i++) {\n" +
"// var relation = {\n" +
"// direction: types.entitySearchDirection.to,\n" +
"// relationType: relationsTo[i].type,\n" +
"// relatedEntity: relationsTo[i].from\n" +
"// };\n" +
"// vm.relations.push(relation);\n" +
"// }\n" +
"// }\n" +
"// \n" +
"// function getEntityInfo() {\n" +
"// entityService.getEntity(entityId.entityType, entityId.id).then(\n" +
"// function(entity) {\n" +
"// vm.entity = entity;\n" +
"// vm.type = vm.entity.type;\n" +
"// });\n" +
"// attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\n" +
"// function(data){\n" +
"// if (data.length) {\n" +
"// getEntityAttributes(data);\n" +
"// }\n" +
"// });\n" +
"// $q.all([entityRelationService.findInfoByFrom(entityId.id, entityId.entityType), entityRelationService.findInfoByTo(entityId.id, entityId.entityType)]).then(\n" +
"// function(relations){\n" +
"// getEntityRelations(relations);\n" +
"// });\n" +
"// }\n" +
"// \n" +
"// function saveAttributes() {\n" +
"// var attributesArray = [];\n" +
"// for (var key in vm.attributes) {\n" +
"// if (vm.attributes[key] !== vm.serverAttributes[key]) {\n" +
"// attributesArray.push({key: key, value: vm.attributes[key]});\n" +
"// }\n" +
"// }\n" +
"// if (attributesArray.length > 0) {\n" +
"// attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray);\n" +
"// } \n" +
"// }\n" +
"// \n" +
"// function saveRelations() {\n" +
"// var tasks = [];\n" +
"// for (var i=0; i < vm.newRelations.length; i++) {\n" +
"// var relation = {\n" +
"// type: vm.newRelations[i].relationType\n" +
"// };\n" +
"// if (vm.newRelations[i].direction == types.entitySearchDirection.from) {\n" +
"// relation.to = vm.newRelations[i].relatedEntity;\n" +
"// relation.from = entityId;\n" +
"// } else {\n" +
"// relation.to = entityId;\n" +
"// relation.from = vm.newRelations[i].relatedEntity;\n" +
"// }\n" +
"// tasks.push(entityRelationService.saveRelation(relation));\n" +
"// }\n" +
"// for (var i=0; i < vm.relationsToDelete.length; i++) {\n" +
"// var relation = {\n" +
"// type: vm.relationsToDelete[i].relationType\n" +
"// };\n" +
"// if (vm.relationsToDelete[i].direction == types.entitySearchDirection.from) {\n" +
"// relation.to = vm.relationsToDelete[i].relatedEntity;\n" +
"// relation.from = entityId;\n" +
"// } else {\n" +
"// relation.to = entityId;\n" +
"// relation.from = vm.relationsToDelete[i].relatedEntity;\n" +
"// }\n" +
"// tasks.push(entityRelationService.deleteRelation(relation.from.id, relation.from.entityType, relation.type, relation.to.id, relation.to.entityType));\n" +
"// }\n" +
"// $q.all(tasks).then(function(){\n" +
"// vm.relations = vm.relations.concat(vm.newRelations);\n" +
"// vm.newRelations = [];\n" +
"// vm.relationsToDelete = [];\n" +
"// updateAliasData();\n" +
"// });\n" +
"// }\n" +
"// \n" +
"// function updateAliasData() {\n" +
"// var aliasIds = [];\n" +
"// for (var id in widgetContext.aliasController.resolvedAliases) {\n" +
"// aliasIds.push(id);\n" +
"// }\n" +
"// var tasks = [];\n" +
"// aliasIds.forEach(function(aliasId) {\n" +
"// widgetContext.aliasController.setAliasUnresolved(aliasId);\n" +
"// tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n" +
"// });\n" +
"// $q.all(tasks).then(function() {\n" +
"// $rootScope.$broadcast('entityAliasesChanged', aliasIds);\n" +
"// });\n" +
"// }\n" +
"//}\n" +
"//\n" +
"/* Add entity example */\n" +
"//\n" +
"//var $injector = widgetContext.$scope.$injector;\n" +
"//var $mdDialog = $injector.get('$mdDialog'),\n" +
"// $document = $injector.get('$document'),\n" +
"// $q = $injector.get('$q'),\n" +
"// $rootScope = $injector.get('$rootScope'),\n" +
"// types = $injector.get('types'),\n" +
"// assetService = $injector.get('assetService'),\n" +
"// deviceService = $injector.get('deviceService'),\n" +
"// attributeService = $injector.get('attributeService'),\n" +
"// entityRelationService = $injector.get('entityRelationService');\n" +
"//\n" +
"//openAddEntityDialog();\n" +
"//\n" +
"//function openAddEntityDialog() {\n" +
"// $mdDialog.show({\n" +
"// controller: ['$scope','$mdDialog', AddEntityDialogController],\n" +
"// controllerAs: 'vm',\n" +
"// template: htmlTemplate,\n" +
"// locals: {\n" +
"// entityId: entityId\n" +
"// },\n" +
"// parent: angular.element($document[0].body),\n" +
"// targetEvent: $event,\n" +
"// multiple: true,\n" +
"// clickOutsideToClose: false\n" +
"// });\n" +
"//}\n" +
"//\n" +
"//function AddEntityDialogController($scope, $mdDialog) {\n" +
"// var vm = this;\n" +
"// vm.allowedEntityTypes = [types.entityType.asset, types.entityType.device];\n" +
"// vm.allowedRelatedEntityTypes = [];\n" +
"// vm.entitySearchDirection = types.entitySearchDirection;\n" +
"// vm.attributes = {};\n" +
"// vm.relations = [];\n" +
"// \n" +
"// vm.addRelation = function() {\n" +
"// var relation = {\n" +
"// direction: null,\n" +
"// relationType: null,\n" +
"// relatedEntity: null\n" +
"// };\n" +
"// vm.relations.push(relation);\n" +
"// };\n" +
"// vm.removeRelation = function(index) {\n" +
"// if (index > -1) {\n" +
"// vm.relations.splice(index, 1);\n" +
"// }\n" +
"// };\n" +
"// vm.save = function() {\n" +
"// $scope.addEntityForm.$setPristine();\n" +
"// saveEntityPromise().then(\n" +
"// function (entity) {\n" +
"// saveAttributes(entity.id);\n" +
"// saveRelations(entity.id);\n" +
"// $mdDialog.hide();\n" +
"// }\n" +
"// );\n" +
"// };\n" +
"// vm.cancel = function() {\n" +
"// $mdDialog.hide();\n" +
"// };\n" +
"// \n" +
"// \n" +
"// function saveEntityPromise() {\n" +
"// var entity = {\n" +
"// name: vm.entityName,\n" +
"// type: vm.type\n" +
"// };\n" +
"// if (vm.entityType == types.entityType.asset) {\n" +
"// return assetService.saveAsset(entity);\n" +
"// } else if (vm.entityType == types.entityType.device) {\n" +
"// return deviceService.saveDevice(entity);\n" +
"// }\n" +
"// }\n" +
"// \n" +
"// function saveAttributes(entityId) {\n" +
"// var attributesArray = [];\n" +
"// for (var key in vm.attributes) {\n" +
"// attributesArray.push({key: key, value: vm.attributes[key]});\n" +
"// }\n" +
"// if (attributesArray.length > 0) {\n" +
"// attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray);\n" +
"// } \n" +
"// }\n" +
"// \n" +
"// function saveRelations(entityId) {\n" +
"// var tasks = [];\n" +
"// for (var i=0; i < vm.relations.length; i++) {\n" +
"// var relation = {\n" +
"// type: vm.relations[i].relationType\n" +
"// };\n" +
"// if (vm.relations[i].direction == types.entitySearchDirection.from) {\n" +
"// relation.to = vm.relations[i].relatedEntity;\n" +
"// relation.from = entityId;\n" +
"// } else {\n" +
"// relation.to = entityId;\n" +
"// relation.from = vm.relations[i].relatedEntity;\n" +
"// }\n" +
"// tasks.push(entityRelationService.saveRelation(relation));\n" +
"// }\n" +
"// $q.all(tasks).then(function(){\n" +
"// updateAliasData();\n" +
"// });\n" +
"// }\n" +
"// \n" +
"// function updateAliasData() {\n" +
"// var aliasIds = [];\n" +
"// for (var id in widgetContext.aliasController.resolvedAliases) {\n" +
"// aliasIds.push(id);\n" +
"// }\n" +
"// var tasks = [];\n" +
"// aliasIds.forEach(function(aliasId) {\n" +
"// widgetContext.aliasController.setAliasUnresolved(aliasId);\n" +
"// tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n" +
"// });\n" +
"// $q.all(tasks).then(function() {\n" +
"// $rootScope.$broadcast('entityAliasesChanged', aliasIds);\n" +
"// });\n" +
"// }\n" +
"//}\n" +
"\n" +
"\n";
var sampleHtmlTemplate = '<!-- There are two example templates: for edit and add entity -->\n' +
'<!-- Edit entity example -->\n' +
'<!-- -->\n' +
'<!--<md-dialog aria-label="Edit entity">-->\n' +
'<!-- <form name="editEntityForm" class="edit-entity-form" ng-submit="vm.save()">-->\n' +
'<!-- <md-toolbar>-->\n' +
'<!-- <div class="md-toolbar-tools">-->\n' +
'<!-- <h2>Edit {{vm.entityType.toLowerCase()}} {{vm.entityName}}</h2>-->\n' +
'<!-- <span flex></span>-->\n' +
'<!-- <md-button class="md-icon-button" ng-click="vm.cancel()">-->\n' +
'<!-- <ng-md-icon icon="close" aria-label="Close"></ng-md-icon>-->\n' +
'<!-- </md-button>-->\n' +
'<!-- </div>-->\n' +
'<!-- </md-toolbar>-->\n' +
'<!-- <md-dialog-content>-->\n' +
'<!-- <div class="md-dialog-content">-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Entity Name</label>-->\n' +
'<!-- <input ng-model="vm.entityName" readonly>-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Entity Type</label>-->\n' +
'<!-- <input ng-model="vm.entityType" readonly>-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Type</label>-->\n' +
'<!-- <input ng-model="vm.type" readonly>-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Latitude</label>-->\n' +
'<!-- <input name="latitude" type="number" step="any" ng-model="vm.attributes.latitude">-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Longitude</label>-->\n' +
'<!-- <input name="longitude" type="number" step="any" ng-model="vm.attributes.longitude">-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Address</label>-->\n' +
'<!-- <input ng-model="vm.attributes.address">-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Owner</label>-->\n' +
'<!-- <input ng-model="vm.attributes.owner">-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Integer Value</label>-->\n' +
'<!-- <input name="integerNumber" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="vm.attributes.number">-->\n' +
'<!-- <div ng-messages="editEntityForm.integerNumber.$error">-->\n' +
'<!-- <div ng-message="pattern">Invalid integer value.</div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <div class="boolean-value-input" layout="column" layout-align="center start" flex>-->\n' +
'<!-- <label class="checkbox-label">Boolean Value</label>-->\n' +
'<!-- <md-checkbox ng-model="vm.attributes.booleanValue" style="margin-bottom: 40px;">-->\n' +
'<!-- {{ (vm.attributes.booleanValue ? "value.true" : "value.false") | translate }}-->\n' +
'<!-- </md-checkbox>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div class="relations-list old-relations">-->\n' +
'<!-- <div class="md-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div>-->\n' +
'<!-- <div class="body" ng-show="vm.relations.length">-->\n' +
'<!-- <div class="row" layout="row" layout-align="start center" ng-repeat="relation in vm.relations track by $index">-->\n' +
'<!-- <div class="md-whiteframe-1dp" flex layout="row" style="padding-left: 5px; margin-bottom: 3px;">-->\n' +
'<!-- <div flex layout="column">-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <md-input-container class="md-block" style="min-width: 100px;">-->\n' +
'<!-- <label>Direction</label>-->\n' +
'<!-- <md-select ng-disabled="true" required ng-model="relation.direction">-->\n' +
'<!-- <md-option ng-repeat="direction in vm.entitySearchDirection" ng-value="direction">-->\n' +
'<!-- {{ ("relation.search-direction." + direction) | translate}}-->\n' +
'<!-- </md-option>-->\n' +
'<!-- </md-select>-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <tb-relation-type-autocomplete ng-disabled="true" flex class="md-block"-->\n' +
'<!-- the-form="editEntityForm"-->\n' +
'<!-- ng-model="relation.relationType"-->\n' +
'<!-- tb-required="true">-->\n' +
'<!-- </tb-relation-type-autocomplete>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <tb-entity-select flex class="md-block"-->\n' +
'<!-- the-form="editEntityForm"-->\n' +
'<!-- ng-disabled="true"-->\n' +
'<!-- tb-required="true"-->\n' +
'<!-- ng-model="relation.relatedEntity">-->\n' +
'<!-- </tb-entity-select>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="column" layout-align="center center">-->\n' +
'<!-- <md-button class="md-icon-button md-primary" style="width: 40px; min-width: 40px;"-->\n' +
'<!-- ng-click="vm.removeOldRelation($index,relation)" aria-label="Remove">-->\n' +
'<!-- <md-tooltip md-direction="top">Remove relation</md-tooltip>-->\n' +
'<!-- <md-icon aria-label="Remove" class="material-icons">-->\n' +
'<!-- close-->\n' +
'<!-- </md-icon>-->\n' +
'<!-- </md-button>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div class="relations-list">-->\n' +
'<!-- <div class="md-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">New Relations</div>-->\n' +
'<!-- <div class="body" ng-show="vm.newRelations.length">-->\n' +
'<!-- <div class="row" layout="row" layout-align="start center" ng-repeat="relation in vm.newRelations track by $index">-->\n' +
'<!-- <div class="md-whiteframe-1dp" flex layout="row" style="padding-left: 5px; margin-bottom: 3px;">-->\n' +
'<!-- <div flex layout="column">-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <md-input-container class="md-block" style="min-width: 100px;">-->\n' +
'<!-- <label>Direction</label>-->\n' +
'<!-- <md-select name="direction" required ng-model="relation.direction">-->\n' +
'<!-- <md-option ng-repeat="direction in vm.entitySearchDirection" ng-value="direction">-->\n' +
'<!-- {{ ("relation.search-direction." + direction) | translate}}-->\n' +
'<!-- </md-option>-->\n' +
'<!-- </md-select>-->\n' +
'<!-- <div ng-messages="editEntityForm.direction.$error">-->\n' +
'<!-- <div ng-message="required">Relation direction is required.</div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <tb-relation-type-autocomplete flex class="md-block"-->\n' +
'<!-- the-form="editEntityForm"-->\n' +
'<!-- ng-model="relation.relationType"-->\n' +
'<!-- tb-required="true">-->\n' +
'<!-- </tb-relation-type-autocomplete>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <tb-entity-select flex class="md-block"-->\n' +
'<!-- the-form="editEntityForm"-->\n' +
'<!-- tb-required="true"-->\n' +
'<!-- ng-model="relation.relatedEntity">-->\n' +
'<!-- </tb-entity-select>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="column" layout-align="center center">-->\n' +
'<!-- <md-button class="md-icon-button md-primary" style="width: 40px; min-width: 40px;"-->\n' +
'<!-- ng-click="vm.removeRelation($index)" aria-label="Remove">-->\n' +
'<!-- <md-tooltip md-direction="top">Remove relation</md-tooltip>-->\n' +
'<!-- <md-icon aria-label="Remove" class="material-icons">-->\n' +
'<!-- close-->\n' +
'<!-- </md-icon>-->\n' +
'<!-- </md-button>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div>-->\n' +
'<!-- <md-button class="md-primary md-raised" ng-click="vm.addRelation()" aria-label="Add">-->\n' +
'<!-- <md-tooltip md-direction="top">Add Relation</md-tooltip>-->\n' +
'<!-- Add-->\n' +
'<!-- </md-button>-->\n' +
'<!-- </div> -->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </md-dialog-content>-->\n' +
'<!-- <md-dialog-actions>-->\n' +
'<!-- <md-button type="submit" ng-disabled="editEntityForm.$invalid || !editEntityForm.$dirty" class="md-raised md-primary">Save</md-button>-->\n' +
'<!-- <md-button ng-click="vm.cancel()" class="md-primary">Cancel</md-button>-->\n' +
'<!-- </md-dialog-actions>-->\n' +
'<!-- </form>-->\n' +
'<!--</md-dialog>-->\n' +
'<!-- -->\n' +
'<!-- Add entity example -->\n' +
'<!-- -->\n' +
'<!--<md-dialog aria-label="Add entity">-->\n' +
'<!-- <form name="addEntityForm" class="add-entity-form" ng-submit="vm.save()">-->\n' +
'<!-- <md-toolbar>-->\n' +
'<!-- <div class="md-toolbar-tools">-->\n' +
'<!-- <h2>Add entity</h2>-->\n' +
'<!-- <span flex></span>-->\n' +
'<!-- <md-button class="md-icon-button" ng-click="vm.cancel()">-->\n' +
'<!-- <ng-md-icon icon="close" aria-label="Close"></ng-md-icon>-->\n' +
'<!-- </md-button>-->\n' +
'<!-- </div>-->\n' +
'<!-- </md-toolbar>-->\n' +
'<!-- <md-dialog-content>-->\n' +
'<!-- <div class="md-dialog-content">-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Entity Name</label>-->\n' +
'<!-- <input ng-model="vm.entityName" name=entityName required>-->\n' +
'<!-- <div ng-messages="addEntityForm.entityName.$error">-->\n' +
'<!-- <div ng-message="required">Entity name is required.</div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <tb-entity-type-select class="md-block" style="min-width: 100px; width: 100px;"-->\n' +
'<!-- the-form="addEntityForm"-->\n' +
'<!-- tb-required="true"-->\n' +
'<!-- allowed-entity-types="vm.allowedEntityTypes"-->\n' +
'<!-- ng-model="vm.entityType">-->\n' +
'<!-- </tb-entity-type-select>-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Entity Subtype</label>-->\n' +
'<!-- <input ng-model="vm.type" name=type required>-->\n' +
'<!-- <div ng-messages="addEntityForm.type.$error">-->\n' +
'<!-- <div ng-message="required">Entity subtype is required.</div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Latitude</label>-->\n' +
'<!-- <input name="latitude" type="number" step="any" ng-model="vm.attributes.latitude">-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Longitude</label>-->\n' +
'<!-- <input name="longitude" type="number" step="any" ng-model="vm.attributes.longitude">-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Address</label>-->\n' +
'<!-- <input ng-model="vm.attributes.address">-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Owner</label>-->\n' +
'<!-- <input ng-model="vm.attributes.owner">-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <md-input-container flex class="md-block">-->\n' +
'<!-- <label>Integer Value</label>-->\n' +
'<!-- <input name="integerNumber" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="vm.attributes.number">-->\n' +
'<!-- <div ng-messages="addEntityForm.integerNumber.$error">-->\n' +
'<!-- <div ng-message="pattern">Invalid integer value.</div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <div class="boolean-value-input" layout="column" layout-align="center start" flex>-->\n' +
'<!-- <label class="checkbox-label">Boolean Value</label>-->\n' +
'<!-- <md-checkbox ng-model="vm.attributes.booleanValue" style="margin-bottom: 40px;">-->\n' +
'<!-- {{ (vm.attributes.booleanValue ? "value.true" : "value.false") | translate }}-->\n' +
'<!-- </md-checkbox>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div class="relations-list">-->\n' +
'<!-- <div class="md-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div>-->\n' +
'<!-- <div class="body" ng-show="vm.relations.length">-->\n' +
'<!-- <div class="row" layout="row" layout-align="start center" ng-repeat="relation in vm.relations track by $index">-->\n' +
'<!-- <div class="md-whiteframe-1dp" flex layout="row" style="padding-left: 5px;">-->\n' +
'<!-- <div flex layout="column">-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <md-input-container class="md-block" style="min-width: 100px;">-->\n' +
'<!-- <label>Direction</label>-->\n' +
'<!-- <md-select name="direction" required ng-model="relation.direction">-->\n' +
'<!-- <md-option ng-repeat="direction in vm.entitySearchDirection" ng-value="direction">-->\n' +
'<!-- {{ ("relation.search-direction." + direction) | translate}}-->\n' +
'<!-- </md-option>-->\n' +
'<!-- </md-select>-->\n' +
'<!-- <div ng-messages="addEntityForm.direction.$error">-->\n' +
'<!-- <div ng-message="required">Relation direction is required.</div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </md-input-container>-->\n' +
'<!-- <tb-relation-type-autocomplete flex class="md-block"-->\n' +
'<!-- the-form="addEntityForm"-->\n' +
'<!-- ng-model="relation.relationType"-->\n' +
'<!-- tb-required="true">-->\n' +
'<!-- </tb-relation-type-autocomplete>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="row">-->\n' +
'<!-- <tb-entity-select flex class="md-block"-->\n' +
'<!-- the-form="addEntityForm"-->\n' +
'<!-- tb-required="true"-->\n' +
'<!-- ng-model="relation.relatedEntity">-->\n' +
'<!-- </tb-entity-select>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div layout="column" layout-align="center center">-->\n' +
'<!-- <md-button class="md-icon-button md-primary" style="width: 40px; min-width: 40px;"-->\n' +
'<!-- ng-click="vm.removeRelation($index)" aria-label="Remove">-->\n' +
'<!-- <md-tooltip md-direction="top">Remove relation</md-tooltip>-->\n' +
'<!-- <md-icon aria-label="Remove" class="material-icons">-->\n' +
'<!-- close-->\n' +
'<!-- </md-icon>-->\n' +
'<!-- </md-button>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- <div>-->\n' +
'<!-- <md-button class="md-primary md-raised" ng-click="vm.addRelation()" aria-label="Add">-->\n' +
'<!-- <md-tooltip md-direction="top">Add Relation</md-tooltip>-->\n' +
'<!-- Add-->\n' +
'<!-- </md-button>-->\n' +
'<!-- </div> -->\n' +
'<!-- </div>-->\n' +
'<!-- </div>-->\n' +
'<!-- </md-dialog-content>-->\n' +
'<!-- <md-dialog-actions>-->\n' +
'<!-- <md-button type="submit" ng-disabled="addEntityForm.$invalid || !addEntityForm.$dirty" class="md-raised md-primary">Create</md-button>-->\n' +
'<!-- <md-button ng-click="vm.cancel()" class="md-primary">Cancel</md-button>-->\n' +
'<!-- </md-dialog-actions>-->\n' +
'<!-- </form>-->\n' +
'<!--</md-dialog>-->\n';
var sampleCss = '/* There are two examples: for edit and add entity */\n' +
'/* Edit entity example */\n' +
'/*\n' +
'.edit-entity-form md-input-container {\n' +
' padding-right: 10px;\n' +
'}\n' +
'\n' +
'.edit-entity-form .boolean-value-input {\n' +
' padding-left: 5px;\n' +
'}\n' +
'\n' +
'.edit-entity-form .boolean-value-input .checkbox-label {\n' +
' margin-bottom: 8px;\n' +
' color: rgba(0,0,0,0.54);\n' +
' font-size: 12px;\n' +
'}\n' +
'\n' +
'.relations-list .header {\n' +
' padding-right: 5px;\n' +
' padding-bottom: 5px;\n' +
' padding-left: 5px;\n' +
'}\n' +
'\n' +
'.relations-list .header .cell {\n' +
' padding-right: 5px;\n' +
' padding-left: 5px;\n' +
' font-size: 12px;\n' +
' font-weight: 700;\n' +
' color: rgba(0, 0, 0, .54);\n' +
' white-space: nowrap;\n' +
'}\n' +
'\n' +
'.relations-list .body {\n' +
' padding-right: 5px;\n' +
' padding-bottom: 15px;\n' +
' padding-left: 5px;\n' +
'}\n' +
'\n' +
'.relations-list .body .row {\n' +
' padding-top: 5px;\n' +
'}\n' +
'\n' +
'.relations-list .body .cell {\n' +
' padding-right: 5px;\n' +
' padding-left: 5px;\n' +
'}\n' +
'\n' +
'.relations-list .body md-autocomplete-wrap md-input-container {\n' +
' height: 30px;\n' +
'}\n' +
'\n' +
'.relations-list .body .md-button {\n' +
' margin: 0;\n' +
'}\n' +
'\n' +
'.relations-list.old-relations tb-entity-select tb-entity-autocomplete button {\n' +
' display: none;\n' +
'} \n' +
'*/\n' +
'/* Add entity example */\n' +
'/*\n' +
'.add-entity-form md-input-container {\n' +
' padding-right: 10px;\n' +
'}\n' +
'\n' +
'.add-entity-form .boolean-value-input {\n' +
' padding-left: 5px;\n' +
'}\n' +
'\n' +
'.add-entity-form .boolean-value-input .checkbox-label {\n' +
' margin-bottom: 8px;\n' +
' color: rgba(0,0,0,0.54);\n' +
' font-size: 12px;\n' +
'}\n' +
'\n' +
'.relations-list .header {\n' +
' padding-right: 5px;\n' +
' padding-bottom: 5px;\n' +
' padding-left: 5px;\n' +
'}\n' +
'\n' +
'.relations-list .header .cell {\n' +
' padding-right: 5px;\n' +
' padding-left: 5px;\n' +
' font-size: 12px;\n' +
' font-weight: 700;\n' +
' color: rgba(0, 0, 0, .54);\n' +
' white-space: nowrap;\n' +
'}\n' +
'\n' +
'.relations-list .body {\n' +
' padding-right: 5px;\n' +
' padding-bottom: 15px;\n' +
' padding-left: 5px;\n' +
'}\n' +
'\n' +
'.relations-list .body .row {\n' +
' padding-top: 5px;\n' +
'}\n' +
'\n' +
'.relations-list .body .cell {\n' +
' padding-right: 5px;\n' +
' padding-left: 5px;\n' +
'}\n' +
'\n' +
'.relations-list .body md-autocomplete-wrap md-input-container {\n' +
' height: 30px;\n' +
'}\n' +
'\n' +
'.relations-list .body .md-button {\n' +
' margin: 0;\n' +
'}\n' +
'*/\n' +
'\n' +
'\n';
scope.$watch('action', function () {
ngModelCtrl.$setViewValue(scope.action);
});
ngModelCtrl.$render = function () {
scope.action = ngModelCtrl.$viewValue;
if (angular.isUndefined(scope.action.customHtml) && angular.isUndefined(scope.action.customCss) && angular.isUndefined(scope.action.customFunction)) {
scope.action.customFunction = sampleJsFunction;
scope.action.customHtml = sampleHtmlTemplate;
scope.action.customCss = sampleCss;
}
};
function removeResource(index) {
if (index > -1) {
scope.action.customResources.splice(index, 1);
scope.theForm.$setDirty();
}
}
function addResource() {
if (!scope.action.customResources) {
scope.action.customResources = [];
}
scope.action.customResources.push({url: ''});
scope.theForm.$setDirty();
}
function beautifyHtml() {
var res = html_beautify(scope.action.customHtml, {indent_size: 4, wrap_line_length: 60});
scope.action.customHtml = res;
}
function beautifyCss() {
var res = css_beautify(scope.action.customCss, {indent_size: 4});
scope.action.customCss = res;
}
function toggleFullscreen() {
scope.fullscreen = !scope.fullscreen;
if (scope.fullscreen) {
scope.customActionEditorElement = angular.element('.tb-custom-action-editor');
angular.element(scope.customActionEditorElement[0]).ready(function () {
var w = scope.customActionEditorElement.width();
if (w > 0) {
initSplitLayout();
} else {
scope.$watch(
function () {
return scope.customActionEditorElement[0].offsetWidth || parseInt(scope.customActionEditorElement.css('width'), 10);
},
function (newSize) {
if (newSize > 0) {
initSplitLayout();
}
}
);
}
});
} else {
scope.layoutInited = false;
}
}
function onDividerDrag() {
scope.$broadcast('update-ace-editor-size');
for (var i = 0; i < ace_editors.length; i++) {
var ace = ace_editors[i];
ace.resize();
ace.renderer.updateFull();
}
}
function initSplitLayout() {
if (!scope.layoutInited) {
Split([angular.element('#left-panel', scope.customActionEditorElement)[0], angular.element('#right-panel', scope.customActionEditorElement)[0]], {
sizes: [50, 50],
gutterSize: 8,
cursor: 'col-resize',
onDrag: function () {
onDividerDrag()
}
});
onDividerDrag();
scope.$applyAsync(function () {
scope.layoutInited = true;
var w = angular.element($window);
$timeout(function () {
w.triggerHandler('resize')
});
});
}
}
$compile(element.contents())(scope);
};
return {
restrict: "E",
require: "^ngModel",
scope: {
theForm: '=?',
},
link: linker
};
}

View File

@ -0,0 +1,96 @@
/**
* Copyright © 2016-2019 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-custom-action-pretty {
box-sizing: border-box;
padding: 8px;
background-color: #fff;
.tb-fullscreen-panel {
.tb-custom-action-editor-container {
height: calc(100% - 40px);
}
#right-panel {
padding-top: 8px;
padding-left: 3px;
}
tb-js-func .tb-js-func-panel {
box-sizing: border-box;
}
md-tabs-content-wrapper,
md-tab-content {
height: 100%;
}
}
.html-panel,
.css-panel {
width: 100%;
min-width: 200px;
height: 100%;
min-height: 200px;
}
.tb-split {
box-sizing: border-box;
overflow-x: hidden;
overflow-y: auto;
}
.tb-content {
border: 1px solid #c0c0c0;
}
.gutter {
background-color: #eee;
background-repeat: no-repeat;
background-position: 50%;
}
.gutter.gutter-horizontal {
cursor: col-resize;
background-image: url("../../../../../node_modules/split.js/grips/vertical.png");
}
.tb-split.tb-split-horizontal,
.gutter.gutter-horizontal {
float: left;
height: 100%;
}
.tb-action-expand-button {
position: absolute;
right: 14px;
z-index: 1;
&.tb-fullscreen-editor {
position: relative;
right: 0;
}
.md-button {
min-width: auto;
}
}
.tb-custom-action-editor {
&.tb-fullscreen-editor {
height: 100%;
}
}
}

View File

@ -0,0 +1,186 @@
<!--
Copyright © 2016-2019 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-custom-action-pretty md-whiteframe-z1"
tb-expand-fullscreen="fullscreen" fullscreen-zindex="100" hide-expand-button="true">
<div layout="row" layout-align="end center" class="tb-action-expand-button" ng-class="{'tb-fullscreen-editor': fullscreen}">
<md-button hide-xs hide-sm aria-label="{{ 'widget.toggle-fullscreen' | translate }}" ng-click="toggleFullscreen()">
<md-tooltip md-direction="bottom">
{{ 'widget.toggle-fullscreen' | translate }}
</md-tooltip>
<md-icon ng-show="!fullscreen" aria-label="{{ 'widget.toggle-fullscreen' | translate }}">
fullscreen
</md-icon>
<md-icon ng-show="fullscreen" aria-label="{{ 'widget.toggle-fullscreen' | translate }}">
fullscreen_exit
</md-icon>
<span ng-if="fullscreen" translate hide-xs hide-sm>widget.toggle-fullscreen</span>
</md-button>
</div>
<div class="tb-custom-action-editor" ng-class="{'tb-fullscreen-editor': fullscreen}">
<div ng-if="!fullscreen">
<md-tabs md-selected="3" md-dynamic-height md-border-bottom style="width: 100%;">
<md-tab label="{{ 'widget.resources' | translate }}">
<div class="tb-custom-action-editor-container" style="background-color: #fff;">
<div class="md-padding">
<div flex layout="row"
ng-repeat="resource in action.customResources track by $index"
style="max-height: 40px;" layout-align="start center">
<md-input-container flex md-no-float class="md-block"
style="margin: 10px 0px 0px 0px; max-height: 40px;">
<input placeholder="{{ 'widget.resource-url' | translate }}"
ng-required="true" name="resource" ng-model="resource.url">
</md-input-container>
<md-button ng-disabled="$root.loading" class="md-icon-button md-primary"
ng-click="removeResource($index)"
aria-label="{{ 'action.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.remove-resource' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.delete' | translate }}"
class="material-icons">
close
</md-icon>
</md-button>
</div>
<div>
<md-button ng-disabled="$root.loading" class="md-primary md-raised"
ng-click="addResource()"
aria-label="{{ 'action.add' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.add-resource' | translate }}
</md-tooltip>
<span translate>action.add</span>
</md-button>
</div>
</div>
</div>
</md-tab>
<md-tab label="{{ 'widget.css' | translate }}">
<div class="tb-custom-action-editor-container" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button">
<div class="tb-editor-area-title-panel">
<md-button aria-label="{{ 'widget.tidy' | translate }}"
ng-click="beautifyCss()">{{ 'widget.tidy' | translate }}
</md-button>
<md-button id="expand-button"
aria-label="{{ 'fullscreen.fullscreen' | translate }}"
class="md-icon-button tb-md-32"></md-button>
</div>
<div id="css-panel" class="css-panel" ui-ace="cssEditorOptions"
ng-model="action.customCss"></div>
</div>
</md-tab>
<md-tab label="{{ 'widget.html' | translate }}">
<div class="tb-custom-action-editor-container" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button">
<div class="tb-editor-area-title-panel">
<md-button aria-label="{{ 'widget.tidy' | translate }}"
ng-click="beautifyHtml()">{{ 'widget.tidy' | translate }}
</md-button>
<md-button id="expand-button"
aria-label="{{ 'fullscreen.fullscreen' | translate }}"
class="md-icon-button tb-md-32"></md-button>
</div>
<div id="html-panel" class="html-panel" ui-ace="htmlEditorOptions"
ng-model="action.customHtml"></div>
</div>
</md-tab>
<md-tab label="{{ 'widget.js' | translate }}">
<tb-js-func ng-model="action.customFunction"
function-args="{{ ['$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams'] }}"
validation-args="{{ [] }}">
</tb-js-func>
</md-tab>
</md-tabs>
</div>
<div ng-if="fullscreen" class="tb-fullscreen-panel" layout="row" layout-fill>
<div id="left-panel" class="tb-split tb-content">
<md-tabs md-selected="2" md-dynamic-height md-border-bottom layout="column" layout-fill>
<md-tab label="{{ 'widget.resources' | translate }}">
<div class="tb-custom-action-editor-container" style="background-color: #fff;">
<div class="md-padding">
<div flex layout="row"
ng-repeat="resource in action.customResources track by $index"
style="max-height: 40px;" layout-align="start center">
<md-input-container flex md-no-float class="md-block"
style="margin: 10px 0px 0px 0px; max-height: 40px;">
<input placeholder="{{ 'widget.resource-url' | translate }}"
ng-required="true" name="resource" ng-model="resource.url">
</md-input-container>
<md-button ng-disabled="$root.loading" class="md-icon-button md-primary"
ng-click="removeResource($index)"
aria-label="{{ 'action.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.remove-resource' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.delete' | translate }}"
class="material-icons">
close
</md-icon>
</md-button>
</div>
<div>
<md-button ng-disabled="$root.loading" class="md-primary md-raised"
ng-click="addResource()"
aria-label="{{ 'action.add' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.add-resource' | translate }}
</md-tooltip>
<span translate>action.add</span>
</md-button>
</div>
</div>
</div>
</md-tab>
<md-tab label="{{ 'widget.css' | translate }}">
<div class="tb-custom-action-editor-container" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button">
<div class="tb-editor-area-title-panel">
<md-button aria-label="{{ 'widget.tidy' | translate }}"
ng-click="beautifyCss()">{{ 'widget.tidy' | translate }}
</md-button>
<md-button id="expand-button"
aria-label="{{ 'fullscreen.fullscreen' | translate }}"
class="md-icon-button tb-md-32"></md-button>
</div>
<div id="css-panel" class="css-panel" ui-ace="cssEditorOptions"
ng-model="action.customCss"></div>
</div>
</md-tab>
<md-tab label="{{ 'widget.html' | translate }}">
<div class="tb-custom-action-editor-container" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button">
<div class="tb-editor-area-title-panel">
<md-button aria-label="{{ 'widget.tidy' | translate }}"
ng-click="beautifyHtml()">{{ 'widget.tidy' | translate }}
</md-button>
<md-button id="expand-button"
aria-label="{{ 'fullscreen.fullscreen' | translate }}"
class="md-icon-button tb-md-32"></md-button>
</div>
<div id="html-panel" class="html-panel" ui-ace="htmlEditorOptions"
ng-model="action.customHtml"></div>
</div>
</md-tab>
</md-tabs>
</div>
<div id="right-panel" class="tb-split tb-content">
<tb-js-func ng-model="action.customFunction"
function-args="{{ ['$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams'] }}"
validation-args="{{ [] }}" fill-height="true">
</tb-js-func>
</div>
</div>
</div>
</div>

View File

@ -37,6 +37,7 @@ import './manage-widget-actions.scss';
import thingsboardMaterialIconSelect from '../../material-icon-select.directive';
import WidgetActionDialogController from './widget-action-dialog.controller';
import CustomActionPrettyEditor from './custom-action-pretty-editor.directive';
/* eslint-disable import/no-unresolved, import/default */
@ -45,7 +46,7 @@ import widgetActionDialogTemplate from './widget-action-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.widgetActions', [thingsboardMaterialIconSelect])
export default angular.module('thingsboard.directives.widgetActions', [thingsboardMaterialIconSelect, CustomActionPrettyEditor])
.controller('WidgetActionDialogController', WidgetActionDialogController)
.directive('tbManageWidgetActions', ManageWidgetActions)
.name;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
/*@ngInject*/
export default function WidgetActionDialogController($scope, $mdDialog, $filter, $q, dashboardService, dashboardUtils, types, utils,
export default function WidgetActionDialogController($scope, $mdDialog, $filter, $q, dashboardService, dashboardUtils, types, toast, utils,
isAdd, fetchDashboardStates, actionSources, widgetActions, action) {
var vm = this;
@ -41,7 +41,7 @@ export default function WidgetActionDialogController($scope, $mdDialog, $filter,
vm.actionSourceName = actionSourceName;
vm.targetDashboardStateSearchTextChanged = function() {
}
};
vm.dashboardStateSearch = dashboardStateSearch;
vm.cancel = cancel;
@ -155,6 +155,12 @@ export default function WidgetActionDialogController($scope, $mdDialog, $filter,
case vm.types.widgetActionTypes.custom.value:
result.customFunction = action.customFunction;
break;
case vm.types.widgetActionTypes.customPretty.value:
result.customResources = action.customResources;
result.customHtml = action.customHtml;
result.customCss = action.customCss;
result.customFunction = action.customFunction;
break;
}
return result;
}

View File

@ -123,6 +123,7 @@
function-args="{{ ['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams'] }}"
validation-args="{{ [] }}">
</tb-js-func>
<tb-custom-action-pretty-editor ng-model="vm.action" the-form="theForm" ng-if="vm.action.type == vm.types.widgetActionTypes.customPretty.value"></tb-custom-action-pretty-editor>
</fieldset>
</md-content>
</div>

View File

@ -17,12 +17,15 @@ import $ from 'jquery';
import 'javascript-detect-element-resize/detect-element-resize';
import Subscription from '../../api/subscription';
import 'oclazyload';
import cssjs from '../../../vendor/css.js/css';
/* eslint-disable angular/angularelement */
/*@ngInject*/
export default function WidgetController($scope, $state, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
export default function WidgetController($scope, $state, $timeout, $window, $ocLazyLoad, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
datasourceService, alarmService, entityService, dashboardService, deviceService, visibleRect, isEdit, isMobile, dashboardTimewindow,
dashboardTimewindowApi, dashboard, widget, aliasController, stateController, widgetInfo, widgetType) {
dashboardTimewindowApi, dashboard, widget, aliasController, stateController, widgetInfo, widgetType, toast) {
var vm = this;
@ -38,6 +41,12 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
vm.dashboardTimewindow = dashboardTimewindow;
$window.lazyLoad = $ocLazyLoad;
$window.cssjs = cssjs;
var cssParser = new cssjs();
cssParser.testMode = false;
var gridsterItemInited = false;
var subscriptionInited = false;
var widgetSizeDetected = false;
@ -522,7 +531,93 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
}
}
break;
case types.widgetActionTypes.customPretty.value:
var customPrettyFunction = descriptor.customFunction;
var customHtml = descriptor.customHtml;
var customCss = descriptor.customCss;
var customResources = descriptor.customResources;
var actionNamespace = 'custom-action-pretty-'+descriptor.name.toLowerCase();
var htmlTemplate = '';
if (angular.isDefined(customHtml) && customHtml.length > 0) {
htmlTemplate = customHtml;
}
loadCustomActionResources(actionNamespace, customCss, customResources).then(
function success() {
if (angular.isDefined(customPrettyFunction) && customPrettyFunction.length > 0) {
try {
if (!additionalParams) {
additionalParams = {};
}
var customActionPrettyFunction = new Function('$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams', customPrettyFunction);
customActionPrettyFunction($event, widgetContext, entityId, entityName, htmlTemplate, additionalParams);
} catch (e) {
//
}
}
},
function fail(errorMessages) {
processResourcesLoadErrors(errorMessages);
}
);
break;
}
}
function loadCustomActionResources(actionNamespace, customCss, customResources) {
var deferred = $q.defer();
if (angular.isDefined(customCss) && customCss.length > 0) {
cssParser.cssPreviewNamespace = actionNamespace;
cssParser.createStyleElement(actionNamespace, customCss, 'nonamespace');
}
function loadNextOrComplete(i) {
i++;
if (i < customResources.length) {
loadNext(i);
} else {
if (errors.length > 0) {
deferred.reject(errors);
} else {
deferred.resolve();
}
}
}
function loadNext(i) {
var resourceUrl = customResources[i].url;
if (resourceUrl && resourceUrl.length > 0) {
$ocLazyLoad.load(resourceUrl).then(
function success () {
loadNextOrComplete(i);
},
function fail() {
errors.push('Failed to load custom action resource: \'' + resourceUrl + '\'');
loadNextOrComplete(i);
}
);
} else {
loadNextOrComplete(i);
}
}
if (angular.isDefined(customResources) && customResources.length > 0) {
var errors = [];
loadNext(0);
} else {
deferred.resolve();
}
return deferred.promise;
}
function processResourcesLoadErrors(errorMessages) {
var messageToShow = '';
for (var e in errorMessages) {
var error = errorMessages[e];
messageToShow += '<div>' + error + '</div>';
}
toast.showError(messageToShow);
}
function getActiveEntityInfo() {

View File

@ -1512,6 +1512,7 @@
"settings-schema": "Settings schema",
"datakey-settings-schema": "Data key settings schema",
"javascript": "Javascript",
"js": "JS",
"remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?",
"remove-widget-type-text": "After the confirmation the widget type and all related data will become unrecoverable.",
"remove-widget-type": "Remove widget type",
@ -1528,6 +1529,7 @@
"update-dashboard-state": "Update current dashboard state",
"open-dashboard": "Navigate to other dashboard",
"custom": "Custom action",
"custom-pretty": "Custom action (with HTML template)",
"target-dashboard-state": "Target dashboard state",
"target-dashboard-state-required": "Target dashboard state is required",
"set-entity-from-widget": "Set entity from widget",

View File

@ -1464,6 +1464,7 @@
"update-dashboard-state": "Обновить текущее состояние дашборда",
"open-dashboard": "Перейти к другому дашборду",
"custom": "Пользовательское действие",
"custom-pretty": "Пользовательское действие (с HTML шаблоном)",
"target-dashboard-state": "Целевое состояние дашборда",
"target-dashboard-state-required": "Целевое состояние дашборда обязательно",
"set-entity-from-widget": "Установить объект из виджета",

View File

@ -2032,6 +2032,7 @@
"update-dashboard-state": "Оновити поточний стан панелі візуалізації",
"open-dashboard": "Перейти до іншої панелі візуалізації",
"custom": "Дії користувачів",
"custom-pretty": "Дії користувачів (з HTML шаблоном)",
"target-dashboard-state": "Цільовий стан панелі візуалізації",
"target-dashboard-state-required": "Необхідно вказати цільовий стан панелі візуалізації",
"set-entity-from-widget": "Встановити сутність із віджета",