Merge branch 'master' into jcCompactList
diff --git a/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.directive.js b/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.directive.js
index b3e0eac..e448d53 100644
--- a/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.directive.js
+++ b/ui-modules/blueprint-composer/app/components/blueprint-data-manager/blueprint-data-manager.directive.js
@@ -16,15 +16,27 @@
* specific language governing permissions and limitations
* under the License.
*/
-import template from "./blueprint-data-manager.template.html";
-import {saveAs} from "file-saver/FileSaver";
-const VALID_FILENAME_REGEX = /^.*\.ya?ml$/
-const FILETYPE_TOKEN_REGEX = /^.*\.(.*)$/
+import angular from 'angular';
+import template from './blueprint-data-manager.template.html';
+import {saveAs} from 'file-saver/FileSaver';
+
+const MODULE_NAME = 'brooklyn.components.blueprint-data-manager';
+const TEMPLATE_URL = 'blueprint-composer/component/blueprint-data-manager/index.html';
+const VALID_FILENAME_REGEX = /^.*\.ya?ml$/;
+const FILETYPE_TOKEN_REGEX = /^.*\.(.*)$/;
+
+angular.module(MODULE_NAME, [])
+ .directive('blueprintDataManager', blueprintDataManagerDirective)
+ .run(['$templateCache', templateCache]);
+
+export default MODULE_NAME;
export function blueprintDataManagerDirective() {
return {
restrict: 'E',
- template: template,
+ templateUrl: function(tElement, tAttrs) {
+ return tAttrs.templateUrl || TEMPLATE_URL;
+ },
controller: ['$rootScope', '$scope', '$element', '$document', 'blueprintService', 'brSnackbar', controller]
};
@@ -88,7 +100,7 @@
function readFile(file) {
if (VALID_FILENAME_REGEX.test(file.name)) {
var reader = new FileReader();
- reader.addEventListener("load", function () {
+ reader.addEventListener('load', function () {
try {
var yaml = reader.result;
blueprintService.setFromYaml(yaml, true);
@@ -118,3 +130,7 @@
});
}
}
+
+function templateCache($templateCache) {
+ $templateCache.put(TEMPLATE_URL, template);
+}
diff --git a/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.directive.js b/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.directive.js
index 72b6ea8..6c5edd1 100644
--- a/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.directive.js
+++ b/ui-modules/blueprint-composer/app/components/breacrumbs/breadcrumbs.directive.js
@@ -16,33 +16,49 @@
* specific language governing permissions and limitations
* under the License.
*/
+import angular from 'angular';
import template from './breadcrumbs.template.html';
+const MODULE_NAME = 'brooklyn.components.breadcrumbs';
+const TEMPLATE_URL = 'blueprint-composer/component/breadcrumbs/index.html';
+
+angular.module(MODULE_NAME, [])
+ .directive('breadcrumbs', breadcrumbsDirective)
+ .run(['$templateCache', templateCache]);
+
+export default MODULE_NAME;
+
export function breadcrumbsDirective() {
return {
restrict: 'E',
+ templateUrl: function(tElement, tAttrs) {
+ return tAttrs.templateUrl || TEMPLATE_URL;
+ },
scope: {
entity: '<',
current: '<'
},
- template: template,
link: link
+ };
+
+ function link(scope) {
+ if (scope.entity) {
+ scope.breadcrumbs = [];
+ if (scope.current) {
+ scope.breadcrumbs.push(scope.current);
+ }
+
+ let currentEntity = scope.entity;
+ while (currentEntity.hasParent()) {
+ scope.breadcrumbs.push(currentEntity);
+ currentEntity = currentEntity.parent;
+ }
+ scope.breadcrumbs.push(currentEntity);
+ scope.breadcrumbs.reverse();
+ }
}
}
-function link(scope) {
- if (scope.entity) {
- scope.breadcrumbs = [];
- if (scope.current) {
- scope.breadcrumbs.push(scope.current);
- }
-
- let currentEntity = scope.entity;
- while (currentEntity.hasParent()) {
- scope.breadcrumbs.push(currentEntity);
- currentEntity = currentEntity.parent;
- }
- scope.breadcrumbs.push(currentEntity);
- scope.breadcrumbs.reverse();
- }
+function templateCache($templateCache) {
+ $templateCache.put(TEMPLATE_URL, template);
}
\ No newline at end of file
diff --git a/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js b/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js
index cb6f0ea..bdfaa67 100644
--- a/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js
+++ b/ui-modules/blueprint-composer/app/components/catalog-saver/catalog-saver.directive.js
@@ -23,10 +23,10 @@
import modalTemplate from './catalog-saver.modal.template.html';
import jsYaml from 'js-yaml';
import brUtils from 'brooklyn-ui-utils/utils/general';
-import {yamlState} from "../../views/main/yaml/yaml.state";
-import {graphicalState} from "../../views/main/graphical/graphical.state";
const MODULE_NAME = 'brooklyn.components.catalog-saver';
+const TEMPLATE_URL = 'blueprint-composer/component/catalog-saver/index.html';
+const TEMPLATE_MODAL_URL = 'blueprint-composer/component/catalog-saver/modal.html';
const REASONS = {
new: 0,
@@ -43,14 +43,17 @@
angular.module(MODULE_NAME, [angularAnimate, uibModal, brUtils])
.directive('catalogSaver', ['$rootScope', '$uibModal', '$injector', 'composerOverrides', saveToCatalogModalDirective])
- .directive('catalogVersion', ['$parse', catalogVersionDirective]);
+ .directive('catalogVersion', ['$parse', catalogVersionDirective])
+ .run(['$templateCache', templateCache]);
export default MODULE_NAME;
export function saveToCatalogModalDirective($rootScope, $uibModal, $injector, composerOverrides) {
return {
restrict: 'E',
- template: template,
+ templateUrl: function (tElement, tAttrs) {
+ return tAttrs.templateUrl || TEMPLATE_URL;
+ },
scope: {
config: '='
},
@@ -60,14 +63,12 @@
function link($scope, $element) {
$scope.buttonText = $scope.config.label || ($scope.config.itemType ? `Update ${$scope.config.name || $scope.config.symbolicName}` : 'Add to catalog');
- $injector.get('$templateCache').put('catalog-saver.modal.template.html', modalTemplate);
-
$scope.activateModal = () => {
// Override callback to update catalog configuration data in other applications
$scope.config = (composerOverrides.updateCatalogConfig || (($scope, $element) => $scope.config))($scope, $element);
let modalInstance = $uibModal.open({
- templateUrl: 'catalog-saver.modal.template.html',
+ templateUrl: TEMPLATE_MODAL_URL,
size: 'save',
controller: ['$scope', 'blueprintService', 'paletteApi', 'brUtilsGeneral', CatalogItemModalController],
scope: $scope,
@@ -162,7 +163,7 @@
link: link
};
- function link (scope, elm, attr, ctrl) {
+ function link(scope, elm, attr, ctrl) {
if (!ctrl) {
return;
}
@@ -188,3 +189,8 @@
};
}
}
+
+function templateCache($templateCache) {
+ $templateCache.put(TEMPLATE_URL, template);
+ $templateCache.put(TEMPLATE_MODAL_URL, modalTemplate);
+}
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 298ba80..3d90b92 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
@@ -22,90 +22,382 @@
import footerTemplate from './catalog-selector-palette-footer.html';
import { distanceInWordsToNow } from 'date-fns';
+const MODULE_NAME = 'brooklyn.composer.component.catalog-selector';
+const TEMPLATE_URL = 'blueprint-composer/component/catalog-selector/index.html';
+const TEMPLATE_SUBHEAD_URL = 'blueprint-composer/component/catalog-selector/subhead.html';
+const TEMPLATE_FOOTER_URL = 'blueprint-composer/component/catalog-selector/footer.html';
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 },
compactList: { name: "Compact list", classes: "col-xs-12 item-compact-list", itemsPerRow: 1, rowHeightPx: 30 },
};
// 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'];
+
+angular.module(MODULE_NAME, [])
+ .directive('catalogSelector', catalogSelectorDirective)
+ .filter('catalogSelectorSearch', catalogSelectorSearchFilter)
+ .filter('catalogSelectorFilters', catalogSelectorFiltersFilter)
+ .run(['$templateCache', templateCache]);
+
+export default MODULE_NAME;
export function catalogSelectorDirective() {
return {
restrict: 'E',
+ templateUrl: function (tElement, tAttrs) {
+ return tAttrs.templateUrl || TEMPLATE_URL;
+ },
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],
link: link,
};
-}
-function link($scope, $element, attrs, controller) {
- let main = angular.element($element[0].querySelector(".catalog-palette-main"));
-
- // repaginate when load completes (and items are shown), or it is resized
- $scope.$watchGroup(
- [ () => $scope.isLoading, () => main[0].offsetHeight, () => $scope.state.viewMode.name ],
- (values) => controller.$timeout( () => repaginate($scope, $element) ) );
- // also repaginate on window resize
- angular.element(window).bind('resize', () => repaginate($scope, $element));
-}
-
-function repaginate($scope, $element) {
- let rowsPerPage = $scope.rowsPerPage;
- if (!rowsPerPage) {
+ function link($scope, $element, attrs, controller) {
let main = angular.element($element[0].querySelector(".catalog-palette-main"));
- if (!main || main[0].offsetHeight==0) {
- // no main, or hidden, or items per page fixed
- return;
+
+ // repaginate when load completes (and items are shown), or it is resized
+ $scope.$watchGroup(
+ [() => $scope.isLoading, () => main[0].offsetHeight, () => $scope.state.viewMode.name],
+ (values) => controller.$timeout(() => repaginate($scope, $element)));
+ // also repaginate on window resize
+ angular.element(window).bind('resize', () => repaginate($scope, $element));
+
+ $scope.templateUrls = {
+ subhead: TEMPLATE_SUBHEAD_URL,
+ footer: TEMPLATE_FOOTER_URL
}
- let header = angular.element(main[0].querySelector(".catalog-palette-header"));
- let footer = angular.element(main[0].querySelector(".catalog-palette-footer"));
- rowsPerPage = Math.max(MIN_ROWS_PER_PAGE, Math.floor( (main[0].offsetHeight - header[0].offsetHeight - footer[0].offsetHeight - 16) / ($scope.state.viewMode.rowHeightPx || 96)) );
}
- $scope.$apply( () => $scope.pagination.itemsPerPage = rowsPerPage * $scope.state.viewMode.itemsPerRow );
+
+ function controller($scope, $element, $timeout, $q, $uibModal, $log, $templateCache, paletteApi, paletteDragAndDropService, iconGenerator, composerOverrides, recentlyUsedService) {
+ this.$timeout = $timeout;
+
+ $scope.viewModes = PALETTE_VIEW_MODES;
+ $scope.viewOrders = PALETTE_VIEW_ORDERS;
+
+ if (!$scope.state) $scope.state = {};
+ if (!$scope.state.viewMode) $scope.state.viewMode = PALETTE_VIEW_MODES.normal;
+
+ $scope.pagination = {
+ page: 1,
+ itemsPerPage: $scope.state.viewMode.itemsPerRow * ($scope.rowsPerPage || 1) // will fill out after load
+ };
+
+ $scope.getEntityNameForPalette = function(item, entityName) {
+ return (composerOverrides.getEntityNameForPalette ||
+ // above can be overridden with function of signature below to customize display name in palette
+ function(item, entityName, scope) { return entityName; }
+ )(item, entityName, $scope);
+ };
+
+ $scope.getPlaceHolder = function () {
+ return 'Search';
+ };
+
+ $scope.isLoading = true;
+
+ $scope.$watch('search', () => {
+ $scope.freeFormTile = {
+ symbolicName: $scope.search,
+ name: $scope.search,
+ displayName: $scope.search,
+ supertypes: ($scope.family ? [ $scope.family.superType ] : []),
+ };
+ });
+
+ $scope.getItems = function (search) {
+ let defer = $q.resolve([]);
+
+ switch ($scope.family) {
+ case EntityFamily.ENTITY:
+ case EntityFamily.SPEC:
+ defer = paletteApi.getTypes({params: {supertype: 'entity', fragment: search}});
+ break;
+ case EntityFamily.POLICY:
+ defer = paletteApi.getTypes({params: {supertype: 'policy', fragment: search}});
+ break;
+ case EntityFamily.ENRICHER:
+ defer = paletteApi.getTypes({params: {supertype: 'enricher', fragment: search}});
+ break;
+ case EntityFamily.LOCATION:
+ defer = paletteApi.getLocations();
+ break;
+ }
+
+ return defer.then(data => {
+ data = $scope.filterPaletteItemsForMode(data, $scope);
+ data.forEach( recentlyUsedService.embellish );
+ return data;
+
+ }).catch(error => {
+ return [];
+ }).finally(() => {
+ $scope.isLoading = false;
+ });
+ };
+ function tryMarkUsed(item) {
+ try {
+ recentlyUsedService.markUsed(item);
+ } catch (e) {
+ // session storage can get full; usually the culprit is icons not this,
+ // but we may wish to clear out old items to ensure we don't bleed here
+ $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});
+ }
+ $scope.search = '';
+ };
+ $scope.onDragItem = function (item, event) {
+ let frame = document.createElement('div');
+ frame.classList.add('drag-frame');
+ event.target.appendChild(frame);
+ setTimeout(function() {
+ // can remove at end of this cycle, browser will have grabbed its drag image
+ frame.parentNode.removeChild(frame);
+ }, 0);
+ /* have tried many other ways to get a nice drag image;
+ this seems to work best, adding an empty div which forces the size to be larger,
+ so when grabbing the image it grabs the drop-shadow.
+ things that _didn't_ work include:
+ - styling event.target now then unstyling (normally this would work, in posts on the web, but it doesn't here; angular?)
+ - make a restyled cloned copy offscreen (this comes so close but remote img srcs aren't loaded
+ */
+
+ paletteDragAndDropService.dragStart(item);
+ };
+ $scope.onDragEnd = function (item, event) {
+ 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 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
+ ].indexOf($scope.family) > -1;
+ };
+ $scope.isReserved = function () {
+ if (!$scope.reservedKeys || !angular.isArray($scope.reservedKeys)) {
+ return false;
+ }
+ return $scope.reservedKeys.indexOf($scope.search) > -1;
+ };
+ $scope.onImageError = (scope, el, attrs) => {
+ $log.warn("Icon for "+attrs.itemId+" at "+angular.element(el).attr("src")+" could not be loaded; generating icon");
+ angular.element(el).attr("src", iconGenerator(attrs.itemId));
+ };
+
+ // Init
+ $scope.items = [];
+ function getDisplayTags(tags) {
+ if (!tags || !tags.length || !tags.reduce) return tags;
+ return tags.reduce((result, tag) => {
+ if (!(/[=:\[\]()]/.exec(tag))) {
+ result.push(tag);
+ }
+ return result;
+ }, []);
+ }
+ $scope.getItems().then((items)=> {
+ // add displayTags, as any tag that doesn't contain = : or ( ) [ ]
+ // any tag that is an object will be eliminated as it is toStringed to make [ object object ]
+ items.forEach(item => {
+ if (item.tags) {
+ item.displayTags = getDisplayTags(item.tags);
+ }
+ });
+ $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( () => {
+ // check do we need to show the multiline
+ let filters = angular.element($element[0].querySelector(".filters"));
+ $scope.$apply( () => $scope.filterSettings.filtersMultilineAvailable = filters[0].scrollHeight > filters[0].offsetHeight + 6 );
+
+ repaginate($scope, $element);
+ } );
+ };
+ $scope.togglePaletteControls = () => {
+ $scope.showPaletteControls = !$scope.showPaletteControls;
+ $timeout( () => repaginate($scope, $element) );
+ };
+ $scope.toggleShowAllFilters = () => {
+ $scope.filterSettings.showAllFilters = !$scope.filterSettings.showAllFilters;
+ $timeout( () => repaginate($scope, $element) );
+ };
+ $scope.filterSettings = {};
+
+ $scope.filters = [
+ { label: 'Recent', icon: 'clock-o', title: "Recently used and standard favorites", limitToOnePage: true,
+ filterInit: items => {
+ $scope.recentItems = items.filter( i => i.lastUsed && i.lastUsed>0 );
+ $scope.recentItems.sort( (a,b) => b.lastUsed - a.lastUsed );
+ return $scope.recentItems;
+ }, enabled: false },
+ ];
+ $scope.disableFilters = (showFilters) => {
+ $scope.filters.forEach( f => f.enabled = false );
+ if (showFilters !== false) {
+ $scope.showPaletteControls = true;
+ }
+ };
+
+ // 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;
+ $scope.filterPaletteItemsForMode = (items) => items;
+
+ // allow downstream to configure this controller and/or scope
+ (composerOverrides.configurePaletteController || function() {})(this, $scope, $element);
+ }
+
+ function repaginate($scope, $element) {
+ let rowsPerPage = $scope.rowsPerPage;
+ if (!rowsPerPage) {
+ let main = angular.element($element[0].querySelector(".catalog-palette-main"));
+ if (!main || main[0].offsetHeight == 0) {
+ // no main, or hidden, or items per page fixed
+ return;
+ }
+ let header = angular.element(main[0].querySelector(".catalog-palette-header"));
+ let footer = angular.element(main[0].querySelector(".catalog-palette-footer"));
+ rowsPerPage = Math.max(MIN_ROWS_PER_PAGE, Math.floor((main[0].offsetHeight - header[0].offsetHeight - footer[0].offsetHeight - 16) / ($scope.state.viewMode.rowHeightPx || 96)));
+ }
+ $scope.$apply(() => $scope.pagination.itemsPerPage = rowsPerPage * $scope.state.viewMode.itemsPerRow);
+ }
}
export function catalogSelectorSearchFilter() {
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;
}
}
@@ -143,206 +435,8 @@
}
}
-function controller($scope, $element, $timeout, $q, $uibModal, $log, $templateCache, paletteApi, paletteDragAndDropService, iconGenerator, composerOverrides, recentlyUsedService) {
- this.$timeout = $timeout;
-
- $scope.viewModes = PALETTE_VIEW_MODES;
- $scope.viewOrders = PALETTE_VIEW_ORDERS;
-
- 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,
- itemsPerPage: $scope.state.viewMode.itemsPerRow * ($scope.rowsPerPage || 1) // will fill out after load
- };
-
- $scope.getEntityNameForPalette = function(item, entityName) {
- return (composerOverrides.getEntityNameForPalette ||
- // above can be overridden with function of signature below to customize display name in palette
- function(item, entityName, scope) { return entityName; }
- )(item, entityName, $scope);
- }
-
- $scope.getPlaceHolder = function () {
- return 'Search';
- };
-
- $scope.isLoading = true;
-
- $scope.$watch('search', () => {
- $scope.freeFormTile = {
- symbolicName: $scope.search,
- name: $scope.search,
- displayName: $scope.search,
- supertypes: ($scope.family ? [ $scope.family.superType ] : []),
- };
- });
-
- $scope.getItems = function (search) {
- let defer = $q.resolve([]);
-
- switch ($scope.family) {
- case EntityFamily.ENTITY:
- case EntityFamily.SPEC:
- defer = paletteApi.getTypes({params: {supertype: 'entity', fragment: search}});
- break;
- case EntityFamily.POLICY:
- defer = paletteApi.getTypes({params: {supertype: 'policy', fragment: search}});
- break;
- case EntityFamily.ENRICHER:
- defer = paletteApi.getTypes({params: {supertype: 'enricher', fragment: search}});
- break;
- case EntityFamily.LOCATION:
- defer = paletteApi.getLocations();
- break;
- }
-
- return defer.then(data => {
- data = $scope.filterPaletteItemsForMode(data, $scope);
- data.forEach( recentlyUsedService.embellish );
- return data;
-
- }).catch(error => {
- return [];
- }).finally(() => {
- $scope.isLoading = false;
- });
- };
- function tryMarkUsed(item) {
- try {
- recentlyUsedService.markUsed(item);
- } catch (e) {
- // session storage can get full; usually the culprit is icons not this,
- // but we may wish to clear out old items to ensure we don't bleed here
- $log.warn("Could not mark item as used: "+item, e);
- }
- }
- $scope.onSelectItem = function (item) {
- if (angular.isFunction($scope.onSelect)) {
- tryMarkUsed(item);
- $scope.onSelect({item: item});
- }
- $scope.search = '';
- };
- $scope.onDragItem = function (item, event) {
- let frame = document.createElement('div');
- frame.classList.add('drag-frame');
- event.target.appendChild(frame);
- setTimeout(function() {
- // can remove at end of this cycle, browser will have grabbed its drag image
- frame.parentNode.removeChild(frame);
- }, 0);
- /* have tried many other ways to get a nice drag image;
- this seems to work best, adding an empty div which forces the size to be larger,
- so when grabbing the image it grabs the drop-shadow.
- things that _didn't_ work include:
- - styling event.target now then unstyling (normally this would work, in posts on the web, but it doesn't here; angular?)
- - make a restyled cloned copy offscreen (this comes so close but remote img srcs aren't loaded
- */
-
- paletteDragAndDropService.dragStart(item);
- };
- $scope.onDragEnd = function (item, event) {
- paletteDragAndDropService.dragEnd();
- tryMarkUsed(item);
- };
- $scope.sortBy = function (order) {
- let newOrder = [].concat($scope.state.currentOrder);
- newOrder = newOrder.filter( (o) => o !== order.field );
- $scope.state.currentOrder = [order.field].concat(newOrder);
- };
- $scope.allowFreeForm = function () {
- return [
- EntityFamily.LOCATION
- ].indexOf($scope.family) > -1;
- };
- $scope.isReserved = function () {
- if (!$scope.reservedKeys || !angular.isArray($scope.reservedKeys)) {
- return false;
- }
- return $scope.reservedKeys.indexOf($scope.search) > -1;
- };
- $scope.onImageError = (scope, el, attrs) => {
- $log.warn("Icon for "+attrs.itemId+" at "+angular.element(el).attr("src")+" could not be loaded; generating icon");
- angular.element(el).attr("src", iconGenerator(attrs.itemId));
- };
-
- // Init
- $scope.items = [];
- function getDisplayTags(tags) {
- if (!tags || !tags.length || !tags.reduce) return tags;
- return tags.reduce((result, tag) => {
- if (!(/[=:\[\]()]/.exec(tag))) {
- result.push(tag);
- }
- return result;
- }, []);
- }
- $scope.getItems().then((items)=> {
- // add displayTags, as any tag that doesn't contain = : or ( ) [ ]
- // any tag that is an object will be eliminated as it is toStringed to make [ object object ]
- items.forEach(item => {
- if (item.tags) {
- item.displayTags = getDisplayTags(item.tags);
- }
- });
- $scope.items = items;
- });
- $scope.lastUsedText = (item) => {
- 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( () => {
- // check do we need to show the multiline
- let filters = angular.element($element[0].querySelector(".filters"));
- $scope.$apply( () => $scope.filterSettings.filtersMultilineAvailable = filters[0].scrollHeight > filters[0].offsetHeight + 6 );
-
- repaginate($scope, $element);
- } );
- };
- $scope.togglePaletteControls = () => {
- $scope.showPaletteControls = !$scope.showPaletteControls;
- $timeout( () => repaginate($scope, $element) );
- }
- $scope.toggleShowAllFilters = () => {
- $scope.filterSettings.showAllFilters = !$scope.filterSettings.showAllFilters;
- $timeout( () => repaginate($scope, $element) );
- };
- $scope.filterSettings = {};
-
- $scope.filters = [
- { label: 'Recent', icon: 'clock-o', title: "Recently used and standard favorites", limitToOnePage: true,
- filterInit: items => {
- $scope.recentItems = items.filter( i => i.lastUsed && i.lastUsed>0 );
- $scope.recentItems.sort( (a,b) => b.lastUsed - a.lastUsed );
- return $scope.recentItems;
- }, enabled: false },
- ];
- $scope.disableFilters = (showFilters) => {
- $scope.filters.forEach( f => f.enabled = false );
- if (showFilters !== false) {
- $scope.showPaletteControls = 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;
- $scope.filterPaletteItemsForMode = (items) => items;
-
- // downstream can override this to insert lines below the header
- $scope.customSubHeadTemplateName = 'composer-palette-empty-sub-head';
- $templateCache.put($scope.customSubHeadTemplateName, '');
-
- $scope.customFooterTemplateName = 'composer-palette-default-footer';
- $templateCache.put($scope.customFooterTemplateName, footerTemplate);
-
- // allow downstream to configure this controller and/or scope
- (composerOverrides.configurePaletteController || function() {})(this, $scope, $element);
+function templateCache($templateCache) {
+ $templateCache.put(TEMPLATE_URL, template);
+ $templateCache.put(TEMPLATE_SUBHEAD_URL, '');
+ $templateCache.put(TEMPLATE_FOOTER_URL, footerTemplate);
}
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 95e550e..56f2318 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
@@ -280,6 +280,9 @@
.label {
line-height: 2.2;
}
+ .closer .br-icon-close-bar {
+ stroke: @brand-primary;
+ }
}
.deprecated-marker {
float: right;
@@ -296,6 +299,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..79ea8e7 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>
@@ -92,7 +92,7 @@
</ul>
</span>
</div>
- <ng-include src="customSubHeadTemplateName"/>
+ <ng-include src="templateUrls.subhead"/>
</div>
</div>
@@ -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}}"/>
@@ -110,17 +110,19 @@
<h3>{{ getEntityNameForPalette(item, item | entityName) }}</h3>
</div>
<i class="fa fa-info-circle"
- uib-popover-template="'QuickInfoTemplate.html'"
- popover-title="{{item | entityName}}"
- popover-placement="right-top" popover-trigger="'mouseenter'"
+ uib-popover-template="'blueprint-composer/component/catalog-selector/quick-info.html'"
+ 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="'blueprint-composer/component/catalog-selector/quick-info.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.
@@ -137,26 +146,38 @@
<div class="catalog-palette-footer">
<div uib-pagination total-items="searchedItems.length" items-per-page="pagination.itemsPerPage" ng-model="pagination.page" boundary-link-numbers="true" rotate="false" max-size="4" ng-show="searchedItems.length > pagination.itemsPerPage" class="pagination-sm pull-right"></div>
- <ng-include src="customFooterTemplateName"/>
+ <ng-include src="templateUrls.footer"/>
</div>
</div>
</div>
<!-- QUICK INFO TEMPLATE :: START-->
-<script type="text/ng-template" id="QuickInfoTemplate.html">
+<script type="text/ng-template" id="blueprint-composer/component/catalog-selector/quick-info.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/custom-config-widget/suggestion-dropdown.html b/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.html
index ad59161..93d35ac 100644
--- a/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.html
+++ b/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.html
@@ -22,7 +22,7 @@
<span class="info-spec-configuration">
<i class="fa fa-fw fa-info-circle" popover-trigger="'mouseenter'"
popover-title="{{item.label || item.name}}"
- uib-popover-template="'ConfigInfoTemplate.html'"
+ uib-popover-template="'blueprint-composer/component/spec-editor/config-info.html'"
popover-class="spec-editor-popover" popover-placement="top-left" popover-append-to-body="true"></i>
</span>
</label>
diff --git a/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.js b/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.js
index c32bc72..d58ec88 100644
--- a/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.js
+++ b/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.js
@@ -21,9 +21,11 @@
import template from './suggestion-dropdown.html';
const MODULE_NAME = 'brooklyn.components.custom-config-widget.suggestion-dropdown';
+const TEMPLATE_URL = 'blueprint-composer/component/suggestion-dropdown/index.html';
angular.module(MODULE_NAME, [])
- .directive('suggestionDropdown', ['$rootScope', suggestionDropdownDirective]);
+ .directive('suggestionDropdown', ['$rootScope', suggestionDropdownDirective])
+ .run(['$templateCache', templateCache]);
export default MODULE_NAME;
@@ -31,13 +33,15 @@
return {
require: "^^specEditor", // only intended for use in spec editor, and calls functions on that controller
restrict: 'E',
+ templateUrl: function (tElement, tAttrs) {
+ return tAttrs.templateUrl || TEMPLATE_URL;
+ },
scope: {
item: '=',
params: '=',
config: '=',
model: '=',
},
- template: template,
link: link,
};
@@ -58,5 +62,8 @@
}
};
}
-
+}
+
+function templateCache($templateCache) {
+ $templateCache.put(TEMPLATE_URL, template);
}
\ No newline at end of file
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..cbcb0ff 100644
--- a/ui-modules/blueprint-composer/app/components/designer/designer.directive.js
+++ b/ui-modules/blueprint-composer/app/components/designer/designer.directive.js
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import angular from 'angular';
import {Entity} from "../util/model/entity.model";
import {D3Blueprint} from "../util/d3-blueprint";
import {EntityFamily} from '../util/model/entity.model';
@@ -23,16 +24,29 @@
import {graphicalEditSpecState} from '../../views/main/graphical/edit/spec/edit.spec.controller';
import {graphicalEditPolicyState} from '../../views/main/graphical/edit/policy/edit.policy.controller';
import {graphicalEditEnricherState} from '../../views/main/graphical/edit/enricher/edit.enricher.controller';
+
+const MODULE_NAME = 'brooklyn.components.designer';
+const TEMPLATE_URL = 'blueprint-composer/component/designer/index.html';
const ANY_MEMBERSPEC_REGEX = /(^.*[m,M]ember[s,S]pec$)/;
const TAG = 'DIRECTIVE :: DESIGNER :: ';
+angular.module(MODULE_NAME, [])
+ .directive('designer', ['$log', '$state', '$q', 'iconGenerator', 'catalogApi', 'blueprintService', 'brSnackbar', 'paletteDragAndDropService', designerDirective])
+ .run(['$templateCache', templateCache]);
+
+export default MODULE_NAME;
+
export function designerDirective($log, $state, $q, iconGenerator, catalogApi, blueprintService, brSnackbar, paletteDragAndDropService) {
- let directive = {
+ return {
restrict: 'E',
- template: '',
+ templateUrl: function (tElement, tAttrs) {
+ return tAttrs.templateUrl || TEMPLATE_URL;
+ },
+ scope: {
+ onSelectionChange: '<?'
+ },
link: link
};
- return directive;
function link($scope, $element) {
let blueprintGraph = new D3Blueprint($element[0]).center();
@@ -109,13 +123,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');
@@ -229,3 +246,7 @@
}
}
}
+
+function templateCache($templateCache) {
+ $templateCache.put(TEMPLATE_URL, '');
+}
\ No newline at end of file
diff --git a/ui-modules/blueprint-composer/app/components/dsl-editor/dsl-editor.js b/ui-modules/blueprint-composer/app/components/dsl-editor/dsl-editor.js
index 707560b..6fb1d26 100644
--- a/ui-modules/blueprint-composer/app/components/dsl-editor/dsl-editor.js
+++ b/ui-modules/blueprint-composer/app/components/dsl-editor/dsl-editor.js
@@ -24,6 +24,7 @@
import brUtils from 'brooklyn-ui-utils/utils/general';
const MODULE_NAME = 'brooklyn.components.dsl-editor';
+const TEMPLATE_URL = 'blueprint-composer/component/dsl-editor/index.html';
const DSL_KINDS = {
ALL: {
id: 'all',
@@ -48,14 +49,17 @@
};
angular.module(MODULE_NAME, [angularSanitize, brAutoFocus, brUtils])
- .directive('dslEditor', ['$rootScope', '$filter', '$log', 'brUtilsGeneral', 'blueprintService', dslEditorDirective]);
+ .directive('dslEditor', ['$rootScope', '$filter', '$log', 'brUtilsGeneral', 'blueprintService', dslEditorDirective])
+ .run(['$templateCache', templateCache]);
export default MODULE_NAME;
export function dslEditorDirective($rootScope, $filter, $log, brUtilsGeneral, blueprintService) {
return {
restrict: 'E',
- template: template,
+ templateUrl: function (tElement, tAttrs) {
+ return tAttrs.templateUrl || TEMPLATE_URL;
+ },
scope: {
definition: '=',
entity: '=',
@@ -361,3 +365,7 @@
return dsl && dsl.kind === KIND.TARGET && dsl.name === 'self';
}
}
+
+function templateCache($templateCache) {
+ $templateCache.put(TEMPLATE_URL, template);
+}
diff --git a/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.js b/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.js
index abdd233..e751638 100644
--- a/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.js
+++ b/ui-modules/blueprint-composer/app/components/dsl-viewer/dsl-viewer.js
@@ -21,19 +21,23 @@
import {KIND} from '../util/model/dsl.model';
const MODULE_NAME = 'brooklyn.components.dsl-viewer';
+const TEMPLATE_URL = 'blueprint-composer/component/dsl-viewer/index.html';
angular.module(MODULE_NAME, [])
- .directive('dslViewer', dslViewerDirective);
+ .directive('dslViewer', dslViewerDirective)
+ .run(['$templateCache', templateCache]);
export default MODULE_NAME;
export function dslViewerDirective() {
return {
restrict: 'E',
+ templateUrl: function (tElement, tAttrs) {
+ return tAttrs.templateUrl || TEMPLATE_URL;
+ },
scope: {
dsl: '<'
},
- template: template,
link: link
};
@@ -64,3 +68,7 @@
}
}
}
+
+function templateCache($templateCache) {
+ $templateCache.put(TEMPLATE_URL, template);
+}
diff --git a/ui-modules/blueprint-composer/app/components/factories/object-cache.factory.js b/ui-modules/blueprint-composer/app/components/factories/object-cache.factory.js
index cf714b4..a37f3a6 100644
--- a/ui-modules/blueprint-composer/app/components/factories/object-cache.factory.js
+++ b/ui-modules/blueprint-composer/app/components/factories/object-cache.factory.js
@@ -16,6 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
+import angular from 'angular';
+
+const MODULE_NAME = 'brooklyn.factory.object-cache';
+
+angular.module(MODULE_NAME, [])
+ .factory('objectCache', ['$cacheFactory', objectCacheFactory]);
+
+export default MODULE_NAME;
+
export function objectCacheFactory($cacheFactory) {
return $cacheFactory('blueprint-composer');
}
\ No newline at end of file
diff --git a/ui-modules/blueprint-composer/app/components/factories/recursion-helper.factory.js b/ui-modules/blueprint-composer/app/components/factories/recursion-helper.factory.js
deleted file mode 100644
index cf0abae..0000000
--- a/ui-modules/blueprint-composer/app/components/factories/recursion-helper.factory.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- */
-export function recursionHelperFactory($compile) {
- return {
- /**
- * Manually compiles the element, fixing the recursion loop.
- *
- * @param {object} element
- * @param {function} link a post link function,
- * or an object with function(s) registered via pre and post properties.
- *
- */
- compile: function (element, link) {
- // Normalize the link parameter
- if (angular.isFunction(link)) {
- link = {post: link};
- }
-
- // Break the recursion loop by removing the contents
- var contents = element.contents().remove();
- var compiledContents;
- return {
- pre: (link && link.pre) ? link.pre : null,
- /**
- * Compiles and re-adds the contents
- */
- post: function (scope, element) {
- // Compile the contents
- if (!compiledContents) {
- compiledContents = $compile(contents);
- }
- // Re-add the compiled contents to the element
- compiledContents(scope, function (clone) {
- element.append(clone);
- });
-
- // Call the post-linking function, if any
- if (link && link.post) {
- link.post.apply(null, arguments);
- }
- }
- };
- }
- };
-}
\ No newline at end of file
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..34a1930 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,24 @@
* specific language governing permissions and limitations
* under the License.
*/
-const DEFAULT = '';
+import angular from 'angular';
+
+const MODULE_NAME = 'brooklyn.filters.entity';
+
+angular.module(MODULE_NAME, [])
+ .filter('entityName', entityNameFilter)
+ .filter('entityVersion', entityVersionFilter)
+ .filter('entityTypes', entityTypesFilter);
+
+export default MODULE_NAME;
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/filters/locations.filter.js b/ui-modules/blueprint-composer/app/components/filters/locations.filter.js
index c096944..7a1378c 100644
--- a/ui-modules/blueprint-composer/app/components/filters/locations.filter.js
+++ b/ui-modules/blueprint-composer/app/components/filters/locations.filter.js
@@ -16,6 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
+import angular from 'angular';
+
+const MODULE_NAME = 'brooklyn.filters.location';
+
+angular.module(MODULE_NAME, [])
+ .filter('locations', locationsFilter);
+
+export default MODULE_NAME;
+
export function locationsFilter() {
return function (input, search) {
return input.then(function (response) {
diff --git a/ui-modules/blueprint-composer/app/components/providers/action-service.provider.js b/ui-modules/blueprint-composer/app/components/providers/action-service.provider.js
index 9f00374..a699d67 100644
--- a/ui-modules/blueprint-composer/app/components/providers/action-service.provider.js
+++ b/ui-modules/blueprint-composer/app/components/providers/action-service.provider.js
@@ -16,6 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
+import angular from 'angular';
+
+const MODULE_NAME = 'brooklyn.composer.service.action-service';
+
+angular.module(MODULE_NAME, [])
+ .provider('actionService', actionServiceProvider);
+
+export default MODULE_NAME;
+
export function actionServiceProvider() {
let actions = {};
return {
@@ -41,7 +50,7 @@
return {
addAction: addAction,
getActions: getActions
- }
+ };
function addAction(id, action) {
if (!action || !action.hasOwnProperty('html')) {
diff --git a/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js b/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
index 69ba3f6..0140faf 100644
--- a/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
+++ b/ui-modules/blueprint-composer/app/components/providers/blueprint-service.provider.js
@@ -16,13 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
+import angular from 'angular';
import {Entity, EntityFamily} from "../util/model/entity.model";
import {Issue, ISSUE_LEVEL} from '../util/model/issue.model';
import {Dsl} from "../util/model/dsl.model";
import jsYaml from "js-yaml";
import typeNotFoundIcon from "../../img/icon-not-found.svg";
+const MODULE_NAME = 'brooklyn.composer.service.blueprint-service';
const TAG = 'SERVICE :: BLUEPRINT :: ';
+
+angular.module(MODULE_NAME, [])
+ .provider('blueprintService', blueprintServiceProvider);
+
+export default MODULE_NAME;
+
export const RESERVED_KEYS = ['name', 'location', 'locations', 'type', 'services', 'brooklyn.config', 'brooklyn.children', 'brooklyn.enrichers', 'brooklyn.policies'];
export const DSL_ENTITY_SPEC = '$brooklyn:entitySpec';
diff --git a/ui-modules/blueprint-composer/app/components/providers/dsl-service.provider.js b/ui-modules/blueprint-composer/app/components/providers/dsl-service.provider.js
index 3d5f665..ae89b37 100644
--- a/ui-modules/blueprint-composer/app/components/providers/dsl-service.provider.js
+++ b/ui-modules/blueprint-composer/app/components/providers/dsl-service.provider.js
@@ -16,11 +16,18 @@
* specific language governing permissions and limitations
* under the License.
*/
+import angular from 'angular';
import {Dsl, DslParser, KIND} from "../util/model/dsl.model";
import {Entity} from "../util/model/entity.model";
+const MODULE_NAME = 'brooklyn.composer.service.dsl-service';
const TAG = 'SERVICE :: DSL :: ';
+angular.module(MODULE_NAME, [])
+ .provider('dslService', dslServiceProvider);
+
+export default MODULE_NAME;
+
export function dslServiceProvider() {
return {
$get: ['$log', function ($log) {
diff --git a/ui-modules/blueprint-composer/app/components/providers/palette-dragndrop.provider.js b/ui-modules/blueprint-composer/app/components/providers/palette-dragndrop.provider.js
index dee0836..1105880 100644
--- a/ui-modules/blueprint-composer/app/components/providers/palette-dragndrop.provider.js
+++ b/ui-modules/blueprint-composer/app/components/providers/palette-dragndrop.provider.js
@@ -16,10 +16,17 @@
* specific language governing permissions and limitations
* under the License.
*/
+import angular from 'angular';
import {Entity} from '../util/model/entity.model';
+const MODULE_NAME = 'brooklyn.composer.service.palette-dragndrop-service';
const TAG = 'SERVICE :: DRAGNDROP :: ';
+angular.module(MODULE_NAME, [])
+ .provider('paletteDragAndDropService', paletteDragAndDropServiceProvider);
+
+export default MODULE_NAME;
+
export function paletteDragAndDropServiceProvider() {
return {
$get: ['$log', function ($log) {
diff --git a/ui-modules/blueprint-composer/app/components/providers/recently-used-service.provider.js b/ui-modules/blueprint-composer/app/components/providers/recently-used-service.provider.js
index cc48ab5..994932d 100644
--- a/ui-modules/blueprint-composer/app/components/providers/recently-used-service.provider.js
+++ b/ui-modules/blueprint-composer/app/components/providers/recently-used-service.provider.js
@@ -16,6 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
+import angular from 'angular';
+
+const MODULE_NAME = 'brooklyn.composer.service.recently-user';
+
+angular.module(MODULE_NAME, [])
+ .provider('recentlyUsedService', recentlyUsedServiceProvider);
+
+export default MODULE_NAME;
export function recentlyUsedServiceProvider() {
return {
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..bfd51a9 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
@@ -86,7 +86,7 @@
placeholder="Add a new configuration key or open existing"
class="form-control"
uib-typeahead="config.name for config in state.config.add.list | filter:{name:$viewValue}"
- typeahead-template-url="ConfigItemTemplate.html"
+ typeahead-template-url="blueprint-composer/component/spec-editor/config-item.html"
typeahead-editable="true"
typeahead-show-hint="true"
typeahead-min-length="0"
@@ -146,7 +146,7 @@
<span class="info-spec-configuration">
<i class="fa fa-fw fa-info-circle" popover-trigger="'mouseenter'"
popover-title="{{item.label || item.name}}"
- uib-popover-template="'ConfigInfoTemplate.html'"
+ uib-popover-template="'blueprint-composer/component/spec-editor/config-info.html'"
popover-class="spec-editor-popover" popover-placement="top-left" popover-append-to-body="true"></i>
</span>
</label>
@@ -309,7 +309,7 @@
class="open-entity-spec"
title="Open in spec editor"
ng-focus="specEditor.recordFocus(item)"></a>
- <ng-include src="'AdjunctTemplate.html'"></ng-include>
+ <ng-include src="'blueprint-composer/component/spec-editor/adjunct.html'"></ng-include>
</div>
<a ng-if="!config[item.name][REPLACED_DSL_ENTITYSPEC]" ui-sref="main.graphical.edit.add({entityId: model._id, family: 'spec', configKey: item.name})" class="no-spec">
(no spec set)
@@ -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="'blueprint-composer/component/spec-editor/section-locations.html'"></ng-include>
+<script type="text/ng-template" id="blueprint-composer/component/spec-editor/section-locations.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,16 +367,19 @@
<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="'blueprint-composer/component/spec-editor/section-policies.html'"></ng-include>
+<script type="text/ng-template" id="blueprint-composer/component/spec-editor/section-policies.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>
<span class="pull-right" ng-show="$parent.stateWrapped.state">
- <i class="fa fa-search collapsible-action" title="Filter policies" ng-click="$event.stopPropagation(); $event.preventDefault();" ng-class="{'text-success': state.policy.search.length > 0}" uib-popover-template="'SearchPolicyTemplate.html'" popover-placement="bottom-right" popover-trigger="'outsideClick'"></i>
+ <i class="fa fa-search collapsible-action" title="Filter policies" ng-click="$event.stopPropagation(); $event.preventDefault();" ng-class="{'text-success': state.policy.search.length > 0}" uib-popover-template="'blueprint-composer/component/spec-editor/search-policy.html'" popover-placement="bottom-right" popover-trigger="'outsideClick'"></i>
<a class="fa fa-plus collapsible-action" title="Add policy" ui-sref="main.graphical.edit.add({entityId: model._id, family: 'policy'})" ng-click="$event.stopPropagation()" ></a>
</span>
</heading>
@@ -395,19 +400,22 @@
<div ng-repeat="adjunct in filteredPolicies = (model.getPoliciesAsArray() | specEditorType:state.policy.search) track by adjunct._id" class="spec-policy spec-adjunct">
<a ui-sref="main.graphical.edit.policy({entityId: model._id, policyId: adjunct._id})"></a>
- <ng-include src="'AdjunctTemplate.html'"></ng-include>
+ <ng-include src="'blueprint-composer/component/spec-editor/adjunct.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="'blueprint-composer/component/spec-editor/section-enrichers.html'"></ng-include>
+<script type="text/ng-template" id="blueprint-composer/component/spec-editor/section-enrichers.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>
<span class="pull-right" ng-show="$parent.stateWrapped.state">
- <i class="fa fa-search collapsible-action" title="Search enrichers" ng-click="$event.stopPropagation(); $event.preventDefault();" ng-class="{'text-success': state.enricher.search.length > 0}" uib-popover-template="'SearchEnricherTemplate.html'" popover-placement="bottom-right" popover-trigger="'outsideClick'"></i>
+ <i class="fa fa-search collapsible-action" title="Search enrichers" ng-click="$event.stopPropagation(); $event.preventDefault();" ng-class="{'text-success': state.enricher.search.length > 0}" uib-popover-template="'blueprint-composer/component/spec-editor/search-enricher.html'" popover-placement="bottom-right" popover-trigger="'outsideClick'"></i>
<a class="fa fa-plus collapsible-action" title="Add enricher" ui-sref="main.graphical.edit.add({entityId: model._id, family: 'enricher'})" ng-click="$event.stopPropagation()" ></a>
</span>
</heading>
@@ -428,13 +436,19 @@
<div ng-repeat="adjunct in filteredEnrichers = (model.getEnrichersAsArray() | specEditorType:state.enricher.search) track by adjunct._id" class="spec-enricher spec-adjunct">
<a ui-sref="main.graphical.edit.enricher({entityId: model._id, enricherId: adjunct._id})"></a>
- <ng-include src="'AdjunctTemplate.html'"></ng-include>
+ <ng-include src="'blueprint-composer/component/spec-editor/adjunct.html'"></ng-include>
</div>
</div>
-</br-collapsible>
+ </br-collapsible>
+</script>
+
+<ng-include src="'blueprint-composer/component/spec-editor/section-others.html'"></ng-include>
+<script type="text/ng-template" id="blueprint-composer/component/spec-editor/section-others.html">
+</script>
+
<!-- CONFIG INFO TEMPLATE :: START -->
-<script type="text/ng-template" id="ConfigInfoTemplate.html">
+<script type="text/ng-template" id="blueprint-composer/component/spec-editor/config-info.html">
<div class="config-item-quick-info">
<div class="quick-info-metadata">
<p><i class="mini-icon fa fa-fw fa-cog"></i> <samp class="type-symbolic-name">{{item.name}}</samp>
@@ -449,7 +463,7 @@
<!-- CONFIG INFO TEMPLATE :: START-->
<!-- SEARCH POLICY TEMPLATE :: START -->
-<script type="text/ng-template" id="SearchPolicyTemplate.html">
+<script type="text/ng-template" id="blueprint-composer/component/spec-editor/search-policy.html">
<div ng-click="$event.stopPropagation(); $event.preventDefault();">
<input ng-model="state.policy.search" type="text" class="form-control" placeholder="Search for a policy" auto-focus blur-on-enter />
</div>
@@ -457,7 +471,7 @@
<!--SEARCH POLICY TEMPLATE :: START-->
<!-- SEARCH ENRICHER TEMPLATE :: START -->
-<script type="text/ng-template" id="SearchEnricherTemplate.html">
+<script type="text/ng-template" id="blueprint-composer/component/spec-editor/search-enricher.html">
<div ng-click="$event.stopPropagation(); $event.preventDefault();">
<input ng-model="state.enricher.search" type="text" class="form-control" placeholder="Search for an enricher" auto-focus blur-on-enter />
</div>
@@ -465,7 +479,7 @@
<!--SEARCH ENRICHER TEMPLATE :: START-->
<!--TYPEAHEAD TEMPLATE :: START-->
-<script type="text/ng-template" id="ConfigItemTemplate.html">
+<script type="text/ng-template" id="blueprint-composer/component/spec-editor/config-item.html">
<div class="dropdown-item" ng-init="item = match.model">
<div class="dropdown-row">
<span ng-bind-html="match.model.name | uibTypeaheadHighlight:query" class="config-name"></span>
@@ -480,7 +494,7 @@
<!--TYPEAHEAD TEMPLATE :: END-->
<!--ADJUNCT TEMPLATE :: START-->
-<script type="text/ng-template" id="AdjunctTemplate.html">
+<script type="text/ng-template" id="blueprint-composer/component/spec-editor/adjunct.html">
<div class="media" ng-class="{'has-issues': adjunct.hasIssues()}">
<div class="media-left media-middle">
<img ng-src="{{adjunct.icon}}" alt="{{adjunct | entityName}} logo" class="media-object" />
diff --git a/ui-modules/blueprint-composer/app/index.js b/ui-modules/blueprint-composer/app/index.js
index 7dc3720..34b3c5b 100755
--- a/ui-modules/blueprint-composer/app/index.js
+++ b/ui-modules/blueprint-composer/app/index.js
@@ -43,25 +43,19 @@
import blueprintLoaderApiProvider from "./components/providers/blueprint-loader-api.provider";
import brooklynApi from "brooklyn-ui-utils/brooklyn.api/brooklyn.api";
-import {designerDirective} from "./components/designer/designer.directive";
-import {
- catalogSelectorDirective,
- catalogSelectorSearchFilter,
- catalogSelectorFiltersFilter,
-} from "./components/catalog-selector/catalog-selector.directive";
+import designer from './components/designer/designer.directive';
+import catalogSelector from './components/catalog-selector/catalog-selector.directive';
import customActionDirective from "./components/custom-action/custom-action.directive";
import customConfigSuggestionDropdown from "./components/custom-config-widget/suggestion-dropdown";
-import {onErrorDirective} from "./components/catalog-selector/on-error.directive";
-import {breadcrumbsDirective} from "./components/breacrumbs/breadcrumbs.directive";
-import {recursionHelperFactory} from "./components/factories/recursion-helper.factory";
-import {objectCacheFactory} from './components/factories/object-cache.factory';
-import {entityNameFilter, entityVersionFilter, entityTypesFilter} from "./components/filters/entity.filter";
-import {locationsFilter} from "./components/filters/locations.filter";
-import {blueprintServiceProvider} from "./components/providers/blueprint-service.provider";
-import {recentlyUsedServiceProvider} from "./components/providers/recently-used-service.provider";
-import {dslServiceProvider} from "./components/providers/dsl-service.provider";
-import {paletteDragAndDropServiceProvider} from "./components/providers/palette-dragndrop.provider";
-import {actionServiceProvider} from "./components/providers/action-service.provider";
+import breadcrumbs from "./components/breacrumbs/breadcrumbs.directive";
+import objectCache from './components/factories/object-cache.factory';
+import entityFilters from "./components/filters/entity.filter";
+import locationFilter from "./components/filters/locations.filter";
+import blueprintService from "./components/providers/blueprint-service.provider";
+import recentlyUsedService from "./components/providers/recently-used-service.provider";
+import dslService from "./components/providers/dsl-service.provider";
+import paletteDragAndDropService from "./components/providers/palette-dragndrop.provider";
+import actionService from "./components/providers/action-service.provider";
import {mainState} from "./views/main/main.controller";
import {yamlState} from "./views/main/yaml/yaml.state";
import {graphicalState} from "./views/main/graphical/graphical.state";
@@ -76,28 +70,13 @@
import stackViewer from 'angular-java-stack-viewer';
import {EntityFamily} from "./components/util/model/entity.model";
-angular.module('app', [ngAnimate, ngResource, ngCookies, ngClipboard, uiRouter, 'ui.router.state.events', brCore,
- brServerStatus, brAutoFocus, brIconGenerator, brInterstitialSpinner, brooklynModuleLinks, brooklynUserManagement,
- brYamlEditor, brUtils, brSpecEditor, brooklynCatalogSaver, brooklynApi, bottomSheet, stackViewer, brDragndrop,
- customActionDirective, customConfigSuggestionDropdown, paletteApiProvider, paletteServiceProvider, blueprintLoaderApiProvider])
- .directive('designer', ['$log', '$state', '$q', 'iconGenerator', 'catalogApi', 'blueprintService', 'brSnackbar', 'paletteDragAndDropService', designerDirective])
- .directive('onError', onErrorDirective)
- .directive('catalogSelector', catalogSelectorDirective)
- .directive('breadcrumbs', breadcrumbsDirective)
- .provider('blueprintService', blueprintServiceProvider)
- .provider('recentlyUsedService', recentlyUsedServiceProvider)
- .provider('dslService', dslServiceProvider)
- .provider('paletteDragAndDropService', paletteDragAndDropServiceProvider)
- .provider('actionService', actionServiceProvider)
+angular.module('app', [ngAnimate, ngResource, ngCookies, ngClipboard, uiRouter, 'ui.router.state.events', brCore,
+ brServerStatus, brAutoFocus, brIconGenerator, brInterstitialSpinner, brooklynModuleLinks, brooklynUserManagement,
+ brYamlEditor, brUtils, brSpecEditor, brooklynCatalogSaver, brooklynApi, bottomSheet, stackViewer, brDragndrop,
+ customActionDirective, customConfigSuggestionDropdown, paletteApiProvider, paletteServiceProvider, blueprintLoaderApiProvider,
+ breadcrumbs, catalogSelector, designer, objectCache, entityFilters, locationFilter, actionService, blueprintService,
+ dslService, paletteDragAndDropService, recentlyUsedService])
.provider('composerOverrides', composerOverridesProvider)
- .factory('recursionHelper', ['$compile', recursionHelperFactory])
- .factory('objectCache', ['$cacheFactory', objectCacheFactory])
- .filter('entityName', entityNameFilter)
- .filter('entityVersion', entityVersionFilter)
- .filter('entityTypes', entityTypesFilter)
- .filter('locations', locationsFilter)
- .filter('catalogSelectorSearch', catalogSelectorSearchFilter)
- .filter('catalogSelectorFilters', catalogSelectorFiltersFilter)
.filter('dslParamLabel', ['$filter', dslParamLabelFilter])
.config(['$urlRouterProvider', '$stateProvider', '$logProvider', applicationConfig])
.config(['actionServiceProvider', actionConfig])
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});
}
};
}