blob: 348d0c0f50ea0c70fde2ecaaf6bb6b4d179fc40a [file] [log] [blame]
/*
* 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.
*/
import angular from 'angular';
import {EntityFamily} from '../util/model/entity.model';
import template from './catalog-selector.template.html';
const ITEMS_PER_PAGE = 30;
// fields in either bundle or type record:
const FIELDS_TO_SEARCH = ['name', 'displayName', 'symbolicName', 'version', 'type', 'supertypes', 'containingBundle', 'description', 'displayTags'];
export function catalogSelectorDirective() {
return {
restrict: 'E',
scope: {
family: '<',
onSelect: '&',
itemsPerPage: '<',
reservedKeys: '<?',
mode: '@?', // for use by downstream projects to pass in special modes
},
template: template,
controller: ['$scope', '$element', '$q', '$uibModal', '$log', '$templateCache', 'paletteApi', 'paletteDragAndDropService', 'iconGenerator', 'composerOverrides', controller]
};
}
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) => {
if (match) return true;
let text = item[field];
if (!text.toLowerCase) {
text = JSON.stringify(text).toLowerCase();
} else {
text = text.toLowerCase();
}
return match || text.indexOf(part) > -1;
}, false)
, true);
});
} else {
return items;
}
}
}
export function catalogSelectorSortFilter($filter) {
return function (items, family) {
return items.sort(function (left, right) {
let nameLeft;
let nameRight;
if (family) {
switch (family) {
case EntityFamily.ENTITY:
case EntityFamily.SPEC:
case EntityFamily.POLICY:
case EntityFamily.ENRICHER:
case EntityFamily.LOCATION:
nameLeft = $filter('entityName')(left);
nameRight = $filter('entityName')(right);
break;
}
}
if (!nameLeft || !nameRight) {
return 0;
}
let nameCompare = nameLeft.localeCompare(nameRight);
if (nameCompare !== 0) {
return nameCompare;
}
let versionCompare = right.version.localeCompare(left.version);
if (versionCompare !== 0) {
return versionCompare
}
// TODO should symbolic name be the sorted field?
let symNameCompare = left.symbolicName.localeCompare(right.symbolicName);
if (symNameCompare !== 0) {
return symNameCompare
}
let containingBundleCompare = right.containingBundle.localeCompare(left.containingBundle);
if (containingBundleCompare !== 0) {
return containingBundleCompare
}
return 0;
});
}
}
function controller($scope, $element, $q, $uibModal, $log, $templateCache, paletteApi, paletteDragAndDropService, iconGenerator, composerOverrides) {
$scope.pagination = {
page: 1,
itemsPerPage: $scope.itemsPerPage || ITEMS_PER_PAGE
};
$scope.state = {
orders: ['name', 'type', 'id'],
currentOrder: 'name'
};
$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.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 => {
return data;
}).catch(error => {
return [];
}).finally(() => {
$scope.isLoading = false;
});
};
$scope.onSelectItem = function (item) {
if (angular.isFunction($scope.onSelect)) {
$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();
};
$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.filterPaletteItems = (items) => items;
// downstream can override this to insert lines below the header
$scope.customSubHeadTemplateName = 'composer-palette-empty-sub-head';
$templateCache.put($scope.customSubHeadTemplateName, '');
// allow downstream to configure this controller and/or scope
(composerOverrides.configurePaletteController || function() {})(this, $scope, $element);
}