Commit 75702b6474ef424e2a4c77046b50b8b32e302573

Authored by Chantsova Ekaterina
Committed by Igor Kulikov
1 parent d324a89f

Custom action with additional resources

... ... @@ -828,6 +828,10 @@ export default angular.module('thingsboard.types', [])
828 828 custom: {
829 829 name: 'widget-action.custom',
830 830 value: 'custom'
  831 + },
  832 + customPretty: {
  833 + name: 'widget-action.custom-pretty',
  834 + value: 'customPretty'
831 835 }
832 836 },
833 837 systemBundleAlias: {
... ...
  1 +/*
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import './custom-action-pretty-editor.scss';
  17 +import customActionPrettyEditorTemplate from './custom-action-pretty-editor.tpl.html';
  18 +
  19 +import 'brace/ext/language_tools';
  20 +import 'brace/ext/searchbox';
  21 +import 'brace/mode/html';
  22 +import 'brace/mode/css';
  23 +import 'brace/snippets/text';
  24 +import 'brace/snippets/html';
  25 +import 'brace/snippets/css';
  26 +
  27 +import beautify from 'js-beautify';
  28 +import Split from "split.js";
  29 +
  30 +const html_beautify = beautify.html;
  31 +const css_beautify = beautify.css;
  32 +
  33 +export default angular.module('thingsboard.directives.customActionPrettyEditor', [])
  34 + .directive('tbCustomActionPrettyEditor', CustomActionPrettyEditor)
  35 + .name;
  36 +
  37 +/*@ngInject*/
  38 +function CustomActionPrettyEditor($compile, $templateCache, $window, $timeout) {
  39 +
  40 + var linker = function (scope, element, attrs, ngModelCtrl) {
  41 + var template = $templateCache.get(customActionPrettyEditorTemplate);
  42 + element.html(template);
  43 + var ace_editors = [];
  44 + scope.fullscreen = false;
  45 + scope.htmlEditorOptions = {
  46 + useWrapMode: true,
  47 + mode: 'html',
  48 + advanced: {
  49 + enableSnippets: true,
  50 + enableBasicAutocompletion: true,
  51 + enableLiveAutocompletion: true
  52 + },
  53 + onLoad: function (_ace) {
  54 + ace_editors.push(_ace);
  55 + }
  56 + };
  57 + scope.cssEditorOptions = {
  58 + useWrapMode: true,
  59 + mode: 'css',
  60 + advanced: {
  61 + enableSnippets: true,
  62 + enableBasicAutocompletion: true,
  63 + enableLiveAutocompletion: true
  64 + },
  65 + onLoad: function (_ace) {
  66 + ace_editors.push(_ace);
  67 + }
  68 + };
  69 +
  70 + scope.addResource = addResource;
  71 + scope.beautifyCss = beautifyCss;
  72 + scope.beautifyHtml = beautifyHtml;
  73 + scope.removeResource = removeResource;
  74 + scope.toggleFullscreen = toggleFullscreen;
  75 +
  76 + var sampleJsFunction = "/* There are three examples: for delete, edit and add entity */\n" +
  77 + "/* Delete entity example */\n" +
  78 + "//\n" +
  79 + "//var $injector = widgetContext.$scope.$injector;\n" +
  80 + "//var $mdDialog = $injector.get('$mdDialog'),\n" +
  81 + "// $document = $injector.get('$document'),\n" +
  82 + "// types = $injector.get('types'),\n" +
  83 + "// assetService = $injector.get('assetService'),\n" +
  84 + "// deviceService = $injector.get('deviceService')\n" +
  85 + "// $rootScope = $injector.get('$rootScope'),\n" +
  86 + "// $q = $injector.get('$q');\n" +
  87 + "//\n" +
  88 + "//openDeleteEntityDialog();\n" +
  89 + "//\n" +
  90 + "//function openDeleteEntityDialog() {\n" +
  91 + "// var title = 'Delete ' + entityId.entityType\n" +
  92 + "// .toLowerCase() + ' ' +\n" +
  93 + "// entityName;\n" +
  94 + "// var content = 'Are you sure you want to delete the ' +\n" +
  95 + "// entityId.entityType.toLowerCase() + ' ' +\n" +
  96 + "// entityName + '?';\n" +
  97 + "// var confirm = $mdDialog.confirm()\n" +
  98 + "// .targetEvent($event)\n" +
  99 + "// .title(title)\n" +
  100 + "// .htmlContent(content)\n" +
  101 + "// .ariaLabel(title)\n" +
  102 + "// .cancel('Cancel')\n" +
  103 + "// .ok('Delete');\n" +
  104 + "// $mdDialog.show(confirm).then(function() {\n" +
  105 + "// deleteEntity();\n" +
  106 + "// })\n" +
  107 + "//}\n" +
  108 + "//\n" +
  109 + "//function deleteEntity() {\n" +
  110 + "// deleteEntityPromise(entityId).then(\n" +
  111 + "// function success() {\n" +
  112 + "// updateAliasData();\n" +
  113 + "// },\n" +
  114 + "// function fail() {\n" +
  115 + "// showErrorDialog();\n" +
  116 + "// }\n" +
  117 + "// );\n" +
  118 + "//}\n" +
  119 + "//\n" +
  120 + "//function deleteEntityPromise(entityId) {\n" +
  121 + "// if (entityId.entityType == types.entityType.asset) {\n" +
  122 + "// return assetService.deleteAsset(entityId.id);\n" +
  123 + "// } else if (entityId.entityType == types.entityType.device) {\n" +
  124 + "// return deviceService.deleteDevice(entityId.id);\n" +
  125 + "// }\n" +
  126 + "//}\n" +
  127 + "//\n" +
  128 + "//function updateAliasData() {\n" +
  129 + "// var aliasIds = [];\n" +
  130 + "// for (var id in widgetContext.aliasController.resolvedAliases) {\n" +
  131 + "// aliasIds.push(id);\n" +
  132 + "// }\n" +
  133 + "// var tasks = [];\n" +
  134 + "// aliasIds.forEach(function(aliasId) {\n" +
  135 + "// widgetContext.aliasController.setAliasUnresolved(aliasId);\n" +
  136 + "// tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n" +
  137 + "// });\n" +
  138 + "// $q.all(tasks).then(function() {\n" +
  139 + "// $rootScope.$broadcast('entityAliasesChanged', aliasIds);\n" +
  140 + "// });\n" +
  141 + "//}\n" +
  142 + "//\n" +
  143 + "//function showErrorDialog() {\n" +
  144 + "// var title = 'Error';\n" +
  145 + "// var content = 'An error occurred while deleting the entity. Please try again.';\n" +
  146 + "// var alert = $mdDialog.alert()\n" +
  147 + "// .title(title)\n" +
  148 + "// .htmlContent(content)\n" +
  149 + "// .ariaLabel(title)\n" +
  150 + "// .parent(angular.element($document[0].body))\n" +
  151 + "// .targetEvent($event)\n" +
  152 + "// .multiple(true)\n" +
  153 + "// .clickOutsideToClose(true)\n" +
  154 + "// .ok('CLOSE');\n" +
  155 + "// $mdDialog.show(alert);\n" +
  156 + "//}\n" +
  157 + "//\n" +
  158 + "/* Edit entity example */\n" +
  159 + "//\n" +
  160 + "//var $injector = widgetContext.$scope.$injector;\n" +
  161 + "//var $mdDialog = $injector.get('$mdDialog'),\n" +
  162 + "// $document = $injector.get('$document'),\n" +
  163 + "// $q = $injector.get('$q'),\n" +
  164 + "// types = $injector.get('types'),\n" +
  165 + "// $rootScope = $injector.get('$rootScope'),\n" +
  166 + "// entityService = $injector.get('entityService'),\n" +
  167 + "// attributeService = $injector.get('attributeService'),\n" +
  168 + "// entityRelationService = $injector.get('entityRelationService');\n" +
  169 + "//\n" +
  170 + "//openEditEntityDialog();\n" +
  171 + "//\n" +
  172 + "//function openEditEntityDialog() {\n" +
  173 + "// $mdDialog.show({\n" +
  174 + "// controller: ['$scope','$mdDialog', EditEntityDialogController],\n" +
  175 + "// controllerAs: 'vm',\n" +
  176 + "// template: htmlTemplate,\n" +
  177 + "// locals: {\n" +
  178 + "// entityId: entityId\n" +
  179 + "// },\n" +
  180 + "// parent: angular.element($document[0].body),\n" +
  181 + "// targetEvent: $event,\n" +
  182 + "// multiple: true,\n" +
  183 + "// clickOutsideToClose: false\n" +
  184 + "// });\n" +
  185 + "//}\n" +
  186 + "//\n" +
  187 + "//function EditEntityDialogController($scope,$mdDialog) {\n" +
  188 + "// var vm = this;\n" +
  189 + "// vm.entityId = entityId;\n" +
  190 + "// vm.entityName = entityName;\n" +
  191 + "// vm.entityType = entityId.entityType;\n" +
  192 + "// vm.allowedEntityTypes = [types.entityType.asset, types.entityType.device];\n" +
  193 + "// vm.allowedRelatedEntityTypes = [];\n" +
  194 + "// vm.entitySearchDirection = types.entitySearchDirection;\n" +
  195 + "// vm.attributes = {};\n" +
  196 + "// vm.serverAttributes = {};\n" +
  197 + "// vm.relations = [];\n" +
  198 + "// vm.newRelations = [];\n" +
  199 + "// vm.relationsToDelete = [];\n" +
  200 + "// getEntityInfo();\n" +
  201 + "// \n" +
  202 + "// vm.addRelation = function() {\n" +
  203 + "// var relation = {\n" +
  204 + "// direction: null,\n" +
  205 + "// relationType: null,\n" +
  206 + "// relatedEntity: null\n" +
  207 + "// };\n" +
  208 + "// vm.newRelations.push(relation);\n" +
  209 + "// $scope.editEntityForm.$setDirty();\n" +
  210 + "// };\n" +
  211 + "// vm.removeRelation = function(index) {\n" +
  212 + "// if (index > -1) {\n" +
  213 + "// vm.newRelations.splice(index, 1);\n" +
  214 + "// $scope.editEntityForm.$setDirty();\n" +
  215 + "// }\n" +
  216 + "// };\n" +
  217 + "// vm.removeOldRelation = function(index, relation) {\n" +
  218 + "// if (index > -1) {\n" +
  219 + "// vm.relations.splice(index, 1);\n" +
  220 + "// vm.relationsToDelete.push(relation);\n" +
  221 + "// $scope.editEntityForm.$setDirty();\n" +
  222 + "// }\n" +
  223 + "// };\n" +
  224 + "// vm.save = function() {\n" +
  225 + "// saveAttributes();\n" +
  226 + "// saveRelations();\n" +
  227 + "// $scope.editEntityForm.$setPristine();\n" +
  228 + "// };\n" +
  229 + "// vm.cancel = function() {\n" +
  230 + "// $mdDialog.hide();\n" +
  231 + "// };\n" +
  232 + "// \n" +
  233 + "// function getEntityAttributes(attributes) {\n" +
  234 + "// for (var i = 0; i < attributes.length; i++) {\n" +
  235 + "// vm.attributes[attributes[i].key] = attributes[i].value; \n" +
  236 + "// }\n" +
  237 + "// vm.serverAttributes = angular.copy(vm.attributes);\n" +
  238 + "// }\n" +
  239 + "// \n" +
  240 + "// function getEntityRelations(relations) {\n" +
  241 + "// var relationsFrom = relations[0];\n" +
  242 + "// var relationsTo = relations[1];\n" +
  243 + "// for (var i=0; i < relationsFrom.length; i++) {\n" +
  244 + "// var relation = {\n" +
  245 + "// direction: types.entitySearchDirection.from,\n" +
  246 + "// relationType: relationsFrom[i].type,\n" +
  247 + "// relatedEntity: relationsFrom[i].to\n" +
  248 + "// };\n" +
  249 + "// vm.relations.push(relation);\n" +
  250 + "// }\n" +
  251 + "// for (var i=0; i < relationsTo.length; i++) {\n" +
  252 + "// var relation = {\n" +
  253 + "// direction: types.entitySearchDirection.to,\n" +
  254 + "// relationType: relationsTo[i].type,\n" +
  255 + "// relatedEntity: relationsTo[i].from\n" +
  256 + "// };\n" +
  257 + "// vm.relations.push(relation);\n" +
  258 + "// }\n" +
  259 + "// }\n" +
  260 + "// \n" +
  261 + "// function getEntityInfo() {\n" +
  262 + "// entityService.getEntity(entityId.entityType, entityId.id).then(\n" +
  263 + "// function(entity) {\n" +
  264 + "// vm.entity = entity;\n" +
  265 + "// vm.type = vm.entity.type;\n" +
  266 + "// });\n" +
  267 + "// attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\n" +
  268 + "// function(data){\n" +
  269 + "// if (data.length) {\n" +
  270 + "// getEntityAttributes(data);\n" +
  271 + "// }\n" +
  272 + "// });\n" +
  273 + "// $q.all([entityRelationService.findInfoByFrom(entityId.id, entityId.entityType), entityRelationService.findInfoByTo(entityId.id, entityId.entityType)]).then(\n" +
  274 + "// function(relations){\n" +
  275 + "// getEntityRelations(relations);\n" +
  276 + "// });\n" +
  277 + "// }\n" +
  278 + "// \n" +
  279 + "// function saveAttributes() {\n" +
  280 + "// var attributesArray = [];\n" +
  281 + "// for (var key in vm.attributes) {\n" +
  282 + "// if (vm.attributes[key] !== vm.serverAttributes[key]) {\n" +
  283 + "// attributesArray.push({key: key, value: vm.attributes[key]});\n" +
  284 + "// }\n" +
  285 + "// }\n" +
  286 + "// if (attributesArray.length > 0) {\n" +
  287 + "// attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray);\n" +
  288 + "// } \n" +
  289 + "// }\n" +
  290 + "// \n" +
  291 + "// function saveRelations() {\n" +
  292 + "// var tasks = [];\n" +
  293 + "// for (var i=0; i < vm.newRelations.length; i++) {\n" +
  294 + "// var relation = {\n" +
  295 + "// type: vm.newRelations[i].relationType\n" +
  296 + "// };\n" +
  297 + "// if (vm.newRelations[i].direction == types.entitySearchDirection.from) {\n" +
  298 + "// relation.to = vm.newRelations[i].relatedEntity;\n" +
  299 + "// relation.from = entityId;\n" +
  300 + "// } else {\n" +
  301 + "// relation.to = entityId;\n" +
  302 + "// relation.from = vm.newRelations[i].relatedEntity;\n" +
  303 + "// }\n" +
  304 + "// tasks.push(entityRelationService.saveRelation(relation));\n" +
  305 + "// }\n" +
  306 + "// for (var i=0; i < vm.relationsToDelete.length; i++) {\n" +
  307 + "// var relation = {\n" +
  308 + "// type: vm.relationsToDelete[i].relationType\n" +
  309 + "// };\n" +
  310 + "// if (vm.relationsToDelete[i].direction == types.entitySearchDirection.from) {\n" +
  311 + "// relation.to = vm.relationsToDelete[i].relatedEntity;\n" +
  312 + "// relation.from = entityId;\n" +
  313 + "// } else {\n" +
  314 + "// relation.to = entityId;\n" +
  315 + "// relation.from = vm.relationsToDelete[i].relatedEntity;\n" +
  316 + "// }\n" +
  317 + "// tasks.push(entityRelationService.deleteRelation(relation.from.id, relation.from.entityType, relation.type, relation.to.id, relation.to.entityType));\n" +
  318 + "// }\n" +
  319 + "// $q.all(tasks).then(function(){\n" +
  320 + "// vm.relations = vm.relations.concat(vm.newRelations);\n" +
  321 + "// vm.newRelations = [];\n" +
  322 + "// vm.relationsToDelete = [];\n" +
  323 + "// updateAliasData();\n" +
  324 + "// });\n" +
  325 + "// }\n" +
  326 + "// \n" +
  327 + "// function updateAliasData() {\n" +
  328 + "// var aliasIds = [];\n" +
  329 + "// for (var id in widgetContext.aliasController.resolvedAliases) {\n" +
  330 + "// aliasIds.push(id);\n" +
  331 + "// }\n" +
  332 + "// var tasks = [];\n" +
  333 + "// aliasIds.forEach(function(aliasId) {\n" +
  334 + "// widgetContext.aliasController.setAliasUnresolved(aliasId);\n" +
  335 + "// tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n" +
  336 + "// });\n" +
  337 + "// $q.all(tasks).then(function() {\n" +
  338 + "// $rootScope.$broadcast('entityAliasesChanged', aliasIds);\n" +
  339 + "// });\n" +
  340 + "// }\n" +
  341 + "//}\n" +
  342 + "//\n" +
  343 + "/* Add entity example */\n" +
  344 + "//\n" +
  345 + "//var $injector = widgetContext.$scope.$injector;\n" +
  346 + "//var $mdDialog = $injector.get('$mdDialog'),\n" +
  347 + "// $document = $injector.get('$document'),\n" +
  348 + "// $q = $injector.get('$q'),\n" +
  349 + "// $rootScope = $injector.get('$rootScope'),\n" +
  350 + "// types = $injector.get('types'),\n" +
  351 + "// assetService = $injector.get('assetService'),\n" +
  352 + "// deviceService = $injector.get('deviceService'),\n" +
  353 + "// attributeService = $injector.get('attributeService'),\n" +
  354 + "// entityRelationService = $injector.get('entityRelationService');\n" +
  355 + "//\n" +
  356 + "//openAddEntityDialog();\n" +
  357 + "//\n" +
  358 + "//function openAddEntityDialog() {\n" +
  359 + "// $mdDialog.show({\n" +
  360 + "// controller: ['$scope','$mdDialog', AddEntityDialogController],\n" +
  361 + "// controllerAs: 'vm',\n" +
  362 + "// template: htmlTemplate,\n" +
  363 + "// locals: {\n" +
  364 + "// entityId: entityId\n" +
  365 + "// },\n" +
  366 + "// parent: angular.element($document[0].body),\n" +
  367 + "// targetEvent: $event,\n" +
  368 + "// multiple: true,\n" +
  369 + "// clickOutsideToClose: false\n" +
  370 + "// });\n" +
  371 + "//}\n" +
  372 + "//\n" +
  373 + "//function AddEntityDialogController($scope, $mdDialog) {\n" +
  374 + "// var vm = this;\n" +
  375 + "// vm.allowedEntityTypes = [types.entityType.asset, types.entityType.device];\n" +
  376 + "// vm.allowedRelatedEntityTypes = [];\n" +
  377 + "// vm.entitySearchDirection = types.entitySearchDirection;\n" +
  378 + "// vm.attributes = {};\n" +
  379 + "// vm.relations = [];\n" +
  380 + "// \n" +
  381 + "// vm.addRelation = function() {\n" +
  382 + "// var relation = {\n" +
  383 + "// direction: null,\n" +
  384 + "// relationType: null,\n" +
  385 + "// relatedEntity: null\n" +
  386 + "// };\n" +
  387 + "// vm.relations.push(relation);\n" +
  388 + "// };\n" +
  389 + "// vm.removeRelation = function(index) {\n" +
  390 + "// if (index > -1) {\n" +
  391 + "// vm.relations.splice(index, 1);\n" +
  392 + "// }\n" +
  393 + "// };\n" +
  394 + "// vm.save = function() {\n" +
  395 + "// $scope.addEntityForm.$setPristine();\n" +
  396 + "// saveEntityPromise().then(\n" +
  397 + "// function (entity) {\n" +
  398 + "// saveAttributes(entity.id);\n" +
  399 + "// saveRelations(entity.id);\n" +
  400 + "// $mdDialog.hide();\n" +
  401 + "// }\n" +
  402 + "// );\n" +
  403 + "// };\n" +
  404 + "// vm.cancel = function() {\n" +
  405 + "// $mdDialog.hide();\n" +
  406 + "// };\n" +
  407 + "// \n" +
  408 + "// \n" +
  409 + "// function saveEntityPromise() {\n" +
  410 + "// var entity = {\n" +
  411 + "// name: vm.entityName,\n" +
  412 + "// type: vm.type\n" +
  413 + "// };\n" +
  414 + "// if (vm.entityType == types.entityType.asset) {\n" +
  415 + "// return assetService.saveAsset(entity);\n" +
  416 + "// } else if (vm.entityType == types.entityType.device) {\n" +
  417 + "// return deviceService.saveDevice(entity);\n" +
  418 + "// }\n" +
  419 + "// }\n" +
  420 + "// \n" +
  421 + "// function saveAttributes(entityId) {\n" +
  422 + "// var attributesArray = [];\n" +
  423 + "// for (var key in vm.attributes) {\n" +
  424 + "// attributesArray.push({key: key, value: vm.attributes[key]});\n" +
  425 + "// }\n" +
  426 + "// if (attributesArray.length > 0) {\n" +
  427 + "// attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray);\n" +
  428 + "// } \n" +
  429 + "// }\n" +
  430 + "// \n" +
  431 + "// function saveRelations(entityId) {\n" +
  432 + "// var tasks = [];\n" +
  433 + "// for (var i=0; i < vm.relations.length; i++) {\n" +
  434 + "// var relation = {\n" +
  435 + "// type: vm.relations[i].relationType\n" +
  436 + "// };\n" +
  437 + "// if (vm.relations[i].direction == types.entitySearchDirection.from) {\n" +
  438 + "// relation.to = vm.relations[i].relatedEntity;\n" +
  439 + "// relation.from = entityId;\n" +
  440 + "// } else {\n" +
  441 + "// relation.to = entityId;\n" +
  442 + "// relation.from = vm.relations[i].relatedEntity;\n" +
  443 + "// }\n" +
  444 + "// tasks.push(entityRelationService.saveRelation(relation));\n" +
  445 + "// }\n" +
  446 + "// $q.all(tasks).then(function(){\n" +
  447 + "// updateAliasData();\n" +
  448 + "// });\n" +
  449 + "// }\n" +
  450 + "// \n" +
  451 + "// function updateAliasData() {\n" +
  452 + "// var aliasIds = [];\n" +
  453 + "// for (var id in widgetContext.aliasController.resolvedAliases) {\n" +
  454 + "// aliasIds.push(id);\n" +
  455 + "// }\n" +
  456 + "// var tasks = [];\n" +
  457 + "// aliasIds.forEach(function(aliasId) {\n" +
  458 + "// widgetContext.aliasController.setAliasUnresolved(aliasId);\n" +
  459 + "// tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n" +
  460 + "// });\n" +
  461 + "// $q.all(tasks).then(function() {\n" +
  462 + "// $rootScope.$broadcast('entityAliasesChanged', aliasIds);\n" +
  463 + "// });\n" +
  464 + "// }\n" +
  465 + "//}\n" +
  466 + "\n" +
  467 + "\n";
  468 +
  469 + var sampleHtmlTemplate = '<!-- There are two example templates: for edit and add entity -->\n' +
  470 + '<!-- Edit entity example -->\n' +
  471 + '<!-- -->\n' +
  472 + '<!--<md-dialog aria-label="Edit entity">-->\n' +
  473 + '<!-- <form name="editEntityForm" class="edit-entity-form" ng-submit="vm.save()">-->\n' +
  474 + '<!-- <md-toolbar>-->\n' +
  475 + '<!-- <div class="md-toolbar-tools">-->\n' +
  476 + '<!-- <h2>Edit {{vm.entityType.toLowerCase()}} {{vm.entityName}}</h2>-->\n' +
  477 + '<!-- <span flex></span>-->\n' +
  478 + '<!-- <md-button class="md-icon-button" ng-click="vm.cancel()">-->\n' +
  479 + '<!-- <ng-md-icon icon="close" aria-label="Close"></ng-md-icon>-->\n' +
  480 + '<!-- </md-button>-->\n' +
  481 + '<!-- </div>-->\n' +
  482 + '<!-- </md-toolbar>-->\n' +
  483 + '<!-- <md-dialog-content>-->\n' +
  484 + '<!-- <div class="md-dialog-content">-->\n' +
  485 + '<!-- <div layout="row">-->\n' +
  486 + '<!-- <md-input-container flex class="md-block">-->\n' +
  487 + '<!-- <label>Entity Name</label>-->\n' +
  488 + '<!-- <input ng-model="vm.entityName" readonly>-->\n' +
  489 + '<!-- </md-input-container>-->\n' +
  490 + '<!-- <md-input-container flex class="md-block">-->\n' +
  491 + '<!-- <label>Entity Type</label>-->\n' +
  492 + '<!-- <input ng-model="vm.entityType" readonly>-->\n' +
  493 + '<!-- </md-input-container>-->\n' +
  494 + '<!-- <md-input-container flex class="md-block">-->\n' +
  495 + '<!-- <label>Type</label>-->\n' +
  496 + '<!-- <input ng-model="vm.type" readonly>-->\n' +
  497 + '<!-- </md-input-container>-->\n' +
  498 + '<!-- </div>-->\n' +
  499 + '<!-- <div layout="row">-->\n' +
  500 + '<!-- <md-input-container flex class="md-block">-->\n' +
  501 + '<!-- <label>Latitude</label>-->\n' +
  502 + '<!-- <input name="latitude" type="number" step="any" ng-model="vm.attributes.latitude">-->\n' +
  503 + '<!-- </md-input-container>-->\n' +
  504 + '<!-- <md-input-container flex class="md-block">-->\n' +
  505 + '<!-- <label>Longitude</label>-->\n' +
  506 + '<!-- <input name="longitude" type="number" step="any" ng-model="vm.attributes.longitude">-->\n' +
  507 + '<!-- </md-input-container>-->\n' +
  508 + '<!-- </div>-->\n' +
  509 + '<!-- <div layout="row">-->\n' +
  510 + '<!-- <md-input-container flex class="md-block">-->\n' +
  511 + '<!-- <label>Address</label>-->\n' +
  512 + '<!-- <input ng-model="vm.attributes.address">-->\n' +
  513 + '<!-- </md-input-container>-->\n' +
  514 + '<!-- <md-input-container flex class="md-block">-->\n' +
  515 + '<!-- <label>Owner</label>-->\n' +
  516 + '<!-- <input ng-model="vm.attributes.owner">-->\n' +
  517 + '<!-- </md-input-container>-->\n' +
  518 + '<!-- </div>-->\n' +
  519 + '<!-- <div layout="row">-->\n' +
  520 + '<!-- <md-input-container flex class="md-block">-->\n' +
  521 + '<!-- <label>Integer Value</label>-->\n' +
  522 + '<!-- <input name="integerNumber" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="vm.attributes.number">-->\n' +
  523 + '<!-- <div ng-messages="editEntityForm.integerNumber.$error">-->\n' +
  524 + '<!-- <div ng-message="pattern">Invalid integer value.</div>-->\n' +
  525 + '<!-- </div>-->\n' +
  526 + '<!-- </md-input-container>-->\n' +
  527 + '<!-- <div class="boolean-value-input" layout="column" layout-align="center start" flex>-->\n' +
  528 + '<!-- <label class="checkbox-label">Boolean Value</label>-->\n' +
  529 + '<!-- <md-checkbox ng-model="vm.attributes.booleanValue" style="margin-bottom: 40px;">-->\n' +
  530 + '<!-- {{ (vm.attributes.booleanValue ? "value.true" : "value.false") | translate }}-->\n' +
  531 + '<!-- </md-checkbox>-->\n' +
  532 + '<!-- </div>-->\n' +
  533 + '<!-- </div>-->\n' +
  534 + '<!-- <div class="relations-list old-relations">-->\n' +
  535 + '<!-- <div class="md-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div>-->\n' +
  536 + '<!-- <div class="body" ng-show="vm.relations.length">-->\n' +
  537 + '<!-- <div class="row" layout="row" layout-align="start center" ng-repeat="relation in vm.relations track by $index">-->\n' +
  538 + '<!-- <div class="md-whiteframe-1dp" flex layout="row" style="padding-left: 5px; margin-bottom: 3px;">-->\n' +
  539 + '<!-- <div flex layout="column">-->\n' +
  540 + '<!-- <div layout="row">-->\n' +
  541 + '<!-- <md-input-container class="md-block" style="min-width: 100px;">-->\n' +
  542 + '<!-- <label>Direction</label>-->\n' +
  543 + '<!-- <md-select ng-disabled="true" required ng-model="relation.direction">-->\n' +
  544 + '<!-- <md-option ng-repeat="direction in vm.entitySearchDirection" ng-value="direction">-->\n' +
  545 + '<!-- {{ ("relation.search-direction." + direction) | translate}}-->\n' +
  546 + '<!-- </md-option>-->\n' +
  547 + '<!-- </md-select>-->\n' +
  548 + '<!-- </md-input-container>-->\n' +
  549 + '<!-- <tb-relation-type-autocomplete ng-disabled="true" flex class="md-block"-->\n' +
  550 + '<!-- the-form="editEntityForm"-->\n' +
  551 + '<!-- ng-model="relation.relationType"-->\n' +
  552 + '<!-- tb-required="true">-->\n' +
  553 + '<!-- </tb-relation-type-autocomplete>-->\n' +
  554 + '<!-- </div>-->\n' +
  555 + '<!-- <div layout="row">-->\n' +
  556 + '<!-- <tb-entity-select flex class="md-block"-->\n' +
  557 + '<!-- the-form="editEntityForm"-->\n' +
  558 + '<!-- ng-disabled="true"-->\n' +
  559 + '<!-- tb-required="true"-->\n' +
  560 + '<!-- ng-model="relation.relatedEntity">-->\n' +
  561 + '<!-- </tb-entity-select>-->\n' +
  562 + '<!-- </div>-->\n' +
  563 + '<!-- </div>-->\n' +
  564 + '<!-- <div layout="column" layout-align="center center">-->\n' +
  565 + '<!-- <md-button class="md-icon-button md-primary" style="width: 40px; min-width: 40px;"-->\n' +
  566 + '<!-- ng-click="vm.removeOldRelation($index,relation)" aria-label="Remove">-->\n' +
  567 + '<!-- <md-tooltip md-direction="top">Remove relation</md-tooltip>-->\n' +
  568 + '<!-- <md-icon aria-label="Remove" class="material-icons">-->\n' +
  569 + '<!-- close-->\n' +
  570 + '<!-- </md-icon>-->\n' +
  571 + '<!-- </md-button>-->\n' +
  572 + '<!-- </div>-->\n' +
  573 + '<!-- </div>-->\n' +
  574 + '<!-- </div>-->\n' +
  575 + '<!-- </div>-->\n' +
  576 + '<!-- </div>-->\n' +
  577 + '<!-- <div class="relations-list">-->\n' +
  578 + '<!-- <div class="md-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">New Relations</div>-->\n' +
  579 + '<!-- <div class="body" ng-show="vm.newRelations.length">-->\n' +
  580 + '<!-- <div class="row" layout="row" layout-align="start center" ng-repeat="relation in vm.newRelations track by $index">-->\n' +
  581 + '<!-- <div class="md-whiteframe-1dp" flex layout="row" style="padding-left: 5px; margin-bottom: 3px;">-->\n' +
  582 + '<!-- <div flex layout="column">-->\n' +
  583 + '<!-- <div layout="row">-->\n' +
  584 + '<!-- <md-input-container class="md-block" style="min-width: 100px;">-->\n' +
  585 + '<!-- <label>Direction</label>-->\n' +
  586 + '<!-- <md-select name="direction" required ng-model="relation.direction">-->\n' +
  587 + '<!-- <md-option ng-repeat="direction in vm.entitySearchDirection" ng-value="direction">-->\n' +
  588 + '<!-- {{ ("relation.search-direction." + direction) | translate}}-->\n' +
  589 + '<!-- </md-option>-->\n' +
  590 + '<!-- </md-select>-->\n' +
  591 + '<!-- <div ng-messages="editEntityForm.direction.$error">-->\n' +
  592 + '<!-- <div ng-message="required">Relation direction is required.</div>-->\n' +
  593 + '<!-- </div>-->\n' +
  594 + '<!-- </md-input-container>-->\n' +
  595 + '<!-- <tb-relation-type-autocomplete flex class="md-block"-->\n' +
  596 + '<!-- the-form="editEntityForm"-->\n' +
  597 + '<!-- ng-model="relation.relationType"-->\n' +
  598 + '<!-- tb-required="true">-->\n' +
  599 + '<!-- </tb-relation-type-autocomplete>-->\n' +
  600 + '<!-- </div>-->\n' +
  601 + '<!-- <div layout="row">-->\n' +
  602 + '<!-- <tb-entity-select flex class="md-block"-->\n' +
  603 + '<!-- the-form="editEntityForm"-->\n' +
  604 + '<!-- tb-required="true"-->\n' +
  605 + '<!-- ng-model="relation.relatedEntity">-->\n' +
  606 + '<!-- </tb-entity-select>-->\n' +
  607 + '<!-- </div>-->\n' +
  608 + '<!-- </div>-->\n' +
  609 + '<!-- <div layout="column" layout-align="center center">-->\n' +
  610 + '<!-- <md-button class="md-icon-button md-primary" style="width: 40px; min-width: 40px;"-->\n' +
  611 + '<!-- ng-click="vm.removeRelation($index)" aria-label="Remove">-->\n' +
  612 + '<!-- <md-tooltip md-direction="top">Remove relation</md-tooltip>-->\n' +
  613 + '<!-- <md-icon aria-label="Remove" class="material-icons">-->\n' +
  614 + '<!-- close-->\n' +
  615 + '<!-- </md-icon>-->\n' +
  616 + '<!-- </md-button>-->\n' +
  617 + '<!-- </div>-->\n' +
  618 + '<!-- </div>-->\n' +
  619 + '<!-- </div>-->\n' +
  620 + '<!-- </div>-->\n' +
  621 + '<!-- <div>-->\n' +
  622 + '<!-- <md-button class="md-primary md-raised" ng-click="vm.addRelation()" aria-label="Add">-->\n' +
  623 + '<!-- <md-tooltip md-direction="top">Add Relation</md-tooltip>-->\n' +
  624 + '<!-- Add-->\n' +
  625 + '<!-- </md-button>-->\n' +
  626 + '<!-- </div> -->\n' +
  627 + '<!-- </div>-->\n' +
  628 + '<!-- </div>-->\n' +
  629 + '<!-- </md-dialog-content>-->\n' +
  630 + '<!-- <md-dialog-actions>-->\n' +
  631 + '<!-- <md-button type="submit" ng-disabled="editEntityForm.$invalid || !editEntityForm.$dirty" class="md-raised md-primary">Save</md-button>-->\n' +
  632 + '<!-- <md-button ng-click="vm.cancel()" class="md-primary">Cancel</md-button>-->\n' +
  633 + '<!-- </md-dialog-actions>-->\n' +
  634 + '<!-- </form>-->\n' +
  635 + '<!--</md-dialog>-->\n' +
  636 + '<!-- -->\n' +
  637 + '<!-- Add entity example -->\n' +
  638 + '<!-- -->\n' +
  639 + '<!--<md-dialog aria-label="Add entity">-->\n' +
  640 + '<!-- <form name="addEntityForm" class="add-entity-form" ng-submit="vm.save()">-->\n' +
  641 + '<!-- <md-toolbar>-->\n' +
  642 + '<!-- <div class="md-toolbar-tools">-->\n' +
  643 + '<!-- <h2>Add entity</h2>-->\n' +
  644 + '<!-- <span flex></span>-->\n' +
  645 + '<!-- <md-button class="md-icon-button" ng-click="vm.cancel()">-->\n' +
  646 + '<!-- <ng-md-icon icon="close" aria-label="Close"></ng-md-icon>-->\n' +
  647 + '<!-- </md-button>-->\n' +
  648 + '<!-- </div>-->\n' +
  649 + '<!-- </md-toolbar>-->\n' +
  650 + '<!-- <md-dialog-content>-->\n' +
  651 + '<!-- <div class="md-dialog-content">-->\n' +
  652 + '<!-- <div layout="row">-->\n' +
  653 + '<!-- <md-input-container flex class="md-block">-->\n' +
  654 + '<!-- <label>Entity Name</label>-->\n' +
  655 + '<!-- <input ng-model="vm.entityName" name=entityName required>-->\n' +
  656 + '<!-- <div ng-messages="addEntityForm.entityName.$error">-->\n' +
  657 + '<!-- <div ng-message="required">Entity name is required.</div>-->\n' +
  658 + '<!-- </div>-->\n' +
  659 + '<!-- </md-input-container>-->\n' +
  660 + '<!-- <tb-entity-type-select class="md-block" style="min-width: 100px; width: 100px;"-->\n' +
  661 + '<!-- the-form="addEntityForm"-->\n' +
  662 + '<!-- tb-required="true"-->\n' +
  663 + '<!-- allowed-entity-types="vm.allowedEntityTypes"-->\n' +
  664 + '<!-- ng-model="vm.entityType">-->\n' +
  665 + '<!-- </tb-entity-type-select>-->\n' +
  666 + '<!-- <md-input-container flex class="md-block">-->\n' +
  667 + '<!-- <label>Entity Subtype</label>-->\n' +
  668 + '<!-- <input ng-model="vm.type" name=type required>-->\n' +
  669 + '<!-- <div ng-messages="addEntityForm.type.$error">-->\n' +
  670 + '<!-- <div ng-message="required">Entity subtype is required.</div>-->\n' +
  671 + '<!-- </div>-->\n' +
  672 + '<!-- </md-input-container>-->\n' +
  673 + '<!-- </div>-->\n' +
  674 + '<!-- <div layout="row">-->\n' +
  675 + '<!-- <md-input-container flex class="md-block">-->\n' +
  676 + '<!-- <label>Latitude</label>-->\n' +
  677 + '<!-- <input name="latitude" type="number" step="any" ng-model="vm.attributes.latitude">-->\n' +
  678 + '<!-- </md-input-container>-->\n' +
  679 + '<!-- <md-input-container flex class="md-block">-->\n' +
  680 + '<!-- <label>Longitude</label>-->\n' +
  681 + '<!-- <input name="longitude" type="number" step="any" ng-model="vm.attributes.longitude">-->\n' +
  682 + '<!-- </md-input-container>-->\n' +
  683 + '<!-- </div>-->\n' +
  684 + '<!-- <div layout="row">-->\n' +
  685 + '<!-- <md-input-container flex class="md-block">-->\n' +
  686 + '<!-- <label>Address</label>-->\n' +
  687 + '<!-- <input ng-model="vm.attributes.address">-->\n' +
  688 + '<!-- </md-input-container>-->\n' +
  689 + '<!-- <md-input-container flex class="md-block">-->\n' +
  690 + '<!-- <label>Owner</label>-->\n' +
  691 + '<!-- <input ng-model="vm.attributes.owner">-->\n' +
  692 + '<!-- </md-input-container>-->\n' +
  693 + '<!-- </div>-->\n' +
  694 + '<!-- <div layout="row">-->\n' +
  695 + '<!-- <md-input-container flex class="md-block">-->\n' +
  696 + '<!-- <label>Integer Value</label>-->\n' +
  697 + '<!-- <input name="integerNumber" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="vm.attributes.number">-->\n' +
  698 + '<!-- <div ng-messages="addEntityForm.integerNumber.$error">-->\n' +
  699 + '<!-- <div ng-message="pattern">Invalid integer value.</div>-->\n' +
  700 + '<!-- </div>-->\n' +
  701 + '<!-- </md-input-container>-->\n' +
  702 + '<!-- <div class="boolean-value-input" layout="column" layout-align="center start" flex>-->\n' +
  703 + '<!-- <label class="checkbox-label">Boolean Value</label>-->\n' +
  704 + '<!-- <md-checkbox ng-model="vm.attributes.booleanValue" style="margin-bottom: 40px;">-->\n' +
  705 + '<!-- {{ (vm.attributes.booleanValue ? "value.true" : "value.false") | translate }}-->\n' +
  706 + '<!-- </md-checkbox>-->\n' +
  707 + '<!-- </div>-->\n' +
  708 + '<!-- </div>-->\n' +
  709 + '<!-- <div class="relations-list">-->\n' +
  710 + '<!-- <div class="md-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div>-->\n' +
  711 + '<!-- <div class="body" ng-show="vm.relations.length">-->\n' +
  712 + '<!-- <div class="row" layout="row" layout-align="start center" ng-repeat="relation in vm.relations track by $index">-->\n' +
  713 + '<!-- <div class="md-whiteframe-1dp" flex layout="row" style="padding-left: 5px;">-->\n' +
  714 + '<!-- <div flex layout="column">-->\n' +
  715 + '<!-- <div layout="row">-->\n' +
  716 + '<!-- <md-input-container class="md-block" style="min-width: 100px;">-->\n' +
  717 + '<!-- <label>Direction</label>-->\n' +
  718 + '<!-- <md-select name="direction" required ng-model="relation.direction">-->\n' +
  719 + '<!-- <md-option ng-repeat="direction in vm.entitySearchDirection" ng-value="direction">-->\n' +
  720 + '<!-- {{ ("relation.search-direction." + direction) | translate}}-->\n' +
  721 + '<!-- </md-option>-->\n' +
  722 + '<!-- </md-select>-->\n' +
  723 + '<!-- <div ng-messages="addEntityForm.direction.$error">-->\n' +
  724 + '<!-- <div ng-message="required">Relation direction is required.</div>-->\n' +
  725 + '<!-- </div>-->\n' +
  726 + '<!-- </md-input-container>-->\n' +
  727 + '<!-- <tb-relation-type-autocomplete flex class="md-block"-->\n' +
  728 + '<!-- the-form="addEntityForm"-->\n' +
  729 + '<!-- ng-model="relation.relationType"-->\n' +
  730 + '<!-- tb-required="true">-->\n' +
  731 + '<!-- </tb-relation-type-autocomplete>-->\n' +
  732 + '<!-- </div>-->\n' +
  733 + '<!-- <div layout="row">-->\n' +
  734 + '<!-- <tb-entity-select flex class="md-block"-->\n' +
  735 + '<!-- the-form="addEntityForm"-->\n' +
  736 + '<!-- tb-required="true"-->\n' +
  737 + '<!-- ng-model="relation.relatedEntity">-->\n' +
  738 + '<!-- </tb-entity-select>-->\n' +
  739 + '<!-- </div>-->\n' +
  740 + '<!-- </div>-->\n' +
  741 + '<!-- <div layout="column" layout-align="center center">-->\n' +
  742 + '<!-- <md-button class="md-icon-button md-primary" style="width: 40px; min-width: 40px;"-->\n' +
  743 + '<!-- ng-click="vm.removeRelation($index)" aria-label="Remove">-->\n' +
  744 + '<!-- <md-tooltip md-direction="top">Remove relation</md-tooltip>-->\n' +
  745 + '<!-- <md-icon aria-label="Remove" class="material-icons">-->\n' +
  746 + '<!-- close-->\n' +
  747 + '<!-- </md-icon>-->\n' +
  748 + '<!-- </md-button>-->\n' +
  749 + '<!-- </div>-->\n' +
  750 + '<!-- </div>-->\n' +
  751 + '<!-- </div>-->\n' +
  752 + '<!-- </div>-->\n' +
  753 + '<!-- <div>-->\n' +
  754 + '<!-- <md-button class="md-primary md-raised" ng-click="vm.addRelation()" aria-label="Add">-->\n' +
  755 + '<!-- <md-tooltip md-direction="top">Add Relation</md-tooltip>-->\n' +
  756 + '<!-- Add-->\n' +
  757 + '<!-- </md-button>-->\n' +
  758 + '<!-- </div> -->\n' +
  759 + '<!-- </div>-->\n' +
  760 + '<!-- </div>-->\n' +
  761 + '<!-- </md-dialog-content>-->\n' +
  762 + '<!-- <md-dialog-actions>-->\n' +
  763 + '<!-- <md-button type="submit" ng-disabled="addEntityForm.$invalid || !addEntityForm.$dirty" class="md-raised md-primary">Create</md-button>-->\n' +
  764 + '<!-- <md-button ng-click="vm.cancel()" class="md-primary">Cancel</md-button>-->\n' +
  765 + '<!-- </md-dialog-actions>-->\n' +
  766 + '<!-- </form>-->\n' +
  767 + '<!--</md-dialog>-->\n';
  768 +
  769 + var sampleCss = '/* There are two examples: for edit and add entity */\n' +
  770 + '/* Edit entity example */\n' +
  771 + '/*\n' +
  772 + '.edit-entity-form md-input-container {\n' +
  773 + ' padding-right: 10px;\n' +
  774 + '}\n' +
  775 + '\n' +
  776 + '.edit-entity-form .boolean-value-input {\n' +
  777 + ' padding-left: 5px;\n' +
  778 + '}\n' +
  779 + '\n' +
  780 + '.edit-entity-form .boolean-value-input .checkbox-label {\n' +
  781 + ' margin-bottom: 8px;\n' +
  782 + ' color: rgba(0,0,0,0.54);\n' +
  783 + ' font-size: 12px;\n' +
  784 + '}\n' +
  785 + '\n' +
  786 + '.relations-list .header {\n' +
  787 + ' padding-right: 5px;\n' +
  788 + ' padding-bottom: 5px;\n' +
  789 + ' padding-left: 5px;\n' +
  790 + '}\n' +
  791 + '\n' +
  792 + '.relations-list .header .cell {\n' +
  793 + ' padding-right: 5px;\n' +
  794 + ' padding-left: 5px;\n' +
  795 + ' font-size: 12px;\n' +
  796 + ' font-weight: 700;\n' +
  797 + ' color: rgba(0, 0, 0, .54);\n' +
  798 + ' white-space: nowrap;\n' +
  799 + '}\n' +
  800 + '\n' +
  801 + '.relations-list .body {\n' +
  802 + ' padding-right: 5px;\n' +
  803 + ' padding-bottom: 15px;\n' +
  804 + ' padding-left: 5px;\n' +
  805 + '}\n' +
  806 + '\n' +
  807 + '.relations-list .body .row {\n' +
  808 + ' padding-top: 5px;\n' +
  809 + '}\n' +
  810 + '\n' +
  811 + '.relations-list .body .cell {\n' +
  812 + ' padding-right: 5px;\n' +
  813 + ' padding-left: 5px;\n' +
  814 + '}\n' +
  815 + '\n' +
  816 + '.relations-list .body md-autocomplete-wrap md-input-container {\n' +
  817 + ' height: 30px;\n' +
  818 + '}\n' +
  819 + '\n' +
  820 + '.relations-list .body .md-button {\n' +
  821 + ' margin: 0;\n' +
  822 + '}\n' +
  823 + '\n' +
  824 + '.relations-list.old-relations tb-entity-select tb-entity-autocomplete button {\n' +
  825 + ' display: none;\n' +
  826 + '} \n' +
  827 + '*/\n' +
  828 + '/* Add entity example */\n' +
  829 + '/*\n' +
  830 + '.add-entity-form md-input-container {\n' +
  831 + ' padding-right: 10px;\n' +
  832 + '}\n' +
  833 + '\n' +
  834 + '.add-entity-form .boolean-value-input {\n' +
  835 + ' padding-left: 5px;\n' +
  836 + '}\n' +
  837 + '\n' +
  838 + '.add-entity-form .boolean-value-input .checkbox-label {\n' +
  839 + ' margin-bottom: 8px;\n' +
  840 + ' color: rgba(0,0,0,0.54);\n' +
  841 + ' font-size: 12px;\n' +
  842 + '}\n' +
  843 + '\n' +
  844 + '.relations-list .header {\n' +
  845 + ' padding-right: 5px;\n' +
  846 + ' padding-bottom: 5px;\n' +
  847 + ' padding-left: 5px;\n' +
  848 + '}\n' +
  849 + '\n' +
  850 + '.relations-list .header .cell {\n' +
  851 + ' padding-right: 5px;\n' +
  852 + ' padding-left: 5px;\n' +
  853 + ' font-size: 12px;\n' +
  854 + ' font-weight: 700;\n' +
  855 + ' color: rgba(0, 0, 0, .54);\n' +
  856 + ' white-space: nowrap;\n' +
  857 + '}\n' +
  858 + '\n' +
  859 + '.relations-list .body {\n' +
  860 + ' padding-right: 5px;\n' +
  861 + ' padding-bottom: 15px;\n' +
  862 + ' padding-left: 5px;\n' +
  863 + '}\n' +
  864 + '\n' +
  865 + '.relations-list .body .row {\n' +
  866 + ' padding-top: 5px;\n' +
  867 + '}\n' +
  868 + '\n' +
  869 + '.relations-list .body .cell {\n' +
  870 + ' padding-right: 5px;\n' +
  871 + ' padding-left: 5px;\n' +
  872 + '}\n' +
  873 + '\n' +
  874 + '.relations-list .body md-autocomplete-wrap md-input-container {\n' +
  875 + ' height: 30px;\n' +
  876 + '}\n' +
  877 + '\n' +
  878 + '.relations-list .body .md-button {\n' +
  879 + ' margin: 0;\n' +
  880 + '}\n' +
  881 + '*/\n' +
  882 + '\n' +
  883 + '\n';
  884 +
  885 + scope.$watch('action', function () {
  886 + ngModelCtrl.$setViewValue(scope.action);
  887 + });
  888 +
  889 + ngModelCtrl.$render = function () {
  890 + scope.action = ngModelCtrl.$viewValue;
  891 + if (angular.isUndefined(scope.action.customHtml) && angular.isUndefined(scope.action.customCss) && angular.isUndefined(scope.action.customFunction)) {
  892 + scope.action.customFunction = sampleJsFunction;
  893 + scope.action.customHtml = sampleHtmlTemplate;
  894 + scope.action.customCss = sampleCss;
  895 + }
  896 + };
  897 +
  898 + function removeResource(index) {
  899 + if (index > -1) {
  900 + scope.action.customResources.splice(index, 1);
  901 + scope.theForm.$setDirty();
  902 + }
  903 + }
  904 +
  905 + function addResource() {
  906 + if (!scope.action.customResources) {
  907 + scope.action.customResources = [];
  908 + }
  909 + scope.action.customResources.push({url: ''});
  910 + scope.theForm.$setDirty();
  911 + }
  912 +
  913 + function beautifyHtml() {
  914 + var res = html_beautify(scope.action.customHtml, {indent_size: 4, wrap_line_length: 60});
  915 + scope.action.customHtml = res;
  916 + }
  917 +
  918 + function beautifyCss() {
  919 + var res = css_beautify(scope.action.customCss, {indent_size: 4});
  920 + scope.action.customCss = res;
  921 + }
  922 +
  923 + function toggleFullscreen() {
  924 + scope.fullscreen = !scope.fullscreen;
  925 + if (scope.fullscreen) {
  926 + scope.customActionEditorElement = angular.element('.tb-custom-action-editor');
  927 + angular.element(scope.customActionEditorElement[0]).ready(function () {
  928 + var w = scope.customActionEditorElement.width();
  929 + if (w > 0) {
  930 + initSplitLayout();
  931 + } else {
  932 + scope.$watch(
  933 + function () {
  934 + return scope.customActionEditorElement[0].offsetWidth || parseInt(scope.customActionEditorElement.css('width'), 10);
  935 + },
  936 + function (newSize) {
  937 + if (newSize > 0) {
  938 + initSplitLayout();
  939 + }
  940 + }
  941 + );
  942 + }
  943 + });
  944 + } else {
  945 + scope.layoutInited = false;
  946 + }
  947 + }
  948 +
  949 + function onDividerDrag() {
  950 + scope.$broadcast('update-ace-editor-size');
  951 + for (var i = 0; i < ace_editors.length; i++) {
  952 + var ace = ace_editors[i];
  953 + ace.resize();
  954 + ace.renderer.updateFull();
  955 + }
  956 + }
  957 +
  958 + function initSplitLayout() {
  959 + if (!scope.layoutInited) {
  960 + Split([angular.element('#left-panel', scope.customActionEditorElement)[0], angular.element('#right-panel', scope.customActionEditorElement)[0]], {
  961 + sizes: [50, 50],
  962 + gutterSize: 8,
  963 + cursor: 'col-resize',
  964 + onDrag: function () {
  965 + onDividerDrag()
  966 + }
  967 + });
  968 +
  969 + onDividerDrag();
  970 +
  971 + scope.$applyAsync(function () {
  972 + scope.layoutInited = true;
  973 + var w = angular.element($window);
  974 + $timeout(function () {
  975 + w.triggerHandler('resize')
  976 + });
  977 + });
  978 +
  979 + }
  980 + }
  981 +
  982 + $compile(element.contents())(scope);
  983 + };
  984 +
  985 + return {
  986 + restrict: "E",
  987 + require: "^ngModel",
  988 + scope: {
  989 + theForm: '=?',
  990 + },
  991 + link: linker
  992 + };
  993 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +.tb-custom-action-pretty {
  17 + box-sizing: border-box;
  18 + padding: 8px;
  19 + background-color: #fff;
  20 +
  21 + .tb-fullscreen-panel {
  22 + .tb-custom-action-editor-container {
  23 + height: calc(100% - 40px);
  24 + }
  25 +
  26 + #right-panel {
  27 + padding-top: 8px;
  28 + padding-left: 3px;
  29 + }
  30 +
  31 + tb-js-func .tb-js-func-panel {
  32 + box-sizing: border-box;
  33 + }
  34 +
  35 + md-tabs-content-wrapper,
  36 + md-tab-content {
  37 + height: 100%;
  38 + }
  39 + }
  40 +
  41 + .html-panel,
  42 + .css-panel {
  43 + width: 100%;
  44 + min-width: 200px;
  45 + height: 100%;
  46 + min-height: 200px;
  47 + }
  48 +
  49 + .tb-split {
  50 + box-sizing: border-box;
  51 + overflow-x: hidden;
  52 + overflow-y: auto;
  53 + }
  54 +
  55 + .tb-content {
  56 + border: 1px solid #c0c0c0;
  57 + }
  58 +
  59 + .gutter {
  60 + background-color: #eee;
  61 + background-repeat: no-repeat;
  62 + background-position: 50%;
  63 + }
  64 +
  65 + .gutter.gutter-horizontal {
  66 + cursor: col-resize;
  67 + background-image: url("../../../../../node_modules/split.js/grips/vertical.png");
  68 + }
  69 +
  70 + .tb-split.tb-split-horizontal,
  71 + .gutter.gutter-horizontal {
  72 + float: left;
  73 + height: 100%;
  74 + }
  75 +
  76 + .tb-action-expand-button {
  77 + position: absolute;
  78 + right: 14px;
  79 + z-index: 1;
  80 +
  81 + &.tb-fullscreen-editor {
  82 + position: relative;
  83 + right: 0;
  84 + }
  85 +
  86 + .md-button {
  87 + min-width: auto;
  88 + }
  89 + }
  90 +
  91 + .tb-custom-action-editor {
  92 + &.tb-fullscreen-editor {
  93 + height: 100%;
  94 + }
  95 + }
  96 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div class="tb-custom-action-pretty md-whiteframe-z1"
  19 + tb-expand-fullscreen="fullscreen" fullscreen-zindex="100" hide-expand-button="true">
  20 + <div layout="row" layout-align="end center" class="tb-action-expand-button" ng-class="{'tb-fullscreen-editor': fullscreen}">
  21 + <md-button hide-xs hide-sm aria-label="{{ 'widget.toggle-fullscreen' | translate }}" ng-click="toggleFullscreen()">
  22 + <md-tooltip md-direction="bottom">
  23 + {{ 'widget.toggle-fullscreen' | translate }}
  24 + </md-tooltip>
  25 + <md-icon ng-show="!fullscreen" aria-label="{{ 'widget.toggle-fullscreen' | translate }}">
  26 + fullscreen
  27 + </md-icon>
  28 + <md-icon ng-show="fullscreen" aria-label="{{ 'widget.toggle-fullscreen' | translate }}">
  29 + fullscreen_exit
  30 + </md-icon>
  31 + <span ng-if="fullscreen" translate hide-xs hide-sm>widget.toggle-fullscreen</span>
  32 + </md-button>
  33 + </div>
  34 + <div class="tb-custom-action-editor" ng-class="{'tb-fullscreen-editor': fullscreen}">
  35 + <div ng-if="!fullscreen">
  36 + <md-tabs md-selected="3" md-dynamic-height md-border-bottom style="width: 100%;">
  37 + <md-tab label="{{ 'widget.resources' | translate }}">
  38 + <div class="tb-custom-action-editor-container" style="background-color: #fff;">
  39 + <div class="md-padding">
  40 + <div flex layout="row"
  41 + ng-repeat="resource in action.customResources track by $index"
  42 + style="max-height: 40px;" layout-align="start center">
  43 + <md-input-container flex md-no-float class="md-block"
  44 + style="margin: 10px 0px 0px 0px; max-height: 40px;">
  45 + <input placeholder="{{ 'widget.resource-url' | translate }}"
  46 + ng-required="true" name="resource" ng-model="resource.url">
  47 + </md-input-container>
  48 + <md-button ng-disabled="$root.loading" class="md-icon-button md-primary"
  49 + ng-click="removeResource($index)"
  50 + aria-label="{{ 'action.remove' | translate }}">
  51 + <md-tooltip md-direction="top">
  52 + {{ 'widget.remove-resource' | translate }}
  53 + </md-tooltip>
  54 + <md-icon aria-label="{{ 'action.delete' | translate }}"
  55 + class="material-icons">
  56 + close
  57 + </md-icon>
  58 + </md-button>
  59 + </div>
  60 + <div>
  61 + <md-button ng-disabled="$root.loading" class="md-primary md-raised"
  62 + ng-click="addResource()"
  63 + aria-label="{{ 'action.add' | translate }}">
  64 + <md-tooltip md-direction="top">
  65 + {{ 'widget.add-resource' | translate }}
  66 + </md-tooltip>
  67 + <span translate>action.add</span>
  68 + </md-button>
  69 + </div>
  70 + </div>
  71 + </div>
  72 + </md-tab>
  73 + <md-tab label="{{ 'widget.css' | translate }}">
  74 + <div class="tb-custom-action-editor-container" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button">
  75 + <div class="tb-editor-area-title-panel">
  76 + <md-button aria-label="{{ 'widget.tidy' | translate }}"
  77 + ng-click="beautifyCss()">{{ 'widget.tidy' | translate }}
  78 + </md-button>
  79 + <md-button id="expand-button"
  80 + aria-label="{{ 'fullscreen.fullscreen' | translate }}"
  81 + class="md-icon-button tb-md-32"></md-button>
  82 + </div>
  83 + <div id="css-panel" class="css-panel" ui-ace="cssEditorOptions"
  84 + ng-model="action.customCss"></div>
  85 + </div>
  86 + </md-tab>
  87 + <md-tab label="{{ 'widget.html' | translate }}">
  88 + <div class="tb-custom-action-editor-container" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button">
  89 + <div class="tb-editor-area-title-panel">
  90 + <md-button aria-label="{{ 'widget.tidy' | translate }}"
  91 + ng-click="beautifyHtml()">{{ 'widget.tidy' | translate }}
  92 + </md-button>
  93 + <md-button id="expand-button"
  94 + aria-label="{{ 'fullscreen.fullscreen' | translate }}"
  95 + class="md-icon-button tb-md-32"></md-button>
  96 + </div>
  97 + <div id="html-panel" class="html-panel" ui-ace="htmlEditorOptions"
  98 + ng-model="action.customHtml"></div>
  99 + </div>
  100 + </md-tab>
  101 + <md-tab label="{{ 'widget.js' | translate }}">
  102 + <tb-js-func ng-model="action.customFunction"
  103 + function-args="{{ ['$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams'] }}"
  104 + validation-args="{{ [] }}">
  105 + </tb-js-func>
  106 + </md-tab>
  107 + </md-tabs>
  108 + </div>
  109 + <div ng-if="fullscreen" class="tb-fullscreen-panel" layout="row" layout-fill>
  110 + <div id="left-panel" class="tb-split tb-content">
  111 + <md-tabs md-selected="2" md-dynamic-height md-border-bottom layout="column" layout-fill>
  112 + <md-tab label="{{ 'widget.resources' | translate }}">
  113 + <div class="tb-custom-action-editor-container" style="background-color: #fff;">
  114 + <div class="md-padding">
  115 + <div flex layout="row"
  116 + ng-repeat="resource in action.customResources track by $index"
  117 + style="max-height: 40px;" layout-align="start center">
  118 + <md-input-container flex md-no-float class="md-block"
  119 + style="margin: 10px 0px 0px 0px; max-height: 40px;">
  120 + <input placeholder="{{ 'widget.resource-url' | translate }}"
  121 + ng-required="true" name="resource" ng-model="resource.url">
  122 + </md-input-container>
  123 + <md-button ng-disabled="$root.loading" class="md-icon-button md-primary"
  124 + ng-click="removeResource($index)"
  125 + aria-label="{{ 'action.remove' | translate }}">
  126 + <md-tooltip md-direction="top">
  127 + {{ 'widget.remove-resource' | translate }}
  128 + </md-tooltip>
  129 + <md-icon aria-label="{{ 'action.delete' | translate }}"
  130 + class="material-icons">
  131 + close
  132 + </md-icon>
  133 + </md-button>
  134 + </div>
  135 + <div>
  136 + <md-button ng-disabled="$root.loading" class="md-primary md-raised"
  137 + ng-click="addResource()"
  138 + aria-label="{{ 'action.add' | translate }}">
  139 + <md-tooltip md-direction="top">
  140 + {{ 'widget.add-resource' | translate }}
  141 + </md-tooltip>
  142 + <span translate>action.add</span>
  143 + </md-button>
  144 + </div>
  145 + </div>
  146 + </div>
  147 + </md-tab>
  148 + <md-tab label="{{ 'widget.css' | translate }}">
  149 + <div class="tb-custom-action-editor-container" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button">
  150 + <div class="tb-editor-area-title-panel">
  151 + <md-button aria-label="{{ 'widget.tidy' | translate }}"
  152 + ng-click="beautifyCss()">{{ 'widget.tidy' | translate }}
  153 + </md-button>
  154 + <md-button id="expand-button"
  155 + aria-label="{{ 'fullscreen.fullscreen' | translate }}"
  156 + class="md-icon-button tb-md-32"></md-button>
  157 + </div>
  158 + <div id="css-panel" class="css-panel" ui-ace="cssEditorOptions"
  159 + ng-model="action.customCss"></div>
  160 + </div>
  161 + </md-tab>
  162 + <md-tab label="{{ 'widget.html' | translate }}">
  163 + <div class="tb-custom-action-editor-container" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button">
  164 + <div class="tb-editor-area-title-panel">
  165 + <md-button aria-label="{{ 'widget.tidy' | translate }}"
  166 + ng-click="beautifyHtml()">{{ 'widget.tidy' | translate }}
  167 + </md-button>
  168 + <md-button id="expand-button"
  169 + aria-label="{{ 'fullscreen.fullscreen' | translate }}"
  170 + class="md-icon-button tb-md-32"></md-button>
  171 + </div>
  172 + <div id="html-panel" class="html-panel" ui-ace="htmlEditorOptions"
  173 + ng-model="action.customHtml"></div>
  174 + </div>
  175 + </md-tab>
  176 + </md-tabs>
  177 + </div>
  178 + <div id="right-panel" class="tb-split tb-content">
  179 + <tb-js-func ng-model="action.customFunction"
  180 + function-args="{{ ['$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams'] }}"
  181 + validation-args="{{ [] }}" fill-height="true">
  182 + </tb-js-func>
  183 + </div>
  184 + </div>
  185 + </div>
  186 +</div>
\ No newline at end of file
... ...
... ... @@ -37,6 +37,7 @@ import './manage-widget-actions.scss';
37 37 import thingsboardMaterialIconSelect from '../../material-icon-select.directive';
38 38
39 39 import WidgetActionDialogController from './widget-action-dialog.controller';
  40 +import CustomActionPrettyEditor from './custom-action-pretty-editor.directive';
40 41
41 42 /* eslint-disable import/no-unresolved, import/default */
42 43
... ... @@ -45,7 +46,7 @@ import widgetActionDialogTemplate from './widget-action-dialog.tpl.html';
45 46
46 47 /* eslint-enable import/no-unresolved, import/default */
47 48
48   -export default angular.module('thingsboard.directives.widgetActions', [thingsboardMaterialIconSelect])
  49 +export default angular.module('thingsboard.directives.widgetActions', [thingsboardMaterialIconSelect, CustomActionPrettyEditor])
49 50 .controller('WidgetActionDialogController', WidgetActionDialogController)
50 51 .directive('tbManageWidgetActions', ManageWidgetActions)
51 52 .name;
... ...
... ... @@ -14,7 +14,7 @@
14 14 * limitations under the License.
15 15 */
16 16 /*@ngInject*/
17   -export default function WidgetActionDialogController($scope, $mdDialog, $filter, $q, dashboardService, dashboardUtils, types, utils,
  17 +export default function WidgetActionDialogController($scope, $mdDialog, $filter, $q, dashboardService, dashboardUtils, types, toast, utils,
18 18 isAdd, fetchDashboardStates, actionSources, widgetActions, action) {
19 19
20 20 var vm = this;
... ... @@ -41,7 +41,7 @@ export default function WidgetActionDialogController($scope, $mdDialog, $filter,
41 41 vm.actionSourceName = actionSourceName;
42 42
43 43 vm.targetDashboardStateSearchTextChanged = function() {
44   - }
  44 + };
45 45
46 46 vm.dashboardStateSearch = dashboardStateSearch;
47 47 vm.cancel = cancel;
... ... @@ -155,6 +155,12 @@ export default function WidgetActionDialogController($scope, $mdDialog, $filter,
155 155 case vm.types.widgetActionTypes.custom.value:
156 156 result.customFunction = action.customFunction;
157 157 break;
  158 + case vm.types.widgetActionTypes.customPretty.value:
  159 + result.customResources = action.customResources;
  160 + result.customHtml = action.customHtml;
  161 + result.customCss = action.customCss;
  162 + result.customFunction = action.customFunction;
  163 + break;
158 164 }
159 165 return result;
160 166 }
... ...
... ... @@ -123,6 +123,7 @@
123 123 function-args="{{ ['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams'] }}"
124 124 validation-args="{{ [] }}">
125 125 </tb-js-func>
  126 + <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>
126 127 </fieldset>
127 128 </md-content>
128 129 </div>
... ...
... ... @@ -17,12 +17,15 @@ import $ from 'jquery';
17 17 import 'javascript-detect-element-resize/detect-element-resize';
18 18 import Subscription from '../../api/subscription';
19 19
  20 +import 'oclazyload';
  21 +import cssjs from '../../../vendor/css.js/css';
  22 +
20 23 /* eslint-disable angular/angularelement */
21 24
22 25 /*@ngInject*/
23   -export default function WidgetController($scope, $state, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
  26 +export default function WidgetController($scope, $state, $timeout, $window, $ocLazyLoad, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
24 27 datasourceService, alarmService, entityService, dashboardService, deviceService, visibleRect, isEdit, isMobile, dashboardTimewindow,
25   - dashboardTimewindowApi, dashboard, widget, aliasController, stateController, widgetInfo, widgetType) {
  28 + dashboardTimewindowApi, dashboard, widget, aliasController, stateController, widgetInfo, widgetType, toast) {
26 29
27 30 var vm = this;
28 31
... ... @@ -38,6 +41,12 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
38 41
39 42 vm.dashboardTimewindow = dashboardTimewindow;
40 43
  44 + $window.lazyLoad = $ocLazyLoad;
  45 + $window.cssjs = cssjs;
  46 +
  47 + var cssParser = new cssjs();
  48 + cssParser.testMode = false;
  49 +
41 50 var gridsterItemInited = false;
42 51 var subscriptionInited = false;
43 52 var widgetSizeDetected = false;
... ... @@ -522,7 +531,93 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
522 531 }
523 532 }
524 533 break;
  534 + case types.widgetActionTypes.customPretty.value:
  535 + var customPrettyFunction = descriptor.customFunction;
  536 + var customHtml = descriptor.customHtml;
  537 + var customCss = descriptor.customCss;
  538 + var customResources = descriptor.customResources;
  539 + var actionNamespace = 'custom-action-pretty-'+descriptor.name.toLowerCase();
  540 + var htmlTemplate = '';
  541 + if (angular.isDefined(customHtml) && customHtml.length > 0) {
  542 + htmlTemplate = customHtml;
  543 + }
  544 + loadCustomActionResources(actionNamespace, customCss, customResources).then(
  545 + function success() {
  546 + if (angular.isDefined(customPrettyFunction) && customPrettyFunction.length > 0) {
  547 + try {
  548 + if (!additionalParams) {
  549 + additionalParams = {};
  550 + }
  551 + var customActionPrettyFunction = new Function('$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams', customPrettyFunction);
  552 + customActionPrettyFunction($event, widgetContext, entityId, entityName, htmlTemplate, additionalParams);
  553 + } catch (e) {
  554 + //
  555 + }
  556 + }
  557 + },
  558 + function fail(errorMessages) {
  559 + processResourcesLoadErrors(errorMessages);
  560 + }
  561 + );
  562 + break;
  563 + }
  564 + }
  565 +
  566 + function loadCustomActionResources(actionNamespace, customCss, customResources) {
  567 + var deferred = $q.defer();
  568 +
  569 + if (angular.isDefined(customCss) && customCss.length > 0) {
  570 + cssParser.cssPreviewNamespace = actionNamespace;
  571 + cssParser.createStyleElement(actionNamespace, customCss, 'nonamespace');
  572 + }
  573 +
  574 + function loadNextOrComplete(i) {
  575 + i++;
  576 + if (i < customResources.length) {
  577 + loadNext(i);
  578 + } else {
  579 + if (errors.length > 0) {
  580 + deferred.reject(errors);
  581 + } else {
  582 + deferred.resolve();
  583 + }
  584 + }
  585 + }
  586 +
  587 + function loadNext(i) {
  588 + var resourceUrl = customResources[i].url;
  589 + if (resourceUrl && resourceUrl.length > 0) {
  590 + $ocLazyLoad.load(resourceUrl).then(
  591 + function success () {
  592 + loadNextOrComplete(i);
  593 + },
  594 + function fail() {
  595 + errors.push('Failed to load custom action resource: \'' + resourceUrl + '\'');
  596 + loadNextOrComplete(i);
  597 + }
  598 + );
  599 + } else {
  600 + loadNextOrComplete(i);
  601 + }
  602 + }
  603 +
  604 + if (angular.isDefined(customResources) && customResources.length > 0) {
  605 + var errors = [];
  606 + loadNext(0);
  607 + } else {
  608 + deferred.resolve();
  609 + }
  610 +
  611 + return deferred.promise;
  612 + }
  613 +
  614 + function processResourcesLoadErrors(errorMessages) {
  615 + var messageToShow = '';
  616 + for (var e in errorMessages) {
  617 + var error = errorMessages[e];
  618 + messageToShow += '<div>' + error + '</div>';
525 619 }
  620 + toast.showError(messageToShow);
526 621 }
527 622
528 623 function getActiveEntityInfo() {
... ...
... ... @@ -1512,6 +1512,7 @@
1512 1512 "settings-schema": "Settings schema",
1513 1513 "datakey-settings-schema": "Data key settings schema",
1514 1514 "javascript": "Javascript",
  1515 + "js": "JS",
1515 1516 "remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?",
1516 1517 "remove-widget-type-text": "After the confirmation the widget type and all related data will become unrecoverable.",
1517 1518 "remove-widget-type": "Remove widget type",
... ... @@ -1528,6 +1529,7 @@
1528 1529 "update-dashboard-state": "Update current dashboard state",
1529 1530 "open-dashboard": "Navigate to other dashboard",
1530 1531 "custom": "Custom action",
  1532 + "custom-pretty": "Custom action (with HTML template)",
1531 1533 "target-dashboard-state": "Target dashboard state",
1532 1534 "target-dashboard-state-required": "Target dashboard state is required",
1533 1535 "set-entity-from-widget": "Set entity from widget",
... ...
... ... @@ -1464,6 +1464,7 @@
1464 1464 "update-dashboard-state": "Обновить текущее состояние дашборда",
1465 1465 "open-dashboard": "Перейти к другому дашборду",
1466 1466 "custom": "Пользовательское действие",
  1467 + "custom-pretty": "Пользовательское действие (с HTML шаблоном)",
1467 1468 "target-dashboard-state": "Целевое состояние дашборда",
1468 1469 "target-dashboard-state-required": "Целевое состояние дашборда обязательно",
1469 1470 "set-entity-from-widget": "Установить объект из виджета",
... ...
... ... @@ -2032,6 +2032,7 @@
2032 2032 "update-dashboard-state": "Оновити поточний стан панелі візуалізації",
2033 2033 "open-dashboard": "Перейти до іншої панелі візуалізації",
2034 2034 "custom": "Дії користувачів",
  2035 + "custom-pretty": "Дії користувачів (з HTML шаблоном)",
2035 2036 "target-dashboard-state": "Цільовий стан панелі візуалізації",
2036 2037 "target-dashboard-state-required": "Необхідно вказати цільовий стан панелі візуалізації",
2037 2038 "set-entity-from-widget": "Встановити сутність із віджета",
... ...