From 7337adf498b83ba09e7d100b411f509321a9d356 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Thu, 9 Jan 2014 18:54:59 -0500 Subject: [PATCH] Switch to a new single-selected-context layout and system in the view repository screen. Now selecting tags or images changes the context --- static/css/quay.css | 41 +++++++++++---- static/js/controllers.js | 94 ++++++++++++++++++++++++++++------ static/js/graphing.js | 45 ++++++++++------ static/partials/view-repo.html | 81 +++++++++++++++++++---------- 4 files changed, 193 insertions(+), 68 deletions(-) diff --git a/static/css/quay.css b/static/css/quay.css index ff86a9c68..9a9a3c914 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -1180,24 +1180,47 @@ p.editable:hover i { transition: opacity 200ms ease-in-out; } -.tag-heading:hover .tag-controls { - opacity: 1; -} - .right-title { display: inline-block; float: right; - padding: 4px; font-size: 12px; color: #aaa; } -.tag-dropdown .tag-count { +.right-tag-controls { + display: inline-block; + float: right; + padding: 4px; + padding-left: 10px; + border-left: 1px solid #ccc; + vertical-align: middle; + margin-top: -2px; + margin-right: -10px; + color: #666; +} + +.right-tag-controls .tag-count { display: inline-block; margin-left: 4px; margin-right: 6px; - padding-right: 10px; - border-right: 1px solid #ccc; +} + +.tag-image-sizes .tag-image-size { + height: 22px; +} + +.tag-image-sizes .tag-image-size .size-bar { + display: inline-block; + background: steelblue; + height: 12px; + margin-right: 90px; +} + +#current-tag .control-bar { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid #eee; + text-align: right; } .tag-dropdown a { @@ -1281,7 +1304,7 @@ p.editable:hover i { .repo .description { margin-top: 10px; - margin-bottom: 40px; + margin-bottom: 20px; } .repo .empty-message { diff --git a/static/js/controllers.js b/static/js/controllers.js index 6e55dfe54..90f00b2bd 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -151,7 +151,13 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo // Watch for changes to the tag parameter. $scope.$on('$routeUpdate', function(){ - $scope.setTag($location.search().tag, false); + if ($location.search().tag) { + $scope.setTag($location.search().tag, false); + } else if ($location.search().image) { + $scope.setImage($location.search().image, false); + } else { + $scope.setTag($location.search().tag, false); + } }); // Start scope methods ////////////////////////////////////////// @@ -186,11 +192,28 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo addedDisplayed - removedDisplayed - changedDisplayed; }; - $scope.setImage = function(image) { + $scope.setImage = function(imageId, opt_updateURL) { + var image = null; + for (var i = 0; i < $scope.images.length; ++i) { + var currentImage = $scope.images[i]; + if (currentImage.id == imageId || currentImage.id.substr(0, 12) == imageId) { + image = currentImage; + break; + } + } + + if (!image) { return; } + + $scope.currentTag = null; $scope.currentImage = image; $scope.loadImageChanges(image); if ($scope.tree) { - $scope.tree.setImage($scope.currentImage.id); + $scope.tree.setImage(image.id); + } + + if (opt_updateURL) { + $location.search('tag', null); + $location.search('image', imageId.substr(0, 12)); } }; @@ -205,20 +228,13 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo } var getIdsForTag = function(currentTag) { - var ancestors = currentTag.image.ancestors.split('/'); - var dbid = currentTag.image.dbid; var ids = {}; - - ids[dbid] = true; - for (var i = 0; i < ancestors.length; ++i) { - if (ancestors[i]) { - ids[ancestors[i]] = true; - } - } + forAllTagImages(currentTag, function(image) { + ids[image.dbid] = true; + }); return ids; }; - // Remove any IDs that match other tags. var toDelete = getIdsForTag(tag); for (var currentTagName in $scope.repo.tags) { @@ -252,7 +268,6 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo return images; }; - $scope.askDeleteTag = function(tagName) { if (!$scope.repo.can_admin) { return; } @@ -285,6 +300,27 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo }); }; + $scope.getImagesForTagBySize = function(tag) { + var images = []; + forAllTagImages(tag, function(image) { + images.push(image); + }); + + images.sort(function(a, b) { + return b.size - a.size; + }); + + return images; + }; + + $scope.getTotalSize = function(tag) { + var size = 0; + forAllTagImages(tag, function(image) { + size += image.size; + }); + return size; + }; + $scope.setTag = function(tagName, opt_updateURL) { var repo = $scope.repo; var proposedTag = repo.tags[tagName]; @@ -306,6 +342,7 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo } if (opt_updateURL) { + $location.search('image', null); $location.search('tag', $scope.currentTag.name); } } @@ -387,6 +424,20 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo } }; + var forAllTagImages = function(tag, callback) { + if (!tag || !$scope.imageByDBID) { return; } + + callback(tag.image); + + var ancestors = tag.image.ancestors.split('/'); + for (var i = 0; i < ancestors.length; ++i) { + var image = $scope.imageByDBID[ancestors[i]]; + if (image) { + callback(image); + } + } + }; + var fetchRepository = function() { var params = {'repository': namespace + '/' + name}; $rootScope.title = 'Loading Repository...'; @@ -467,6 +518,13 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo $scope.images = resp.images; $scope.specificImages = []; + // Build various images for quick lookup of images. + $scope.imageByDBID = {}; + for (var i = 0; i < $scope.images.length; ++i) { + var currentImage = $scope.images[i]; + $scope.imageByDBID[currentImage.dbid] = currentImage; + } + // Dispose of any existing tree. if ($scope.tree) { $scope.tree.dispose(); @@ -489,7 +547,7 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo }); $($scope.tree).bind('imageChanged', function(e) { - $scope.$apply(function() { $scope.setImage(e.image); }); + $scope.$apply(function() { $scope.setImage(e.image.id, true); }); }); $($scope.tree).bind('showTagMenu', function(e) { @@ -500,6 +558,10 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo $scope.$apply(function() { $scope.hideTagMenu(); }); }); + if ($routeParams.image) { + $scope.setImage($routeParams.image); + } + return resp.images; }); }; @@ -936,7 +998,7 @@ function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService) { }, 10); }; - var fetchImage = function() { + var fetchImages = function() { var params = { 'repository': namespace + '/' + name, 'image_id': imageid diff --git a/static/js/graphing.js b/static/js/graphing.js index 7b478dc8a..fa5816fc6 100644 --- a/static/js/graphing.js +++ b/static/js/graphing.js @@ -40,6 +40,11 @@ function ImageHistoryTree(namespace, name, images, formatComment, formatTime) { */ this.currentImage_ = null; + /** + * The currently highlighted node (if any). + */ + this.currentNode_ = null; + /** * Counter for creating unique IDs. */ @@ -231,6 +236,23 @@ ImageHistoryTree.prototype.setImage = function(imageId) { }; +/** + * Updates the highlighted path in the tree. + */ +ImageHistoryTree.prototype.setHighlightedPath_ = function(image) { + if (this.currentNode_) { + this.markPath_(this.currentNode_, false); + } + + var imageByDBID = this.imageByDBID_; + var currentNode = imageByDBID[image.dbid]; + if (currentNode) { + this.markPath_(currentNode, true); + this.currentNode_ = currentNode; + } +}; + + /** * Returns the ancestors of the given image. */ @@ -468,26 +490,15 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) { // Save the current tag. var previousTagName = this.currentTag_; this.currentTag_ = tagName; + this.currentImage_ = null; - // Update the state of each existing node to no longer be highlighted. - var previousImage = this.findImage_(function(image) { - return image.tags.indexOf(previousTagName || '(no tag specified)') >= 0; - }); - - if (previousImage) { - var currentNode = imageByDBID[previousImage.dbid]; - this.markPath_(currentNode, false); - } - - // Find the new current image (if any). - this.currentImage_ = this.findImage_(function(image) { + // Update the path. + var tagImage = this.findImage_(function(image) { return image.tags.indexOf(tagName || '(no tag specified)') >= 0; }); - // Update the state of the new node path. - if (this.currentImage_) { - var currentNode = imageByDBID[this.currentImage_.dbid]; - this.markPath_(currentNode, true); + if (tagImage) { + this.setHighlightedPath_(tagImage); } // Ensure that the children are in the correct order. @@ -531,7 +542,9 @@ ImageHistoryTree.prototype.setImage_ = function(imageId) { return; } + this.setHighlightedPath_(newImage); this.currentImage_ = newImage; + this.currentTag_ = null; this.update_(this.root_); }; diff --git a/static/partials/view-repo.html b/static/partials/view-repo.html index b28e9e266..7ce2e6593 100644 --- a/static/partials/view-repo.html +++ b/static/partials/view-repo.html @@ -53,7 +53,7 @@ content-changed="updateForDescription" field-title="'repository description'"> -
+
This repository is empty
@@ -72,30 +72,13 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}
-
+
-
-
- - - Tags - - Delete Tag - -
- +
@@ -107,21 +90,65 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}
- -
-
+ +
+
+
Last Modified
+
+
Total Compressed Size
+
{{ getTotalSize(currentTag) | bytes }} +
+
+ + + +
+ +
+
+ + +