diff --git a/static/css/directives/repo-view/repo-panel-tags.css b/static/css/directives/repo-view/repo-panel-tags.css
index e50f43a34..69fdd91a2 100644
--- a/static/css/directives/repo-view/repo-panel-tags.css
+++ b/static/css/directives/repo-view/repo-panel-tags.css
@@ -11,33 +11,29 @@
position: relative;
}
-.repo-panel-tags-element .image-track-dot:after {
- content: "\f10c";
- font-family: FontAwesome;
-
+.repo-panel-tags-element .image-track-dot {
display: inline-block;
position: absolute;
top: 15px;
- left: 0px;
- width: 17px;
-
- font-size: 11px;
- text-align: center;
+ left: 2px;
+ width: 12px;
+ height: 12px;
background: white;
z-index: 300;
- height: 13px;
+
+ border: 2px solid black;
+ border-radius: 50%;
}
.repo-panel-tags-element .image-track-line {
position: absolute;
top: 0px;
- bottom: -11px;
+ bottom: -1px;
left: 7px;
width: 0px;
display: inline-block;
- height: 100%;
border-left: 2px solid black;
display: none;
diff --git a/static/directives/repo-view/repo-panel-changes.html b/static/directives/repo-view/repo-panel-changes.html
index 470fc5649..1922b49b9 100644
--- a/static/directives/repo-view/repo-panel-changes.html
+++ b/static/directives/repo-view/repo-panel-changes.html
@@ -60,5 +60,4 @@
\ No newline at end of file
+ action-handler="tagActionHandler" tag-changed="handleTagChanged(data)">
\ No newline at end of file
diff --git a/static/directives/repo-view/repo-panel-tags.html b/static/directives/repo-view/repo-panel-tags.html
index 11dc45847..43d1f725e 100644
--- a/static/directives/repo-view/repo-panel-tags.html
+++ b/static/directives/repo-view/repo-panel-tags.html
@@ -21,8 +21,9 @@
- Visualize
+
+ Visualize
+
Delete
@@ -60,8 +61,9 @@
{{ tag.size | bytes }}
{{ tag.image_id.substr(0, 12) }}
-
-
+
+
diff --git a/static/js/directives/repo-view/repo-panel-changes.js b/static/js/directives/repo-view/repo-panel-changes.js
index c85b19530..a2eee551c 100644
--- a/static/js/directives/repo-view/repo-panel-changes.js
+++ b/static/js/directives/repo-view/repo-panel-changes.js
@@ -87,33 +87,32 @@ angular.module('quay').directive('repoPanelChanges', function () {
transclude: false,
restrict: 'C',
scope: {
- 'repository': '=repository'
+ 'repository': '=repository',
+ 'selectedTags': '=selectedTags',
+ 'isEnabled': '=isEnabled'
},
- controller: function($scope, $element, $location, $timeout, ApiService, UtilService, ImageMetadataService) {
+ controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) {
var update = function() {
- if (!$scope.repository) { return; }
-
- var tagString = $location.search()['tags'] || '';
- if (!tagString) {
- $scope.selectedTags = [];
- return;
- }
+ if (!$scope.repository || !$scope.selectedTags) { return; }
$scope.currentImage = null;
- $scope.currentImage = null;
- $scope.selectedTags = tagString.split(',');
+ $scope.currentTag = null;
- if (!$scope.imageResource) {
+ if (!$scope.imagesResource) {
loadImages();
- } else {
- refreshTree();
}
};
- $scope.$on('$routeUpdate', update);
+ $scope.$watch('selectedTags', update)
$scope.$watch('repository', update);
+ $scope.$watch('isEnabled', function(isEnabled) {
+ if (isEnabled) {
+ refreshTree();
+ }
+ });
+
var refreshTree = function() {
if (!$scope.repository || !$scope.images) { return; }
@@ -157,10 +156,6 @@ angular.module('quay').directive('repoPanelChanges', function () {
$scope.images = resp.images;
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
- $scope.selectedTags = $.grep($scope.selectedTags, function(tag) {
- return !!$scope.tracker.lookupTag(tag);
- });
-
if ($scope.selectedTags && $scope.selectedTags.length) {
refreshTree();
}
@@ -193,26 +188,16 @@ angular.module('quay').directive('repoPanelChanges', function () {
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
data.removed.map(function(tag) {
- $scope.selectedTags = $.grep($scope.selectedTags, function(cTag) {
- return cTag != tag;
- });
-
- if ($scope.selectedTags.length) {
- $location.search('tags', $scope.selectedTags.join(','));
- } else {
- $location.search('tags', null);
- }
-
$scope.currentImage = null;
$scope.currentTag = null;
});
data.added.map(function(tag) {
$scope.selectedTags.push(tag);
- $location.search('tags', $scope.selectedTags.join(','));
-
$scope.currentTag = tag;
});
+
+ refreshTree();
};
}
};
diff --git a/static/js/directives/repo-view/repo-panel-tags.js b/static/js/directives/repo-view/repo-panel-tags.js
index 9934eed18..5acdb9fed 100644
--- a/static/js/directives/repo-view/repo-panel-tags.js
+++ b/static/js/directives/repo-view/repo-panel-tags.js
@@ -10,9 +10,9 @@ angular.module('quay').directive('repoPanelTags', function () {
restrict: 'C',
scope: {
'repository': '=repository',
- 'repositoryUpdated': '&repositoryUpdated'
+ 'selectedTags': '=selectedTags'
},
- controller: function($scope, $element, $filter, ApiService, UIService) {
+ controller: function($scope, $element, $filter, $location, ApiService, UIService) {
var orderBy = $filter('orderBy');
$scope.checkedTags = UIService.createCheckStateController([]);
@@ -35,7 +35,7 @@ angular.module('quay').directive('repoPanelTags', function () {
};
var setTagState = function() {
- if (!$scope.repository) { return; }
+ if (!$scope.repository || !$scope.selectedTags) { return; }
var tags = [];
var allTags = [];
@@ -60,21 +60,29 @@ angular.module('quay').directive('repoPanelTags', function () {
}
// Sort the tags by the predicate and the reverse, and map the information.
+ var imageIDs = [];
var ordered = orderBy(tags, $scope.options.predicate, $scope.options.reverse);
+ var checked = [];
+
for (var i = 0; i < ordered.length; ++i) {
var tagInfo = ordered[i];
if (!imageMap[tagInfo.image_id]) {
imageMap[tagInfo.image_id] = [];
+ imageIDs.push(tagInfo.image_id)
}
imageMap[tagInfo.image_id].push(tagInfo);
+
+ if ($.inArray(tagInfo.name, $scope.selectedTags) >= 0) {
+ checked.push(tagInfo);
+ }
};
// Calculate the image tracks.
var colors = d3.scale.category10();
var index = 0;
- for (var image_id in imageMap) {
+ imageIDs.sort().map(function(image_id) {
if (imageMap[image_id].length >= 2){
imageTracks.push({
'image_id': image_id,
@@ -84,19 +92,34 @@ angular.module('quay').directive('repoPanelTags', function () {
});
++index;
}
- }
+ });
$scope.imageMap = imageMap;
$scope.imageTracks = imageTracks;
+
$scope.tags = ordered;
- $scope.checkedTags = UIService.createCheckStateController(ordered);
$scope.allTags = allTags;
- $scope.iterationState = {};
+
+ $scope.checkedTags = UIService.createCheckStateController(ordered, checked);
+ $scope.checkedTags.listen(function(checked) {
+ $scope.selectedTags = checked.map(function(tag_info) {
+ return tag_info.name;
+ });
+ });
}
$scope.$watch('options.predicate', setTagState);
$scope.$watch('options.reverse', setTagState);
$scope.$watch('options.tagFilter', setTagState);
+
+ $scope.$watch('selectedTags', function(selectedTags) {
+ if (!selectedTags || !$scope.repository || !$scope.imageMap) { return; }
+
+ $scope.checkedTags.checked = selectedTags.map(function(tag) {
+ return $scope.repository.tags[tag];
+ });
+ }, true);
+
$scope.$watch('repository', function(repository) {
if (!repository) { return; }
@@ -175,10 +198,8 @@ angular.module('quay').directive('repoPanelTags', function () {
return tag.image_id == image_id;
};
- $scope.getCheckedTagsString = function(checked) {
- return checked.map(function(tag_info) {
- return tag_info.name;
- }).join(',');
+ $scope.setTab = function(tab) {
+ $location.search('tab', tab);
};
}
};
diff --git a/static/js/directives/ui/tag-operations-dialog.js b/static/js/directives/ui/tag-operations-dialog.js
index 5deb538ef..f9645be4d 100644
--- a/static/js/directives/ui/tag-operations-dialog.js
+++ b/static/js/directives/ui/tag-operations-dialog.js
@@ -30,9 +30,14 @@ angular.module('quay').directive('tagOperationsDialog', function () {
ApiService.listRepositoryImages(null, params).then(function(resp) {
$scope.images = resp.images;
- $scope.tagChanged({
- 'data': { 'added': added, 'removed': removed }
- });
+
+ // Note: We need the timeout here so that Angular can $digest the images change
+ // on the parent scope before the tagChanged callback occurs.
+ $timeout(function() {
+ $scope.tagChanged({
+ 'data': { 'added': added, 'removed': removed }
+ });
+ }, 1);
})
});
};
@@ -72,7 +77,6 @@ angular.module('quay').directive('tagOperationsDialog', function () {
ApiService.changeTagImage(data, params).then(function(resp) {
$element.find('#createOrMoveTagModal').modal('hide');
$scope.addingTag = false;
-
markChanged([tag], []);
}, errorHandler);
};
diff --git a/static/js/pages/repo-view.js b/static/js/pages/repo-view.js
index c1b188739..34c575f85 100644
--- a/static/js/pages/repo-view.js
+++ b/static/js/pages/repo-view.js
@@ -13,25 +13,56 @@
}, ['old-layout']);
}]);
- function RepoViewCtrl($scope, $routeParams, ApiService, UserService, AngularPollChannel) {
+ function RepoViewCtrl($scope, $routeParams, $location, ApiService, UserService, AngularPollChannel) {
$scope.namespace = $routeParams.namespace;
$scope.name = $routeParams.name;
$scope.logsShown = 0;
+ $scope.viewScope = {
+ 'selectedTags': [],
+ 'repository': null,
+ 'builds': null,
+ 'changesVisible': false
+ };
var buildPollChannel = null;
// Make sure we track the current user.
UserService.updateUserIn($scope);
+ // Watch the selected tags and update the URL accordingly.
+ $scope.$watch('viewScope.selectedTags', function(selectedTags) {
+ if (!selectedTags || !$scope.viewScope.repository) { return; }
+
+ var tags = filterTags(selectedTags);
+ if (!tags.length) {
+ $location.search('tag', null);
+ return;
+ }
+
+ $location.search('tag', tags.join(','));
+ }, true);
+
+ // Watch the repository to filter any tags removed.
+ $scope.$watch('viewScope.repository', function(repository) {
+ if (!repository) { return; }
+ $scope.viewScope.selectedTags = filterTags($scope.viewScope.selectedTags);
+ });
+
+ var filterTags = function(tags) {
+ return (tags || []).filter(function(tag) {
+ return !!$scope.viewScope.repository.tags[tag];
+ });
+ };
+
var loadRepository = function() {
var params = {
'repository': $scope.namespace + '/' + $scope.name
};
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
- $scope.repository = repo;
- $scope.setTag($routeParams.tag);
+ $scope.viewScope.repository = repo;
+ $scope.setTags($routeParams.tag);
// Track builds.
buildPollChannel = AngularPollChannel.create($scope, loadRepositoryBuilds, 5000 /* 5s */);
@@ -49,7 +80,7 @@
};
$scope.repositoryBuildsResource = ApiService.getRepoBuildsAsResource(params, /* background */true).get(function(resp) {
- $scope.builds = resp.builds;
+ $scope.viewScope.builds = resp.builds;
callback(true);
}, errorHandler);
};
@@ -57,14 +88,22 @@
// Load the repository.
loadRepository();
+ $scope.setTags = function(tagNames) {
+ if (!tagNames) {
+ $scope.viewScope.selectedTags = [];
+ return;
+ }
- $scope.setTag = function(tagName) {
- window.console.log('set tag')
+ $scope.viewScope.selectedTags = $.unique(tagNames.split(','));
};
$scope.showLogs = function() {
$scope.logsShown++;
};
+
+ $scope.handleChangesState = function(value) {
+ $scope.viewScope.changesVisible = value;
+ };
}
function OldRepoViewCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiService, $routeParams, $rootScope, $location, $timeout, Config, UtilService) {
diff --git a/static/js/services/ui-service.js b/static/js/services/ui-service.js
index 1f084c6d6..2e857e8fa 100644
--- a/static/js/services/ui-service.js
+++ b/static/js/services/ui-service.js
@@ -2,9 +2,14 @@
* Service which provides helper methods for performing some simple UI operations.
*/
angular.module('quay').factory('UIService', [function() {
- var CheckStateController = function(items) {
+ var CheckStateController = function(items, opt_checked) {
this.items = items;
- this.checked = [];
+ this.checked = opt_checked || [];
+ this.listeners_ = [];
+ };
+
+ CheckStateController.prototype.listen = function(callback) {
+ this.listeners_.push(callback);
};
CheckStateController.prototype.isChecked = function(item) {
@@ -25,22 +30,32 @@ angular.module('quay').factory('UIService', [function() {
} else {
this.checked = this.items.slice();
}
+ this.callListeners_();
};
CheckStateController.prototype.checkByFilter = function(filter) {
this.checked = $.grep(this.items, filter);
+ this.callListeners_();
};
CheckStateController.prototype.checkItem = function(item) {
this.checked.push(item);
+ this.callListeners_();
};
CheckStateController.prototype.uncheckItem = function(item) {
this.checked = $.grep(this.checked, function(cItem) {
return cItem != item;
});
+ this.callListeners_();
};
+ CheckStateController.prototype.callListeners_ = function() {
+ var checked = this.checked;
+ this.listeners_.map(function(listener) {
+ listener(checked);
+ });
+ };
var uiService = {};
@@ -73,8 +88,8 @@ angular.module('quay').factory('UIService', [function() {
}
};
- uiService.createCheckStateController = function(items) {
- return new CheckStateController(items);
+ uiService.createCheckStateController = function(items, opt_checked) {
+ return new CheckStateController(items, opt_checked);
};
return uiService;
diff --git a/static/partials/repo-view.html b/static/partials/repo-view.html
index c7fdb5b54..5d00dc98c 100644
--- a/static/partials/repo-view.html
+++ b/static/partials/repo-view.html
@@ -5,9 +5,9 @@
-
+
{{ namespace }} / {{ name }}
-
+
@@ -25,18 +25,19 @@
-
+
+ ng-if="viewScope.repository.can_admin">
+ ng-if="viewScope.repository.can_admin">
@@ -44,12 +45,16 @@