This closes #96
diff --git a/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.directive.js b/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.directive.js
index 60f7beb..b8b3bc6 100644
--- a/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.directive.js
+++ b/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.directive.js
@@ -25,32 +25,36 @@
const MIN_ROWS_PER_PAGE = 4;
const PALETTE_VIEW_ORDERS = {
- name: { label: "Name", field: "displayName" },
- lastUsed: { label: "Recent", field: "-lastUsed" },
- bundle: { label: "Bundle", field: "containingBundle" },
- id: { label: "ID", field: "symbolicName" },
+ relevance: { id: "relevance", label: "Relevance", field: "relevance" },
+ name: { id: "name", label: "Name", field: "displayName" },
+ lastUsed: { id: "lastUsed", label: "Recent", field: "-lastUsed" },
+ bundle: { id: "bundle", label: "Bundle", field: "containingBundle" },
+ id: { id: "id", label: "ID", field: "symbolicName" },
};
const PALETTE_VIEW_MODES = {
- compact: { name: "Compact", classes: "col-xs-2 item-compact", itemsPerRow: 6, rowHeightPx: 75, hideName: true },
- normal: { name: "Normal", classes: "col-xs-3", itemsPerRow: 4 },
- large: { name: "Large", classes: "col-xs-4", itemsPerRow: 3 },
+ tiny: { name: "Tiny", classes: "col-xs-2 item-compact", itemsPerRow: 6, rowHeightPx: 75, hideName: true },
+ compact: { name: "Compact", classes: "col-xs-3", itemsPerRow: 4 },
+ normal: { name: "Normal", classes: "col-xs-4", itemsPerRow: 3 },
+ large: { name: "Large", classes: "col-xs-6", itemsPerRow: 2 },
list: { name: "List", classes: "col-xs-12 item-full-width", itemsPerRow: 1 },
};
// fields in either bundle or type record:
-const FIELDS_TO_SEARCH = ['name', 'displayName', 'symbolicName', 'version', 'type', 'supertypes', 'containingBundle', 'description', 'displayTags', 'tags'];
+const FIELDS_TO_SEARCH = ['displayName', 'name', 'symbolicName', 'type', 'version', 'containingBundle', 'description', 'displayTags', 'tags', 'supertypes'];
export function catalogSelectorDirective() {
return {
restrict: 'E',
scope: {
family: '<',
- onSelect: '&',
- rowsPerPage: '<?', // if unset then fill
+ onSelect: '&', // action to do when item is selected
+ onSelectText: "&?", // function returning text to show in the "on select" button for an item
+ iconSelects: '<?', // boolean whether clicking the icon triggers selection directly or shows popup (false, default)
+ rowsPerPage: '<?', // optionally show fixed number of rows; unset (default and normal) computes based on available height
reservedKeys: '<?',
- state: '<?',
- mode: '@?', // for use by downstream projects to pass in special modes
+ state: '<?', // for shared state usage
+ mode: '@?', // for use by downstream projects to pass in special modes to do add'l processing / rendering
},
template: template,
controller: ['$scope', '$element', '$timeout', '$q', '$uibModal', '$log', '$templateCache', 'paletteApi', 'paletteDragAndDropService', 'iconGenerator', 'composerOverrides', 'recentlyUsedService', controller],
@@ -88,23 +92,42 @@
return function (items, search) {
if (search) {
return items.filter(function (item) {
- return search.toLowerCase().split(' ').reduce( (found, part) =>
- found &&
- FIELDS_TO_SEARCH
- .filter(field => item.hasOwnProperty(field) && item[field])
- .reduce((match, field) => {
+ item.relevance = 0;
+ let wordNum = 0;
+ return search.toLowerCase().split(' ').reduce( (found, part) => {
+ wordNum++;
+ let fieldNum = 0;
+ return found &&
+ FIELDS_TO_SEARCH.reduce((match, field) => {
if (match) return true;
+ fieldNum++;
+ if (!item.hasOwnProperty(field) || !item[field]) return false;
let text = item[field];
if (!text.toLowerCase) {
text = JSON.stringify(text).toLowerCase();
} else {
text = text.toLowerCase();
}
- return match || text.indexOf(part) > -1;
+ let index = text.indexOf(part);
+ if (index == -1) return false;
+ // found, set relevance -- uses an ad hoc heuristic preferring first fields and short text length,
+ // earlier occurrences and earlier words weighted more highly (smaller number is better)
+ let score = fieldNum * (2 / (1 + wordNum)) * Math.log(1 + text.length * index);
+ /* to debug the scoring function:
+ if (item.symbolicName.indexOf("EIP") >= 0 || item.symbolicName.indexOf("OpsWorks") >= 0) {
+ console.log(item.symbolicName, ": match", part, "in", field,
+ "#", fieldNum, wordNum,
+ "pos", index, "/", text.length,
+ ":", item.relevance, "+=", score);
+ }
+ */
+ item.relevance += score;
+ return true;
}, false)
- , true);
+ }, true);
});
} else {
+ items.forEach( item => item.relevance = 0 );
return items;
}
}
@@ -150,7 +173,6 @@
if (!$scope.state) $scope.state = {};
if (!$scope.state.viewMode) $scope.state.viewMode = PALETTE_VIEW_MODES.normal;
- if (!$scope.state.currentOrder) $scope.state.currentOrder = [ PALETTE_VIEW_ORDERS.name.field, '-version' ];
$scope.pagination = {
page: 1,
@@ -218,7 +240,41 @@
$log.warn("Could not mark item as used: "+item, e);
}
}
+ $scope.mouseInfoPopover = (item, enter) => {
+ if ($scope.popoverModal && $scope.popoverVisible && $scope.popover==item) {
+ // ignore if modal
+ return;
+ }
+ $scope.popoverModal = false;
+ if (enter) {
+ $scope.popover = item;
+ $scope.popoverVisible = true;
+ } else {
+ $scope.popoverVisible = false;
+ }
+ }
+ $scope.onClickItem = (item, isInfoIcon, $event) => {
+ if (!isInfoIcon && $scope.iconSelects) {
+ $scope.onSelectItem(item);
+ } else if ($scope.popoverModal && $scope.popoverVisible && $scope.popover == item) {
+ $scope.closePopover();
+ } else {
+ $scope.popover = item;
+ $scope.popoverVisible = true;
+ $scope.popoverModal = true;
+ }
+ $event.stopPropagation();
+ }
+ $scope.closePopover = () => {
+ $scope.popoverVisible = false;
+ $scope.popoverModal = false;
+ }
+ $scope.getOnSelectText = function (item) {
+ if (!($scope.onSelectText)) return "Select";
+ return $scope.onSelectText({item: item});
+ }
$scope.onSelectItem = function (item) {
+ $scope.closePopover();
if (angular.isFunction($scope.onSelect)) {
tryMarkUsed(item);
$scope.onSelect({item: item});
@@ -247,11 +303,26 @@
paletteDragAndDropService.dragEnd();
tryMarkUsed(item);
};
+
+ $scope.getOpenCatalogLink = (item) => {
+ return "/brooklyn-ui-catalog/#!/bundles/"+item.containingBundle.replace(":","/")+"/types/"+item.symbolicName+"/"+item.version;
+ }
$scope.sortBy = function (order) {
- let newOrder = [].concat($scope.state.currentOrder);
- newOrder = newOrder.filter( (o) => o !== order.field );
- $scope.state.currentOrder = [order.field].concat(newOrder);
+ let newFirst = {};
+ if (order) {
+ newFirst[order.id] = order;
+ }
+ $scope.state.currentOrder = Object.assign(newFirst, $scope.state.currentOrder, newFirst);
+ $scope.state.currentOrderFields = [];
+ $scope.state.currentOrderValues = [];
+ Object.values($scope.state.currentOrder).forEach( it => {
+ $scope.state.currentOrderValues.push(it);
+ $scope.state.currentOrderFields.push(it.field);
+ });
};
+ if (!$scope.state.currentOrder) $scope.state.currentOrder = Object.assign({}, PALETTE_VIEW_ORDERS);
+ $scope.sortBy();
+
$scope.allowFreeForm = function () {
return [
EntityFamily.LOCATION
@@ -290,11 +361,13 @@
$scope.items = items;
});
$scope.lastUsedText = (item) => {
+ if (item==null) return "";
let l = (Number)(item.lastUsed);
if (!l || isNaN(l) || l<=0) return "";
if (l < 100000) return 'Preselected for inclusion in "Recent" filter.';
return 'Last used: ' + distanceInWordsToNow(l, { includeSeconds: true, addSuffix: true });
};
+
$scope.showPaletteControls = false;
$scope.onFiltersShown = () => {
$timeout( () => {
@@ -330,6 +403,9 @@
}
}
+ // can be overridden to disable "open in catalog" button
+ $scope.allowOpenInCatalog = true;
+
// this can be overridden for palette sections/modes which show a subset of the types returned by the server;
// this is applied when the data is received from the server.
// it is used by catalogSelectorFiltersFilter;
diff --git a/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less b/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less
index d22ef1f..c7aae95 100644
--- a/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less
+++ b/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.less
@@ -264,6 +264,9 @@
.label {
line-height: 2.2;
}
+ .closer .br-icon-close-bar {
+ stroke: @brand-primary;
+ }
}
.deprecated-marker {
float: right;
@@ -280,6 +283,44 @@
.palette-item-tag {
margin-right: 4px;
}
+ .quick-info-title {
+ background-color: @gray-lightest;
+ border-bottom: 1px solid @popover-border-color;
+ margin-left: -15px;
+ margin-right: -15px;
+ margin-top: -10px;
+ margin-bottom: 12px;
+ padding: 8px 16px 6px 16px;
+ border-radius: 5px 5px 0 0;
+ .closer {
+ margin-top: 6px;
+ }
+ }
+ .closer {
+ width: 10px;
+ > svg { width: 10px; }
+ cursor: pointer;
+ margin-left: 6px;
+ }
+ .quick-info-buttons {
+ border-top: 1px solid @popover-border-color;
+ margin-top: 10px;
+ padding-top: 10px;
+ display: flex;
+ div.spacer {
+ flex: 1 1 auto;
+ }
+ button {
+ padding: 6px 9px;
+ line-height: 1;
+ margin-left: 12px;
+ }
+ .select-item-button {
+ flex: 0 1 auto;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+ }
}
p:last-child {
diff --git a/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.template.html b/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.template.html
index 870600c..3b6eaa3 100644
--- a/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.template.html
+++ b/ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.template.html
@@ -74,7 +74,7 @@
<i class="fa fa-sort"></i></div>
</a>
<ul class="dropdown-menu right-align-icon" role="menu" uib-dropdown-menu aria-labelledby="palette-sort">
- <li role="menuitem" ng-repeat="order in viewOrders track by $index" ng-class="{'active': state.currentOrder[0] === order.field}" class="layer">
+ <li role="menuitem" ng-repeat="order in state.currentOrderValues track by $index" class="layer">
<a ng-click="sortBy(order)"><i class="fa fa-fw fa-circle"></i> {{ order.label }}</a>
</li>
</ul>
@@ -100,8 +100,8 @@
<!-- here and below, col-xs-3 or -4 or -2 all work giving different densities;
this could be configurable ("compressed"=xs-2 w no labels, "normal"=xs-3, "big"=xs-4) -->
<div class="catalog-palette-item" ng-class="state.viewMode.classes"
- ng-repeat="item in searchedItems = (items | catalogSelectorSearch:search | catalogSelectorFilters:this) | orderBy:state.currentOrder | limitTo:pagination.itemsPerPage:(pagination.page-1)*pagination.itemsPerPage track by (item.containingBundle + ':' + item.symbolicName + ':' + item.version)"
- ng-click="onSelectItem(item)">
+ ng-repeat="item in searchedItems = (items | catalogSelectorSearch:search | catalogSelectorFilters:this) | orderBy:state.currentOrderFields | limitTo:pagination.itemsPerPage:(pagination.page-1)*pagination.itemsPerPage track by (item.containingBundle + ':' + item.symbolicName + ':' + item.version)"
+ ng-click="onClickItem(item, false, $event)">
<div class="item" draggable="true" ng-dragstart="onDragItem(item, $event)" ng-dragend="onDragEnd(item, $event)">
<div class="item-logo">
<img ng-src="{{item | iconGeneratorPipe:'symbolicName'}}" alt="{{item.displayName}} logo" on-error="onImageError" item-id="{{item.symbolicName}}"/>
@@ -111,16 +111,18 @@
</div>
<i class="fa fa-info-circle"
uib-popover-template="'QuickInfoTemplate.html'"
- popover-title="{{item | entityName}}"
- popover-placement="right-top" popover-trigger="'mouseenter'"
+ ng-click="onClickItem(item, true, $event)"
+ popover-is-open="popover == item && popoverVisible"
+ popover-placement="right" popover-trigger="'none'"
popover-class="catalog-selector-popover" popover-append-to-body="true"
- ng-click="$event.stopPropagation()"></i>
+ ng-mouseenter="mouseInfoPopover(item, true)"
+ ng-mouseleave="mouseInfoPopover(item, false)"></i>
</div>
</div>
<div class="catalog-palette-item"
ng-class="state.viewMode.classes"
- ng-if="searchedItems.length === 0 && search && allowFreeForm()" ng-click="onSelectItem(freeFormTile)">
+ ng-if="searchedItems.length === 0 && search && allowFreeForm()" ng-click="onClickItem(freeFormTile, $event)">
<div class="item" draggable="true" ng-dragstart="onDragItem(freeFormTile, $event)" ng-dragend="onDragEnd(freeFormTile, $event)">
<div class="item-logo">
<img ng-src="{{freeFormTile | iconGeneratorPipe:'symbolicName'}}" alt="{{freeFormTile.displayName}} logo" on-error="onImageError" item-id="{{freeFormTile.symbolicName}}"/>
@@ -128,6 +130,13 @@
<div class="item-content" ng-hide="state.viewMode.hideName">
<h3>{{freeFormTile | entityName}}</h3>
</div>
+ <i class="fa fa-info-circle"
+ uib-popover-template="'QuickInfoTemplate.html'"
+ popover-is-open="popover == freeFormTile && popoverVisible"
+ popover-placement="right-top" popover-trigger="'none'"
+ popover-class="catalog-selector-popover" popover-append-to-body="true"
+ ng-mouseenter="mouseInfoPopover(freeFormTile, true)"
+ ng-mouseleave="mouseInfoPopover(freeFormTile, false)"></i>
</div>
<div class="text-danger" ng-if="isReserved()">
Cannot add <code>{{freeFormTile.symbolicName}}</code> because it is reserved.
@@ -145,18 +154,30 @@
<!-- QUICK INFO TEMPLATE :: START-->
<script type="text/ng-template" id="QuickInfoTemplate.html">
<div class="palette-item-quick-info">
- <div class="deprecated-marker" ng-if="item.deprecated">DEPRECATED</div>
- <div class="quick-info-metadata">
- <p><i class="mini-icon fa fa-fw fa-bookmark"></i> <samp class="type-symbolic-name">{{item.symbolicName}}</samp></p>
- <p ng-if="item.version"><i class="mini-icon fa fa-fw fa-code-fork"></i> {{item.version}}</p>
+ <div class="quick-info-title">{{ popover | entityName }}
+ <br-svg type="close" class="pull-right closer" ng-click="closePopover()"></br-svg>
</div>
- <p class="quick-info-description" ng-if="item.description">{{item.description}}</p>
+ <div class="deprecated-marker" ng-if="popover.deprecated">DEPRECATED</div>
+ <div class="quick-info-metadata">
+ <p><i class="mini-icon fa fa-fw fa-bookmark"></i> <samp class="type-symbolic-name">{{popover.symbolicName}}</samp></p>
+ <p ng-if="popover.version"><i class="mini-icon fa fa-fw fa-code-fork"></i> {{popover.version}}</p>
+ </div>
+ <p class="quick-info-description" ng-if="popover.description">{{popover.description}}</p>
+ <p class="quick-info-description" ng-if="popover == freeFormTile">This is an ad hoc tile for an item entered by the user not known in the catalog.</p>
<div class="quick-info-metadata bundle">
- <p ng-if="lastUsedText(item)"><i class="mini-icon fa fa-clock-o"></i> {{ lastUsedText(item) }}</p>
- <p ng-if="item.displayTags && item.displayTags.length"><i class="mini-icon fa fa-fw fa-tags"></i>
- <span ng-repeat-start="tag in item.displayTags" class="label label-primary palette-item-tag">{{ tag }}</span>
+ <p ng-if="lastUsedText(popover)"><i class="mini-icon fa fa-clock-o"></i> {{ lastUsedText(popover) }}
+ <br-svg type="close" class="closer" ng-click="popover.lastUsed = 0"></br-svg>
+ </p>
+ <p ng-if="popover.displayTags && popover.displayTags.length"><i class="mini-icon fa fa-fw fa-tags"></i>
+ <span ng-repeat-start="tag in popover.displayTags" class="label label-primary palette-item-tag">{{ tag }}</span>
<span ng-repeat-end> </span> </p>
- <p><i class="mini-icon fa fa-fw fa-file-zip-o"></i> {{item.containingBundle}}</p>
+ <p ng-if="popover.containingBundle"><i class="mini-icon fa fa-fw fa-file-zip-o"></i> {{popover.containingBundle}}</p>
+ <p ng-if="popover.relevance"><i class="mini-icon fa fa-sort-numeric-asc"></i> Relevance score: {{ popover.relevance | number:2 }}</p>
+ </div>
+ <div class="quick-info-buttons">
+ <div class="spacer"></div>
+ <button class="btn btn-primary btn-outline select-item-button" ng-click="onSelectItem(popover, false, $event)">{{ getOnSelectText(popover) }}</button>
+ <a ng-if="popover.containingBundle && allowOpenInCatalog" href="{{ getOpenCatalogLink(popover) }}" target="_blank"><button class="btn btn-info btn-outline">Open in catalog</button></a>
</div>
</div>
</script>
diff --git a/ui-modules/blueprint-composer/app/components/designer/designer.directive.js b/ui-modules/blueprint-composer/app/components/designer/designer.directive.js
index 2f91745..e704928 100644
--- a/ui-modules/blueprint-composer/app/components/designer/designer.directive.js
+++ b/ui-modules/blueprint-composer/app/components/designer/designer.directive.js
@@ -29,6 +29,9 @@
export function designerDirective($log, $state, $q, iconGenerator, catalogApi, blueprintService, brSnackbar, paletteDragAndDropService) {
let directive = {
restrict: 'E',
+ scope: {
+ onSelectionChange: '<?'
+ },
template: '',
link: link
};
@@ -109,13 +112,16 @@
break;
}
if (angular.isDefined(id)) {
+ $log.debug(TAG + 'Select canvas, selected node: ' + id);
$scope.selectedEntity = blueprintService.findAny(id);
+ if ($scope.onSelectionChange) $scope.onSelectionChange($scope.selectedEntity);
}
});
$element.bind('click-svg', (event)=> {
$log.debug(TAG + 'Select canvas, un-select node (if one selected before)');
$scope.selectedEntity = null;
+ if ($scope.onSelectionChange) $scope.onSelectionChange($scope.selectedEntity);
$scope.$apply(()=> {
redrawGraph();
$state.go('main.graphical');
diff --git a/ui-modules/blueprint-composer/app/components/filters/entity.filter.js b/ui-modules/blueprint-composer/app/components/filters/entity.filter.js
index 2fa5c47..32a785b 100644
--- a/ui-modules/blueprint-composer/app/components/filters/entity.filter.js
+++ b/ui-modules/blueprint-composer/app/components/filters/entity.filter.js
@@ -16,11 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-const DEFAULT = '';
-
export function entityNameFilter() {
return function (input) {
- var result = input ? (input.displayName || input.name || input.symbolicName || input.type || DEFAULT) : DEFAULT;
+ var result = input ? (input.displayName || input.name || input.symbolicName || input.type || null) : null;
+ if (!result) {
+ if (input && !input.parent) result = 'Application';
+ else result = 'Unnamed entity';
+ }
if (result.match(/^[^\w]*deprecated[^\w]*/i)) {
result = result.replace(/^[^\w]*deprecated[^\w]*/i, '');
}
diff --git a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
index a5341b9..9c18129 100644
--- a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
+++ b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
@@ -78,13 +78,15 @@
scope: {
model: '='
},
- controller: controller,
+ controller: ['$scope', '$element', controller],
template: template,
link: link,
controllerAs: 'specEditor',
};
- function controller() {
+ function controller($scope, $element) {
+ (composerOverrides.configureSpecEditorController || function() {})(this, $scope, $element);
+
// does very little currently, but link adds to this
return this;
}
@@ -318,7 +320,7 @@
scope.state.config.filter.values.all = true;
}
};
- scope.recordFocus = specEditor.recordFocus = ($item)=> {
+ scope.recordFocus = specEditor.recordFocus = ($item) => {
scope.state.config.focus = $item.name;
};
diff --git a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
index 2b0a4c4..937bbaa 100644
--- a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
+++ b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
@@ -341,7 +341,9 @@
</br-collapsible>
<!-- ENTITY LOCATION -->
-<br-collapsible ng-if="[FAMILIES.ENTITY, FAMILIES.SPEC].indexOf(model.family) > -1" state="state.location.open">
+<ng-include src="'SpecEditorLocationSection.html'"></ng-include>
+<script type="text/ng-template" id="SpecEditorLocationSection.html">
+ <br-collapsible ng-if="[FAMILIES.ENTITY, FAMILIES.SPEC].indexOf(model.family) > -1" state="state.location.open">
<heading>
Location
<span ng-if="(model.issues | filter:{group:'location'}).length> 0" class="badge" ng-class="getBadgeClass((model.issues | filter:{group:'location'}))">{{(model.issues | filter:{group:'location'}).length}}</span>
@@ -365,10 +367,13 @@
<button class="btn btn-danger btn-link" ng-click="model.clearIssues({group: 'location'}).removeLocation()">Remove</button>
</div>
</div>
-</br-collapsible>
+ </br-collapsible>
+</script>
<!-- ENTITY POLICIES -->
-<br-collapsible ng-if="[FAMILIES.ENTITY, FAMILIES.SPEC].indexOf(model.family) > -1" state="state.policy.open">
+<ng-include src="'SpecEditorPoliciesSection.html'"></ng-include>
+<script type="text/ng-template" id="SpecEditorPoliciesSection.html">
+ <br-collapsible ng-if="[FAMILIES.ENTITY, FAMILIES.SPEC].indexOf(model.family) > -1" state="state.policy.open">
<heading>
Policies
<span ng-if="getPoliciesIssues().length> 0" class="badge" ng-class="getBadgeClass(getPoliciesIssues())">{{getPoliciesIssues().length}}</span>
@@ -398,10 +403,13 @@
<ng-include src="'AdjunctTemplate.html'"></ng-include>
</div>
</div>
-</br-collapsible>
+ </br-collapsible>
+</script>
<!-- ENTITY ENRICHERS -->
-<br-collapsible ng-if="[FAMILIES.ENTITY, FAMILIES.SPEC].indexOf(model.family) > -1" state="state.enricher.open">
+<ng-include src="'SpecEditorEnrichersSection.html'"></ng-include>
+<script type="text/ng-template" id="SpecEditorEnrichersSection.html">
+ <br-collapsible ng-if="[FAMILIES.ENTITY, FAMILIES.SPEC].indexOf(model.family) > -1" state="state.enricher.open">
<heading>
Enrichers
<span ng-if="getEnrichersIssues().length> 0" class="badge" ng-class="getBadgeClass(getEnrichersIssues())">{{getEnrichersIssues().length}}</span>
@@ -431,7 +439,13 @@
<ng-include src="'AdjunctTemplate.html'"></ng-include>
</div>
</div>
-</br-collapsible>
+ </br-collapsible>
+</script>
+
+<ng-include src="'SpecEditorOtherSections.html'"></ng-include>
+<script type="text/ng-template" id="SpecEditorOtherSections.html">
+</script>
+
<!-- CONFIG INFO TEMPLATE :: START -->
<script type="text/ng-template" id="ConfigInfoTemplate.html">
diff --git a/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.html b/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.html
index dfabeaf..24acefc 100644
--- a/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.html
+++ b/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.html
@@ -45,7 +45,7 @@
<br-svg type="close" class="pull-right" ng-click="vm.selectedSection = undefined"></br-svg>
</h3>
</div>
- <catalog-selector state="paletteState" family="section.type" mode="{{ section.mode }}" on-select="vm.onTypeSelected(item)" class="palette-full-height-wrapper"></catalog-selector>
+ <catalog-selector state="paletteState" family="section.type" mode="{{ section.mode }}" on-select="vm.onTypeSelected(item)" on-select-text="vm.getOnSelectText(item)" icon-selects="true" class="palette-full-height-wrapper"></catalog-selector>
</div>
</div>
</div>
diff --git a/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.js b/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.js
index e409655..f1b92f9 100644
--- a/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.js
+++ b/ui-modules/blueprint-composer/app/views/main/graphical/edit/add/add.js
@@ -88,6 +88,17 @@
return label;
};
+
+ this.getOnSelectText = () => {
+ switch ($scope.family) {
+ case EntityFamily.ENTITY: return "Add as child";
+ case EntityFamily.SPEC: return "Set as spec";
+ case EntityFamily.POLICY: return "Add this policy";
+ case EntityFamily.ENRICHER: return "Add this enricher";
+ case EntityFamily.LOCATION: return "Add this location";
+ }
+ return "Select";
+ };
this.onTypeSelected = (type)=> {
switch ($scope.family) {
diff --git a/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html b/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html
index fd69c55..711d4c1 100644
--- a/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html
+++ b/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.html
@@ -31,7 +31,7 @@
</div>
</div>
- <designer></designer>
+ <designer on-selection-change="vm.onCanvasSelection"></designer>
</div>
<div class="pane pane-palette" ng-if="vm.selectedSection">
@@ -44,7 +44,7 @@
<br-svg type="close" class="pull-right" ng-click="vm.selectedSection = undefined"></br-svg>
</h3>
</div>
- <catalog-selector state="paletteState" family="section.type" mode="{{ section.mode }}" on-select="vm.onTypeSelected(item)" class="palette-full-height-wrapper"></catalog-selector>
+ <catalog-selector state="paletteState" family="section.type" mode="{{ section.mode }}" on-select="vm.addSelectedTypeToTargetEntity(item)" on-select-text="vm.getOnSelectText()" class="palette-full-height-wrapper"></catalog-selector>
</div>
</div>
diff --git a/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js b/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js
index 4f053cc..ffb9616 100644
--- a/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js
+++ b/ui-modules/blueprint-composer/app/views/main/graphical/graphical.state.js
@@ -28,47 +28,53 @@
templateProvider: function(composerOverrides) {
return composerOverrides.paletteGraphicalStateTemplate || template;
},
- controller: ['$scope', '$state', 'blueprintService', 'paletteService', graphicalController],
+ controller: ['$scope', '$state', '$filter', 'blueprintService', 'paletteService', graphicalController],
controllerAs: 'vm',
data: {
label: 'Graphical Designer'
}
};
-function graphicalController($scope, $state, blueprintService, paletteService) {
+function graphicalController($scope, $state, $filter, blueprintService, paletteService) {
this.EntityFamily = EntityFamily;
this.sections = paletteService.getSections();
this.selectedSection = Object.values(this.sections).find(section => section.type === EntityFamily.ENTITY);
$scope.paletteState = {}; // share state among all sections
- this.onTypeSelected = (selectedType)=> {
- let rootEntity = blueprintService.get();
+ this.onCanvasSelection = (item) => {
+ $scope.canvasSelectedItem = item;
+ }
+ this.getOnSelectText = (selectableType) => $scope.canvasSelectedItem ? "Add to " + $filter('entityName')($scope.canvasSelectedItem) : "Add to application";
+
+ this.addSelectedTypeToTargetEntity = (selectedType, targetEntity) => {
+ if (!targetEntity) targetEntity = $scope.canvasSelectedItem;
+ if (!targetEntity) targetEntity = blueprintService.get();
if (selectedType.supertypes.includes(EntityFamily.ENTITY.superType)) {
let newEntity = blueprintService.populateEntityFromApi(new Entity(), selectedType);
- rootEntity.addChild(newEntity);
+ targetEntity.addChild(newEntity);
blueprintService.refreshEntityMetadata(newEntity, EntityFamily.ENTITY).then(() => {
$state.go(graphicalEditEntityState, {entityId: newEntity._id});
})
}
else if (selectedType.supertypes.includes(EntityFamily.POLICY.superType)) {
let newPolicy = blueprintService.populateEntityFromApi(new Entity(), selectedType);
- rootEntity.addPolicy(newPolicy);
+ targetEntity.addPolicy(newPolicy);
blueprintService.refreshEntityMetadata(newPolicy, EntityFamily.POLICY).then(() => {
- $state.go(graphicalEditPolicyState, {entityId: rootEntity._id, policyId: newPolicy._id});
+ $state.go(graphicalEditPolicyState, {entityId: targetEntity._id, policyId: newPolicy._id});
});
}
else if (selectedType.supertypes.includes(EntityFamily.ENRICHER.superType)) {
let newEnricher = blueprintService.populateEntityFromApi(new Entity(), selectedType);
- rootEntity.addEnricher(newEnricher);
+ targetEntity.addEnricher(newEnricher);
blueprintService.refreshEntityMetadata(newEnricher, EntityFamily.ENRICHER).then(() => {
- $state.go(graphicalEditEnricherState, {entityId: rootEntity._id, enricherId: newEnricher._id});
+ $state.go(graphicalEditEnricherState, {entityId: targetEntity._id, enricherId: newEnricher._id});
});
}
else if (selectedType.supertypes.includes(EntityFamily.LOCATION.superType)) {
- blueprintService.populateLocationFromApi(rootEntity, selectedType);
- $state.go(graphicalEditEntityState, {entityId: rootEntity._id});
+ blueprintService.populateLocationFromApi(targetEntity, selectedType);
+ $state.go(graphicalEditEntityState, {entityId: targetEntity._id});
}
};
}