Commit f081b656d076f43afef1b43bdb393c1b31b943ab

Authored by Igor Kulikov
1 parent 1f58c1b0

Multiple labels support.

... ... @@ -32,6 +32,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
32 32 getRuleNodeComponents: getRuleNodeComponents,
33 33 getRuleNodeComponentByClazz: getRuleNodeComponentByClazz,
34 34 getRuleNodeSupportedLinks: getRuleNodeSupportedLinks,
  35 + ruleNodeAllowCustomLinks: ruleNodeAllowCustomLinks,
35 36 resolveTargetRuleChains: resolveTargetRuleChains,
36 37 testScript: testScript,
37 38 getLatestRuleNodeDebugInput: getLatestRuleNodeDebugInput
... ... @@ -127,21 +128,21 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
127 128
128 129 function getRuleNodeSupportedLinks(component) {
129 130 var relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes;
130   - var customRelations = component.configurationDescriptor.nodeDefinition.customRelations;
131   - var linkLabels = [];
  131 + var linkLabels = {};
132 132 for (var i=0;i<relationTypes.length;i++) {
133   - linkLabels.push({
134   - name: relationTypes[i], custom: false
135   - });
136   - }
137   - if (customRelations) {
138   - linkLabels.push(
139   - { name: 'Custom', custom: true }
140   - );
  133 + var label = relationTypes[i];
  134 + linkLabels[label] = {
  135 + name: label,
  136 + value: label
  137 + };
141 138 }
142 139 return linkLabels;
143 140 }
144 141
  142 + function ruleNodeAllowCustomLinks(component) {
  143 + return component.configurationDescriptor.nodeDefinition.customRelations;
  144 + }
  145 +
145 146 function getRuleNodeComponents() {
146 147 var deferred = $q.defer();
147 148 if (ruleNodeComponents) {
... ... @@ -226,7 +227,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
226 227 if (res && res.length) {
227 228 return res[0];
228 229 }
229   - return null;
  230 + var unknownComponent = angular.copy(types.unknownNodeComponent);
  231 + unknownComponent.clazz = clazz;
  232 + unknownComponent.configurationDescriptor.nodeDefinition.details = "Unknown Rule Node class: " + clazz;
  233 + return unknownComponent;
230 234 }
231 235
232 236 function resolveTargetRuleChains(ruleChainConnections) {
... ...
... ... @@ -510,6 +510,22 @@ export default angular.module('thingsboard.types', [])
510 510 }
511 511 }
512 512 },
  513 + unknownNodeComponent: {
  514 + type: 'UNKNOWN',
  515 + name: 'unknown',
  516 + clazz: 'tb.internal.Unknown',
  517 + configurationDescriptor: {
  518 + nodeDefinition: {
  519 + description: "",
  520 + details: "",
  521 + inEnabled: true,
  522 + outEnabled: true,
  523 + relationTypes: [],
  524 + customRelations: false,
  525 + defaultConfiguration: {}
  526 + }
  527 + }
  528 + },
513 529 inputNodeComponent: {
514 530 type: 'INPUT',
515 531 name: 'Input',
... ... @@ -565,6 +581,13 @@ export default angular.module('thingsboard.types', [])
565 581 nodeClass: "tb-input-type",
566 582 icon: "input",
567 583 special: true
  584 + },
  585 + UNKNOWN: {
  586 + value: "UNKNOWN",
  587 + name: "rulenode.type-unknown",
  588 + details: "rulenode.type-unknown-details",
  589 + nodeClass: "tb-unknown-type",
  590 + icon: "help_outline"
568 591 }
569 592 },
570 593 valueType: {
... ...
... ... @@ -16,8 +16,8 @@
16 16
17 17 -->
18 18 <div flex layout="column" style="margin-top: -10px;">
19   - <div flex>{{vm.item.additionalInfo.description}}</div>
20   - <div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
21   - <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
22   - <div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
  19 + <div style="text-transform: uppercase; padding-bottom: 5px;">{{vm.item.type}}</div>
  20 + <div class="tb-card-description">{{vm.item.additionalInfo.description}}</div>
  21 + <div style="padding-top: 5px;" class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
  22 + <div style="padding-top: 5px;" class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
23 23 </div>
... ...
... ... @@ -1157,6 +1157,11 @@ export default angular.module('thingsboard.locale', [])
1157 1157 "link-label-required": "Link label is required.",
1158 1158 "custom-link-label": "Custom link label",
1159 1159 "custom-link-label-required": "Custom link label is required.",
  1160 + "link-labels": "Link labels",
  1161 + "link-labels-required": "Link labels is required.",
  1162 + "no-link-labels-found": "No link labels found",
  1163 + "no-link-label-matching": "'{{label}}' not found.",
  1164 + "create-new-link-label": "Create a new one!",
1160 1165 "type-filter": "Filter",
1161 1166 "type-filter-details": "Filter incoming messages with configured conditions",
1162 1167 "type-enrichment": "Enrichment",
... ... @@ -1171,6 +1176,8 @@ export default angular.module('thingsboard.locale', [])
1171 1176 "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
1172 1177 "type-input": "Input",
1173 1178 "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node",
  1179 + "type-unknown": "Unknown",
  1180 + "type-unknown-details": "Unresolved Rule Node",
1174 1181 "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
1175 1182 "ui-resources-load-error": "Failed to load configuration ui resources.",
1176 1183 "invalid-target-rulechain": "Unable to resolve target rule chain!",
... ...
... ... @@ -31,7 +31,7 @@
31 31 <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
32 32 <md-dialog-content>
33 33 <div class="md-dialog-content">
34   - <tb-rule-node-link link="vm.link" labels="vm.labels" is-edit="true" the-form="theForm"></tb-rule-node-link>
  34 + <tb-rule-node-link ng-model="vm.link" allowed-labels="vm.labels" is-edit="true" allow-custom="vm.allowCustomLabels"></tb-rule-node-link>
35 35 </div>
36 36 </md-dialog-content>
37 37 <md-dialog-actions layout="row">
... ...
... ... @@ -17,7 +17,46 @@
17 17 -->
18 18 <md-content class="md-padding tb-link" layout="column">
19 19 <fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
20   - <md-input-container class="md-block">
  20 + <label translate class="tb-title no-padding" ng-class="{'tb-required': required}">rulenode.link-labels</label>
  21 + <md-chips id="link_label_chips"
  22 + ng-required="true"
  23 + readonly="$root.loading || !isEdit || isReadOnly"
  24 + ng-model="labels" md-autocomplete-snap
  25 + md-transform-chip="transformLinkLabelChip($chip)"
  26 + md-require-match="!allowCustom">
  27 + <md-autocomplete
  28 + id="link_label"
  29 + md-no-cache="true"
  30 + md-selected-item="selectedLabel"
  31 + md-search-text="labelSearchText"
  32 + md-items="item in labelsSearch(labelSearchText)"
  33 + md-item-text="item.name"
  34 + md-min-length="0"
  35 + placeholder="{{'rulenode.link-label' | translate }}"
  36 + md-menu-class="tb-link-label-autocomplete">
  37 + <span md-highlight-text="labelSearchText" md-highlight-flags="^i">{{item}}</span>
  38 + <md-not-found>
  39 + <div class="tb-not-found">
  40 + <div class="tb-no-entries" ng-if="!labelSearchText || !labelSearchText.length">
  41 + <span translate>rulenode.no-link-labels-found</span>
  42 + </div>
  43 + <div ng-if="labelSearchText && labelSearchText.length">
  44 + <span translate translate-values='{ label: "{{labelSearchText | truncate:true:6:&apos;...&apos;}}" }'>rulenode.no-link-label-matching</span>
  45 + <span ng-if="allowCustom">
  46 + <a translate ng-click="createLinkLabel($event, '#link_label_chips')">rulenode.create-new-link-label</a>
  47 + </span>
  48 + </div>
  49 + </div>
  50 + </md-not-found>
  51 + </md-autocomplete>
  52 + <md-chip-template>
  53 + <span>{{$chip.name}}</span>
  54 + </md-chip-template>
  55 + </md-chips>
  56 + <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
  57 + <div translate ng-message="linkLabels" class="tb-error-message">rulenode.link-labels-required</div>
  58 + </div>
  59 + <!--md-input-container class="md-block">
21 60 <label translate>rulenode.link-label</label>
22 61 <md-select ng-model="selectedLabel" ng-change="selectedLabelChanged()">
23 62 <md-option ng-repeat="label in labels" ng-value="label">
... ... @@ -34,6 +73,6 @@
34 73 <div ng-messages="theForm.customLinkLabel.$error">
35 74 <div translate ng-message="required">rulenode.custom-link-label-required</div>
36 75 </div>
37   - </md-input-container>
  76 + </md-input-container-->
38 77 </fieldset>
39 78 </md-content>
... ...
... ... @@ -14,6 +14,8 @@
14 14 * limitations under the License.
15 15 */
16 16
  17 +import './link.scss';
  18 +
17 19 /* eslint-disable import/no-unresolved, import/default */
18 20
19 21 import linkFieldsetTemplate from './link-fieldset.tpl.html';
... ... @@ -22,13 +24,18 @@ import linkFieldsetTemplate from './link-fieldset.tpl.html';
22 24
23 25 /*@ngInject*/
24 26 export default function LinkDirective($compile, $templateCache, $filter) {
25   - var linker = function (scope, element) {
  27 + var linker = function (scope, element, attrs, ngModelCtrl) {
26 28 var template = $templateCache.get(linkFieldsetTemplate);
27 29 element.html(template);
28 30
29 31 scope.selectedLabel = null;
  32 + scope.labelSearchText = null;
  33 +
  34 + scope.ngModelCtrl = ngModelCtrl;
  35 +
  36 + var labelsList = [];
30 37
31   - scope.$watch('link', function() {
  38 + /*scope.$watch('link', function() {
32 39 scope.selectedLabel = null;
33 40 if (scope.link && scope.labels) {
34 41 if (scope.link.label) {
... ... @@ -53,19 +60,100 @@ export default function LinkDirective($compile, $templateCache, $filter) {
53 60 scope.link.label = "";
54 61 }
55 62 }
  63 + };*/
  64 +
  65 + scope.transformLinkLabelChip = function (chip) {
  66 + var res = $filter('filter')(labelsList, {name: chip}, true);
  67 + var result;
  68 + if (res && res.length) {
  69 + result = angular.copy(res[0]);
  70 + } else {
  71 + result = {
  72 + name: chip,
  73 + value: chip
  74 + };
  75 + }
  76 + return result;
  77 + };
  78 +
  79 + scope.labelsSearch = function (searchText) {
  80 + var labels = searchText ? $filter('filter')(labelsList, {name: searchText}) : labelsList;
  81 + return labels.map((label) => label.name);
  82 + };
  83 +
  84 + scope.createLinkLabel = function (event, chipsId) {
  85 + var chipsChild = angular.element(chipsId, element)[0].firstElementChild;
  86 + var el = angular.element(chipsChild);
  87 + var chipBuffer = el.scope().$mdChipsCtrl.getChipBuffer();
  88 + event.preventDefault();
  89 + event.stopPropagation();
  90 + el.scope().$mdChipsCtrl.appendChip(chipBuffer.trim());
  91 + el.scope().$mdChipsCtrl.resetChipBuffer();
56 92 };
57 93
  94 +
  95 + ngModelCtrl.$render = function () {
  96 + labelsList.length = 0;
  97 + for (var label in scope.allowedLabels) {
  98 + var linkLabel = {
  99 + name: scope.allowedLabels[label].name,
  100 + value: scope.allowedLabels[label].value
  101 + };
  102 + labelsList.push(linkLabel);
  103 + }
  104 +
  105 + var link = ngModelCtrl.$viewValue;
  106 + var labels = [];
  107 + if (link && link.labels) {
  108 + for (var i = 0; i < link.labels.length; i++) {
  109 + label = link.labels[i];
  110 + if (scope.allowedLabels[label]) {
  111 + labels.push(angular.copy(scope.allowedLabels[label]));
  112 + } else {
  113 + labels.push({
  114 + name: label,
  115 + value: label
  116 + });
  117 + }
  118 + }
  119 + }
  120 + scope.labels = labels;
  121 + scope.$watch('labels', function (newVal, prevVal) {
  122 + if (!angular.equals(newVal, prevVal)) {
  123 + updateLabels();
  124 + }
  125 + }, true);
  126 + };
  127 +
  128 + function updateLabels() {
  129 + if (ngModelCtrl.$viewValue) {
  130 + var labels = [];
  131 + for (var i = 0; i < scope.labels.length; i++) {
  132 + labels.push(scope.labels[i].value);
  133 + }
  134 + ngModelCtrl.$viewValue.labels = labels;
  135 + ngModelCtrl.$viewValue.label = labels.join(' / ');
  136 + updateValidity();
  137 + }
  138 + }
  139 +
  140 + function updateValidity() {
  141 + var valid = ngModelCtrl.$viewValue.labels &&
  142 + ngModelCtrl.$viewValue.labels.length ? true : false;
  143 + ngModelCtrl.$setValidity('linkLabels', valid);
  144 + }
  145 +
58 146 $compile(element.contents())(scope);
59 147 }
60 148 return {
61 149 restrict: "E",
  150 + require: "^ngModel",
62 151 link: linker,
63 152 scope: {
64   - link: '=',
65   - labels: '=',
  153 + allowedLabels: '=',
  154 + allowCustom: '=',
66 155 isEdit: '=',
67   - isReadOnly: '=',
68   - theForm: '='
  156 + isReadOnly: '='
69 157 }
70 158 };
71 159 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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 +
  17 +.tb-link-label-autocomplete {
  18 + .tb-not-found {
  19 + display: block;
  20 + line-height: 1.5;
  21 + height: 48px;
  22 + .tb-no-entries {
  23 + line-height: 48px;
  24 + }
  25 + }
  26 + li {
  27 + height: auto !important;
  28 + white-space: normal !important;
  29 + }
  30 +}
... ...
... ... @@ -669,11 +669,15 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
669 669 }
670 670 } else {
671 671 if (edge.label) {
  672 + if (!edge.labels) {
  673 + edge.labels = edge.label.split(' / ');
  674 + }
672 675 deferred.resolve(edge);
673 676 } else {
674 677 var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
  678 + var allowCustomLabels = ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
675 679 vm.enableHotKeys = false;
676   - addRuleNodeLink(event, edge, labels).then(
  680 + addRuleNodeLink(event, edge, labels, allowCustomLabels).then(
677 681 (link) => {
678 682 deferred.resolve(link);
679 683 vm.enableHotKeys = true;
... ... @@ -713,6 +717,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
713 717 vm.isEditingRuleNode = false;
714 718 vm.editingRuleNode = null;
715 719 vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
  720 + vm.editingRuleNodeAllowCustomLabels = ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
716 721 vm.isEditingRuleNodeLink = true;
717 722 vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
718 723 vm.editingRuleNodeLink = angular.copy(edge);
... ... @@ -744,7 +749,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
744 749 isInputSource: isInputSource,
745 750 fromIndex: fromIndex,
746 751 toIndex: toIndex,
747   - label: edge.label
  752 + label: edge.label,
  753 + labels: edge.labels
748 754 };
749 755 connections.push(connection);
750 756 }
... ... @@ -816,7 +822,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
816 822 var edge = {
817 823 source: source,
818 824 destination: destination,
819   - label: connection.label
  825 + label: connection.label,
  826 + labels: connection.labels
820 827 };
821 828 vm.ruleChainModel.edges.push(edge);
822 829 vm.modelservice.edges.select(edge);
... ... @@ -1024,6 +1031,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
1024 1031 }
1025 1032
1026 1033 if (vm.ruleChainMetaData.connections) {
  1034 + var edgeMap = {};
1027 1035 for (i = 0; i < vm.ruleChainMetaData.connections.length; i++) {
1028 1036 var connection = vm.ruleChainMetaData.connections[i];
1029 1037 var sourceNode = nodes[connection.fromIndex];
... ... @@ -1032,12 +1040,23 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
1032 1040 var sourceConnectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
1033 1041 var destConnectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType);
1034 1042 if (sourceConnectors && sourceConnectors.length && destConnectors && destConnectors.length) {
1035   - edge = {
1036   - source: sourceConnectors[0].id,
1037   - destination: destConnectors[0].id,
1038   - label: connection.type
1039   - };
1040   - vm.ruleChainModel.edges.push(edge);
  1043 + var sourceId = sourceConnectors[0].id;
  1044 + var destId = destConnectors[0].id;
  1045 + var edgeKey = sourceId + '_' + destId;
  1046 + edge = edgeMap[edgeKey];
  1047 + if (!edge) {
  1048 + edge = {
  1049 + source: sourceId,
  1050 + destination: destId,
  1051 + label: connection.type,
  1052 + labels: [connection.type]
  1053 + };
  1054 + edgeMap[edgeKey] = edge;
  1055 + vm.ruleChainModel.edges.push(edge);
  1056 + } else {
  1057 + edge.label += ' / ' +connection.type;
  1058 + edge.labels.push(connection.type);
  1059 + }
1041 1060 }
1042 1061 }
1043 1062 }
... ... @@ -1045,6 +1064,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
1045 1064
1046 1065 if (vm.ruleChainMetaData.ruleChainConnections) {
1047 1066 var ruleChainNodesMap = {};
  1067 + var ruleChainEdgeMap = {};
1048 1068 for (i = 0; i < vm.ruleChainMetaData.ruleChainConnections.length; i++) {
1049 1069 var ruleChainConnection = vm.ruleChainMetaData.ruleChainConnections[i];
1050 1070 var ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id];
... ... @@ -1081,12 +1101,23 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
1081 1101 if (sourceNode) {
1082 1102 connectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
1083 1103 if (connectors && connectors.length) {
1084   - var ruleChainEdge = {
1085   - source: connectors[0].id,
1086   - destination: ruleChainNode.connectors[0].id,
1087   - label: ruleChainConnection.type
1088   - };
1089   - vm.ruleChainModel.edges.push(ruleChainEdge);
  1104 + sourceId = connectors[0].id;
  1105 + destId = ruleChainNode.connectors[0].id;
  1106 + edgeKey = sourceId + '_' + destId;
  1107 + var ruleChainEdge = ruleChainEdgeMap[edgeKey];
  1108 + if (!ruleChainEdge) {
  1109 + ruleChainEdge = {
  1110 + source: sourceId,
  1111 + destination: destId,
  1112 + label: ruleChainConnection.type,
  1113 + labels: [ruleChainConnection.type]
  1114 + };
  1115 + ruleChainEdgeMap[edgeKey] = ruleChainEdge;
  1116 + vm.ruleChainModel.edges.push(ruleChainEdge);
  1117 + } else {
  1118 + ruleChainEdge.label += ' / ' +ruleChainConnection.type;
  1119 + ruleChainEdge.labels.push(ruleChainConnection.type);
  1120 + }
1090 1121 }
1091 1122 }
1092 1123 }
... ... @@ -1199,8 +1230,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
1199 1230 var ruleChainConnection = {
1200 1231 fromIndex: fromIndex,
1201 1232 targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId},
1202   - additionalInfo: destNode.additionalInfo,
1203   - type: edge.label
  1233 + additionalInfo: destNode.additionalInfo
1204 1234 };
1205 1235 if (!ruleChainConnection.additionalInfo) {
1206 1236 ruleChainConnection.additionalInfo = {};
... ... @@ -1208,15 +1238,22 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
1208 1238 ruleChainConnection.additionalInfo.layoutX = Math.round(destNode.x);
1209 1239 ruleChainConnection.additionalInfo.layoutY = Math.round(destNode.y);
1210 1240 ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id;
1211   - ruleChainMetaData.ruleChainConnections.push(ruleChainConnection);
  1241 + for (var rcIndex=0;rcIndex<edge.labels.length;rcIndex++) {
  1242 + var newRuleChainConnection = angular.copy(ruleChainConnection);
  1243 + newRuleChainConnection.type = edge.labels[rcIndex];
  1244 + ruleChainMetaData.ruleChainConnections.push(newRuleChainConnection);
  1245 + }
1212 1246 } else {
1213 1247 var toIndex = nodes.indexOf(destNode);
1214 1248 var nodeConnection = {
1215 1249 fromIndex: fromIndex,
1216   - toIndex: toIndex,
1217   - type: edge.label
  1250 + toIndex: toIndex
1218 1251 };
1219   - ruleChainMetaData.connections.push(nodeConnection);
  1252 + for (var cIndex=0;cIndex<edge.labels.length;cIndex++) {
  1253 + var newNodeConnection = angular.copy(nodeConnection);
  1254 + newNodeConnection.type = edge.labels[cIndex];
  1255 + ruleChainMetaData.connections.push(newNodeConnection);
  1256 + }
1220 1257 }
1221 1258 }
1222 1259 }
... ... @@ -1285,13 +1322,13 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
1285 1322 });
1286 1323 }
1287 1324
1288   - function addRuleNodeLink($event, link, labels) {
  1325 + function addRuleNodeLink($event, link, labels, allowCustomLabels) {
1289 1326 return $mdDialog.show({
1290 1327 controller: 'AddRuleNodeLinkController',
1291 1328 controllerAs: 'vm',
1292 1329 templateUrl: addRuleNodeLinkTemplate,
1293 1330 parent: angular.element($document[0].body),
1294   - locals: {link: link, labels: labels},
  1331 + locals: {link: link, labels: labels, allowCustomLabels: allowCustomLabels},
1295 1332 fullscreen: true,
1296 1333 targetEvent: $event
1297 1334 });
... ... @@ -1335,13 +1372,14 @@ export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId,
1335 1372 }
1336 1373
1337 1374 /*@ngInject*/
1338   -export function AddRuleNodeLinkController($scope, $mdDialog, link, labels, helpLinks) {
  1375 +export function AddRuleNodeLinkController($scope, $mdDialog, link, labels, allowCustomLabels, helpLinks) {
1339 1376
1340 1377 var vm = this;
1341 1378
1342 1379 vm.helpLinks = helpLinks;
1343 1380 vm.link = link;
1344 1381 vm.labels = labels;
  1382 + vm.allowCustomLabels = allowCustomLabels;
1345 1383
1346 1384 vm.add = add;
1347 1385 vm.cancel = cancel;
... ...
... ... @@ -170,6 +170,9 @@
170 170 &.tb-rule-chain-type {
171 171 background-color: #d6c4f1;
172 172 }
  173 + &.tb-unknown-type {
  174 + background-color: #f16c29;
  175 + }
173 176 }
174 177
175 178 .tb-rule-node {
... ... @@ -202,6 +205,7 @@
202 205 background-color: #a3eaa9;
203 206 user-select: none;
204 207 }
  208 +
205 209 md-icon {
206 210 font-size: 20px;
207 211 width: 20px;
... ...
... ... @@ -207,11 +207,11 @@
207 207 </details-buttons>
208 208 <form name="vm.ruleNodeLinkForm" ng-if="vm.isEditingRuleNodeLink">
209 209 <tb-rule-node-link
210   - link="vm.editingRuleNodeLink"
211   - labels="vm.editingRuleNodeLinkLabels"
  210 + ng-model="vm.editingRuleNodeLink"
  211 + allowed-labels="vm.editingRuleNodeLinkLabels"
  212 + allow-custom="vm.editingRuleNodeAllowCustomLabels"
212 213 is-edit="true"
213   - is-read-only="false"
214   - the-form="vm.ruleNodeLinkForm">
  214 + is-read-only="false">
215 215 </tb-rule-node-link>
216 216 </form>
217 217 </tb-details-sidenav>
... ...
... ... @@ -16,6 +16,7 @@
16 16 @import "~compass-sass-mixins/lib/compass";
17 17 @import "constants";
18 18 @import "animations";
  19 +@import "mixins";
19 20 @import "fonts";
20 21
21 22 /***************
... ... @@ -437,6 +438,12 @@ pre.tb-highlight {
437 438 }
438 439 }
439 440
  441 +.tb-card-description {
  442 + color: rgba(0,0,0,0.54);
  443 + font-size: 13px;
  444 + @include line-clamp(2, 1.1);
  445 +}
  446 +
440 447 /***********************
441 448 * Flow
442 449 ***********************/
... ...
... ... @@ -31,4 +31,29 @@
31 31 &:-ms-input-placeholder {
32 32 @content;
33 33 }
34   -}
\ No newline at end of file
  34 +}
  35 +
  36 +@mixin line-clamp($numLines: 1, $lineHeight: 1.412) {
  37 + overflow: hidden;
  38 + position: relative;
  39 + line-height: $lineHeight;
  40 + text-align: justify;
  41 + margin-right: -1em;
  42 + padding-right: 2em;
  43 + max-height: ($numLines*$lineHeight)+em;
  44 + &:before {
  45 + content: '...';
  46 + position: absolute;
  47 + right: 1em;
  48 + bottom: 0;
  49 + }
  50 + &:after {
  51 + content: '';
  52 + position: absolute;
  53 + right: 1em;
  54 + width: 1em;
  55 + height: 1em;
  56 + margin-top: 0.2em;
  57 + background: white;
  58 + }
  59 +}
... ...