Add a table view to the repos list page

Fixes #104
This commit is contained in:
Joseph Schorr 2015-06-09 17:58:57 -04:00
parent 7043ddc935
commit 2b1bbcb579
16 changed files with 416 additions and 134 deletions

View file

@ -0,0 +1,32 @@
.repo-list-table {
margin-top: 40px;
}
.repo-list-table .repo-name-icon .avatar {
margin-right: 10px;
}
.repo-list-table .repo-name-icon .namespace {
color: #444;
font-size: 85%;
}
.repo-list-table .repo-name-icon .namespace:after {
content: " / ";
}
.repo-list-table .empty {
color: #ccc;
}
.repo-list-table .last-modified {
font-size: 13px;
}
.repo-list-table .strength-indicator {
display: inline-block;
}
.repo-list-table .popularity {
line-height: 10px;
}

View file

@ -0,0 +1,42 @@
.strength-indicator .indicator-sliver {
margin: 1px;
width: 14px;
height: 3px;
border: 1px solid #D5D5D5;
transition: 0.5s ease;
}
.strength-indicator .strength-indicator-element.good .indicator-sliver {
background: green;
border: 1px solid green;
}
.strength-indicator .strength-indicator-element.fair .indicator-sliver {
background: orange;
border: 1px solid orange;
}
.strength-indicator .strength-indicator-element.fair .indicator-sliver:last-child {
border: 1px solid #D5D5D5;
background: transparent;
}
.strength-indicator .strength-indicator-element.barely .indicator-sliver {
background: rgb(255, 61, 0);
border: 1px solid rgb(255, 61, 0);
}
.strength-indicator .strength-indicator-element.barely .indicator-sliver:last-child {
border: 1px solid #D5D5D5;
background: transparent;
}
.strength-indicator .strength-indicator-element.barely .indicator-sliver:nth-child(3) {
border: 1px solid #D5D5D5;
background: transparent;
}
.strength-indicator .strength-indicator-element.poor .indicator-sliver:first-child {
border: 1px solid red;
background: red;
}

View file

@ -1,6 +1,11 @@
.repo-list .repo-list-panel {
padding: 20px;
padding-top: 0px;
padding-top: 20px;
}
.repo-list .repo-list-panel .btn-group {
float: right;
margin-bottom: 10px;
}
.repo-list .repo-list-namespaces h4 {

View file

@ -3943,54 +3943,11 @@ pre.command:before {
transition: opacity 0.5s ease;
}
.location-view .ping-tower {
.location-view .strength-indicator {
display: inline-block;
vertical-align: middle;
}
.location-view .ping-sliver {
margin: 1px;
width: 14px;
height: 3px;
border: 1px solid #D5D5D5;
transition: 0.5s ease;
}
.location-view .ping-tower.good .ping-sliver {
background: green;
border: 1px solid green;
}
.location-view .ping-tower.fair .ping-sliver {
background: orange;
border: 1px solid orange;
}
.location-view .ping-tower.fair .ping-sliver:first-child {
border: 1px solid #D5D5D5;
background: transparent;
}
.location-view .ping-tower.barely .ping-sliver {
background: rgb(255, 61, 0);
border: 1px solid rgb(255, 61, 0);
}
.location-view .ping-tower.barely .ping-sliver:first-child {
border: 1px solid #D5D5D5;
background: transparent;
}
.location-view .ping-tower.barely .ping-sliver:nth-child(2) {
border: 1px solid #D5D5D5;
background: transparent;
}
.location-view .ping-tower.poor .ping-sliver:last-child {
border: 1px solid red;
background: red;
}
/*
This is the visible area of you carousel.
Set a width here to define how much items are visible.

View file

@ -3,10 +3,5 @@
<img ng-src="{{ '/static/img/flags/' + getLocationImage(location) }}">
</span>
<span class="ping-tower" ng-class="locationPingClass">
<div class="ping-sliver"></div>
<div class="ping-sliver"></div>
<div class="ping-sliver"></div>
<div class="ping-sliver"></div>
</span>
<span class="strength-indicator" value="1000 - (locationPing || 0)" maximum="1000"></span>
</span>

View file

@ -0,0 +1,49 @@
<div class="repo-list-table-element">
<table class="co-table">
<thead>
<td class="hidden-xs"
ng-class="tablePredicateClass('full_name', options.predicate, options.reverse)">
<a href="javascript:void(0)" ng-click="orderBy('full_name')">Repository Name</a>
</td>
<td class="hidden-xs"
ng-class="tablePredicateClass('last_modified_datetime', options.predicate, options.reverse)"
style="min-width: 120px;">
<a href="javascript:void(0)" ng-click="orderBy('last_modified_datetime')">Last Modified</a>
</td>
<td class="hidden-xs"
ng-class="tablePredicateClass('popularity', options.predicate, options.reverse)"
style="min-width: 20px;">
<a href="javascript:void(0)" ng-click="orderBy('popularity')">Activity</a>
</td>
<td class="hidden-xs"
ng-class="tablePredicateClass('is_starred', options.predicate, options.reverse)"
style="width: 70px">
<a href="javascript:void(0)" ng-click="orderBy('is_starred')">Star</a>
</td>
</thead>
<tbody>
<tr ng-repeat="repository in orderedRepositories">
<td class="repo-name-icon">
<span class="avatar" size="24" data="getAvatarData(repository.namespace)"></span>
<a href="/repository/{{ repository.namespace }}/{{ repository.name }}">
<span class="namespace">{{ repository.namespace }}</span>
<span class="name">{{ repository.name }}</span>
</a>
</td>
<td class="last-modified">
<span ng-if="repository.last_modified">
{{ repository.last_modified * 1000 | amCalendar }}
</span>
<span class="empty" ng-if="!repository.last_modified">(Empty Repository)</span>
</td>
<td class="popularity hidden-xs">
<span class="strength-indicator" value="repository.popularity" maximum="maxPopularity"></span>
</td>
<td>
<span class="repo-star" repository="repository"
star-toggled="starToggled({'repository': repository})"></span></td>
</tr>
</tbody>
</table>
</div>

View file

@ -0,0 +1,6 @@
<span class="strength-indicator-element" ng-class="strengthClass">
<span class="indicator-sliver"></span>
<span class="indicator-sliver"></span>
<span class="indicator-sliver"></span>
<span class="indicator-sliver"></span>
</span>

View file

@ -30,7 +30,6 @@ angular.module('quay').directive('locationView', function () {
};
$scope.locationPing = null;
$scope.locationPingClass = null;
$scope.getLocationTooltip = function(location, ping) {
var tip = $scope.getLocationTitle(location) + '<br>';
@ -71,35 +70,6 @@ angular.module('quay').directive('locationView', function () {
if (!location) { return; }
$scope.getLocationPing(location);
});
$scope.$watch('locationPing', function(locationPing) {
if (locationPing == null) {
$scope.locationPingClass = null;
return;
}
if (locationPing < 0) {
$scope.locationPingClass = 'error';
return;
}
if (locationPing < 100) {
$scope.locationPingClass = 'good';
return;
}
if (locationPing < 250) {
$scope.locationPingClass = 'fair';
return;
}
if (locationPing < 500) {
$scope.locationPingClass = 'barely';
return;
}
$scope.locationPingClass = 'poor';
});
}
};
return directiveDefinitionObject;

View file

@ -0,0 +1,102 @@
/**
* An element which displays a table of repositories.
*/
angular.module('quay').directive('repoListTable', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-list-table.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'repositoriesResources': '=repositoriesResources',
'namespaces': '=namespaces'
},
controller: function($scope, $element, $filter) {
var orderBy = $filter('orderBy');
$scope.repositories = null;
$scope.orderedRepositories = [];
$scope.maxPopularity = 0;
$scope.options = {
'predicate': 'is_starred',
'reverse': true
};
var buildOrderedRepositories = function() {
if (!$scope.repositories) { return; }
var modifier = $scope.options.reverse ? '-' : '';
var fields = [modifier + $scope.options.predicate];
// Secondary ordering by full name.
if ($scope.options.predicate != 'full_name') {
fields.push('full_name');
}
var ordered = orderBy($scope.repositories, fields, false);
$scope.orderedRepositories = ordered;
};
$scope.tablePredicateClass = function(name, predicate, reverse) {
if (name != predicate) {
return '';
}
return 'current ' + (reverse ? 'reversed' : '');
};
$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.getAvatarData = function(namespace) {
var found = {};
$scope.namespaces.forEach(function(current) {
if (current.name == namespace) {
found = current.avatar;
}
});
return found;
};
$scope.getStrengthClass = function(value, max, id) {
var adjusted = Math.round((value / max) * 5);
if (adjusted >= id) {
return 'active-' + adjusted;
}
return '';
};
$scope.$watch('options.predicate', buildOrderedRepositories);
$scope.$watch('options.reverse', buildOrderedRepositories);
$scope.$watch('repositoriesResources', function(resources) {
$scope.repositories = [];
$scope.maxPopularity = 0;
resources.forEach(function(resource) {
(resource.value || []).forEach(function(repository) {
var repositoryInfo = $.extend(repository, {
'full_name': repository.namespace + '/' + repository.name,
'last_modified_datetime': (new Date(repository.last_modified || 0)).valueOf() * (-1)
});
$scope.repositories.push(repositoryInfo);
$scope.maxPopularity = Math.max($scope.maxPopularity, repository.popularity);
});
});
buildOrderedRepositories();
}, /* deep */ true);
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,54 @@
/**
* An element which displays the strength of a value (like a signal indicator on a cell phone).
*/
angular.module('quay').directive('strengthIndicator', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/strength-indicator.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'value': '=value',
'maximum': '=maximum'
},
controller: function($scope, $element) {
$scope.strengthClass = '';
var calculateClass = function() {
if ($scope.value == null || $scope.maximum == null) {
$scope.strengthClass = '';
return;
}
var value = Math.round(($scope.value / $scope.maximum) * 4);
if (value <= 0) {
$scope.strengthClass = 'none';
return;
}
if (value <= 1) {
$scope.strengthClass = 'poor';
return;
}
if (value <= 2) {
$scope.strengthClass = 'barely';
return;
}
if (value <= 3) {
$scope.strengthClass = 'fair';
return;
}
$scope.strengthClass = 'good';
};
$scope.$watch('maximum', calculateClass);
$scope.$watch('value', calculateClass);
}
};
return directiveDefinitionObject;
});

View file

@ -16,12 +16,14 @@
}]);
function RepoListCtrl($scope, $sanitize, $q, Restangular, UserService, ApiService) {
function RepoListCtrl($scope, $sanitize, $q, Restangular, UserService, ApiService, CookieService) {
$scope.namespace = null;
$scope.page = 1;
$scope.publicPageCount = null;
$scope.allRepositories = {};
$scope.loading = true;
$scope.resources = [];
$scope.showAsList = CookieService.get('quay.repoview') == 'list';
// When loading the UserService, if the user is logged in, create a list of
// relevant namespaces and collect the relevant repositories.
@ -48,6 +50,11 @@
}
});
$scope.setShowAsList = function(value) {
$scope.showAsList = value;
CookieService.putPermanent('quay.repoview', value ? 'list' : 'grid');
};
$scope.isOrganization = function(namespace) {
return !!UserService.getOrganization(namespace);
};
@ -97,11 +104,15 @@
'public': false,
'sort': true,
'namespace': namespace.name,
'last_modified': true,
'popularity': true
};
namespace.repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
return resp.repositories.map(findDuplicateRepo);
});
$scope.resources.push(namespace.repositories);
});
};
}

View file

@ -52,23 +52,37 @@
<div class="col-lg-9 col-lg-pull-3 col-md-9 col-md-pull-3 col-sm-12">
<div class="repo-list-panel co-main-content-panel">
<!-- Starred Repository Listing -->
<div class="repo-list-grid" repositories-resource="starred_repositories"
starred="true"
star-toggled="starToggled(repository)">
<div class="repo-list-toggleb btn-group">
<i class="btn btn-default fa fa-th-large" ng-class="!showAsList ? 'active' : ''"
ng-click="setShowAsList(false)" title="Grid View" data-container="body" bs-tooltip></i>
<i class="btn btn-default fa fa-th-list" ng-class="showAsList ? 'active' : ''"
ng-click="setShowAsList(true)" title="List View" data-container="body" bs-tooltip></i>
</div>
<!-- User and Org Repository Listings -->
<div ng-repeat="namespace in namespaces">
<div class="repo-list-grid" repositories-resource="namespace.repositories"
starred="false" user="user" namespace="namespace"
<!-- Table View -->
<div ng-if="showAsList">
<div class="repo-list-table" repositories-resources="resources" namespaces="namespaces"></div>
</div>
<!-- Grid View -->
<div ng-if="!showAsList">
<!-- Starred Repository Listing -->
<div class="repo-list-grid" repositories-resource="starred_repositories"
starred="true"
star-toggled="starToggled(repository)">
</div>
<!-- User and Org Repository Listings -->
<div ng-repeat="namespace in namespaces">
<div class="repo-list-grid" repositories-resource="namespace.repositories"
starred="false" user="user" namespace="namespace"
star-toggled="starToggled(repository)">
</div>
</div>
</div>
</div>
</div>
</div>
</div>