diff --git a/app/assets/stylesheets/katello/widgets/path_selector.scss b/app/assets/stylesheets/katello/widgets/path_selector.scss index 6b3a6249c5a..80c6d76cdc0 100644 --- a/app/assets/stylesheets/katello/widgets/path_selector.scss +++ b/app/assets/stylesheets/katello/widgets/path_selector.scss @@ -116,8 +116,7 @@ $widget-background-color: #F9F8F8; display: block; font-weight: normal; font-size: 10px; - margin-bottom: 0; - height: 37px; + margin: 0; .checkbox_holder { float: left; diff --git a/app/controllers/katello/content_views_controller.rb b/app/controllers/katello/content_views_controller.rb new file mode 100644 index 00000000000..0c18be69a6c --- /dev/null +++ b/app/controllers/katello/content_views_controller.rb @@ -0,0 +1,36 @@ +# +# Copyright 2013 Red Hat, Inc. +# +# This software is licensed to you under the GNU General Public +# License as published by the Free Software Foundation; either version +# 2 of the License (GPLv2) or (at your option) any later version. +# There is NO WARRANTY for this software, express or implied, +# including the implied warranties of MERCHANTABILITY, +# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should +# have received a copy of GPLv2 along with this software; if not, see +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + +module Katello +class ContentViewsController < Katello::ApplicationController + + before_filter :authorize + + def rules + index_rule = lambda { true } + + { + :index => index_rule, + :all => index_rule + } + end + + def index + render 'bastion/layouts/application', :layout => false + end + + def all + redirect_to :action => 'index', :anchor => '/content_views' + end + +end +end diff --git a/app/lib/katello/navigation/items/content_views.rb b/app/lib/katello/navigation/items/content_views.rb new file mode 100644 index 00000000000..c9a7ce14a90 --- /dev/null +++ b/app/lib/katello/navigation/items/content_views.rb @@ -0,0 +1,28 @@ +# +# Copyright 2013 Red Hat, Inc. +# +# This software is licensed to you under the GNU General Public +# License as published by the Free Software Foundation; either version +# 2 of the License (GPLv2) or (at your option) any later version. +# There is NO WARRANTY for this software, express or implied, +# including the implied warranties of MERCHANTABILITY, +# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should +# have received a copy of GPLv2 along with this software; if not, see +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + +module Katello + module Navigation + module Items + class ContentViews < Navigation::Item + + def initialize(organization) + @key = :content_views + @display = _("Content Views (new)") + @authorization = lambda{ ContentView.any_readable?(organization) } + @url = content_views_path + '#/content_views' + end + + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index d8925d066f1..2b7ad4b0067 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -65,6 +65,12 @@ end end + resources :content_views, :only => [:index] do + collection do + get :all + end + end + resources :activation_keys do collection do get :auto_complete_search diff --git a/engines/bastion/app/assets/javascripts/bastion/bastion.js b/engines/bastion/app/assets/javascripts/bastion/bastion.js index 38dceddb9c6..52514d05df1 100644 --- a/engines/bastion/app/assets/javascripts/bastion/bastion.js +++ b/engines/bastion/app/assets/javascripts/bastion/bastion.js @@ -20,6 +20,7 @@ //= require "bastion/angular-gettext/angular-gettext" //= require "bastion/angular-blocks/angular-blocks" //= require_tree "../../../../vendor/assets/javascripts/bastion/angular-bootstrap" +//= require "bastion/angular-animate/angular-animate" //= require "bastion/alchemy/alchemy" //= require "katello/common/katello.global" @@ -59,6 +60,7 @@ //= require "bastion/content-views/content-views.module" //= require_tree "./content-views" +//= stub "bastion/content-views/content-view.factory" //= require "bastion/errata/errata.module" //= require_tree "./errata" diff --git a/engines/bastion/app/assets/javascripts/bastion/bastion.module.js b/engines/bastion/app/assets/javascripts/bastion/bastion.module.js index 3860c37d4cd..adc0941e9a3 100644 --- a/engines/bastion/app/assets/javascripts/bastion/bastion.module.js +++ b/engines/bastion/app/assets/javascripts/bastion/bastion.module.js @@ -26,6 +26,7 @@ angular.module('Bastion', [ 'ngSanitize', 'ui.bootstrap', 'ui.bootstrap.tpls', + 'ngAnimate', 'angular-blocks', 'Bastion.i18n', 'Bastion.menu', diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/content-view-mock.factory.js b/engines/bastion/app/assets/javascripts/bastion/content-views/content-view-mock.factory.js new file mode 100644 index 00000000000..c0cf7478464 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/content-view-mock.factory.js @@ -0,0 +1,274 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + **/ + +/** + * @ngdoc service + * @name Bastion.content-views.factory:ContentView + * + * @description + * Provides a $Resource for interacting with environments. + */ +angular.module('Bastion.content-views').factory('ContentView', + [function($resource, Routes, CurrentOrganization) { + + var Resource = function(id, create) { + var name = id ? 'Content View ' + id : '', + label = id ? 'content_view_' + id : '', + generatedVersions = [], + counts = { + products: 0, + repositories: 0, + puppet_modules: 0 + }; + + this.id = id; + + if (id !== undefined && !create) { + versions.call(this, function(response) { + generatedVersions = response.results; + }); + + counts = { + products: id + id, + repositories: id * id, + puppet_modules: id * id - id + }; + } + + return { + id: id, + name: name, + label: label, + created: new Date(), + environments: [ + 'Library', + 'Dev' + ], + counts: counts, + user: 'mister manager', + permissions: { + editable: true + }, + organization: { + id: 1, + name: 'ACME_Corporation' + }, + $save: save, + $versions: versions, + $version: version, + versions: generatedVersions, + repositories: [], + filters: [], + $publish: publish, + $puppetModules: puppetModules, + $filters: filters, + $addFilter: addFilter + }; + }; + + var Version = function(id) { + return { + id: id, + name: 'Version ' + id, + label: 'version_' + id, + promoted: new Date(), + environments: [ + { + id: 1, + name: 'Library' + },{ + id: 2, + name: 'Dev' + } + ], + counts: { + products: id + id, + repositories: id * id, + packages: id^id, + puppet_modules: id * id - id, + errata: { + bugs: id, + security: id + id, + enhancements: id + } + }, + user: 'mister manager' + }; + }; + + var Filter = function(id) { + return { + id: id, + name: 'Version ' + id, + created: new Date(), + description: '', + contentType: '', + counts: { + products: 0, + repositories: 0, + packages: 0, + puppet_modules: id * id - id, + errata: { + bugs: 0, + security: 0, + enhancements: 0 + } + } + }; + }; + + var save = function(successCallback, errorCallback) { + var view = new Resource(results.length + 1, true); + + view.name = this.name; + + results.push(view); + successCallback(view); + }; + + var versions = function(callback) { + var versions = generateVersions(this.id); + + if (this.id < 10) { + this.versions = versions; + } + + callback({ + offset: 0, + total: versions.length, + subtotal: versions.length, + limit: 25, + search: "", + sort: {by: "name", order: "ASC"}, + results: this.versions + }); + }; + + var version = function(params, callback) { + var found; + + angular.forEach(this.versions, function(version) { + if (params.toString() === version.id.toString()) { + found = version; + } + }); + + callback(found); + }; + + var publish = function(params, callback) { + var version = new Version(this.versions.length + 1); + + version.name = params.name; + + this.versions.push(version); + callback(version); + }; + + var puppetModules = function(callback) { + var modules = generatePuppetModules(this.id); + + if (this.modules.length === 0) { + this.modules = modules; + } + + callback({ + offset: 0, + total: modules.length, + subtotal: modules.length, + limit: 25, + search: "", + sort: {by: "name", order: "ASC"}, + results: this.modules + }); + }; + + var filters = function(callback) { + var filters = this.filters; + + callback({ + offset: 0, + total: filters.length, + subtotal: filters.length, + limit: 25, + search: "", + sort: {by: "name", order: "ASC"}, + results: filters + }); + }; + + var addFilter = function(params, callback) { + var filter = new Filter(this.filters.length + 1); + + filter.name = params.name; + filter.description = params.description; + filter.contentType = params.contentType; + + this.filters.push(filter); + callback(filter); + }; + + var results = generateViews(10, save, versions, publish, puppetModules); + + Resource.query = function(params, callback) { + callback({ + offset: 0, + total: 10, + subtotal: 10, + limit: 25, + search: "", + sort: {by: "name", order: "ASC"}, + results: results + }); + }; + + Resource.get = function(params, callback) { + var view; + + angular.forEach(results, function(result) { + if (params.id.toString() === result.id.toString()) { + view = result; + } + }); + + callback(view); + + return view; + }; + + function generateViews(numViews, save, versions, publish, puppetModules) { + var views = [], + i; + + for(i = 1; i <= numViews; i += 1) { + views.push(new Resource(i)); + } + + return views; + } + + function generateVersions(numVersions) { + var versions = [], + i; + + for(i = 1; i <= numVersions; i += 1) { + versions.push(new Version(i)); + } + + return versions; + } + + return Resource; + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/content-view.factory.js b/engines/bastion/app/assets/javascripts/bastion/content-views/content-view.factory.js index f6366467672..25c0242e02f 100644 --- a/engines/bastion/app/assets/javascripts/bastion/content-views/content-view.factory.js +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/content-view.factory.js @@ -26,11 +26,14 @@ angular.module('Bastion.content-views').factory('ContentView', ['$resource', 'Routes', 'CurrentOrganization', function ($resource, Routes, CurrentOrganization) { - return $resource(Routes.apiOrganizationContentViewsPath(CurrentOrganization) + '/:id/:action', + return $resource('/content_views/:id/:action', {id: '@id'}, { - update: { method: 'PUT' }, - query: { method: 'GET', isArray: false} + update: {method: 'PUT'}, + query: {method: 'GET', isArray: false}, + versions: {method: 'GET', isArray: false, params: {action: versions}}, + publish: {method: 'POST', params: {action: publish}}, + puppetModules: {method: 'GET', isArray: false, params: {action: puppet_modules}} } ); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.controller.js new file mode 100644 index 00000000000..22fddff06af --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.controller.js @@ -0,0 +1,42 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewsController + * + * @requires $scope + * @requires $location + * @requires Nutupane + * @requires ContentView + * @requires CurrentOrganization + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewsController', + ['$scope', '$location', 'Nutupane', 'ContentView', 'CurrentOrganization', + function($scope, $location, Nutupane, ContentView, CurrentOrganization) { + + var nutupane = new Nutupane(ContentView, { + 'organization_id': CurrentOrganization, + 'sort_by': 'name', + 'sort_order': 'ASC', + 'enabled' : true + }); + + $scope.table = nutupane.table; + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.routes.js b/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.routes.js new file mode 100644 index 00000000000..1acada793f2 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/content-views.routes.js @@ -0,0 +1,119 @@ +/** + Copyright 2013 Red Hat, Inc. + + This software is licensed to you under the GNU General Public + License as published by the Free Software Foundation; either version + 2 of the License (GPLv2) or (at your option) any later version. + There is NO WARRANTY for this software, express or implied, + including the implied warranties of MERCHANTABILITY, + NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + have received a copy of GPLv2 along with this software; if not, see + http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + **/ + +/** + * @ngdoc object + * @name Bastion.content-views.config + * + * @requires $stateProvider + * + * @description + * State routes defined for the content views module. + */ +angular.module('Bastion.content-views').config(['$stateProvider', function($stateProvider) { + $stateProvider.state('content-views', { + abstract: true, + controller: 'ContentViewsController', + templateUrl: 'content-views/views/content-views.html' + }) + .state('content-views.index', { + url: '/content_views', + views: { + 'table': { + templateUrl: 'content-views/views/content-views-table-full.html' + } + } + }) + + .state('content-views-new', { + url: '/content_views/new', + controller: 'NewContentViewController', + templateUrl: 'content-views/new/views/content-view-new.html' + }) + + .state('content-views-details', { + abstract: true, + url: '/content_views/:contentViewId', + controller: 'ContentViewDetailsController', + templateUrl: 'content-views/details/views/content-view-details.html' + }) + .state('content-views-details.versions', { + url: '/versions', + controller: 'ContentViewVersionsController', + templateUrl: 'content-views/details/views/content-view-details-versions.html' + }) + .state('content-views-details.promotion', { + url: '/versions/:versionId/promotion', + controller: 'ContentViewPromotionController', + templateUrl: 'content-views/details/views/content-view-promotion.html' + }) + .state('content-views-details.products', { + abstract: true, + template: '
' + }) + .state('content-views-details.products.list', { + url: '/products', + controller: 'ContentViewProductsController', + templateUrl: 'content-views/details/views/content-view-details-products.html' + }) + .state('content-views-details.products.available', { + url: '/products/available', + controller: 'ContentViewAvailableProductsController', + templateUrl: 'content-views/details/views/content-view-details-products.html' + }) + .state('content-views-details.puppet-modules', { + url: '/puppet_modules', + controller: 'ContentViewPuppetModulesController', + templateUrl: 'content-views/details/views/content-view-details-puppet-modules.html' + }) + .state('content-views-details.info', { + controller: 'ContentViewInfoController', + templateUrl: 'content-views/details/views/content-view-details-info.html' + }) + .state('content-views-details.publish', { + url: '/publish', + controller: 'ContentViewPublishController', + templateUrl: 'content-views/details/views/content-view-details-publish.html' + }) + + .state('content-views-details.filters', { + abstract: true, + controller: 'ContentViewFiltersController', + template: '
' + }) + .state('content-views-details.filters.list', { + url: '/filters', + templateUrl: 'content-views/details/filters/views/content-view-details-filters.html' + }) + .state('content-views-details.filters.new', { + url: '/filters/new', + controller: 'ContentViewFiltersNewController', + templateUrl: 'content-views/details/filters/views/content-view-details-filters-new.html' + }) + .state('content-views-details.filters.details', { + abstract: true, + controller: 'ContentViewFilterDetailsController', + templateUrl: 'content-views/details/filters/views/content-view-filter-details.html' + }) + .state('content-views-details.filters.details.packages', { + url: '/filters/:filterId/packages', + controller: 'ContentViewFilterDetailsPackageController', + templateUrl: 'content-views/details/filters/views/content-view-filter-details-packages.html' + }) + .state('content-views-details.filters.details.errata', { + url: '/filters/:filterId/errata', + controller: 'ContentViewFilterDetailsErrataController', + templateUrl: 'content-views/details/filters/views/content-view-filter-details-errata.html' + }) + +}]); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-available-products.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-available-products.controller.js new file mode 100644 index 00000000000..36fe0be0ae1 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-available-products.controller.js @@ -0,0 +1,127 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewAvailableProductsController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewAvailableProductsController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + $scope.repositories = (function() { + var repositories = [ + { + product: {name: 'test', id: 1}, + id: '1', + name: 'Repo Name 87', + content_type: 'test' + }, + { + product: {name: 'test2', id: 2}, + id: '2', + name: 'Repo Name 55', + content_type: 'test' + }, + { + product: {name: 'test', id: 1}, + id: '3', + name: 'Repo Name 66', + content_type: 'test' + }, + { + product: {name: 'test3', id: 3}, + id: '4', + name: 'Repo Name 32', + content_type: 'test' + }], + find = function(repository) { + var found = false; + + angular.forEach($scope.contentView.repositories, function(addedRepository) { + if (repository.id === addedRepository.id) { + found = repository; + } + }); + + return found; + }, + available = []; + + + angular.forEach(repositories, function(repository) { + var found = find(repository); + + if (!found) { + available.push(repository); + } + }); + + return available; + })(); + + $scope.products = extractProducts(); + + $scope.repositoryFilter = function(repository) { + if ($scope.product !== undefined && $scope.product !== null) { + if (repository.product.id === $scope.product.id) { + return true; + } else { + return false; + } + } else { + return true; + } + }; + + $scope.addRepositories = function() { + var kept = []; + + angular.forEach($scope.repositories, function(repository) { + if (!repository.selected) { + repository.selected = false; + kept.push(repository); + } else { + $scope.contentView.repositories.push(repository); + } + }); + + $scope.repositories = kept; + }; + + $scope.selectAll = function(selected) { + angular.forEach($scope.repositories, function(repository) { + repository.selected = selected; + }); + }; + + function extractProducts() { + var products = {}; + + angular.forEach($scope.repositories, function(repository) { + products[repository.product.id] = repository.product; + }); + + return products; + } + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-details.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-details.controller.js new file mode 100644 index 00000000000..cf3828e7197 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-details.controller.js @@ -0,0 +1,35 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewDetailsController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewDetailsController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + ContentView.get({id: $scope.$stateParams.contentViewId}, function(view) { + $scope.contentView = view; + }); + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-info.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-info.controller.js new file mode 100644 index 00000000000..0b806d49142 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-info.controller.js @@ -0,0 +1,31 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewInfoController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewInfoController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-products.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-products.controller.js new file mode 100644 index 00000000000..e8770197f2e --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-products.controller.js @@ -0,0 +1,77 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewProductsController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewProductsController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + $scope.repositories = $scope.contentView.repositories; + + $scope.products = extractProducts(); + + $scope.removeRepositories = function() { + var kept = []; + + angular.forEach($scope.contentView.repositories, function(repository) { + if (!repository.selected) { + repository.selected = false; + kept.push(repository); + } + }); + + $scope.contentView.repositories = kept; + $scope.repositories = $scope.contentView.repositories; + }; + + $scope.selectAll = function(selected) { + angular.forEach($scope.contentView.repositories, function(repository) { + repository.selected = selected; + }); + }; + + $scope.repositoryFilter = function(repository) { + if ($scope.product !== undefined && $scope.product !== null) { + if (repository.product.id === $scope.product.id) { + return true; + } else { + return false; + } + } else { + return true; + } + }; + + function extractProducts() { + var products = {}; + + angular.forEach($scope.repositories, function(repository) { + products[repository.product.id] = repository.product; + }); + + return products; + } + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-promotion.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-promotion.controller.js new file mode 100644 index 00000000000..a6722e09def --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-promotion.controller.js @@ -0,0 +1,74 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewPromotionController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewPromotionController', + ['$scope', 'ContentView', 'CurrentOrganization', '$http', + function($scope, ContentView, CurrentOrganization, $http) { + + $scope.promotion = {}; + + $http.get('/katello/organizations/' + CurrentOrganization + '/environments/registerable_paths') + .success(function(paths) { + + angular.forEach($scope.version.environments, function(environment) { + angular.forEach(paths, function(path) { + angular.forEach(path, function(item, index) { + if (environment.id.toString() === item.id.toString()) { + if (index + 1 < path.length) { + path[index + 1].selectable = true; + } + } + }); + }); + }); + + $scope.availableEnvironments = paths; + }); + + $scope.contentView.$version($scope.$stateParams.versionId, function(version) { + $scope.version = version; + }); + + $scope.$watch('setupSelector', function(selector) { + if (selector !== undefined) { + selector().then(function () { + angular.forEach($scope.version.environments, function(environment) { + $scope.pathSelector.set_selected(environment.id); + }); + }); + } + }); + + $scope.promote = function() { + angular.forEach($scope.availableEnvironments, function(path) { + if (path.selectable) { + $scope.contentView.version.environments.push(path); + } + }); + + $scope.transitionTo('content-views-details.versions', {contentViewId: $scope.contentView.id}); + }; + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-publish.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-publish.controller.js new file mode 100644 index 00000000000..6f619b86e79 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-publish.controller.js @@ -0,0 +1,39 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewPublishController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewPublishController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + $scope.version = {}; + + $scope.publish = function(version) { + $scope.contentView.$publish(version, function() { + $scope.transitionTo('content-views-details.versions', {contentViewId: $scope.contentView.id}); + }); + }; + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-puppet-modules.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-puppet-modules.controller.js new file mode 100644 index 00000000000..b13af09de4b --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-puppet-modules.controller.js @@ -0,0 +1,37 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewPuppetModulesController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewPuppetModulesController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + $scope.table = {}; + $scope.contentView.$versions(function(versions) { + $scope.contentView.versions = versions.results; + $scope.table.rows = $scope.contentView.versions; + }); + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-versions.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-versions.controller.js new file mode 100644 index 00000000000..0c0a6c47816 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/content-view-versions.controller.js @@ -0,0 +1,37 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewVersionsController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewVersionsController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + $scope.table = {}; + $scope.contentView.$versions(function(versions) { + $scope.contentView.versions = versions.results; + $scope.table.rows = $scope.contentView.versions; + }); + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-errata.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-errata.controller.js new file mode 100644 index 00000000000..d99dcd668b4 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-errata.controller.js @@ -0,0 +1,50 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewFilterDetailsErrataController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewFilterDetailsErrataController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + $scope.filter = $scope.contentView.filters[$scope.$stateParams.filterId - 1]; + $scope.filterBy = 'date'; + $scope.rule = { + type: "included", + beginDate: new Date(), + endDate: new Date() + }; + + $scope.filterRules = []; + + $scope.addRule = function(rule) { + rule.added = new Date(); + rule.detail = 'all'; + $scope.filterRules.push(rule); + $scope.rule = { + type: "included" + }; + }; + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-package.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-package.controller.js new file mode 100644 index 00000000000..ea6e6f9732c --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details-package.controller.js @@ -0,0 +1,47 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewFilterDetailsController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewFilterDetailsPackageController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + $scope.filter = $scope.contentView.filters[$scope.$stateParams.filterId - 1]; + $scope.rule = { + type: "included" + }; + + $scope.filterRules = []; + + $scope.addRule = function(rule) { + rule.added = new Date(); + rule.detail = 'all'; + $scope.filterRules.push(rule); + $scope.rule = { + type: "included" + }; + }; + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details.controller.js new file mode 100644 index 00000000000..6641fd08ccf --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filter-details.controller.js @@ -0,0 +1,33 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewFilterDetailsController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewFilterDetailsController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + $scope.filter = $scope.contentView.filters[$scope.$stateParams.filterId - 1]; + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters-new.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters-new.controller.js new file mode 100644 index 00000000000..aa253cd31bc --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters-new.controller.js @@ -0,0 +1,40 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewFiltersNewController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewFiltersNewController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + $scope.filter = {}; + + $scope.save = function(filter) { + $scope.contentView.$addFilter(filter, function() { + console.log(filter); + $scope.transitionTo('content-views-details.filters.list', {contentViewId: $scope.contentView.id}); + }); + }; + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters.controller.js new file mode 100644 index 00000000000..6a349a3048f --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/content-view-filters.controller.js @@ -0,0 +1,37 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:ContentViewFiltersController + * + * @requires $scope + * @requires ContentView + * + * @description + * Provides the functionality specific to ContentViews for use with the Nutupane UI pattern. + * Defines the columns to display and the transform function for how to generate each row + * within the table. + */ +angular.module('Bastion.content-views').controller('ContentViewFiltersController', + ['$scope', 'ContentView', + function($scope, ContentView) { + + $scope.table = {}; + $scope.contentView.$filters(function(filters) { + $scope.contentView.filters = filters.results; + $scope.table.rows = $scope.contentView.filters; + }); + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-details-filters-new.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-details-filters-new.html new file mode 100644 index 00000000000..70fcfa18447 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-details-filters-new.html @@ -0,0 +1,48 @@ +
+ + + +
+

Add New Filter

+
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-details-filters.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-details-filters.html new file mode 100644 index 00000000000..1d07ccc2890 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-details-filters.html @@ -0,0 +1,53 @@ +
+ +
+ + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
{{ "Name" | translate }}{{ "Description" | translate }}{{ "Created" | translate }}{{ "Products" | translate }}{{ "Repositories" | translate }}{{ "Packages" | translate }}{{ "Errata" | translate }}
+ {{ filter.name }} + {{ filter.description }}{{ filter.created | date:"medium" }}{{ filter.counts.products }}{{ filter.counts.repositories }}{{ filter.counts.packages }} + {{ filter.counts.errata.security }} + {{ filter.counts.errata.bugs }} + {{ filter.counts.errata.enhancements }} +
+ +
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-filter-details-errata.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-filter-details-errata.html new file mode 100644 index 00000000000..15161cbcafc --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-filter-details-errata.html @@ -0,0 +1,48 @@ +
+
+ + +
+ + +
+
+ +
+
+ + + From: {{ rule.beginDate | date:'shortDate' }} - To: {{ rule.endDate | date:'shortDate' }} +
+ +
+ + +
+ +
+ + + + + +
+
+ +
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-filter-details-packages.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-filter-details-packages.html new file mode 100644 index 00000000000..f12ebee484f --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-filter-details-packages.html @@ -0,0 +1,66 @@ +
+
+ + +
+ + +
+ + +
+ +
+
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + +
Package NameDetailTypeAdded
{{ rule.name }} + + {{ rule.type }}{{ rule.added | date:"short" }}
+
+
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-filter-details.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-filter-details.html new file mode 100644 index 00000000000..6c8f2885c32 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/filters/views/content-view-filter-details.html @@ -0,0 +1,14 @@ +
+ + + +
+

{{ filter.name }}: {{ filter.contentType }}

+
+ +
+ +
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-info.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-info.html new file mode 100644 index 00000000000..188c240610d --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-info.html @@ -0,0 +1,26 @@ +
+ +
+ Name + + +
+ +
+ Label + {{ contentView.label }} +
+ +
+ Description + + +
+ +
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-products.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-products.html new file mode 100644 index 00000000000..7a5ffeeb810 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-products.html @@ -0,0 +1,107 @@ + + +
+ +
+
+
+ + +
+ +
+
+
+
+ +
+
+ +
+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
NameProductLast SyncSync StatePackagesErrataPuppet Modules
{{ repository.name }}{{ repository.product.name }} + + {{ repository.last_sync | date:"short" }} + + N/A + + + {{ repository.sync_state }} + + N/A + + + {{ repository.content_counts.rpm || 0 }} + + + + {{ repository.content_counts.erratum || 0 }} + + + + {{ repository.content_counts.puppet_module || 0 }} + +
+
+ +
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-publish.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-publish.html new file mode 100644 index 00000000000..8b32a5b2be5 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-publish.html @@ -0,0 +1,37 @@ +
+ +

Publish New Version

+ +

+ This latest version of the '{{ contentView.name }}' content view will be published to the + library. You can see this new version in the '{{ contentView.name }}' views datatable. From there + you are able to promote through any promotion path. +

+ +
+

Version Details

+
+ +
+ +
+ {{ contentView.versions.length + 1 }} +
+ +
+ +
+ +
+
+
+ +
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-puppet-modules.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-puppet-modules.html new file mode 100644 index 00000000000..7f8ed70e69a --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-puppet-modules.html @@ -0,0 +1,21 @@ + + +
diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-versions.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-versions.html new file mode 100644 index 00000000000..1f3c902f503 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details-versions.html @@ -0,0 +1,54 @@ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ "Name" | translate }}{{ "Promoted" | translate }}{{ "Environments" | translate }}{{ "Products" | translate }}{{ "Repositories" | translate }}{{ "Packages" | translate }}{{ "Puppet Modulaes" | translate }}{{ "Errata" | translate }}{{ "Author" | translate }}{{ "Promote" | translate }}
{{ version.name }}{{ version.promoted | date:"medium" }} +
  • + {{ environment.name }} +
  • +
    {{ version.counts.products }}{{ version.counts.repositories }}{{ version.counts.packages }}{{ version.counts.puppet_modules }} + {{ version.counts.errata.security }} + {{ version.counts.errata.bugs }} + {{ version.counts.errata.enhancements }} + {{ version.user }} + + + +
    + +
    diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details.html new file mode 100644 index 00000000000..3ed63728610 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-details.html @@ -0,0 +1,69 @@ +
    + + + +
    +

    {{ contentView.name }}

    +
    + Created {{ contentView.created | date:'short' }} by {{ contentView.user }} + +
    + + + + + +
    + +
    +
    + + + +
    + +
    diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-promotion.html b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-promotion.html new file mode 100644 index 00000000000..328f7871503 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/details/views/content-view-promotion.html @@ -0,0 +1,22 @@ +
    + +

    Promote to Next Environment

    + +

    + Choose one or more lifecycle environments from the existing promotion paths + available in '{{ currentOrganization }}'. +

    + +
    + +
    + + + + Cancel + +
    + +
    diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/new/content-view-new.controller.js b/engines/bastion/app/assets/javascripts/bastion/content-views/new/content-view-new.controller.js new file mode 100644 index 00000000000..d970036b6ed --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/new/content-view-new.controller.js @@ -0,0 +1,57 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +*/ + +/** + * @ngdoc object + * @name Bastion.content-views.controller:NewContentViewController + * + * @requires $scope + * @requires ContentView + * @requires FormUtils + * + * @description + */ +angular.module('Bastion.content-views').controller('NewContentViewController', + ['$scope', 'ContentView', 'FormUtils', + function($scope, ContentView, FormUtils) { + + $scope.contentView = new ContentView(); + $scope.createOption = 'new'; + $scope.table = {}; + + ContentView.query({}, function(response) { + $scope.table.rows = response.results; + }); + + $scope.save = function(contentView) { + contentView.$save(success, error); + }; + + $scope.$watch('contentView.name', function() { + if ($scope.contentViewForm.name) { + $scope.contentViewForm.name.$setValidity('server', true); + $scope.contentView.label = $scope.contentView.name; + //FormUtils.labelize($scope.contentView, $scope.contentViewForm); + } + }); + + function success(response) { + $scope.transitionTo('content-views-details.products.available', {contentViewId: response.id}); + } + + function error(response) { + console.log('error'); + } + + }] +); diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/new/views/content-view-new.html b/engines/bastion/app/assets/javascripts/bastion/content-views/new/views/content-view-new.html new file mode 100644 index 00000000000..9e61b1a8603 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/new/views/content-view-new.html @@ -0,0 +1,81 @@ +
    + + + +
    +

    New Content View

    +
    + +
    +
    + + +
    +
    + +
    +

    View Details

    + +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    +
    +
    +
    + +
    +
    +

    Existing Content Views Choose one or more existing views to build from

    +
    + +
    +
    + +
    diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/views/content-views-table-full.html b/engines/bastion/app/assets/javascripts/bastion/content-views/views/content-views-table-full.html new file mode 100644 index 00000000000..36fdb9b0cb9 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/views/content-views-table-full.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
    {{ "Name" | translate }}{{ "Created" | translate }}{{ "Environments" | translate }}{{ "Products" | translate }}{{ "Repositories" | translate }}{{ "Puppet Modules" | translate }}{{ "Author" | translate }}
    + + {{ contentView.name }} + + + {{ contentView.created | date:"medium" }}{{ contentView.environments }}{{ contentView.counts.products }}{{ contentView.counts.repositories }}{{ contentView.counts.puppet_modules }}{{ contentView.user }}
    diff --git a/engines/bastion/app/assets/javascripts/bastion/content-views/views/content-views.html b/engines/bastion/app/assets/javascripts/bastion/content-views/views/content-views.html new file mode 100644 index 00000000000..a3cb5411a5d --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/content-views/views/content-views.html @@ -0,0 +1,14 @@ +
    + +
    + {{ 'Content Views' | translate }} +
    + +
    + +
    + +
    diff --git a/engines/bastion/app/assets/javascripts/bastion/incubator/alch-form-buttons.directive.js b/engines/bastion/app/assets/javascripts/bastion/incubator/alch-form-buttons.directive.js index 1d8e7641b39..68ad8e5abdf 100644 --- a/engines/bastion/app/assets/javascripts/bastion/incubator/alch-form-buttons.directive.js +++ b/engines/bastion/app/assets/javascripts/bastion/incubator/alch-form-buttons.directive.js @@ -41,8 +41,8 @@ angular.module('alchemy').directive('alchFormButtons', function () { scope.isInvalid = function () { var invalid = controller.$invalid; - angular.forEach(controller, function (value) { - if (value.$error) { + angular.forEach(controller, function(value) { + if (value && value.$error) { if (value.$error.server) { invalid = false; } diff --git a/engines/bastion/app/assets/javascripts/bastion/systems/details/views/system-info.html b/engines/bastion/app/assets/javascripts/bastion/systems/details/views/system-info.html index 865af15214f..7e8d0e28ccc 100644 --- a/engines/bastion/app/assets/javascripts/bastion/systems/details/views/system-info.html +++ b/engines/bastion/app/assets/javascripts/bastion/systems/details/views/system-info.html @@ -213,7 +213,7 @@

    System Content

    Environment diff --git a/engines/bastion/app/assets/javascripts/bastion/widgets/path-selector-wrapper.directive.js b/engines/bastion/app/assets/javascripts/bastion/widgets/path-selector-wrapper.directive.js new file mode 100644 index 00000000000..8bca1634b27 --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/widgets/path-selector-wrapper.directive.js @@ -0,0 +1,75 @@ +/** + * Copyright 2013 Red Hat, Inc. + * + * This software is licensed to you under the GNU General Public + * License as published by the Free Software Foundation; either version + * 2 of the License (GPLv2) or (at your option) any later version. + * There is NO WARRANTY for this software, express or implied, + * including the implied warranties of MERCHANTABILITY, + * NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should + * have received a copy of GPLv2 along with this software; if not, see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + */ + +/*jshint camelcase:false*/ + +/** + * @ngdoc directive + * @name Bastion.widgets.directive:pathSelector + * + * @description + * Provides an angular wrapper for the path selector widget. + * + * @example + */ +angular.module('Bastion.widgets').directive('pathSelectorWrapper', + ['$document', '$http', 'Routes', function($document, $http, Routes) { + return { + restrict: 'AE', + scope: { + pathSelector: '=pathSelectorWrapper', + readonly: '=', + organization: '&', + onChange: '&', + mode: '@' + }, + link: function(scope) { + var pathSelect; + + scope.$watch('pathSelector', function(selected) { + if (selected !== undefined && pathSelect) { + pathSelect.set_selected(selected); + } + }); + + scope.$parent.setupSelector = function() { + return $http.get(Routes.organizationEnvironmentsPath(scope.organization()) + '/registerable_paths') + .success(function(paths) { + var options = { + inline: true, + 'select_mode': scope.mode ? scope.mode : 'single', + expand: false, + selected: scope.pathSelector, + readonly: scope.readonly + }; + + pathSelect = KT.path_select( + 'environment_path_selector', + 'system_details_path_selector', + paths, + options + ); + + $document.bind(pathSelect.get_select_event(), function() { + var environments = pathSelect.get_selected(); + + scope.pathSelector = Object.keys(environments)[0]; + scope.onChange({ environmentId: scope.pathSelector }); + }); + + scope.$parent.pathSelector = pathSelect; + }); + }; + }, + }; +}]); diff --git a/engines/bastion/app/assets/javascripts/bastion/widgets/path-selector.directive.js b/engines/bastion/app/assets/javascripts/bastion/widgets/path-selector.directive.js index 7959879c8f1..cda32c4ee8a 100644 --- a/engines/bastion/app/assets/javascripts/bastion/widgets/path-selector.directive.js +++ b/engines/bastion/app/assets/javascripts/bastion/widgets/path-selector.directive.js @@ -11,64 +11,22 @@ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. */ -/*jshint camelcase:false*/ - /** * @ngdoc directive * @name Bastion.widgets.directive:pathSelector * * @description - * Provides an angular wrapper for the path selector widget. * * @example */ angular.module('Bastion.widgets').directive('pathSelector', - ['$document', '$http', 'Routes', function ($document, $http, Routes) { + [function() { return { restrict: 'AE', scope: { - pathSelector: '=', - readonly: '=', - organization: '&', - onChange: '&' - }, - link: function (scope) { - var pathSelect; - - scope.$watch('pathSelector', function (selected) { - if (selected !== undefined && pathSelect) { - pathSelect.set_selected(selected); - } - }); - - scope.$parent.setupSelector = function () { - $http.get(Routes.organizationEnvironmentsPath(scope.organization()) + '/registerable_paths') - .success(function (paths) { - var options = { - inline: true, - 'select_mode': 'single', - expand: false, - selected: scope.pathSelector, - readonly: scope.readonly - }; - - pathSelect = KT.path_select( - 'environment_path_selector', - 'system_details_path_selector', - paths, - options - ); - - $document.bind(pathSelect.get_select_event(), function () { - var environments = pathSelect.get_selected(); - - scope.pathSelector = Object.keys(environments)[0]; - scope.onChange({ environmentId: scope.pathSelector }); - }); - - scope.$parent.pathSelector = pathSelect; - }); - }; + paths: '=pathSelector', }, + templateUrl: 'widgets/views/path-selector.html', + link: function(scope) {} }; }]); diff --git a/engines/bastion/app/assets/javascripts/bastion/widgets/views/path-selector.html b/engines/bastion/app/assets/javascripts/bastion/widgets/views/path-selector.html new file mode 100644 index 00000000000..c43dc96566b --- /dev/null +++ b/engines/bastion/app/assets/javascripts/bastion/widgets/views/path-selector.html @@ -0,0 +1,10 @@ +
    + +
    diff --git a/engines/bastion/app/assets/stylesheets/bastion/less/animations.less b/engines/bastion/app/assets/stylesheets/bastion/less/animations.less new file mode 100644 index 00000000000..f2a9b6ba7e3 --- /dev/null +++ b/engines/bastion/app/assets/stylesheets/bastion/less/animations.less @@ -0,0 +1,15 @@ +.animate-show { + -webkit-transition: all linear 0.075s; + transition: all linear 0.075s; + opacity: 1; +} + +.animate-show.ng-hide-add, +.animate-show.ng-hide-remove { + display:block !important; +} + +.animate-show.ng-hide { + opacity:0; + padding:0 10px; +} diff --git a/engines/bastion/app/assets/stylesheets/bastion/less/bastion.less b/engines/bastion/app/assets/stylesheets/bastion/less/bastion.less index b95b6ebb658..86b8de88d17 100644 --- a/engines/bastion/app/assets/stylesheets/bastion/less/bastion.less +++ b/engines/bastion/app/assets/stylesheets/bastion/less/bastion.less @@ -12,6 +12,8 @@ @import "helpers"; @import "forms"; @import "gpg-keys"; + @import "animations"; + @import "path-selector"; a { cursor: pointer; @@ -33,3 +35,7 @@ } } } + +.details-header { + margin-bottom: 20px; +} diff --git a/engines/bastion/app/assets/stylesheets/bastion/less/forms.less b/engines/bastion/app/assets/stylesheets/bastion/less/forms.less index 72e709b1b72..785388a9e0d 100644 --- a/engines/bastion/app/assets/stylesheets/bastion/less/forms.less +++ b/engines/bastion/app/assets/stylesheets/bastion/less/forms.less @@ -1,6 +1,4 @@ .required { - color: red; - .control-label { color: inherit; diff --git a/engines/bastion/app/assets/stylesheets/bastion/less/nutupane.less b/engines/bastion/app/assets/stylesheets/bastion/less/nutupane.less index 611b465123c..a09a4530676 100644 --- a/engines/bastion/app/assets/stylesheets/bastion/less/nutupane.less +++ b/engines/bastion/app/assets/stylesheets/bastion/less/nutupane.less @@ -4,7 +4,13 @@ table-layout: fixed; .number-cell { - text-align: center; + text-align: right; + } + + th.row-select, + td.row-select { + width: 25px; + max-width: 25px; } } @@ -117,6 +123,58 @@ td.row-select { .alert-success; } +.details { + display: inline-block; + vertical-align: top; + padding: 0 40px 0 0; + width: 50%; + + .info-label { + font-weight: 600; + display: inline-block; + width: 25%; + vertical-align: top; + } + + .info-value { + width: 69%; + display: inline-block; + padding-left: 4px; + word-wrap: break-word; + } + + .detail:not(:last-child) { + margin-bottom: 5px; + } + + .filter-input { + float: left; + width: 33%; + } +} + +.details-header { + position: relative; + margin-bottom: 10px; + overflow: auto; + + .action-context { + width: 300px; + } +} + +.divider { + height: 30px; + border-bottom: 1px solid #CCC; + display: inline-block; + width: 100%; +} + +.details-full { + width: 100%; + padding: 0; +} + .nutupane-details { height: 100%; left: 20%; @@ -128,6 +186,10 @@ td.row-select { overflow-y: auto; .box-shadow(0 0 5px #dddddd inset); + .details-header { + height: 40px; + } + .nutupane-sub-section { height: 250px; padding-top: 20px; @@ -158,18 +220,6 @@ td.row-select { border-bottom: 3px solid #e3e5e7; } - .divider { - height: 30px; - border-bottom: 1px solid #CCC; - display: inline-block; - width: 100%; - } - - .details-full { - width: 100%; - padding: 0; - } - ul { list-style: none; margin: 0; @@ -188,10 +238,6 @@ td.row-select { clear: both; } - .filter-input { - float: left; - width: 33%; - } } .nutupane-info { @@ -199,19 +245,11 @@ td.row-select { margin-right: 10px; } -.details { - display: inline-block; - vertical-align: top; - padding: 0 40px 0 0; - width: 50%; -} - .detail:not(:last-child) { margin-bottom: 5px; } .details-header { - height: 40px; position: relative; margin-bottom: 10px; @@ -267,27 +305,32 @@ div.alch-dialog.open.info-value { } .details-navigation { - margin-bottom: 10px; + margin-bottom: 30px; border-bottom: 2px solid #e3e5e7; width: 100%; ul { list-style: none; - margin: 0 22px 0 0; + margin: 0 27px 0 0; padding: 0 5px; -webkit-padding-start: 0; - height: 25px; + height: 30px; li { font-size: 15px; display: inline-block; + padding: 0 15px; + + &:first-child { + padding: 0 15px 0 0; + } a { - padding: 0 15px; position: relative; top: 2px; display: block; - height: 25px; + height: 30px; + padding: 0 6px; text-align: center; text-decoration: none; color: black; @@ -296,12 +339,23 @@ div.alch-dialog.open.info-value { color: @listhover_color; border-bottom: 2px solid @listhover_color; } + + &:first-child { + padding: 0 6px 0 3px; + } } } .active { - color: @listhover_color; - border-bottom: 2px solid @listhover_color; + + a { + color: @listhover_color; + border-bottom: 2px solid @listhover_color; + } } } } + +.details-section { + padding: 0 15px; +} diff --git a/engines/bastion/app/assets/stylesheets/bastion/less/path-selector.less b/engines/bastion/app/assets/stylesheets/bastion/less/path-selector.less new file mode 100644 index 00000000000..f832cd830f5 --- /dev/null +++ b/engines/bastion/app/assets/stylesheets/bastion/less/path-selector.less @@ -0,0 +1,95 @@ +@widget-border-color: #D7D7D7; +@widget-background-color: #F9F8F8; +@breadcrumbbg_color: #e9e9e9; +@listhover_color: #00a8d6; +@path-item-color: #e9e9e9; +@path-active-color: #005870; + +.path-list { + list-style: none; + overflow: hidden; + position: relative; + margin-bottom: 20px; + display: inline-block; + border: 1px solid @widget-border-color; + border-radius: 3px; + box-shadow: 0px 0px 15px @widget-border-color; + + .path-list-item { + float: left; + margin-left: 0; + white-space: nowrap; + + &:first-child { + label { + padding-left: 20px; + border-radius: 0 0 3px 3px; + } + } + &:last-child { + label { + border-radius: 3px 3px 0 0; + + &:hover { + color: white; + background: @listhover_color !important; + } + &:after { + border: 0; + border-left: 0px solid @breadcrumbbg_color; + &:hover { border-left-color: @listhover_color !important; } + } + } + } + + label { + color: black; + margin: 0; + padding: 12px 20px 12px 40px; + position: relative; + background: @path-item-color; + + &.active { + background: @path-active-color; + color: white; + text-shadow: 1px 1px #000; + &:after { border-left-color: @path-active-color; }; + &:hover:after { border-left-color: @listhover_color !important; } + } + &:hover { + background: @listhover_color; + color: white; + &:after { border-left-color: @listhover_color; } + } + &:after { + content: " "; + display: block; + width: 0; + height: 0; + border-top: 30px solid transparent; //Go big on the size, and let overflow hide + border-bottom: 30px solid transparent; + border-left: 15px solid @breadcrumbbg_color; + position: absolute; + top: 50%; + margin-top: -30px; + left: 100%; + z-index: 102; + } + &:before { + content: " "; + display: block; + width: 0; + height: 0; + border-top: 30px solid transparent; //Go big on the size, and let overflow hide + border-bottom: 30px solid transparent; + border-left: 15px solid @widget-border-color; + position: absolute; + top: 50%; + margin-top: -30px; + margin-left: 1px; + left: 100%; + z-index: 101; + } + } + } +} diff --git a/engines/bastion/bower.json b/engines/bastion/bower.json index 35df77cbea4..d6213a4e853 100644 --- a/engines/bastion/bower.json +++ b/engines/bastion/bower.json @@ -17,7 +17,8 @@ "underscore": "=1.5.2", "ngUpload": "~0.5.5", "bootstrap": "~3.0.1", - "font-awesome": "~3.2.1" + "font-awesome": "~3.2.1", + "angular-animate": "=1.2.0" }, "devDependencies": { "jquery": "=1.9.1", @@ -59,6 +60,9 @@ "ui-bootstrap-tpls.js" ] }, + "angular-animate": { + "javascripts/bastion": "angular-animate.js" + }, "angular-ui-router": { "javascripts/bastion": "release/angular-ui-router.js" }, diff --git a/engines/bastion/vendor/assets/javascripts/bastion/angular-animate/angular-animate.js b/engines/bastion/vendor/assets/javascripts/bastion/angular-animate/angular-animate.js new file mode 100644 index 00000000000..27d52956ce6 --- /dev/null +++ b/engines/bastion/vendor/assets/javascripts/bastion/angular-animate/angular-animate.js @@ -0,0 +1,1226 @@ +/** + * @license AngularJS v1.2.0 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/* jshint maxlen: false */ + +/** + * @ngdoc overview + * @name ngAnimate + * @description + * + * # ngAnimate + * + * The `ngAnimate` module provides support for JavaScript, CSS3 transition and CSS3 keyframe animation hooks within existing core and custom directives. + * + * {@installModule animate} + * + *
    + * + * # Usage + * + * To see animations in action, all that is required is to define the appropriate CSS classes + * or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are: + * `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation + * by using the `$animate` service. + * + * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives: + * + * | Directive | Supported Animations | + * |---------------------------------------------------------- |----------------------------------------------------| + * | {@link ng.directive:ngRepeat#usage_animations ngRepeat} | enter, leave and move | + * | {@link ngRoute.directive:ngView#usage_animations ngView} | enter and leave | + * | {@link ng.directive:ngInclude#usage_animations ngInclude} | enter and leave | + * | {@link ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave | + * | {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave | + * | {@link ng.directive:ngClass#usage_animations ngClass} | add and remove | + * | {@link ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) | + * + * You can find out more information about animations upon visiting each directive page. + * + * Below is an example of how to apply animations to a directive that supports animation hooks: + * + *
    + * 
    + *
    + * 
    + * 
    + * 
    + * + * Keep in mind that if an animation is running, any child elements cannot be animated until the parent element's + * animation has completed. + * + *

    CSS-defined Animations

    + * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes + * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported + * and can be used to play along with this naming structure. + * + * The following code below demonstrates how to perform animations using **CSS transitions** with Angular: + * + *
    + * 
    + *
    + * 
    + *
    + *
    + *
    + * + * The following code below demonstrates how to perform animations using **CSS animations** with Angular: + * + *
    + * 
    + *
    + * 
    + *
    + *
    + *
    + * + * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing. + * + * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add + * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically + * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be + * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end + * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element + * has no CSS transition/animation classes applied to it. + * + *

    CSS Staggering Animations

    + * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a + * curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be + * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for + * the animation. The style property expected within the stagger class can either be a **transition-delay** or an + * **animation-delay** property (or both if your animation contains both transitions and keyframe animations). + * + *
    + * .my-animation.ng-enter {
    + *   /* standard transition code */
    + *   -webkit-transition: 1s linear all;
    + *   transition: 1s linear all;
    + *   opacity:0;
    + * }
    + * .my-animation.ng-enter-stagger {
    + *   /* this will have a 100ms delay between each successive leave animation */
    + *   -webkit-transition-delay: 0.1s;
    + *   transition-delay: 0.1s;
    + *
    + *   /* in case the stagger doesn't work then these two values
    + *    must be set to 0 to avoid an accidental CSS inheritance */
    + *   -webkit-transition-duration: 0s;
    + *   transition-duration: 0s;
    + * }
    + * .my-animation.ng-enter.ng-enter-active {
    + *   /* standard transition styles */
    + *   opacity:1;
    + * }
    + * 
    + * + * Staggering animations work by default in ngRepeat (so long as the CSS class is defiend). Outside of ngRepeat, to use staggering animations + * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this + * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation + * will also be reset if more than 10ms has passed after the last animation has been fired. + * + * The following code will issue the **ng-leave-stagger** event on the element provided: + * + *
    + * var kids = parent.children();
    + *
    + * $animate.leave(kids[0]); //stagger index=0
    + * $animate.leave(kids[1]); //stagger index=1
    + * $animate.leave(kids[2]); //stagger index=2
    + * $animate.leave(kids[3]); //stagger index=3
    + * $animate.leave(kids[4]); //stagger index=4
    + *
    + * $timeout(function() {
    + *   //stagger has reset itself
    + *   $animate.leave(kids[5]); //stagger index=0
    + *   $animate.leave(kids[6]); //stagger index=1
    + * }, 100, false);
    + * 
    + * + * Stagger animations are currently only supported within CSS-defined animations. + * + *

    JavaScript-defined Animations

    + * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not + * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module. + * + *
    + * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
    + * var ngModule = angular.module('YourApp', []);
    + * ngModule.animation('.my-crazy-animation', function() {
    + *   return {
    + *     enter: function(element, done) {
    + *       //run the animation here and call done when the animation is complete
    + *       return function(cancelled) {
    + *         //this (optional) function will be called when the animation
    + *         //completes or when the animation is cancelled (the cancelled
    + *         //flag will be set to true if cancelled).
    + *       }
    + *     }
    + *     leave: function(element, done) { },
    + *     move: function(element, done) { },
    + *
    + *     //animation that can be triggered before the class is added
    + *     beforeAddClass: function(element, className, done) { },
    + *
    + *     //animation that can be triggered after the class is added
    + *     addClass: function(element, className, done) { },
    + *
    + *     //animation that can be triggered before the class is removed
    + *     beforeRemoveClass: function(element, className, done) { },
    + *
    + *     //animation that can be triggered after the class is removed
    + *     removeClass: function(element, className, done) { }
    + *   }
    + * });
    + * 
    + * + * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run + * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits + * the element's CSS class attribute value and then run the matching animation event function (if found). + * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function + * be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported). + * + * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned. + * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run, + * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation + * or transition code that is defined via a stylesheet). + * + */ + +angular.module('ngAnimate', ['ng']) + + /** + * @ngdoc object + * @name ngAnimate.$animateProvider + * @description + * + * The `$animateProvider` allows developers to register JavaScript animation event handlers directly inside of a module. + * When an animation is triggered, the $animate service will query the $animate service to find any animations that match + * the provided name value. + * + * Requires the {@link ngAnimate `ngAnimate`} module to be installed. + * + * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application. + * + */ + .config(['$provide', '$animateProvider', function($provide, $animateProvider) { + var noop = angular.noop; + var forEach = angular.forEach; + var selectors = $animateProvider.$$selectors; + + var ELEMENT_NODE = 1; + var NG_ANIMATE_STATE = '$$ngAnimateState'; + var NG_ANIMATE_CLASS_NAME = 'ng-animate'; + var rootAnimateState = {running: true}; + + $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document', + function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) { + + $rootElement.data(NG_ANIMATE_STATE, rootAnimateState); + + // disable animations during bootstrap, but once we bootstrapped, enable animations + $rootScope.$$postDigest(function() { + rootAnimateState.running = false; + }); + + function lookup(name) { + if (name) { + var matches = [], + flagMap = {}, + classes = name.substr(1).split('.'); + + //the empty string value is the default animation + //operation which performs CSS transition and keyframe + //animations sniffing. This is always included for each + //element animation procedure if the browser supports + //transitions and/or keyframe animations + if ($sniffer.transitions || $sniffer.animations) { + classes.push(''); + } + + for(var i=0; i < classes.length; i++) { + var klass = classes[i], + selectorFactoryName = selectors[klass]; + if(selectorFactoryName && !flagMap[klass]) { + matches.push($injector.get(selectorFactoryName)); + flagMap[klass] = true; + } + } + return matches; + } + } + + /** + * @ngdoc object + * @name ngAnimate.$animate + * @function + * + * @description + * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations. + * When any of these operations are run, the $animate service + * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object) + * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run. + * + * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives + * will work out of the box without any extra configuration. + * + * Requires the {@link ngAnimate `ngAnimate`} module to be installed. + * + * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application. + * + */ + return { + /** + * @ngdoc function + * @name ngAnimate.$animate#enter + * @methodOf ngAnimate.$animate + * @function + * + * @description + * Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once + * the animation is started, the following CSS classes will be present on the element for the duration of the animation: + * + * Below is a breakdown of each step that occurs during enter animation: + * + * | Animation Step | What the element class attribute looks like | + * |----------------------------------------------------------------------------------------------|---------------------------------------------| + * | 1. $animate.enter(...) is called | class="my-animation" | + * | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" | + * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | + * | 4. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" | + * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" | + * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-enter" | + * | 7. the .ng-enter-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" | + * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" | + * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | + * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" | + * + * @param {jQuery/jqLite element} element the element that will be the focus of the enter animation + * @param {jQuery/jqLite element} parentElement the parent element of the element that will be the focus of the enter animation + * @param {jQuery/jqLite element} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation + * @param {function()=} doneCallback the callback function that will be called once the animation is complete + */ + enter : function(element, parentElement, afterElement, doneCallback) { + this.enabled(false, element); + $delegate.enter(element, parentElement, afterElement); + $rootScope.$$postDigest(function() { + performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback); + }); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#leave + * @methodOf ngAnimate.$animate + * @function + * + * @description + * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once + * the animation is started, the following CSS classes will be added for the duration of the animation: + * + * Below is a breakdown of each step that occurs during enter animation: + * + * | Animation Step | What the element class attribute looks like | + * |----------------------------------------------------------------------------------------------|---------------------------------------------| + * | 1. $animate.leave(...) is called | class="my-animation" | + * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | + * | 3. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" | + * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" | + * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-leave" | + * | 6. the .ng-leave-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" | + * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" | + * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | + * | 9. The element is removed from the DOM | ... | + * | 10. The doneCallback() callback is fired (if provided) | ... | + * + * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation + * @param {function()=} doneCallback the callback function that will be called once the animation is complete + */ + leave : function(element, doneCallback) { + cancelChildAnimations(element); + this.enabled(false, element); + $rootScope.$$postDigest(function() { + performAnimation('leave', 'ng-leave', element, null, null, function() { + $delegate.leave(element); + }, doneCallback); + }); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#move + * @methodOf ngAnimate.$animate + * @function + * + * @description + * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or + * add the element directly after the afterElement element if present. Then the move animation will be run. Once + * the animation is started, the following CSS classes will be added for the duration of the animation: + * + * Below is a breakdown of each step that occurs during move animation: + * + * | Animation Step | What the element class attribute looks like | + * |----------------------------------------------------------------------------------------------|---------------------------------------------| + * | 1. $animate.move(...) is called | class="my-animation" | + * | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" | + * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | + * | 4. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" | + * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" | + * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-move" | + * | 7. the .ng-move-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" | + * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" | + * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | + * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" | + * + * @param {jQuery/jqLite element} element the element that will be the focus of the move animation + * @param {jQuery/jqLite element} parentElement the parentElement element of the element that will be the focus of the move animation + * @param {jQuery/jqLite element} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation + * @param {function()=} doneCallback the callback function that will be called once the animation is complete + */ + move : function(element, parentElement, afterElement, doneCallback) { + cancelChildAnimations(element); + this.enabled(false, element); + $delegate.move(element, parentElement, afterElement); + $rootScope.$$postDigest(function() { + performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback); + }); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#addClass + * @methodOf ngAnimate.$animate + * + * @description + * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class. + * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide + * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions + * or keyframes are defined on the -add or base CSS class). + * + * Below is a breakdown of each step that occurs during addClass animation: + * + * | Animation Step | What the element class attribute looks like | + * |------------------------------------------------------------------------------------------------|---------------------------------------------| + * | 1. $animate.addClass(element, 'super') is called | class="my-animation" | + * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | + * | 3. the .super-add class are added to the element | class="my-animation ng-animate super-add" | + * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-add" | + * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate super-add" | + * | 6. the .super, .super-add-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super super-add super-add-active" | + * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation super-add super-add-active" | + * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" | + * | 9. The super class is kept on the element | class="my-animation super" | + * | 10. The doneCallback() callback is fired (if provided) | class="my-animation super" | + * + * @param {jQuery/jqLite element} element the element that will be animated + * @param {string} className the CSS class that will be added to the element and then animated + * @param {function()=} doneCallback the callback function that will be called once the animation is complete + */ + addClass : function(element, className, doneCallback) { + performAnimation('addClass', className, element, null, null, function() { + $delegate.addClass(element, className); + }, doneCallback); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#removeClass + * @methodOf ngAnimate.$animate + * + * @description + * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value + * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in + * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if + * no CSS transitions or keyframes are defined on the -remove or base CSS classes). + * + * Below is a breakdown of each step that occurs during removeClass animation: + * + * | Animation Step | What the element class attribute looks like | + * |-----------------------------------------------------------------------------------------------|---------------------------------------------| + * | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" | + * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation super ng-animate" | + * | 3. the .super-remove class are added to the element | class="my-animation super ng-animate super-remove"| + * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" | + * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation super ng-animate super-remove" | + * | 6. the .super-remove-active and .ng-animate-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" | + * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" | + * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | + * | 9. The doneCallback() callback is fired (if provided) | class="my-animation" | + * + * + * @param {jQuery/jqLite element} element the element that will be animated + * @param {string} className the CSS class that will be animated and then removed from the element + * @param {function()=} doneCallback the callback function that will be called once the animation is complete + */ + removeClass : function(element, className, doneCallback) { + performAnimation('removeClass', className, element, null, null, function() { + $delegate.removeClass(element, className); + }, doneCallback); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#enabled + * @methodOf ngAnimate.$animate + * @function + * + * @param {boolean=} value If provided then set the animation on or off. + * @return {boolean} Current animation state. + * + * @description + * Globally enables/disables animations. + * + */ + enabled : function(value, element) { + switch(arguments.length) { + case 2: + if(value) { + cleanup(element); + } else { + var data = element.data(NG_ANIMATE_STATE) || {}; + data.disabled = true; + element.data(NG_ANIMATE_STATE, data); + } + break; + + case 1: + rootAnimateState.disabled = !value; + break; + + default: + value = !rootAnimateState.disabled; + break; + } + return !!value; + } + }; + + /* + all animations call this shared animation triggering function internally. + The animationEvent variable refers to the JavaScript animation event that will be triggered + and the className value is the name of the animation that will be applied within the + CSS code. Element, parentElement and afterElement are provided DOM elements for the animation + and the onComplete callback will be fired once the animation is fully complete. + */ + function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) { + var classes = (element.attr('class') || '') + ' ' + className; + var animationLookup = (' ' + classes).replace(/\s+/g,'.'); + if (!parentElement) { + parentElement = afterElement ? afterElement.parent() : element.parent(); + } + + var matches = lookup(animationLookup); + var isClassBased = animationEvent == 'addClass' || animationEvent == 'removeClass'; + var ngAnimateState = element.data(NG_ANIMATE_STATE) || {}; + + //skip the animation if animations are disabled, a parent is already being animated, + //the element is not currently attached to the document body or then completely close + //the animation if any matching animations are not found at all. + //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found. + if (animationsDisabled(element, parentElement) || matches.length === 0) { + domOperation(); + closeAnimation(); + return; + } + + var animations = []; + //only add animations if the currently running animation is not structural + //or if there is no animation running at all + if(!ngAnimateState.running || !(isClassBased && ngAnimateState.structural)) { + forEach(matches, function(animation) { + //add the animation to the queue to if it is allowed to be cancelled + if(!animation.allowCancel || animation.allowCancel(element, animationEvent, className)) { + var beforeFn, afterFn = animation[animationEvent]; + + //Special case for a leave animation since there is no point in performing an + //animation on a element node that has already been removed from the DOM + if(animationEvent == 'leave') { + beforeFn = afterFn; + afterFn = null; //this must be falsy so that the animation is skipped for leave + } else { + beforeFn = animation['before' + animationEvent.charAt(0).toUpperCase() + animationEvent.substr(1)]; + } + animations.push({ + before : beforeFn, + after : afterFn + }); + } + }); + } + + //this would mean that an animation was not allowed so let the existing + //animation do it's thing and close this one early + if(animations.length === 0) { + domOperation(); + fireDoneCallbackAsync(); + return; + } + + if(ngAnimateState.running) { + //if an animation is currently running on the element then lets take the steps + //to cancel that animation and fire any required callbacks + $timeout.cancel(ngAnimateState.closeAnimationTimeout); + cleanup(element); + cancelAnimations(ngAnimateState.animations); + (ngAnimateState.done || noop)(true); + } + + //There is no point in perform a class-based animation if the element already contains + //(on addClass) or doesn't contain (on removeClass) the className being animated. + //The reason why this is being called after the previous animations are cancelled + //is so that the CSS classes present on the element can be properly examined. + if((animationEvent == 'addClass' && element.hasClass(className)) || + (animationEvent == 'removeClass' && !element.hasClass(className))) { + domOperation(); + fireDoneCallbackAsync(); + return; + } + + //the ng-animate class does nothing, but it's here to allow for + //parent animations to find and cancel child animations when needed + element.addClass(NG_ANIMATE_CLASS_NAME); + + element.data(NG_ANIMATE_STATE, { + running:true, + structural:!isClassBased, + animations:animations, + done:onBeforeAnimationsComplete + }); + + //first we run the before animations and when all of those are complete + //then we perform the DOM operation and run the next set of animations + invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete); + + function onBeforeAnimationsComplete(cancelled) { + domOperation(); + if(cancelled === true) { + closeAnimation(); + return; + } + + //set the done function to the final done function + //so that the DOM event won't be executed twice by accident + //if the after animation is cancelled as well + var data = element.data(NG_ANIMATE_STATE); + if(data) { + data.done = closeAnimation; + element.data(NG_ANIMATE_STATE, data); + } + invokeRegisteredAnimationFns(animations, 'after', closeAnimation); + } + + function invokeRegisteredAnimationFns(animations, phase, allAnimationFnsComplete) { + var endFnName = phase + 'End'; + forEach(animations, function(animation, index) { + var animationPhaseCompleted = function() { + progress(index, phase); + }; + + //there are no before functions for enter + move since the DOM + //operations happen before the performAnimation method fires + if(phase == 'before' && (animationEvent == 'enter' || animationEvent == 'move')) { + animationPhaseCompleted(); + return; + } + + if(animation[phase]) { + animation[endFnName] = isClassBased ? + animation[phase](element, className, animationPhaseCompleted) : + animation[phase](element, animationPhaseCompleted); + } else { + animationPhaseCompleted(); + } + }); + + function progress(index, phase) { + var phaseCompletionFlag = phase + 'Complete'; + var currentAnimation = animations[index]; + currentAnimation[phaseCompletionFlag] = true; + (currentAnimation[endFnName] || noop)(); + + for(var i=0;i 0 ? '; ' : '') + style; + node.setAttribute('style', newStyle); + return oldStyle; + } + + function getElementAnimationDetails(element, cacheKey) { + var data = cacheKey ? lookupCache[cacheKey] : null; + if(!data) { + var transitionDuration = 0; + var transitionDelay = 0; + var animationDuration = 0; + var animationDelay = 0; + var transitionDelayStyle; + var animationDelayStyle; + var transitionDurationStyle; + var transitionPropertyStyle; + + //we want all the styles defined before and after + forEach(element, function(element) { + if (element.nodeType == ELEMENT_NODE) { + var elementStyles = $window.getComputedStyle(element) || {}; + + transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY]; + + transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration); + + transitionPropertyStyle = elementStyles[TRANSITION_PROP + PROPERTY_KEY]; + + transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY]; + + transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay); + + animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY]; + + animationDelay = Math.max(parseMaxTime(animationDelayStyle), animationDelay); + + var aDuration = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]); + + if(aDuration > 0) { + aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1; + } + + animationDuration = Math.max(aDuration, animationDuration); + } + }); + data = { + total : 0, + transitionPropertyStyle: transitionPropertyStyle, + transitionDurationStyle: transitionDurationStyle, + transitionDelayStyle: transitionDelayStyle, + transitionDelay: transitionDelay, + transitionDuration: transitionDuration, + animationDelayStyle: animationDelayStyle, + animationDelay: animationDelay, + animationDuration: animationDuration + }; + if(cacheKey) { + lookupCache[cacheKey] = data; + } + } + return data; + } + + function parseMaxTime(str) { + var maxValue = 0; + var values = angular.isString(str) ? + str.split(/\s*,\s*/) : + []; + forEach(values, function(value) { + maxValue = Math.max(parseFloat(value) || 0, maxValue); + }); + return maxValue; + } + + function getCacheKey(element) { + var parentElement = element.parent(); + var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY); + if(!parentID) { + parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter); + parentID = parentCounter; + } + return parentID + '-' + element[0].className; + } + + function animateSetup(element, className) { + var cacheKey = getCacheKey(element); + var eventCacheKey = cacheKey + ' ' + className; + var stagger = {}; + var ii = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0; + + if(ii > 0) { + var staggerClassName = className + '-stagger'; + var staggerCacheKey = cacheKey + ' ' + staggerClassName; + var applyClasses = !lookupCache[staggerCacheKey]; + + applyClasses && element.addClass(staggerClassName); + + stagger = getElementAnimationDetails(element, staggerCacheKey); + + applyClasses && element.removeClass(staggerClassName); + } + + element.addClass(className); + + var timings = getElementAnimationDetails(element, eventCacheKey); + + /* there is no point in performing a reflow if the animation + timeout is empty (this would cause a flicker bug normally + in the page. There is also no point in performing an animation + that only has a delay and no duration */ + var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration); + if(maxDuration === 0) { + element.removeClass(className); + return false; + } + + var node = element[0]; + //temporarily disable the transition so that the enter styles + //don't animate twice (this is here to avoid a bug in Chrome/FF). + var activeClassName = ''; + if(timings.transitionDuration > 0) { + element.addClass(NG_ANIMATE_FALLBACK_CLASS_NAME); + activeClassName += NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME + ' '; + node.style[TRANSITION_PROP + PROPERTY_KEY] = 'none'; + } + + forEach(className.split(' '), function(klass, i) { + activeClassName += (i > 0 ? ' ' : '') + klass + '-active'; + }); + + element.data(NG_ANIMATE_CSS_DATA_KEY, { + className : className, + activeClassName : activeClassName, + maxDuration : maxDuration, + classes : className + ' ' + activeClassName, + timings : timings, + stagger : stagger, + ii : ii + }); + + return true; + } + + function animateRun(element, className, activeAnimationComplete) { + var data = element.data(NG_ANIMATE_CSS_DATA_KEY); + if(!element.hasClass(className) || !data) { + activeAnimationComplete(); + return; + } + + var node = element[0]; + var timings = data.timings; + var stagger = data.stagger; + var maxDuration = data.maxDuration; + var activeClassName = data.activeClassName; + var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000; + var startTime = Date.now(); + var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT; + var formerStyle; + var ii = data.ii; + + var applyFallbackStyle, style = ''; + if(timings.transitionDuration > 0) { + node.style[TRANSITION_PROP + PROPERTY_KEY] = ''; + + var propertyStyle = timings.transitionPropertyStyle; + if(propertyStyle.indexOf('all') == -1) { + applyFallbackStyle = true; + var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'clip'; + style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ', ' + fallbackProperty + '; '; + style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ', ' + timings.transitionDuration + 's; '; + } + } + + if(ii > 0) { + if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) { + var delayStyle = timings.transitionDelayStyle; + if(applyFallbackStyle) { + delayStyle += ', ' + timings.transitionDelay + 's'; + } + + style += CSS_PREFIX + 'transition-delay: ' + + prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; '; + } + + if(stagger.animationDelay > 0 && stagger.animationDuration === 0) { + style += CSS_PREFIX + 'animation-delay: ' + + prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; '; + } + } + + if(style.length > 0) { + formerStyle = applyStyle(node, style); + } + + element.on(css3AnimationEvents, onAnimationProgress); + element.addClass(activeClassName); + + // This will automatically be called by $animate so + // there is no need to attach this internally to the + // timeout done method. + return function onEnd(cancelled) { + element.off(css3AnimationEvents, onAnimationProgress); + element.removeClass(activeClassName); + animateClose(element, className); + if(formerStyle != null) { + formerStyle.length > 0 ? + node.setAttribute('style', formerStyle) : + node.removeAttribute('style'); + } + }; + + function onAnimationProgress(event) { + event.stopPropagation(); + var ev = event.originalEvent || event; + var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now(); + /* $manualTimeStamp is a mocked timeStamp value which is set + * within browserTrigger(). This is only here so that tests can + * mock animations properly. Real events fallback to event.timeStamp, + * or, if they don't, then a timeStamp is automatically created for them. + * We're checking to see if the timeStamp surpasses the expected delay, + * but we're using elapsedTime instead of the timeStamp on the 2nd + * pre-condition since animations sometimes close off early */ + if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && ev.elapsedTime >= maxDuration) { + activeAnimationComplete(); + } + } + } + + function prepareStaggerDelay(delayStyle, staggerDelay, index) { + var style = ''; + forEach(delayStyle.split(','), function(val, i) { + style += (i > 0 ? ',' : '') + + (index * staggerDelay + parseInt(val, 10)) + 's'; + }); + return style; + } + + function animateBefore(element, className) { + if(animateSetup(element, className)) { + return function(cancelled) { + cancelled && animateClose(element, className); + }; + } + } + + function animateAfter(element, className, afterAnimationComplete) { + if(element.data(NG_ANIMATE_CSS_DATA_KEY)) { + return animateRun(element, className, afterAnimationComplete); + } else { + animateClose(element, className); + afterAnimationComplete(); + } + } + + function animate(element, className, animationComplete) { + //If the animateSetup function doesn't bother returning a + //cancellation function then it means that there is no animation + //to perform at all + var preReflowCancellation = animateBefore(element, className); + if(!preReflowCancellation) { + animationComplete(); + return; + } + + //There are two cancellation functions: one is before the first + //reflow animation and the second is during the active state + //animation. The first function will take care of removing the + //data from the element which will not make the 2nd animation + //happen in the first place + var cancel = preReflowCancellation; + afterReflow(function() { + //once the reflow is complete then we point cancel to + //the new cancellation function which will remove all of the + //animation properties from the active animation + cancel = animateAfter(element, className, animationComplete); + }); + + return function(cancelled) { + (cancel || noop)(cancelled); + }; + } + + function animateClose(element, className) { + element.removeClass(className); + element.removeClass(NG_ANIMATE_FALLBACK_CLASS_NAME); + element.removeData(NG_ANIMATE_CSS_DATA_KEY); + } + + return { + allowCancel : function(element, animationEvent, className) { + //always cancel the current animation if it is a + //structural animation + var oldClasses = (element.data(NG_ANIMATE_CSS_DATA_KEY) || {}).classes; + if(!oldClasses || ['enter','leave','move'].indexOf(animationEvent) >= 0) { + return true; + } + + var parentElement = element.parent(); + var clone = angular.element(element[0].cloneNode()); + + //make the element super hidden and override any CSS style values + clone.attr('style','position:absolute; top:-9999px; left:-9999px'); + clone.removeAttr('id'); + clone.html(''); + + forEach(oldClasses.split(' '), function(klass) { + clone.removeClass(klass); + }); + + var suffix = animationEvent == 'addClass' ? '-add' : '-remove'; + clone.addClass(suffixClasses(className, suffix)); + parentElement.append(clone); + + var timings = getElementAnimationDetails(clone); + clone.remove(); + + return Math.max(timings.transitionDuration, timings.animationDuration) > 0; + }, + + enter : function(element, animationCompleted) { + return animate(element, 'ng-enter', animationCompleted); + }, + + leave : function(element, animationCompleted) { + return animate(element, 'ng-leave', animationCompleted); + }, + + move : function(element, animationCompleted) { + return animate(element, 'ng-move', animationCompleted); + }, + + beforeAddClass : function(element, className, animationCompleted) { + var cancellationMethod = animateBefore(element, suffixClasses(className, '-add')); + if(cancellationMethod) { + afterReflow(animationCompleted); + return cancellationMethod; + } + animationCompleted(); + }, + + addClass : function(element, className, animationCompleted) { + return animateAfter(element, suffixClasses(className, '-add'), animationCompleted); + }, + + beforeRemoveClass : function(element, className, animationCompleted) { + var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove')); + if(cancellationMethod) { + afterReflow(animationCompleted); + return cancellationMethod; + } + animationCompleted(); + }, + + removeClass : function(element, className, animationCompleted) { + return animateAfter(element, suffixClasses(className, '-remove'), animationCompleted); + } + }; + + function suffixClasses(classes, suffix) { + var className = ''; + classes = angular.isArray(classes) ? classes : classes.split(/\s+/); + forEach(classes, function(klass, i) { + if(klass && klass.length > 0) { + className += (i > 0 ? ' ' : '') + klass + suffix; + } + }); + return className; + } + }]); + }]); + + +})(window, window.angular); diff --git a/lib/katello/plugin.rb b/lib/katello/plugin.rb index 4e18e916670..149304a01cf 100644 --- a/lib/katello/plugin.rb +++ b/lib/katello/plugin.rb @@ -65,6 +65,13 @@ :engine => Katello::Engine divider :top_menu, :parent => :content + menu :top_menu, + :content_views, + :caption => N_('Content Views (new!)'), + :url_hash => {:controller => 'katello/content_views', + :action => 'all'}, + :engine => Katello::Engine + menu :top_menu, :content_search, :caption => N_('Content Search'),