| /* |
| * 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 template from './catalog-uploader.html'; |
| import catalogApi from '../providers/catalog-api.provider'; |
| |
| const MODULE_NAME = 'brooklyn.components.catalog-uploader'; |
| |
| /** |
| * @ngdoc module |
| * @name brooklyn.components.catalog-uploader |
| * @requires catalogApi |
| * |
| * @description |
| * Adds an overlay on top of the current DOM element to upload files to the catalog. Files can either by added via |
| * classic file selection or drag & drop. This support multiple files to be uploaded at once. |
| */ |
| angular.module(MODULE_NAME, [catalogApi]) |
| .service('brooklynCatalogUploader', ['$q', 'catalogApi', catalogUploaderService]) |
| .directive('customOnChange', customOnChangeDirective) |
| .directive('brooklynCatalogUploader', ['$compile', 'brooklynCatalogUploader', catalogUploaderDirective]); |
| |
| export default MODULE_NAME; |
| |
| /** |
| * @ngdoc directive |
| * @name brooklynCatalogUploader |
| * @module brooklyn.components.catalog-uploader |
| * @restrict A |
| * |
| * @description |
| * Attaches an overlay on the current DOM element to handle file upload to the catalog. Files can either by added via |
| * classic file selection or drag & drop. The overlay can be triggered by broadcasting an event: for this to work, the |
| * event name need to be passed as value for the `brooklynCatalogUploader` attribute. |
| * |
| * @param {string} brooklynCatalogUploader The value can be empty. Otherwise, the directive will listen for any event broadcasted |
| * with this name and will trigger the overlay upon reception. |
| */ |
| export function catalogUploaderDirective($compile, brooklynCatalogUploader) { |
| return { |
| restrict: 'A', |
| link: link |
| }; |
| |
| function link(scope, element, attrs) { |
| let div = document.createElement('div'); |
| if ((('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && 'FormData' in window && 'FileReader' in window) { |
| element.addClass('br-has-drag-upload'); |
| } |
| |
| element.append($compile(template)(scope)); |
| |
| let counter = 0; |
| element.bind('drag dragstart dragend dragover dragenter dragleave drop', (event)=> { |
| event.preventDefault(); |
| event.stopPropagation(); |
| }).bind('drag dragstart dragover dragenter', (event)=> { |
| event.dataTransfer.dropEffect = 'copy'; |
| element.addClass('br-drag-active'); |
| }).bind('dragenter', ()=> { |
| counter++; |
| }).bind('dragleave', (event)=> { |
| counter--; |
| if (counter === 0) { |
| element.removeClass('br-drag-active'); |
| } |
| }).bind('drop', (event)=> { |
| scope.upload(event.dataTransfer.files); |
| }); |
| |
| let field = attrs.brooklynCatalogUploader; |
| if (angular.isDefined(field)) { |
| scope.$on(field, ()=> { |
| counter++; |
| element.addClass('br-drag-active'); |
| }); |
| } |
| |
| scope.selectedFiles = []; |
| |
| scope.close = ()=> { |
| counter--; |
| element.removeClass('br-drag-active'); |
| }; |
| |
| scope.filesChanged = (event)=> { |
| scope.upload(event.target.files); |
| }; |
| |
| scope.upload = (files)=> { |
| for (let i = 0; i < files.length; i++) { |
| let file = files[i]; |
| |
| brooklynCatalogUploader.upload(file).then((data)=> { |
| file.result = data; |
| }).catch((error)=> { |
| file.error = error; |
| }).finally(()=> { |
| scope.$applyAsync(); |
| }); |
| |
| scope.selectedFiles.unshift(file); |
| scope.$apply(); |
| } |
| }; |
| |
| scope.getCatalogItemUrl = (item)=> { |
| let itemTraits = item.tags? item.tags.find(item => item.hasOwnProperty("traits")) : {"traits":[]}; |
| return (item.supertypes ? item.supertypes : itemTraits.traits) |
| .includes('org.apache.brooklyn.api.location.Location') |
| ? `/brooklyn-ui-location-manager/#!/location?symbolicName=${item.symbolicName}&version=${item.version}` |
| : `/brooklyn-ui-catalog/#!/bundles/${item.containingBundle.split(':')[0]}/${item.containingBundle.split(':')[1]}/types/${item.symbolicName}/${item.version}`; |
| }; |
| } |
| } |
| |
| /** |
| * @ngdoc service |
| * @name brooklynCatalogUploader |
| * @module brooklyn.components.catalog-uploader |
| * |
| * @description |
| * Encapsulate the logic to validate files to upload to the catalog. |
| */ |
| export function catalogUploaderService($q, catalogApi) { |
| let extensions = { |
| 'bom': { |
| headers: {'Content-Type': 'application/yaml'} |
| }, |
| 'yml': { |
| headers: {'Content-Type': 'application/yaml'} |
| }, |
| 'yaml': { |
| headers: {'Content-Type': 'application/yaml'} |
| }, |
| 'zip' : { |
| headers: {'Content-Type': 'application/x-zip'}, |
| transformRequest: angular.identity |
| }, |
| 'jar' : { |
| headers: {'Content-Type': 'application/x-jar'}, |
| transformRequest: angular.identity |
| } |
| }; |
| |
| return { |
| /** |
| * @ngdoc method |
| * @name upload |
| * @methodOf brooklynCatalogUploader |
| * |
| * @description |
| * Upload a file to the catalog. This will validate the extensions and reject the promises if the current one is |
| * not supported. |
| * |
| * @param {object} file A file object, representing a file to upload to the catalog. |
| * |
| * @return A promise that gets resolve if the upload *and* process of the file server side is successful; the |
| * resolve data contains the catalog items that have been added a map of `{id: item}`. Otherwise, the promise |
| * is rejected with the error message as parameter. |
| */ |
| upload: upload |
| }; |
| |
| function upload(file) { |
| let defer = $q.defer(); |
| |
| if (new RegExp('^.*\.(' + Object.keys(extensions).join('|') + ')$').test(file.name)) { |
| Object.keys(extensions).forEach((extension)=> { |
| if (!new RegExp('^.*\.(' + extension + ')$').test(file.name)) { |
| return; |
| } |
| |
| let options = extensions[extension]; |
| let reader = new FileReader(); |
| reader.addEventListener('load', ()=> { |
| try { |
| let rawData = new Uint8Array(reader.result); |
| let data = ['zip', 'jar'].indexOf(extension) > -1 ? rawData : String.fromCharCode.apply(null, rawData); |
| catalogApi.create(data, {}, options).then((response)=> { |
| defer.resolve(response); |
| }).catch((response)=> { |
| defer.reject('Cannot upload item to the catalog: ' + response.error.message); |
| }); |
| } catch (error) { |
| defer.reject('Cannot read file: ' + error.message); |
| } |
| }, false); |
| reader.readAsArrayBuffer(file); |
| }); |
| } else { |
| defer.reject('Unsupported file type. Please upload only files with the following extensions: ' + Object.keys(extensions).map((extension)=>('*.' + extension)).join(', ')); |
| } |
| |
| return defer.promise; |
| } |
| } |
| |
| export function customOnChangeDirective() { |
| return { |
| restrict: 'A', |
| link: function (scope, element, attrs) { |
| element.on('change', x => { |
| var onChangeHandler = scope.$eval(attrs.customOnChange); |
| onChangeHandler(x); |
| }); |
| element.on('$destroy', function() { |
| element.off(); |
| }); |
| |
| |
| } |
| }; |
| } |