From f081b656d076f43afef1b43bdb393c1b31b943ab Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 18 Jun 2018 14:33:50 +0300 Subject: [PATCH] Multiple labels support. --- ui/src/app/api/rule-chain.service.js | 26 +++-- ui/src/app/common/types.constant.js | 23 +++++ ui/src/app/device/device-card.tpl.html | 8 +- ui/src/app/locale/locale.constant.js | 7 ++ ui/src/app/rulechain/add-link.tpl.html | 2 +- ui/src/app/rulechain/link-fieldset.tpl.html | 43 +++++++- ui/src/app/rulechain/link.directive.js | 100 +++++++++++++++++-- ui/src/app/rulechain/link.scss | 30 ++++++ ui/src/app/rulechain/rulechain.controller.js | 86 +++++++++++----- ui/src/app/rulechain/rulechain.scss | 4 + ui/src/app/rulechain/rulechain.tpl.html | 8 +- ui/src/scss/main.scss | 7 ++ ui/src/scss/mixins.scss | 27 ++++- 13 files changed, 318 insertions(+), 53 deletions(-) create mode 100644 ui/src/app/rulechain/link.scss diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index e7436dedc6..186e31cce4 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -32,6 +32,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co getRuleNodeComponents: getRuleNodeComponents, getRuleNodeComponentByClazz: getRuleNodeComponentByClazz, getRuleNodeSupportedLinks: getRuleNodeSupportedLinks, + ruleNodeAllowCustomLinks: ruleNodeAllowCustomLinks, resolveTargetRuleChains: resolveTargetRuleChains, testScript: testScript, getLatestRuleNodeDebugInput: getLatestRuleNodeDebugInput @@ -127,21 +128,21 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co function getRuleNodeSupportedLinks(component) { var relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes; - var customRelations = component.configurationDescriptor.nodeDefinition.customRelations; - var linkLabels = []; + var linkLabels = {}; for (var i=0;i
-
{{vm.item.additionalInfo.description}}
-
{{vm.item.type}}
-
{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'
-
{{'device.public' | translate}}
+
{{vm.item.type}}
+
{{vm.item.additionalInfo.description}}
+
{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'
+
{{'device.public' | translate}}
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 29f6f8e8f8..4cf34f8784 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1157,6 +1157,11 @@ export default angular.module('thingsboard.locale', []) "link-label-required": "Link label is required.", "custom-link-label": "Custom link label", "custom-link-label-required": "Custom link label is required.", + "link-labels": "Link labels", + "link-labels-required": "Link labels is required.", + "no-link-labels-found": "No link labels found", + "no-link-label-matching": "'{{label}}' not found.", + "create-new-link-label": "Create a new one!", "type-filter": "Filter", "type-filter-details": "Filter incoming messages with configured conditions", "type-enrichment": "Enrichment", @@ -1171,6 +1176,8 @@ export default angular.module('thingsboard.locale', []) "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", "type-input": "Input", "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node", + "type-unknown": "Unknown", + "type-unknown-details": "Unresolved Rule Node", "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.", "ui-resources-load-error": "Failed to load configuration ui resources.", "invalid-target-rulechain": "Unable to resolve target rule chain!", diff --git a/ui/src/app/rulechain/add-link.tpl.html b/ui/src/app/rulechain/add-link.tpl.html index 0a21104e76..c5855b7738 100644 --- a/ui/src/app/rulechain/add-link.tpl.html +++ b/ui/src/app/rulechain/add-link.tpl.html @@ -31,7 +31,7 @@
- +
diff --git a/ui/src/app/rulechain/link-fieldset.tpl.html b/ui/src/app/rulechain/link-fieldset.tpl.html index 13ec6c3ce8..3dbe33f87f 100644 --- a/ui/src/app/rulechain/link-fieldset.tpl.html +++ b/ui/src/app/rulechain/link-fieldset.tpl.html @@ -17,7 +17,46 @@ -->
- + + + + {{item}} + +
+
+ rulenode.no-link-labels-found +
+
+ rulenode.no-link-label-matching + + rulenode.create-new-link-label + +
+
+
+
+ + {{$chip.name}} + +
+ +
diff --git a/ui/src/app/rulechain/link.directive.js b/ui/src/app/rulechain/link.directive.js index b3565a3954..dd438212a4 100644 --- a/ui/src/app/rulechain/link.directive.js +++ b/ui/src/app/rulechain/link.directive.js @@ -14,6 +14,8 @@ * limitations under the License. */ +import './link.scss'; + /* eslint-disable import/no-unresolved, import/default */ import linkFieldsetTemplate from './link-fieldset.tpl.html'; @@ -22,13 +24,18 @@ import linkFieldsetTemplate from './link-fieldset.tpl.html'; /*@ngInject*/ export default function LinkDirective($compile, $templateCache, $filter) { - var linker = function (scope, element) { + var linker = function (scope, element, attrs, ngModelCtrl) { var template = $templateCache.get(linkFieldsetTemplate); element.html(template); scope.selectedLabel = null; + scope.labelSearchText = null; - scope.$watch('link', function() { + scope.ngModelCtrl = ngModelCtrl; + + var labelsList = []; + + /*scope.$watch('link', function() { scope.selectedLabel = null; if (scope.link && scope.labels) { if (scope.link.label) { @@ -53,19 +60,100 @@ export default function LinkDirective($compile, $templateCache, $filter) { scope.link.label = ""; } } + };*/ + + scope.transformLinkLabelChip = function (chip) { + var res = $filter('filter')(labelsList, {name: chip}, true); + var result; + if (res && res.length) { + result = angular.copy(res[0]); + } else { + result = { + name: chip, + value: chip + }; + } + return result; }; + scope.labelsSearch = function (searchText) { + var labels = searchText ? $filter('filter')(labelsList, {name: searchText}) : labelsList; + return labels.map((label) => label.name); + }; + + scope.createLinkLabel = function (event, chipsId) { + var chipsChild = angular.element(chipsId, element)[0].firstElementChild; + var el = angular.element(chipsChild); + var chipBuffer = el.scope().$mdChipsCtrl.getChipBuffer(); + event.preventDefault(); + event.stopPropagation(); + el.scope().$mdChipsCtrl.appendChip(chipBuffer.trim()); + el.scope().$mdChipsCtrl.resetChipBuffer(); + }; + + + ngModelCtrl.$render = function () { + labelsList.length = 0; + for (var label in scope.allowedLabels) { + var linkLabel = { + name: scope.allowedLabels[label].name, + value: scope.allowedLabels[label].value + }; + labelsList.push(linkLabel); + } + + var link = ngModelCtrl.$viewValue; + var labels = []; + if (link && link.labels) { + for (var i = 0; i < link.labels.length; i++) { + label = link.labels[i]; + if (scope.allowedLabels[label]) { + labels.push(angular.copy(scope.allowedLabels[label])); + } else { + labels.push({ + name: label, + value: label + }); + } + } + } + scope.labels = labels; + scope.$watch('labels', function (newVal, prevVal) { + if (!angular.equals(newVal, prevVal)) { + updateLabels(); + } + }, true); + }; + + function updateLabels() { + if (ngModelCtrl.$viewValue) { + var labels = []; + for (var i = 0; i < scope.labels.length; i++) { + labels.push(scope.labels[i].value); + } + ngModelCtrl.$viewValue.labels = labels; + ngModelCtrl.$viewValue.label = labels.join(' / '); + updateValidity(); + } + } + + function updateValidity() { + var valid = ngModelCtrl.$viewValue.labels && + ngModelCtrl.$viewValue.labels.length ? true : false; + ngModelCtrl.$setValidity('linkLabels', valid); + } + $compile(element.contents())(scope); } return { restrict: "E", + require: "^ngModel", link: linker, scope: { - link: '=', - labels: '=', + allowedLabels: '=', + allowCustom: '=', isEdit: '=', - isReadOnly: '=', - theForm: '=' + isReadOnly: '=' } }; } diff --git a/ui/src/app/rulechain/link.scss b/ui/src/app/rulechain/link.scss new file mode 100644 index 0000000000..3e8661915b --- /dev/null +++ b/ui/src/app/rulechain/link.scss @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2018 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-link-label-autocomplete { + .tb-not-found { + display: block; + line-height: 1.5; + height: 48px; + .tb-no-entries { + line-height: 48px; + } + } + li { + height: auto !important; + white-space: normal !important; + } +} diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index dfd1a971e8..a38f8a8a03 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -669,11 +669,15 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time } } else { if (edge.label) { + if (!edge.labels) { + edge.labels = edge.label.split(' / '); + } deferred.resolve(edge); } else { var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); + var allowCustomLabels = ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component); vm.enableHotKeys = false; - addRuleNodeLink(event, edge, labels).then( + addRuleNodeLink(event, edge, labels, allowCustomLabels).then( (link) => { deferred.resolve(link); vm.enableHotKeys = true; @@ -713,6 +717,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time vm.isEditingRuleNode = false; vm.editingRuleNode = null; vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); + vm.editingRuleNodeAllowCustomLabels = ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component); vm.isEditingRuleNodeLink = true; vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); vm.editingRuleNodeLink = angular.copy(edge); @@ -744,7 +749,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time isInputSource: isInputSource, fromIndex: fromIndex, toIndex: toIndex, - label: edge.label + label: edge.label, + labels: edge.labels }; connections.push(connection); } @@ -816,7 +822,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time var edge = { source: source, destination: destination, - label: connection.label + label: connection.label, + labels: connection.labels }; vm.ruleChainModel.edges.push(edge); vm.modelservice.edges.select(edge); @@ -1024,6 +1031,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time } if (vm.ruleChainMetaData.connections) { + var edgeMap = {}; for (i = 0; i < vm.ruleChainMetaData.connections.length; i++) { var connection = vm.ruleChainMetaData.connections[i]; var sourceNode = nodes[connection.fromIndex]; @@ -1032,12 +1040,23 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time var sourceConnectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType); var destConnectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType); if (sourceConnectors && sourceConnectors.length && destConnectors && destConnectors.length) { - edge = { - source: sourceConnectors[0].id, - destination: destConnectors[0].id, - label: connection.type - }; - vm.ruleChainModel.edges.push(edge); + var sourceId = sourceConnectors[0].id; + var destId = destConnectors[0].id; + var edgeKey = sourceId + '_' + destId; + edge = edgeMap[edgeKey]; + if (!edge) { + edge = { + source: sourceId, + destination: destId, + label: connection.type, + labels: [connection.type] + }; + edgeMap[edgeKey] = edge; + vm.ruleChainModel.edges.push(edge); + } else { + edge.label += ' / ' +connection.type; + edge.labels.push(connection.type); + } } } } @@ -1045,6 +1064,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time if (vm.ruleChainMetaData.ruleChainConnections) { var ruleChainNodesMap = {}; + var ruleChainEdgeMap = {}; for (i = 0; i < vm.ruleChainMetaData.ruleChainConnections.length; i++) { var ruleChainConnection = vm.ruleChainMetaData.ruleChainConnections[i]; var ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id]; @@ -1081,12 +1101,23 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time if (sourceNode) { connectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType); if (connectors && connectors.length) { - var ruleChainEdge = { - source: connectors[0].id, - destination: ruleChainNode.connectors[0].id, - label: ruleChainConnection.type - }; - vm.ruleChainModel.edges.push(ruleChainEdge); + sourceId = connectors[0].id; + destId = ruleChainNode.connectors[0].id; + edgeKey = sourceId + '_' + destId; + var ruleChainEdge = ruleChainEdgeMap[edgeKey]; + if (!ruleChainEdge) { + ruleChainEdge = { + source: sourceId, + destination: destId, + label: ruleChainConnection.type, + labels: [ruleChainConnection.type] + }; + ruleChainEdgeMap[edgeKey] = ruleChainEdge; + vm.ruleChainModel.edges.push(ruleChainEdge); + } else { + ruleChainEdge.label += ' / ' +ruleChainConnection.type; + ruleChainEdge.labels.push(ruleChainConnection.type); + } } } } @@ -1199,8 +1230,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time var ruleChainConnection = { fromIndex: fromIndex, targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId}, - additionalInfo: destNode.additionalInfo, - type: edge.label + additionalInfo: destNode.additionalInfo }; if (!ruleChainConnection.additionalInfo) { ruleChainConnection.additionalInfo = {}; @@ -1208,15 +1238,22 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time ruleChainConnection.additionalInfo.layoutX = Math.round(destNode.x); ruleChainConnection.additionalInfo.layoutY = Math.round(destNode.y); ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id; - ruleChainMetaData.ruleChainConnections.push(ruleChainConnection); + for (var rcIndex=0;rcIndex
+ is-read-only="false">
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss index 8fed892b06..2852a7bd60 100644 --- a/ui/src/scss/main.scss +++ b/ui/src/scss/main.scss @@ -16,6 +16,7 @@ @import "~compass-sass-mixins/lib/compass"; @import "constants"; @import "animations"; +@import "mixins"; @import "fonts"; /*************** @@ -437,6 +438,12 @@ pre.tb-highlight { } } +.tb-card-description { + color: rgba(0,0,0,0.54); + font-size: 13px; + @include line-clamp(2, 1.1); +} + /*********************** * Flow ***********************/ diff --git a/ui/src/scss/mixins.scss b/ui/src/scss/mixins.scss index 9e8b7df349..cb661719f2 100644 --- a/ui/src/scss/mixins.scss +++ b/ui/src/scss/mixins.scss @@ -31,4 +31,29 @@ &:-ms-input-placeholder { @content; } -} \ No newline at end of file +} + +@mixin line-clamp($numLines: 1, $lineHeight: 1.412) { + overflow: hidden; + position: relative; + line-height: $lineHeight; + text-align: justify; + margin-right: -1em; + padding-right: 2em; + max-height: ($numLines*$lineHeight)+em; + &:before { + content: '...'; + position: absolute; + right: 1em; + bottom: 0; + } + &:after { + content: ''; + position: absolute; + right: 1em; + width: 1em; + height: 1em; + margin-top: 0.2em; + background: white; + } +}