diff --git a/static/js/directives/ng-transcope.js b/static/js/directives/ng-transcope.js
new file mode 100644
index 000000000..795256e8b
--- /dev/null
+++ b/static/js/directives/ng-transcope.js
@@ -0,0 +1,25 @@
+/**
+ * Directive to transclude a template under an ng-repeat. From: http://stackoverflow.com/a/24512435
+ */
+angular.module('quay').directive('ngTranscope', function() {
+ return {
+ link: function( $scope, $element, $attrs, controller, $transclude ) {
+ if ( !$transclude ) {
+ throw minErr( 'ngTranscope' )( 'orphan',
+ 'Illegal use of ngTransclude directive in the template! ' +
+ 'No parent directive that requires a transclusion found. ' +
+ 'Element: {0}',
+ startingTag( $element ));
+ }
+ var innerScope = $scope.$new();
+
+ $transclude( innerScope, function( clone ) {
+ $element.empty();
+ $element.append( clone );
+ $element.on( '$destroy', function() {
+ innerScope.$destroy();
+ });
+ });
+ }
+ };
+});
diff --git a/static/js/directives/repo-view/repo-panel-changes.js b/static/js/directives/repo-view/repo-panel-changes.js
index 7b5debe37..585d03c51 100644
--- a/static/js/directives/repo-view/repo-panel-changes.js
+++ b/static/js/directives/repo-view/repo-panel-changes.js
@@ -96,20 +96,24 @@ angular.module('quay').directive('repoPanelChanges', function () {
'isEnabled': '=isEnabled'
},
controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) {
+ $scope.tagNames = [];
var update = function() {
- if (!$scope.repository || !$scope.selectedTags) { return; }
+ if (!$scope.repository || !$scope.isEnabled) { return; }
+ $scope.tagNames = Object.keys($scope.repository.tags);
$scope.currentImage = null;
$scope.currentTag = null;
- if (!$scope.tracker) {
+ if ($scope.tracker) {
+ refreshTree();
+ } else {
updateImages();
}
};
var updateImages = function() {
- if (!$scope.repository || !$scope.images) { return; }
+ if (!$scope.repository || !$scope.images || !$scope.isEnabled) { return; }
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
@@ -120,16 +124,17 @@ angular.module('quay').directive('repoPanelChanges', function () {
$scope.$watch('selectedTags', update)
$scope.$watch('repository', update);
+ $scope.$watch('isEnabled', update);
+
$scope.$watch('images', updateImages);
- $scope.$watch('isEnabled', function(isEnabled) {
- if (isEnabled) {
- refreshTree();
- }
- });
+ $scope.updateState = function() {
+ update();
+ };
var refreshTree = function() {
- if (!$scope.repository || !$scope.images) { return; }
+ if (!$scope.repository || !$scope.images || !$scope.isEnabled) { return; }
+ if ($scope.selectedTags.length < 1) { return; }
$('#image-history-container').empty();
@@ -149,6 +154,7 @@ angular.module('quay').directive('repoPanelChanges', function () {
// Give enough time for the UI to be drawn before we resize the tree.
$timeout(function() {
$scope.tree.notifyResized();
+ $scope.setTag($scope.selectedTags[0]);
}, 100);
// Listen for changes to the selected tag and image in the tree.
diff --git a/static/js/directives/ui/multiselect-dropdown.js b/static/js/directives/ui/multiselect-dropdown.js
new file mode 100644
index 000000000..d87629f8f
--- /dev/null
+++ b/static/js/directives/ui/multiselect-dropdown.js
@@ -0,0 +1,35 @@
+/**
+ * An element which displays a dropdown for selecting multiple elements.
+ */
+angular.module('quay').directive('multiselectDropdown', function ($compile) {
+ var directiveDefinitionObject = {
+ priority: 0,
+ templateUrl: '/static/directives/multiselect-dropdown.html',
+ transclude: true,
+ replace: false,
+ restrict: 'C',
+ scope: {
+ 'items': '=items',
+ 'selectedItems': '=selectedItems',
+ 'itemName': '@itemName',
+ 'itemChecked': '&itemChecked'
+ },
+ controller: function($scope, $element) {
+ $scope.isChecked = function(checked, item) {
+ return checked.indexOf(item) >= 0;
+ };
+
+ $scope.toggleItem = function(item) {
+ var isChecked = $scope.isChecked($scope.selectedItems, item);
+ if (!isChecked) {
+ $scope.selectedItems.push(item);
+ } else {
+ var index = $scope.selectedItems.indexOf(item);
+ $scope.selectedItems.splice(index, 1);
+ }
+ $scope.itemChecked({'item': item, 'checked': !isChecked});
+ };
+ }
+ };
+ return directiveDefinitionObject;
+});
\ No newline at end of file
diff --git a/static/lib/dropdowns-enhancement.js b/static/lib/dropdowns-enhancement.js
new file mode 100644
index 000000000..3b885dc7d
--- /dev/null
+++ b/static/lib/dropdowns-enhancement.js
@@ -0,0 +1,267 @@
+/* ========================================================================
+ * Bootstrap Dropdowns Enhancement: dropdowns-enhancement.js v3.1.1 (Beta 1)
+ * http://behigh.github.io/bootstrap_dropdowns_enhancement/
+ * ========================================================================
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+(function($) {
+ "use strict";
+
+ var toggle = '[data-toggle="dropdown"]',
+ disabled = '.disabled, :disabled',
+ backdrop = '.dropdown-backdrop',
+ menuClass = 'dropdown-menu',
+ subMenuClass = 'dropdown-submenu',
+ namespace = '.bs.dropdown.data-api',
+ eventNamespace = '.bs.dropdown',
+ openClass = 'open',
+ touchSupport = 'ontouchstart' in document.documentElement,
+ opened;
+
+
+ function Dropdown(element) {
+ $(element).on('click' + eventNamespace, this.toggle)
+ }
+
+ var proto = Dropdown.prototype;
+
+ proto.toggle = function(event) {
+ var $element = $(this);
+
+ if ($element.is(disabled)) return;
+
+ var $parent = getParent($element);
+ var isActive = $parent.hasClass(openClass);
+ var isSubMenu = $parent.hasClass(subMenuClass);
+ var menuTree = isSubMenu ? getSubMenuParents($parent) : null;
+
+ closeOpened(event, menuTree);
+
+ if (!isActive) {
+ if (!menuTree)
+ menuTree = [$parent];
+
+ if (touchSupport && !$parent.closest('.navbar-nav').length && !menuTree[0].find(backdrop).length) {
+ // if mobile we use a backdrop because click events don't delegate
+ $('
').appendTo(menuTree[0]).on('click', closeOpened)
+ }
+
+ for (var i = 0, s = menuTree.length; i < s; i++) {
+ if (!menuTree[i].hasClass(openClass)) {
+ menuTree[i].addClass(openClass);
+ positioning(menuTree[i].children('.' + menuClass), menuTree[i]);
+ }
+ }
+ opened = menuTree[0];
+ }
+
+ return false;
+ };
+
+ proto.keydown = function (e) {
+ if (!/(38|40|27)/.test(e.keyCode)) return;
+
+ var $this = $(this);
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ if ($this.is('.disabled, :disabled')) return;
+
+ var $parent = getParent($this);
+ var isActive = $parent.hasClass('open');
+
+ if (!isActive || (isActive && e.keyCode == 27)) {
+ if (e.which == 27) $parent.find(toggle).trigger('focus');
+ return $this.trigger('click')
+ }
+
+ var desc = ' li:not(.divider):visible a';
+ var desc1 = 'li:not(.divider):visible > input:not(disabled) ~ label';
+ var $items = $parent.find(desc1 + ', ' + '[role="menu"]' + desc + ', [role="listbox"]' + desc);
+
+ if (!$items.length) return;
+
+ var index = $items.index($items.filter(':focus'));
+
+ if (e.keyCode == 38 && index > 0) index--; // up
+ if (e.keyCode == 40 && index < $items.length - 1) index++; // down
+ if (!~index) index = 0;
+
+ $items.eq(index).trigger('focus')
+ };
+
+ proto.change = function (e) {
+
+ var
+ $parent,
+ $menu,
+ $toggle,
+ selector,
+ text = '',
+ $items;
+
+ $menu = $(this).closest('.' + menuClass);
+
+ $toggle = $menu.parent().find('[data-label-placement]');
+
+ if (!$toggle || !$toggle.length) {
+ $toggle = $menu.parent().find(toggle);
+ }
+
+ if (!$toggle || !$toggle.length || $toggle.data('placeholder') === false)
+ return; // do nothing, no control
+
+ ($toggle.data('placeholder') == undefined && $toggle.data('placeholder', $.trim($toggle.text())));
+ text = $.data($toggle[0], 'placeholder');
+
+ $items = $menu.find('li > input:checked');
+
+ if ($items.length) {
+ text = [];
+ $items.each(function () {
+ var str = $(this).parent().find('label').eq(0),
+ label = str.find('.data-label');
+
+ if (label.length) {
+ var p = $('
');
+ p.append(label.clone());
+ str = p.html();
+ }
+ else {
+ str = str.html();
+ }
+
+
+ str && text.push($.trim(str));
+ });
+
+ text = text.length < 4 ? text.join(', ') : text.length + ' selected';
+ }
+
+ var caret = $toggle.find('.caret');
+
+ $toggle.html(text || ' ');
+ if (caret.length)
+ $toggle.append(' ') && caret.appendTo($toggle);
+
+ };
+
+ function positioning($menu, $control) {
+ if ($menu.hasClass('pull-center')) {
+ $menu.css('margin-right', $menu.outerWidth() / -2);
+ }
+
+ if ($menu.hasClass('pull-middle')) {
+ $menu.css('margin-top', ($menu.outerHeight() / -2) - ($control.outerHeight() / 2));
+ }
+ }
+
+ function closeOpened(event, menuTree) {
+ if (opened) {
+
+ if (!menuTree) {
+ menuTree = [opened];
+ }
+
+ var parent;
+
+ if (opened[0] !== menuTree[0][0]) {
+ parent = opened;
+ } else {
+ parent = menuTree[menuTree.length - 1];
+ if (parent.parent().hasClass(menuClass)) {
+ parent = parent.parent();
+ }
+ }
+
+ parent.find('.' + openClass).removeClass(openClass);
+
+ if (parent.hasClass(openClass))
+ parent.removeClass(openClass);
+
+ if (parent === opened) {
+ opened = null;
+ $(backdrop).remove();
+ }
+ }
+ }
+
+ function getSubMenuParents($submenu) {
+ var result = [$submenu];
+ var $parent;
+ while (!$parent || $parent.hasClass(subMenuClass)) {
+ $parent = ($parent || $submenu).parent();
+ if ($parent.hasClass(menuClass)) {
+ $parent = $parent.parent();
+ }
+ if ($parent.children(toggle)) {
+ result.unshift($parent);
+ }
+ }
+ return result;
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target');
+
+ if (!selector) {
+ selector = $this.attr('href');
+ selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); //strip for ie7
+ }
+
+ var $parent = selector && $(selector);
+
+ return $parent && $parent.length ? $parent : $this.parent()
+ }
+
+ // DROPDOWN PLUGIN DEFINITION
+ // ==========================
+
+ var old = $.fn.dropdown;
+
+ $.fn.dropdown = function (option) {
+ return this.each(function () {
+ var $this = $(this);
+ var data = $this.data('bs.dropdown');
+
+ if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)));
+ if (typeof option == 'string') data[option].call($this);
+ })
+ };
+
+ $.fn.dropdown.Constructor = Dropdown;
+
+ $.fn.dropdown.clearMenus = function(e) {
+ $(backdrop).remove();
+ $('.' + openClass + ' ' + toggle).each(function () {
+ var $parent = getParent($(this));
+ var relatedTarget = { relatedTarget: this };
+ if (!$parent.hasClass('open')) return;
+ $parent.trigger(e = $.Event('hide' + eventNamespace, relatedTarget));
+ if (e.isDefaultPrevented()) return;
+ $parent.removeClass('open').trigger('hidden' + eventNamespace, relatedTarget);
+ });
+ return this;
+ };
+
+
+ // DROPDOWN NO CONFLICT
+ // ====================
+
+ $.fn.dropdown.noConflict = function () {
+ $.fn.dropdown = old;
+ return this
+ };
+
+
+ $(document).off(namespace)
+ .on('click' + namespace, closeOpened)
+ .on('click' + namespace, toggle, proto.toggle)
+ .on('click' + namespace, '.dropdown-menu > li > input[type="checkbox"] ~ label, .dropdown-menu > li > input[type="checkbox"], .dropdown-menu.noclose > li', function (e) {
+ e.stopPropagation()
+ })
+ .on('change' + namespace, '.dropdown-menu > li > input[type="checkbox"], .dropdown-menu > li > input[type="radio"]', proto.change)
+ .on('keydown' + namespace, toggle + ', [role="menu"], [role="listbox"]', proto.keydown)
+}(jQuery));
\ No newline at end of file