diff --git a/static/css/quay.css b/static/css/quay.css index 96f3a8a2c..fa7c7f23f 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -332,29 +332,27 @@ p.editable:hover i { .tag-dropdown { display: inline-block; - padding: 6px; - border: 1px solid #ddd; - margin-right: 15px; - margin-bottom: 5px; font-size: 1.15em; } -.tag-dropdown i.icon-bookmark { - font-size: 125%; - position: relative; - margin-left: 2px; - margin-right: 4px; +.right-title { + display: inline-block; + float: right; + padding: 4px; + font-size: 12px; + color: #aaa; } -.tag-dropdown i.icon-bookmark .tag-count { - color: #aaa; - position: absolute; - top: 0px; - left: 0px; - font-size: 55%; +.tag-dropdown .tag-count { display: inline-block; - width: 14px; - text-align: center; + margin-left: 4px; + margin-right: 6px; + padding-right: 10px; + border-right: 1px solid #ccc; +} + +.tag-dropdown a { + color: black; } .modal-body textarea { @@ -706,6 +704,11 @@ p.editable:hover i { stroke-width: 2.5px; } +#image-history-container .node text.current { + font-size: 14px; + font-weight: bold; +} + #image-history-container .node text { font-size: 15px; cursor: pointer; diff --git a/static/js/controllers.js b/static/js/controllers.js index 4f25a52e2..7a6da3110 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -196,23 +196,7 @@ function LandingCtrl($scope, $timeout, Restangular, UserService) { } function RepoCtrl($scope, Restangular, $routeParams, $rootScope) { - var tabs = ['current-image', 'image-history']; - $rootScope.title = 'Loading...'; - - $scope.showTab = function(tabName) { - for (var i = 0; i < tabs.length; ++i) { - $('#' + tabs[i]).hide(); - $('#' + tabs[i] + '-tab').removeClass('active'); - } - - $('#' + tabName).show(); - $('#' + tabName + '-tab').addClass('active'); - - if (tabName == 'image-history') { - $scope.listImages(); - } - }; $scope.editDescription = function() { if (!$scope.repo.can_write) { return; } @@ -242,7 +226,7 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope) { return getMarkedDown(getFirstTextLine(commentString)); }; - $scope.getTimeSince= function(createdTime) { + $scope.getTimeSince = function(createdTime) { return moment($scope.parseDate(createdTime)).fromNow(); }; @@ -257,12 +241,35 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope) { var imageFetch = Restangular.one('repository/' + namespace + '/' + name + '/image'); imageFetch.get().then(function(resp) { $scope.imageHistory = resp.images; - var tree = new ImageHistoryTree(namespace, name, resp.images, $scope.currentTag, - $scope.getCommentFirstLine, $scope.getTimeSince); - tree.draw('image-history-container'); + $scope.tree = new ImageHistoryTree(namespace, name, resp.images, $scope.currentTag, + $scope.getCommentFirstLine, $scope.getTimeSince); + + $scope.tree.draw('image-history-container'); + $($scope.tree).bind('tagChanged', function(e) { + $scope.$apply(function() { $scope.setTag(e.tag); }); + }); + $($scope.tree).bind('imageChanged', function(e) { + $scope.$apply(function() { $scope.setImage(e.image); }); + }); }); }; + $scope.setImage = function(image) { + $scope.currentImage = image; + if ($scope.tree) { + $scope.tree.setImage($scope.currentImage.id); + } + }; + + $scope.setTag = function(tagName) { + var repo = $scope.repo; + $scope.currentTag = repo.tags[tagName] || repo.tags['latest']; + $scope.currentImage = $scope.currentTag.image; + if ($scope.tree) { + $scope.tree.setTag($scope.currentTag.name); + } + }; + $scope.getTagCount = function(repo) { if (!repo) { return 0; } var count = 0; @@ -278,11 +285,13 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope) { $scope.loading = true; + // Fetch the repo. var repositoryFetch = Restangular.one('repository/' + namespace + '/' + name); repositoryFetch.get().then(function(repo) { $rootScope.title = namespace + '/' + name; $scope.repo = repo; $scope.currentTag = repo.tags[tag] || repo.tags['latest']; + $scope.setImage($scope.currentTag.image); var clip = new ZeroClipboard($('#copyClipboard'), { 'moviePath': 'static/lib/ZeroClipboard.swf' }); clip.on('complete', function() { @@ -302,6 +311,9 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope) { $scope.loading = false; $rootScope.title = 'Unknown Repository'; }); + + // Fetch the image history. + $scope.listImages(); } function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) { diff --git a/static/js/graphing.js b/static/js/graphing.js index d79f125d1..84f119bf3 100644 --- a/static/js/graphing.js +++ b/static/js/graphing.js @@ -57,14 +57,14 @@ ImageHistoryTree.prototype.draw = function(container) { var width = document.getElementById(container).clientWidth; var height = Math.max(width * 0.625, this.maxHeight_ * (DEPTH_HEIGHT + 10)); - var margin = { top: 40, right: 60, bottom: 20, left: 60 }; + var margin = { top: 40, right: 20, bottom: 20, left: 20 }; var m = [margin.top, margin.right, margin.bottom, margin.left]; var w = width - m[1] - m[3]; var h = height - m[0] - m[2]; // Create the tree and all its components. var tree = d3.layout.tree() - .size([h, w]); + .size([w, h]); var diagonal = d3.svg.diagonal() .projection(function(d) { return [d.x, d.y]; }); @@ -110,10 +110,26 @@ ImageHistoryTree.prototype.draw = function(container) { }; +/** + * Sets the current tag displayed in the tree. + */ +ImageHistoryTree.prototype.setTag = function(tagName) { + this.setTag_(tagName); +}; + + +/** + * Sets the current image displayed in the tree. + */ +ImageHistoryTree.prototype.setImage = function(imageId) { + this.setImage_(imageId); +}; + + /** * Returns the ancestors of the given image. */ -ImageHistoryTree.prototype.getAncestors = function(image) { +ImageHistoryTree.prototype.getAncestors_ = function(image) { var ancestorsString = image.ancestors; // Remove the starting and ending /s. @@ -125,11 +141,37 @@ ImageHistoryTree.prototype.getAncestors = function(image) { }; +/** + * Sets the current tag displayed in the tree and raises the event that the tag + * was changed. + */ +ImageHistoryTree.prototype.changeTag_ = function(tagName) { + $(this).trigger({ + 'type': 'tagChanged', + 'tag': tagName + }); + this.setTag_(tagName); +}; + + +/** + * Sets the current image displayed in the tree and raises the event that the image + * was changed. + */ +ImageHistoryTree.prototype.changeImage_ = function(imageId) { + $(this).trigger({ + 'type': 'imageChanged', + 'image': this.findImage_(function(image) { return image.id == imageId; }) + }); + this.setImage_(imageId); +}; + + /** * Marks the image node with whether it is the current image and/or highlighted. */ ImageHistoryTree.prototype.markWithState_ = function(image, imageNode) { - var currentAncestors = this.getAncestors(this.currentImage_); + var currentAncestors = this.getAncestors_(this.currentImage_); var isCurrent = image.id == this.currentImage_.id; var isHighlighted = currentAncestors.indexOf(image.dbid.toString()) >= 0; imageNode.highlighted = isHighlighted || isCurrent; @@ -169,7 +211,7 @@ ImageHistoryTree.prototype.buildRoot_ = function() { for (var i = 0; i < this.images_.length; ++i) { var image = this.images_[i]; var imageNode = imageByDBID[image.dbid]; - var ancestors = this.getAncestors(image); + var ancestors = this.getAncestors_(image); var immediateParent = ancestors[ancestors.length - 1] * 1; var parent = imageByDBID[immediateParent]; if (parent) { @@ -192,6 +234,22 @@ ImageHistoryTree.prototype.buildRoot_ = function() { }; +/** + * Finds the image where the checker function returns true and returns it or null + * if none. + */ +ImageHistoryTree.prototype.findImage_ = function(checker) { + for (var i = 0; i < this.images_.length; ++i) { + var image = this.images_[i]; + if (checker(image)) { + return image; + } + } + + return null; +}; + + /** * Sets the current tag displayed in the tree. */ @@ -199,16 +257,13 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) { this.currentTag_ = tagName; // Find the new current image. - for (var i = 0; i < this.images_.length; ++i) { - var image = this.images_[i]; - if (image.tags.indexOf(tagName) >= 0) { - this.currentImage_ = image; - } - } + this.currentImage_ = this.findImage_(function(image) { + return image.tags.indexOf(tagName) >= 0; + }); // Update the state of each node. var imageByDBID = this.imageByDBID_; - var currentAncestors = this.getAncestors(this.currentImage_); + var currentAncestors = this.getAncestors_(this.currentImage_); for (var i = 0; i < this.images_.length; ++i) { var image = this.images_[i]; var imageNode = this.imageByDBID_[image.dbid]; @@ -219,7 +274,7 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) { for (var i = 0; i < this.images_.length; ++i) { var image = this.images_[i]; var imageNode = this.imageByDBID_[image.dbid]; - var ancestors = this.getAncestors(image); + var ancestors = this.getAncestors_(image); var immediateParent = ancestors[ancestors.length - 1] * 1; var parent = imageByDBID[immediateParent]; if (parent && imageNode.highlighted) { @@ -236,7 +291,24 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) { } } - // Finally, update the tree. + this.update_(this.root_); +}; + + +/** + * Sets the current image highlighted in the tree. + */ +ImageHistoryTree.prototype.setImage_ = function(imageId) { + // Find the new current image. + var newImage = this.findImage_(function(image) { + return image.id == imageId; + }); + + if (newImage == this.currentImage_) { + return; + } + + this.currentImage_ = newImage; this.update_(this.root_); }; @@ -300,7 +372,7 @@ ImageHistoryTree.prototype.update_ = function(source) { .attr("dy", ".35em") .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; }) .text(function(d) { return d.name; }) - .on("click", function(d) { that.toggle_(d); that.update_(d); }) + .on("click", function(d) { that.changeImage_(d.image.id); }) .on('mouseover', tip.show) .on('mouseout', tip.hide); @@ -345,6 +417,12 @@ ImageHistoryTree.prototype.update_ = function(source) { return d._children ? "lightsteelblue" : "#fff"; }); + // Update the repo text. + nodeUpdate.select("text") + .attr("class", function(d) { + return d.image.id == that.currentImage_.id ? 'current' : ''; + }); + // Ensure that the node is visible. nodeUpdate.select("g") .style("fill-opacity", 1); @@ -369,7 +447,7 @@ ImageHistoryTree.prototype.update_ = function(source) { .on("click", function(d, e) { var tag = this.getAttribute('data-tag'); if (tag) { - that.setTag_(tag); + that.changeTag_(tag); } }); diff --git a/static/partials/view-repo.html b/static/partials/view-repo.html index 6894dbbe6..a9f4c4af0 100644 --- a/static/partials/view-repo.html +++ b/static/partials/view-repo.html @@ -55,48 +55,71 @@
--