Multiple labels support.

This commit is contained in:
Igor Kulikov 2018-06-18 14:33:50 +03:00
parent 1f58c1b0d6
commit f081b656d0
13 changed files with 318 additions and 53 deletions

View File

@ -32,6 +32,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
getRuleNodeComponents: getRuleNodeComponents, getRuleNodeComponents: getRuleNodeComponents,
getRuleNodeComponentByClazz: getRuleNodeComponentByClazz, getRuleNodeComponentByClazz: getRuleNodeComponentByClazz,
getRuleNodeSupportedLinks: getRuleNodeSupportedLinks, getRuleNodeSupportedLinks: getRuleNodeSupportedLinks,
ruleNodeAllowCustomLinks: ruleNodeAllowCustomLinks,
resolveTargetRuleChains: resolveTargetRuleChains, resolveTargetRuleChains: resolveTargetRuleChains,
testScript: testScript, testScript: testScript,
getLatestRuleNodeDebugInput: getLatestRuleNodeDebugInput getLatestRuleNodeDebugInput: getLatestRuleNodeDebugInput
@ -127,21 +128,21 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
function getRuleNodeSupportedLinks(component) { function getRuleNodeSupportedLinks(component) {
var relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes; var relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes;
var customRelations = component.configurationDescriptor.nodeDefinition.customRelations; var linkLabels = {};
var linkLabels = [];
for (var i=0;i<relationTypes.length;i++) { for (var i=0;i<relationTypes.length;i++) {
linkLabels.push({ var label = relationTypes[i];
name: relationTypes[i], custom: false linkLabels[label] = {
}); name: label,
} value: label
if (customRelations) { };
linkLabels.push(
{ name: 'Custom', custom: true }
);
} }
return linkLabels; return linkLabels;
} }
function ruleNodeAllowCustomLinks(component) {
return component.configurationDescriptor.nodeDefinition.customRelations;
}
function getRuleNodeComponents() { function getRuleNodeComponents() {
var deferred = $q.defer(); var deferred = $q.defer();
if (ruleNodeComponents) { if (ruleNodeComponents) {
@ -226,7 +227,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
if (res && res.length) { if (res && res.length) {
return res[0]; return res[0];
} }
return null; var unknownComponent = angular.copy(types.unknownNodeComponent);
unknownComponent.clazz = clazz;
unknownComponent.configurationDescriptor.nodeDefinition.details = "Unknown Rule Node class: " + clazz;
return unknownComponent;
} }
function resolveTargetRuleChains(ruleChainConnections) { function resolveTargetRuleChains(ruleChainConnections) {

View File

@ -510,6 +510,22 @@ export default angular.module('thingsboard.types', [])
} }
} }
}, },
unknownNodeComponent: {
type: 'UNKNOWN',
name: 'unknown',
clazz: 'tb.internal.Unknown',
configurationDescriptor: {
nodeDefinition: {
description: "",
details: "",
inEnabled: true,
outEnabled: true,
relationTypes: [],
customRelations: false,
defaultConfiguration: {}
}
}
},
inputNodeComponent: { inputNodeComponent: {
type: 'INPUT', type: 'INPUT',
name: 'Input', name: 'Input',
@ -565,6 +581,13 @@ export default angular.module('thingsboard.types', [])
nodeClass: "tb-input-type", nodeClass: "tb-input-type",
icon: "input", icon: "input",
special: true special: true
},
UNKNOWN: {
value: "UNKNOWN",
name: "rulenode.type-unknown",
details: "rulenode.type-unknown-details",
nodeClass: "tb-unknown-type",
icon: "help_outline"
} }
}, },
valueType: { valueType: {

View File

@ -16,8 +16,8 @@
--> -->
<div flex layout="column" style="margin-top: -10px;"> <div flex layout="column" style="margin-top: -10px;">
<div flex>{{vm.item.additionalInfo.description}}</div> <div style="text-transform: uppercase; padding-bottom: 5px;">{{vm.item.type}}</div>
<div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div> <div class="tb-card-description">{{vm.item.additionalInfo.description}}</div>
<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div> <div style="padding-top: 5px;" class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
<div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div> <div style="padding-top: 5px;" class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
</div> </div>

View File

@ -1157,6 +1157,11 @@ export default angular.module('thingsboard.locale', [])
"link-label-required": "Link label is required.", "link-label-required": "Link label is required.",
"custom-link-label": "Custom link label", "custom-link-label": "Custom link label",
"custom-link-label-required": "Custom link label is required.", "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": "Filter",
"type-filter-details": "Filter incoming messages with configured conditions", "type-filter-details": "Filter incoming messages with configured conditions",
"type-enrichment": "Enrichment", "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-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
"type-input": "Input", "type-input": "Input",
"type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node", "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.", "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
"ui-resources-load-error": "Failed to load configuration ui resources.", "ui-resources-load-error": "Failed to load configuration ui resources.",
"invalid-target-rulechain": "Unable to resolve target rule chain!", "invalid-target-rulechain": "Unable to resolve target rule chain!",

View File

@ -31,7 +31,7 @@
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span> <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content> <md-dialog-content>
<div class="md-dialog-content"> <div class="md-dialog-content">
<tb-rule-node-link link="vm.link" labels="vm.labels" is-edit="true" the-form="theForm"></tb-rule-node-link> <tb-rule-node-link ng-model="vm.link" allowed-labels="vm.labels" is-edit="true" allow-custom="vm.allowCustomLabels"></tb-rule-node-link>
</div> </div>
</md-dialog-content> </md-dialog-content>
<md-dialog-actions layout="row"> <md-dialog-actions layout="row">

View File

@ -17,7 +17,46 @@
--> -->
<md-content class="md-padding tb-link" layout="column"> <md-content class="md-padding tb-link" layout="column">
<fieldset ng-disabled="$root.loading || !isEdit || isReadOnly"> <fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
<md-input-container class="md-block"> <label translate class="tb-title no-padding" ng-class="{'tb-required': required}">rulenode.link-labels</label>
<md-chips id="link_label_chips"
ng-required="true"
readonly="$root.loading || !isEdit || isReadOnly"
ng-model="labels" md-autocomplete-snap
md-transform-chip="transformLinkLabelChip($chip)"
md-require-match="!allowCustom">
<md-autocomplete
id="link_label"
md-no-cache="true"
md-selected-item="selectedLabel"
md-search-text="labelSearchText"
md-items="item in labelsSearch(labelSearchText)"
md-item-text="item.name"
md-min-length="0"
placeholder="{{'rulenode.link-label' | translate }}"
md-menu-class="tb-link-label-autocomplete">
<span md-highlight-text="labelSearchText" md-highlight-flags="^i">{{item}}</span>
<md-not-found>
<div class="tb-not-found">
<div class="tb-no-entries" ng-if="!labelSearchText || !labelSearchText.length">
<span translate>rulenode.no-link-labels-found</span>
</div>
<div ng-if="labelSearchText && labelSearchText.length">
<span translate translate-values='{ label: "{{labelSearchText | truncate:true:6:&apos;...&apos;}}" }'>rulenode.no-link-label-matching</span>
<span ng-if="allowCustom">
<a translate ng-click="createLinkLabel($event, '#link_label_chips')">rulenode.create-new-link-label</a>
</span>
</div>
</div>
</md-not-found>
</md-autocomplete>
<md-chip-template>
<span>{{$chip.name}}</span>
</md-chip-template>
</md-chips>
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
<div translate ng-message="linkLabels" class="tb-error-message">rulenode.link-labels-required</div>
</div>
<!--md-input-container class="md-block">
<label translate>rulenode.link-label</label> <label translate>rulenode.link-label</label>
<md-select ng-model="selectedLabel" ng-change="selectedLabelChanged()"> <md-select ng-model="selectedLabel" ng-change="selectedLabelChanged()">
<md-option ng-repeat="label in labels" ng-value="label"> <md-option ng-repeat="label in labels" ng-value="label">
@ -34,6 +73,6 @@
<div ng-messages="theForm.customLinkLabel.$error"> <div ng-messages="theForm.customLinkLabel.$error">
<div translate ng-message="required">rulenode.custom-link-label-required</div> <div translate ng-message="required">rulenode.custom-link-label-required</div>
</div> </div>
</md-input-container> </md-input-container-->
</fieldset> </fieldset>
</md-content> </md-content>

View File

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import './link.scss';
/* eslint-disable import/no-unresolved, import/default */ /* eslint-disable import/no-unresolved, import/default */
import linkFieldsetTemplate from './link-fieldset.tpl.html'; import linkFieldsetTemplate from './link-fieldset.tpl.html';
@ -22,13 +24,18 @@ import linkFieldsetTemplate from './link-fieldset.tpl.html';
/*@ngInject*/ /*@ngInject*/
export default function LinkDirective($compile, $templateCache, $filter) { export default function LinkDirective($compile, $templateCache, $filter) {
var linker = function (scope, element) { var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(linkFieldsetTemplate); var template = $templateCache.get(linkFieldsetTemplate);
element.html(template); element.html(template);
scope.selectedLabel = null; scope.selectedLabel = null;
scope.labelSearchText = null;
scope.$watch('link', function() { scope.ngModelCtrl = ngModelCtrl;
var labelsList = [];
/*scope.$watch('link', function() {
scope.selectedLabel = null; scope.selectedLabel = null;
if (scope.link && scope.labels) { if (scope.link && scope.labels) {
if (scope.link.label) { if (scope.link.label) {
@ -53,19 +60,100 @@ export default function LinkDirective($compile, $templateCache, $filter) {
scope.link.label = ""; 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); $compile(element.contents())(scope);
} }
return { return {
restrict: "E", restrict: "E",
require: "^ngModel",
link: linker, link: linker,
scope: { scope: {
link: '=', allowedLabels: '=',
labels: '=', allowCustom: '=',
isEdit: '=', isEdit: '=',
isReadOnly: '=', isReadOnly: '='
theForm: '='
} }
}; };
} }

View File

@ -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;
}
}

View File

@ -669,11 +669,15 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
} }
} else { } else {
if (edge.label) { if (edge.label) {
if (!edge.labels) {
edge.labels = edge.label.split(' / ');
}
deferred.resolve(edge); deferred.resolve(edge);
} else { } else {
var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
var allowCustomLabels = ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
vm.enableHotKeys = false; vm.enableHotKeys = false;
addRuleNodeLink(event, edge, labels).then( addRuleNodeLink(event, edge, labels, allowCustomLabels).then(
(link) => { (link) => {
deferred.resolve(link); deferred.resolve(link);
vm.enableHotKeys = true; vm.enableHotKeys = true;
@ -713,6 +717,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
vm.isEditingRuleNode = false; vm.isEditingRuleNode = false;
vm.editingRuleNode = null; vm.editingRuleNode = null;
vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
vm.editingRuleNodeAllowCustomLabels = ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
vm.isEditingRuleNodeLink = true; vm.isEditingRuleNodeLink = true;
vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
vm.editingRuleNodeLink = angular.copy(edge); vm.editingRuleNodeLink = angular.copy(edge);
@ -744,7 +749,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
isInputSource: isInputSource, isInputSource: isInputSource,
fromIndex: fromIndex, fromIndex: fromIndex,
toIndex: toIndex, toIndex: toIndex,
label: edge.label label: edge.label,
labels: edge.labels
}; };
connections.push(connection); connections.push(connection);
} }
@ -816,7 +822,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var edge = { var edge = {
source: source, source: source,
destination: destination, destination: destination,
label: connection.label label: connection.label,
labels: connection.labels
}; };
vm.ruleChainModel.edges.push(edge); vm.ruleChainModel.edges.push(edge);
vm.modelservice.edges.select(edge); vm.modelservice.edges.select(edge);
@ -1024,6 +1031,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
} }
if (vm.ruleChainMetaData.connections) { if (vm.ruleChainMetaData.connections) {
var edgeMap = {};
for (i = 0; i < vm.ruleChainMetaData.connections.length; i++) { for (i = 0; i < vm.ruleChainMetaData.connections.length; i++) {
var connection = vm.ruleChainMetaData.connections[i]; var connection = vm.ruleChainMetaData.connections[i];
var sourceNode = nodes[connection.fromIndex]; 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 sourceConnectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
var destConnectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType); var destConnectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType);
if (sourceConnectors && sourceConnectors.length && destConnectors && destConnectors.length) { if (sourceConnectors && sourceConnectors.length && destConnectors && destConnectors.length) {
edge = { var sourceId = sourceConnectors[0].id;
source: sourceConnectors[0].id, var destId = destConnectors[0].id;
destination: destConnectors[0].id, var edgeKey = sourceId + '_' + destId;
label: connection.type edge = edgeMap[edgeKey];
}; if (!edge) {
vm.ruleChainModel.edges.push(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) { if (vm.ruleChainMetaData.ruleChainConnections) {
var ruleChainNodesMap = {}; var ruleChainNodesMap = {};
var ruleChainEdgeMap = {};
for (i = 0; i < vm.ruleChainMetaData.ruleChainConnections.length; i++) { for (i = 0; i < vm.ruleChainMetaData.ruleChainConnections.length; i++) {
var ruleChainConnection = vm.ruleChainMetaData.ruleChainConnections[i]; var ruleChainConnection = vm.ruleChainMetaData.ruleChainConnections[i];
var ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id]; var ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id];
@ -1081,12 +1101,23 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
if (sourceNode) { if (sourceNode) {
connectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType); connectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
if (connectors && connectors.length) { if (connectors && connectors.length) {
var ruleChainEdge = { sourceId = connectors[0].id;
source: connectors[0].id, destId = ruleChainNode.connectors[0].id;
destination: ruleChainNode.connectors[0].id, edgeKey = sourceId + '_' + destId;
label: ruleChainConnection.type var ruleChainEdge = ruleChainEdgeMap[edgeKey];
}; if (!ruleChainEdge) {
vm.ruleChainModel.edges.push(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 = { var ruleChainConnection = {
fromIndex: fromIndex, fromIndex: fromIndex,
targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId}, targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId},
additionalInfo: destNode.additionalInfo, additionalInfo: destNode.additionalInfo
type: edge.label
}; };
if (!ruleChainConnection.additionalInfo) { if (!ruleChainConnection.additionalInfo) {
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.layoutX = Math.round(destNode.x);
ruleChainConnection.additionalInfo.layoutY = Math.round(destNode.y); ruleChainConnection.additionalInfo.layoutY = Math.round(destNode.y);
ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id; ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id;
ruleChainMetaData.ruleChainConnections.push(ruleChainConnection); for (var rcIndex=0;rcIndex<edge.labels.length;rcIndex++) {
var newRuleChainConnection = angular.copy(ruleChainConnection);
newRuleChainConnection.type = edge.labels[rcIndex];
ruleChainMetaData.ruleChainConnections.push(newRuleChainConnection);
}
} else { } else {
var toIndex = nodes.indexOf(destNode); var toIndex = nodes.indexOf(destNode);
var nodeConnection = { var nodeConnection = {
fromIndex: fromIndex, fromIndex: fromIndex,
toIndex: toIndex, toIndex: toIndex
type: edge.label
}; };
ruleChainMetaData.connections.push(nodeConnection); for (var cIndex=0;cIndex<edge.labels.length;cIndex++) {
var newNodeConnection = angular.copy(nodeConnection);
newNodeConnection.type = edge.labels[cIndex];
ruleChainMetaData.connections.push(newNodeConnection);
}
} }
} }
} }
@ -1285,13 +1322,13 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
}); });
} }
function addRuleNodeLink($event, link, labels) { function addRuleNodeLink($event, link, labels, allowCustomLabels) {
return $mdDialog.show({ return $mdDialog.show({
controller: 'AddRuleNodeLinkController', controller: 'AddRuleNodeLinkController',
controllerAs: 'vm', controllerAs: 'vm',
templateUrl: addRuleNodeLinkTemplate, templateUrl: addRuleNodeLinkTemplate,
parent: angular.element($document[0].body), parent: angular.element($document[0].body),
locals: {link: link, labels: labels}, locals: {link: link, labels: labels, allowCustomLabels: allowCustomLabels},
fullscreen: true, fullscreen: true,
targetEvent: $event targetEvent: $event
}); });
@ -1335,13 +1372,14 @@ export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId,
} }
/*@ngInject*/ /*@ngInject*/
export function AddRuleNodeLinkController($scope, $mdDialog, link, labels, helpLinks) { export function AddRuleNodeLinkController($scope, $mdDialog, link, labels, allowCustomLabels, helpLinks) {
var vm = this; var vm = this;
vm.helpLinks = helpLinks; vm.helpLinks = helpLinks;
vm.link = link; vm.link = link;
vm.labels = labels; vm.labels = labels;
vm.allowCustomLabels = allowCustomLabels;
vm.add = add; vm.add = add;
vm.cancel = cancel; vm.cancel = cancel;

View File

@ -170,6 +170,9 @@
&.tb-rule-chain-type { &.tb-rule-chain-type {
background-color: #d6c4f1; background-color: #d6c4f1;
} }
&.tb-unknown-type {
background-color: #f16c29;
}
} }
.tb-rule-node { .tb-rule-node {
@ -202,6 +205,7 @@
background-color: #a3eaa9; background-color: #a3eaa9;
user-select: none; user-select: none;
} }
md-icon { md-icon {
font-size: 20px; font-size: 20px;
width: 20px; width: 20px;

View File

@ -207,11 +207,11 @@
</details-buttons> </details-buttons>
<form name="vm.ruleNodeLinkForm" ng-if="vm.isEditingRuleNodeLink"> <form name="vm.ruleNodeLinkForm" ng-if="vm.isEditingRuleNodeLink">
<tb-rule-node-link <tb-rule-node-link
link="vm.editingRuleNodeLink" ng-model="vm.editingRuleNodeLink"
labels="vm.editingRuleNodeLinkLabels" allowed-labels="vm.editingRuleNodeLinkLabels"
allow-custom="vm.editingRuleNodeAllowCustomLabels"
is-edit="true" is-edit="true"
is-read-only="false" is-read-only="false">
the-form="vm.ruleNodeLinkForm">
</tb-rule-node-link> </tb-rule-node-link>
</form> </form>
</tb-details-sidenav> </tb-details-sidenav>

View File

@ -16,6 +16,7 @@
@import "~compass-sass-mixins/lib/compass"; @import "~compass-sass-mixins/lib/compass";
@import "constants"; @import "constants";
@import "animations"; @import "animations";
@import "mixins";
@import "fonts"; @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 * Flow
***********************/ ***********************/

View File

@ -31,4 +31,29 @@
&:-ms-input-placeholder { &:-ms-input-placeholder {
@content; @content;
} }
} }
@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;
}
}