Start on new tag view

This commit is contained in:
Joseph Schorr 2015-03-09 22:03:39 -07:00
parent 581a284744
commit afc8e95e19
103 changed files with 148505 additions and 458 deletions

View file

@ -162,6 +162,51 @@ angular.module("core-ui", [])
return directiveDefinitionObject;
})
.directive('corConfirmDialog', function() {
var directiveDefinitionObject = {
priority: 1,
templateUrl: '/static/directives/cor-confirm-dialog.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'dialogTitle': '@dialogTitle',
'dialogActionTitle': '@dialogActionTitle',
'dialogContext': '=dialogContext',
'dialogAction': '&dialogAction'
},
controller: function($rootScope, $scope, $element) {
$scope.working = false;
$scope.$watch('dialogContext', function(dc) {
if (!dc) { return; }
$scope.show();
});
$scope.performAction = function() {
$scope.working = true;
$scope.dialogAction({
'info': $scope.dialogContext,
'callback': function(result) {
$scope.hide();
}
});
};
$scope.show = function() {
$scope.working = false;
$element.find('.modal').modal({});
};
$scope.hide = function() {
$element.find('.modal').modal('hide');
};
}
};
return directiveDefinitionObject;
})
.directive('corTabPanel', function() {
var directiveDefinitionObject = {
priority: 1,
@ -581,4 +626,84 @@ angular.module("core-ui", [])
}
};
return directiveDefinitionObject;
})
.directive('corCheckableMenu', function() {
var directiveDefinitionObject = {
priority: 1,
templateUrl: '/static/directives/cor-checkable-menu.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'controller': '=controller'
},
controller: function($rootScope, $scope, $element) {
$scope.getClass = function(items, checked) {
if (checked.length == 0) {
return 'none';
}
if (checked.length == items.length) {
return 'all';
}
return 'some';
};
$scope.toggleItems = function($event) {
$event.stopPropagation();
$scope.controller.toggleItems();
};
this.checkByFilter = function(filter) {
$scope.controller.checkByFilter(filter);
};
}
};
return directiveDefinitionObject;
})
.directive('corCheckableMenuItem', function() {
var directiveDefinitionObject = {
priority: 1,
templateUrl: '/static/directives/cor-checkable-menu-item.html',
replace: true,
transclude: true,
restrict: 'C',
require: '^corCheckableMenu',
scope: {
'itemFilter': '=itemFilter'
},
link: function($scope, $element, $attr, parent) {
$scope.parent = parent;
},
controller: function($rootScope, $scope, $element) {
$scope.selected = function() {
$scope.parent.checkByFilter(this.itemFilter);
};
}
};
return directiveDefinitionObject;
})
.directive('corCheckableItem', function() {
var directiveDefinitionObject = {
priority: 1,
templateUrl: '/static/directives/cor-checkable-item.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'item': '=item',
'controller': '=controller'
},
controller: function($rootScope, $scope, $element) {
$scope.toggleItem = function() {
$scope.controller.toggleItem($scope.item);
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,23 @@
/**
* An element which displays the information panel for a repository view.
*/
angular.module('quay').directive('repoPanelInfo', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-view/repo-panel-info.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'repository': '=repository'
},
controller: function($scope, $element, ApiService) {
$scope.updateDescription = function(content) {
$scope.repository.description = content;
$scope.repository.put();
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,222 @@
/**
* An element which displays the tags panel for a repository view.
*/
angular.module('quay').directive('repoPanelTags', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-view/repo-panel-tags.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'repository': '=repository',
'repositoryUpdated': '&repositoryUpdated'
},
controller: function($scope, $element, $filter, ApiService, UIService) {
var orderBy = $filter('orderBy');
$scope.checkedTags = UIService.createCheckStateController([]);
$scope.options = {
'predicate': 'last_modified_datetime',
'reverse': false
};
$scope.iterationState = {};
var loadImages = function() {
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
$scope.imagesResource = ApiService.listRepositoryImagesAsResource(params).get(function(resp) {
$scope.images = resp.images;
});
};
var setTagState = function() {
if (!$scope.repository) { return; }
var tags = [];
var allTags = [];
var imageMap = {};
var imageTracks = [];
// Build a list of tags and filtered tags.
for (var tag in $scope.repository.tags) {
if (!$scope.repository.tags.hasOwnProperty(tag)) { continue; }
var tagInfo = $.extend($scope.repository.tags[tag], {
'name': tag,
'last_modified_datetime': new Date($scope.repository.tags[tag].last_modified)
});
allTags.push(tagInfo);
if (!$scope.options.tagFilter || tag.indexOf($scope.options.tagFilter) >= 0 ||
tagInfo.image_id.indexOf($scope.options.tagFilter) >= 0) {
tags.push(tagInfo);
}
}
// Sort the tags by the predicate and the reverse, and map the information.
var ordered = orderBy(tags, $scope.options.predicate, $scope.options.reverse);
for (var i = 0; i < ordered.length; ++i) {
var tagInfo = ordered[i];
if (!imageMap[tagInfo.image_id]) {
imageMap[tagInfo.image_id] = [];
}
imageMap[tagInfo.image_id].push(tagInfo);
};
// Calculate the image tracks.
var colors = d3.scale.category10();
var index = 0;
for (var image_id in imageMap) {
if (imageMap[image_id].length >= 2){
imageTracks.push({
'image_id': image_id,
'color': colors(index),
'count': imageMap[image_id].length,
'tags': imageMap[image_id]
});
++index;
}
}
$scope.imageMap = imageMap;
$scope.imageTracks = imageTracks;
$scope.tags = ordered;
$scope.checkedTags = UIService.createCheckStateController(ordered);
$scope.allTags = allTags;
$scope.iterationState = {};
}
$scope.$watch('options.predicate', setTagState);
$scope.$watch('options.reverse', setTagState);
$scope.$watch('options.tagFilter', setTagState);
$scope.$watch('repository', function(repository) {
if (!repository) { return; }
// Load the repository's images.
loadImages();
// Process each of the tags.
setTagState();
});
$scope.trackLineClass = function(index, track_info) {
var startIndex = $.inArray(track_info.tags[0], $scope.tags);
var endIndex = $.inArray(track_info.tags[track_info.tags.length - 1], $scope.tags);
if (index == startIndex) {
return 'start';
}
if (index == endIndex) {
return 'end';
}
if (index > startIndex && index < endIndex) {
return 'middle';
}
if (index < startIndex) {
return 'before';
}
if (index > endIndex) {
return 'after';
}
};
$scope.tablePredicateClass = function(name, predicate, reverse) {
if (name != predicate) {
return '';
}
return 'current ' + (reverse ? 'reversed' : '');
};
$scope.askDeleteTag = function(tag) {
$scope.deleteTagInfo = {
'tag': tag
};
};
$scope.askDeleteMultipleTags = function(tags) {
$scope.deleteMultipleTagsInfo = {
'tags': tags
};
};
$scope.deleteMultipleTags = function(tags, callback) {
var count = tags.length;
var perform = function(index) {
if (index >= count) {
callback(true);
return;
}
var tag_info = tags[index];
$scope.deleteTag(tag_info.name, function(result) {
if (!result) {
callback(false);
return;
}
perform(index + 1);
});
};
perform(0);
};
$scope.deleteTag = function(tag, callback) {
if (!$scope.repository.can_admin) { return; }
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'tag': tag
};
var errorHandler = ApiService.errorDisplay('Cannot delete tag', callback);
ApiService.deleteFullTag(null, params).then(function() {
callback(true);
$scope.tags = $.grep($scope.tags, function(tagInfo) {
return tagInfo.name != tag;
});
$scope.checkedTags = UIService.createCheckStateController($scope.tags);
$scope.repositoryUpdated({});
}, errorHandler);
};
$scope.orderBy = function(predicate) {
if (predicate == $scope.options.predicate) {
$scope.options.reverse = !$scope.options.reverse;
return;
}
$scope.options.reverse = false;
$scope.options.predicate = predicate;
};
$scope.commitTagFilter = function(tag) {
var r = new RegExp('^[0-9a-f]{7}$');
return tag.name.match(r);
};
$scope.allTagFilter = function(tag) {
return true;
};
$scope.noTagFilter = function(tag) {
return false;
};
}
};
return directiveDefinitionObject;
});

View file

@ -13,7 +13,7 @@ angular.module('quay').directive('repoListGrid', function () {
starred: '=starred',
user: "=user",
namespace: '=namespace',
toggleStar: '&toggleStar'
starToggled: '&starToggled'
},
controller: function($scope, $element, UserService) {
$scope.isOrganization = function(namespace) {

View file

@ -0,0 +1,53 @@
/**
* An element that displays the star status of a repository and allows it to be toggled.
*/
angular.module('quay').directive('repoStar', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-star.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
repository: '=repository',
starToggled: '&starToggled'
},
controller: function($scope, $element, UserService, ApiService) {
// Star a repository or unstar a repository.
$scope.toggleStar = function() {
if ($scope.repository.is_starred) {
unstarRepo();
} else {
starRepo();
}
};
// Star a repository and update the UI.
var starRepo = function() {
var data = {
'namespace': $scope.repository.namespace,
'repository': $scope.repository.name
};
ApiService.createStar(data).then(function(result) {
$scope.repository.is_starred = true;
$scope.starToggled({'repository': $scope.repository});
}, ApiService.errorDisplay('Could not star repository'));
};
// Unstar a repository and update the UI.
var unstarRepo = function(repo) {
var data = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
ApiService.deleteStar(null, data).then(function(result) {
$scope.repository.is_starred = false;
$scope.starToggled({'repository': $scope.repository});
}, ApiService.errorDisplay('Could not unstar repository'));
};
}
};
return directiveDefinitionObject;
});

View file

@ -27,7 +27,7 @@ angular.module('quay').directive('resourceView', function () {
return 'loading';
}
if (current.error) {
if (current.hasError) {
return 'error';
}
}

View file

@ -52,38 +52,14 @@
return !!UserService.getOrganization(namespace);
};
// Star a repository or unstar a repository.
$scope.toggleStar = function(repo) {
$scope.starToggled = function(repo) {
if (repo.is_starred) {
unstarRepo(repo);
} else {
starRepo(repo);
}
}
// Star a repository and update the UI.
var starRepo = function(repo) {
var data = {
'namespace': repo.namespace,
'repository': repo.name
};
ApiService.createStar(data).then(function(result) {
repo.is_starred = true;
$scope.starred_repositories.value.push(repo);
}, ApiService.errorDisplay('Could not star repository'));
};
// Unstar a repository and update the UI.
var unstarRepo = function(repo) {
var data = {
'repository': repo.namespace + '/' + repo.name
};
ApiService.deleteStar(null, data).then(function(result) {
repo.is_starred = false;
} else {
$scope.starred_repositories.value = $scope.starred_repositories.value.filter(function(repo) {
return repo.is_starred;
});
}, ApiService.errorDisplay('Could not unstar repository'));
}
};
// Finds a duplicate repo if it exists. If it doesn't, inserts the repo.

View file

@ -3,10 +3,49 @@
* Repository view page.
*/
angular.module('quayPages').config(['pages', function(pages) {
pages.create('repo-view', 'repo-view.html', RepoCtrl);
pages.create('repo-view', 'repo-view.html', RepoViewCtrl, {
'newLayout': true,
'title': '{{ namespace }}/{{ name }}',
'description': 'Repository {{ namespace }}/{{ name }}'
}, ['layout'])
pages.create('repo-view', 'old-repo-view.html', OldRepoViewCtrl, {
}, ['old-layout']);
}]);
function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiService, $routeParams, $rootScope, $location, $timeout, Config, UtilService) {
function RepoViewCtrl($scope, $routeParams, ApiService, UserService) {
$scope.namespace = $routeParams.namespace;
$scope.name = $routeParams.name;
$scope.logsShown = 0;
// Make sure we track the current user.
UserService.updateUserIn($scope);
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);
});
};
// Load the repository.
loadRepository();
$scope.setTag = function(tagName) {
window.console.log('set tag')
};
$scope.showLogs = function() {
$scope.logsShown++;
};
}
function OldRepoViewCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiService, $routeParams, $rootScope, $location, $timeout, Config, UtilService) {
$scope.Config = Config;
var namespace = $routeParams.namespace;

View file

@ -2,6 +2,46 @@
* Service which provides helper methods for performing some simple UI operations.
*/
angular.module('quay').factory('UIService', [function() {
var CheckStateController = function(items) {
this.items = items;
this.checked = [];
};
CheckStateController.prototype.isChecked = function(item) {
return $.inArray(item, this.checked) >= 0;
};
CheckStateController.prototype.toggleItem = function(item) {
if (this.isChecked(item)) {
this.uncheckItem(item);
} else {
this.checkItem(item);
}
};
CheckStateController.prototype.toggleItems = function() {
if (this.checked.length) {
this.checked = [];
} else {
this.checked = this.items.slice();
}
};
CheckStateController.prototype.checkByFilter = function(filter) {
this.checked = $.grep(this.items, filter);
};
CheckStateController.prototype.checkItem = function(item) {
this.checked.push(item);
};
CheckStateController.prototype.uncheckItem = function(item) {
this.checked = $.grep(this.checked, function(cItem) {
return cItem != item;
});
};
var uiService = {};
uiService.hidePopover = function(elem) {
@ -33,5 +73,9 @@ angular.module('quay').factory('UIService', [function() {
}
};
uiService.createCheckStateController = function(items) {
return new CheckStateController(items);
};
return uiService;
}]);