Merge remote-tracking branch 'origin/loading-bar'
Conflicts: static/js/app.js
This commit is contained in:
commit
ec01b47608
33 changed files with 2349 additions and 1113 deletions
|
@ -272,9 +272,10 @@ def get_matching_users(prefix):
|
|||
def get_matching_entities(prefix):
|
||||
teams = []
|
||||
|
||||
namespace_name = request.args.get('namespace', None)
|
||||
namespace_name = request.args.get('namespace', '')
|
||||
robot_namespace = None
|
||||
organization = None
|
||||
|
||||
try:
|
||||
organization = model.get_organization(namespace_name)
|
||||
|
||||
|
@ -308,7 +309,7 @@ def get_matching_entities(prefix):
|
|||
'is_robot': user.is_robot,
|
||||
}
|
||||
|
||||
if user.is_org_member is not None:
|
||||
if organization is not None:
|
||||
user_json['is_org_member'] = user.is_robot or user.is_org_member
|
||||
|
||||
return user_json
|
||||
|
|
|
@ -3,6 +3,60 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.resource-view-element {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.resource-view-element .resource-spinner {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
opacity: 0;
|
||||
transition: opacity 0s ease-in-out;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.resource-view-element .resource-content {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.resource-view-element .resource-content.visible {
|
||||
z-index: 2;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.resource-view-element .resource-error {
|
||||
margin: 10px;
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.resource-view-element .resource-spinner.visible {
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.small-spinner {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: solid 2px transparent;
|
||||
border-top-color: #444;
|
||||
border-left-color: #444;
|
||||
border-radius: 10px;
|
||||
-webkit-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-moz-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-ms-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-o-animation: loading-bar-spinner 400ms linear infinite;
|
||||
animation: loading-bar-spinner 400ms linear infinite;
|
||||
}
|
||||
|
||||
#loading-bar-spinner {
|
||||
top: 70px;
|
||||
}
|
||||
|
||||
.entity-search-element input {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
@ -909,12 +963,20 @@ form input.ng-valid.ng-dirty,
|
|||
.entity-mini-listing {
|
||||
margin: 2px;
|
||||
white-space: nowrap !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.entity-mini-listing i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.entity-mini-listing i.fa-exclamation-triangle {
|
||||
position: absolute;
|
||||
right: -16px;
|
||||
top: 4px;
|
||||
color: #c09853;
|
||||
}
|
||||
|
||||
.entity-mini-listing .warning {
|
||||
margin-top: 6px;
|
||||
font-size: 10px;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
Credit Card
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<i class="fa fa-spinner fa-spin fa-2x" ng-show="!currentCard || changingCard"></i>
|
||||
<div class="quay-spinner" ng-show="!currentCard || changingCard"></div>
|
||||
<div class="current-card" ng-show="currentCard && !changingCard">
|
||||
<img src="{{ '/static/img/creditcards/' + getCreditImage(currentCard) }}" ng-show="currentCard.last4">
|
||||
<span class="no-card-outline" ng-show="!currentCard.last4"></span>
|
||||
|
@ -24,7 +24,7 @@
|
|||
<div class="panel">
|
||||
<div class="panel-title">
|
||||
Billing Options
|
||||
<i class="fa fa-spinner fa-spin" ng-show="working"></i>
|
||||
<div class="quay-spinner" ng-show="working"></div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="settings-option">
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="entityDropdownMenu">
|
||||
<li ng-show="lazyLoading"><i class="fa fa-spinner"></i></li>
|
||||
<li ng-show="lazyLoading" style="padding: 10px"><div class="quay-spinner"></div></li>
|
||||
|
||||
<li role="presentation" ng-repeat="team in teams" ng-show="!lazyLoading"
|
||||
ng-click="setEntity(team.name, 'team', false)">
|
||||
|
|
8
static/directives/loading-status.html
Normal file
8
static/directives/loading-status.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div class="container loading-status-element">
|
||||
<div ng-show="hasError && !loading">
|
||||
<span ng-transclude></span>
|
||||
</div>
|
||||
<div ng-show="loading">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
|
@ -21,7 +21,7 @@
|
|||
</div>
|
||||
|
||||
<div ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
<div class="quay-spinner 3x"></div>
|
||||
</div>
|
||||
<div ng-show="!loading">
|
||||
<div id="bar-chart" style="width: 800px; height: 500px;" ng-show="chartVisible">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="plan-manager-element">
|
||||
<!-- Loading/Changing -->
|
||||
<i class="fa fa-spinner fa-spin fa-3x" ng-show="planLoading"></i>
|
||||
<div class="quay-spinner 3x" ng-show="planLoading"></div>
|
||||
|
||||
<!-- Alerts -->
|
||||
<div class="alert alert-danger" ng-show="limit == 'over' && !planLoading">
|
||||
|
@ -45,13 +45,13 @@
|
|||
<button class="btn" ng-show="subscribedPlan.stripeId !== plan.stripeId"
|
||||
ng-class="subscribedPlan.price == 0 ? 'btn-primary' : 'btn-default'"
|
||||
ng-click="changeSubscription(plan.stripeId)">
|
||||
<i class="fa fa-spinner fa-spin" ng-show="planChanging"></i>
|
||||
<span class="quay-spinner" ng-show="planChanging"></span>
|
||||
<span ng-show="!planChanging && subscribedPlan.price != 0">Change</span>
|
||||
<span ng-show="!planChanging && subscribedPlan.price == 0">Subscribe</span>
|
||||
</button>
|
||||
<button class="btn btn-danger" ng-show="subscription.plan === plan.stripeId && plan.price > 0"
|
||||
ng-click="cancelSubscription()">
|
||||
<i class="fa fa-spinner fa-spin" ng-show="planChanging"></i>
|
||||
<span class="quay-spinner" ng-show="planChanging"></span>
|
||||
<span ng-show="!planChanging">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
<i class="fa fa-lock fa-lg" style="{{ repo.is_public ? 'visibility: hidden' : 'visibility: visible' }}" title="Private Repository"></i>
|
||||
<i class="fa fa-lock fa-lg" style="{{ repo.is_public ? 'visibility: hidden' : 'visibility: inherit' }}" title="Private Repository"></i>
|
||||
<i class="fa fa-hdd"></i>
|
||||
|
|
11
static/directives/resource-view.html
Normal file
11
static/directives/resource-view.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div class="resource-view-element">
|
||||
<div class="resource-spinner" ng-class="resource.loading ? 'visible' : ''">
|
||||
<div class="small-spinner"></div>
|
||||
</div>
|
||||
<div class="resource-error" ng-show="!resource.loading && resource.hasError">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
<div class="resource-content" ng-class="(!resource.loading && !resource.hasError) ? 'visible' : ''">
|
||||
<span ng-transclude></span>
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +1,5 @@
|
|||
<div class="robots-manager-element">
|
||||
<i class="fa fa-spinner fa-spin fa-3x" ng-show="loading"></i>
|
||||
<div class="quay-spinner" ng-show="loading"></div>
|
||||
<div class="alert alert-info">Robot accounts allow for delegating access in multiple repositories to role-based accounts that you manage</div>
|
||||
|
||||
<div class="container" ng-show="!loading">
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</div>
|
||||
</form>
|
||||
<div ng-show="registering" style="text-align: center">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
<div class="quay-spinner 2x"></div>
|
||||
</div>
|
||||
<div ng-show="awaitingConfirmation">
|
||||
<div class="sub-message">
|
||||
|
|
1
static/directives/spinner.html
Normal file
1
static/directives/spinner.html
Normal file
|
@ -0,0 +1 @@
|
|||
<div class="small-spinner"></div>
|
102
static/js/app.js
102
static/js/app.js
|
@ -89,7 +89,9 @@ function getMarkedDown(string) {
|
|||
}
|
||||
|
||||
// Start the application code itself.
|
||||
quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'angulartics', 'angulartics.google.analytics', 'angulartics.mixpanel', '$strap.directives', 'ngCookies'], function($provide) {
|
||||
quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'restangular', 'angularMoment', 'angulartics', 'angulartics.google.analytics', 'angulartics.mixpanel', '$strap.directives', 'ngCookies'], function($provide, cfpLoadingBarProvider) {
|
||||
cfpLoadingBarProvider.includeSpinner = false;
|
||||
|
||||
$provide.factory('CookieService', ['$cookies', '$cookieStore', function($cookies, $cookieStore) {
|
||||
var cookieService = {};
|
||||
cookieService.putPermanent = function(name, value) {
|
||||
|
@ -127,6 +129,15 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an
|
|||
return CookieService.get('quay.loggedin') == 'true';
|
||||
};
|
||||
|
||||
userService.updateUserIn = function(scope, opt_callback) {
|
||||
scope.$watch(function () { return userService.currentUser(); }, function (currentUser) {
|
||||
scope.user = currentUser;
|
||||
if (opt_callback) {
|
||||
opt_callback(currentUser);
|
||||
}
|
||||
}, true);
|
||||
};
|
||||
|
||||
userService.load = function(opt_callback) {
|
||||
var userFetch = Restangular.one('user/');
|
||||
userFetch.get().then(function(loadedUser) {
|
||||
|
@ -187,6 +198,48 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an
|
|||
return userService;
|
||||
}]);
|
||||
|
||||
|
||||
$provide.factory('ApiService', ['Restangular', function(Restangular) {
|
||||
var apiService = {}
|
||||
apiService.at = function(locationPieces) {
|
||||
var location = getRestUrl.apply(this, arguments);
|
||||
var info = {
|
||||
'url': location,
|
||||
'caller': Restangular.one(location),
|
||||
'withOptions': function(options) {
|
||||
info.options = options;
|
||||
return info;
|
||||
},
|
||||
'get': function(processor, opt_errorHandler) {
|
||||
var options = info.options;
|
||||
var caller = info.caller;
|
||||
var result = {
|
||||
'loading': true,
|
||||
'value': null,
|
||||
'hasError': false
|
||||
};
|
||||
|
||||
caller.get(options).then(function(resp) {
|
||||
result.value = processor(resp);
|
||||
result.loading = false;
|
||||
}, function(resp) {
|
||||
result.hasError = true;
|
||||
result.loading = false;
|
||||
if (opt_errorHandler) {
|
||||
opt_errorHandler(resp);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
return info;
|
||||
};
|
||||
|
||||
return apiService;
|
||||
}]);
|
||||
|
||||
$provide.factory('KeyService', ['$location', function($location) {
|
||||
var keyService = {}
|
||||
|
||||
|
@ -554,10 +607,10 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an
|
|||
// WARNING WARNING WARNING
|
||||
$routeProvider.
|
||||
when('/repository/:namespace/:name', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl,
|
||||
fixFooter: true, reloadOnSearch: false}).
|
||||
fixFooter: false, reloadOnSearch: false}).
|
||||
when('/repository/:namespace/:name/tag/:tag', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl,
|
||||
fixFooter: true}).
|
||||
when('/repository/:namespace/:name/image/:image', {templateUrl: '/static/partials/image-view.html', controller: ImageViewCtrl}).
|
||||
fixFooter: false}).
|
||||
when('/repository/:namespace/:name/image/:image', {templateUrl: '/static/partials/image-view.html', controller: ImageViewCtrl, reloadOnSearch: false}).
|
||||
when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl, reloadOnSearch: false}).
|
||||
when('/repository/', {title: 'Repositories', description: 'Public and private docker repositories list',
|
||||
templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
|
||||
|
@ -1266,6 +1319,39 @@ quayApp.directive('popupInputButton', function () {
|
|||
});
|
||||
|
||||
|
||||
quayApp.directive('resourceView', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/resource-view.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'resource': '=resource',
|
||||
'errorMessage': '=errorMessage'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('quaySpinner', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/spinner.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {},
|
||||
controller: function($scope, $element) {
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('organizationHeader', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
|
@ -1529,14 +1615,14 @@ quayApp.directive('entitySearch', function () {
|
|||
number++;
|
||||
|
||||
var input = $element[0].firstChild.firstChild;
|
||||
$scope.namespace = $scope.namespace || '';
|
||||
$(input).typeahead({
|
||||
name: 'entities' + number,
|
||||
remote: {
|
||||
url: '/api/entities/%QUERY',
|
||||
replace: function (url, uriEncodedQuery) {
|
||||
var namespace = $scope.namespace || '';
|
||||
url = url.replace('%QUERY', uriEncodedQuery);
|
||||
url += '?namespace=' + encodeURIComponent($scope.namespace);
|
||||
url += '?namespace=' + encodeURIComponent(namespace);
|
||||
if ($scope.includeTeams) {
|
||||
url += '&includeTeams=true'
|
||||
}
|
||||
|
@ -1566,8 +1652,8 @@ quayApp.directive('entitySearch', function () {
|
|||
}
|
||||
template += '<span class="name">' + datum.value + '</span>';
|
||||
|
||||
if (datum.entity.is_org_member !== undefined && !datum.entity.is_org_member && datum.kind == 'user') {
|
||||
template += '<div class="alert-warning warning">This user is outside your organization</div>';
|
||||
if (datum.entity.is_org_member === false && datum.entity.kind == 'user') {
|
||||
template += '<i class="fa fa-exclamation-triangle" title="User is outside the organization"></i>';
|
||||
}
|
||||
|
||||
template += '</div>';
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -54,7 +54,7 @@ ImageHistoryTree.prototype.calculateDimensions_ = function(container) {
|
|||
var cw = Math.max(document.getElementById(container).clientWidth, this.maxWidth_ * DEPTH_WIDTH);
|
||||
var ch = this.maxHeight_ * (DEPTH_HEIGHT + 10);
|
||||
|
||||
var margin = { top: 40, right: 20, bottom: 20, left: 40 };
|
||||
var margin = { top: 40, right: 20, bottom: 20, left: 80 };
|
||||
var m = [margin.top, margin.right, margin.bottom, margin.left];
|
||||
var w = cw - m[1] - m[3];
|
||||
var h = ch - m[0] - m[2];
|
||||
|
@ -87,6 +87,7 @@ ImageHistoryTree.prototype.updateDimensions_ = function() {
|
|||
var viewportHeight = $(window).height();
|
||||
var boundingBox = document.getElementById(container).getBoundingClientRect();
|
||||
document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top - 110) + 'px';
|
||||
|
||||
$('#' + container).overscroll();
|
||||
|
||||
// Update the tree.
|
||||
|
@ -94,9 +95,13 @@ ImageHistoryTree.prototype.updateDimensions_ = function() {
|
|||
var tree = this.tree_;
|
||||
var vis = this.vis_;
|
||||
|
||||
|
||||
var ow = w + m[1] + m[3];
|
||||
var oh = h + m[0] + m[2];
|
||||
rootSvg
|
||||
.attr("width", w + m[1] + m[3])
|
||||
.attr("height", h + m[0] + m[2]);
|
||||
.attr("width", ow)
|
||||
.attr("height", oh)
|
||||
.attr("style", "width: " + ow + "px; height: " + oh + "px");
|
||||
|
||||
tree.size([w, h]);
|
||||
vis.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
|
||||
|
|
793
static/lib/jquery.overscroll.js
Normal file
793
static/lib/jquery.overscroll.js
Normal file
|
@ -0,0 +1,793 @@
|
|||
/**
|
||||
* Overscroll v1.7.3
|
||||
* A jQuery Plugin that emulates the iPhone scrolling experience in a browser.
|
||||
* http://azoffdesign.com/overscroll
|
||||
*
|
||||
* Intended for use with the latest jQuery
|
||||
* http://code.jquery.com/jquery-latest.js
|
||||
*
|
||||
* Copyright 2013, Jonathan Azoff
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/azoff/overscroll/blob/master/mit.license
|
||||
*
|
||||
* For API documentation, see the README file
|
||||
* http://azof.fr/pYCzuM
|
||||
*
|
||||
* Date: Tuesday, March 18th 2013
|
||||
*/
|
||||
(function(global, dom, browser, math, wait, cancel, namespace, $, none){
|
||||
|
||||
// We want to run this plug-in in strict-mode
|
||||
// so that we may benefit from its optimizations
|
||||
'use strict';
|
||||
|
||||
// The key used to bind-instance specific data to an object
|
||||
var datakey = 'overscroll';
|
||||
|
||||
// create <body> node if there's not one present (e.g., for test runners)
|
||||
if (dom.body === null) {
|
||||
dom.documentElement.appendChild(
|
||||
dom.createElement('body')
|
||||
);
|
||||
}
|
||||
|
||||
// quick fix for IE 8 and below since getComputedStyle() is not supported
|
||||
// TODO: find a better solution
|
||||
if (!global.getComputedStyle) {
|
||||
global.getComputedStyle = function (el, pseudo) {
|
||||
this.el = el;
|
||||
this.getPropertyValue = function (prop) {
|
||||
var re = /(\-([a-z]){1})/g;
|
||||
if (prop == 'float') prop = 'styleFloat';
|
||||
if (re.test(prop)) {
|
||||
prop = prop.replace(re, function () {
|
||||
return arguments[2].toUpperCase();
|
||||
});
|
||||
}
|
||||
return el.currentStyle[prop] ? el.currentStyle[prop] : null;
|
||||
};
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
// runs feature detection for overscroll
|
||||
var compat = {
|
||||
animate: (function(){
|
||||
var fn = global.requestAnimationFrame ||
|
||||
global.webkitRequestAnimationFrame ||
|
||||
global.mozRequestAnimationFrame ||
|
||||
global.oRequestAnimationFrame ||
|
||||
global.msRequestAnimationFrame ||
|
||||
function(callback) { wait(callback, 1000/60); };
|
||||
return function(callback) {
|
||||
fn.call(global, callback);
|
||||
};
|
||||
})(),
|
||||
overflowScrolling: (function(){
|
||||
var style = '';
|
||||
var div = dom.createElement('div');
|
||||
var prefixes = ['webkit', 'moz', 'o', 'ms'];
|
||||
dom.body.appendChild(div);
|
||||
$.each(prefixes, function(i, prefix){
|
||||
div.style[prefix + 'OverflowScrolling'] = 'touch';
|
||||
});
|
||||
div.style.overflowScrolling = 'touch';
|
||||
var computedStyle = global.getComputedStyle(div);
|
||||
if (!!computedStyle.overflowScrolling) {
|
||||
style = 'overflow-scrolling';
|
||||
} else {
|
||||
$.each(prefixes, function(i, prefix){
|
||||
if (!!computedStyle[prefix + 'OverflowScrolling']) {
|
||||
style = '-' + prefix + '-overflow-scrolling';
|
||||
}
|
||||
return !style;
|
||||
});
|
||||
}
|
||||
div.parentNode.removeChild(div);
|
||||
return style;
|
||||
})(),
|
||||
cursor: (function() {
|
||||
var div = dom.createElement('div');
|
||||
var prefixes = ['webkit', 'moz'];
|
||||
var gmail = 'https://mail.google.com/mail/images/2/';
|
||||
var style = {
|
||||
grab: 'url('+gmail+'openhand.cur), move',
|
||||
grabbing: 'url('+gmail+'closedhand.cur), move'
|
||||
};
|
||||
dom.body.appendChild(div);
|
||||
$.each(prefixes, function(i, prefix){
|
||||
var found, cursor = '-' + prefix + '-grab';
|
||||
div.style.cursor = cursor;
|
||||
var computedStyle = global.getComputedStyle(div);
|
||||
found = computedStyle.cursor === cursor;
|
||||
if (found) {
|
||||
style = {
|
||||
grab: '-' + prefix + '-grab',
|
||||
grabbing: '-' + prefix + '-grabbing'
|
||||
};
|
||||
}
|
||||
return !found;
|
||||
});
|
||||
div.parentNode.removeChild(div);
|
||||
return style;
|
||||
})()
|
||||
};
|
||||
|
||||
// These are all the events that could possibly
|
||||
// be used by the plug-in
|
||||
var events = {
|
||||
drag: 'mousemove touchmove',
|
||||
end: 'mouseup mouseleave click touchend touchcancel',
|
||||
hover: 'mouseenter mouseleave',
|
||||
ignored: 'select dragstart drag',
|
||||
scroll: 'scroll',
|
||||
start: 'mousedown touchstart',
|
||||
wheel: 'mousewheel DOMMouseScroll'
|
||||
};
|
||||
|
||||
// These settings are used to tweak drift settings
|
||||
// for the plug-in
|
||||
var settings = {
|
||||
captureThreshold: 3,
|
||||
driftDecay: 1.1,
|
||||
driftSequences: 22,
|
||||
driftTimeout: 100,
|
||||
scrollDelta: 15,
|
||||
thumbOpacity: 0.7,
|
||||
thumbThickness: 6,
|
||||
thumbTimeout: 400,
|
||||
wheelDelta: 20,
|
||||
wheelTicks: 120
|
||||
};
|
||||
|
||||
// These defaults are used to complement any options
|
||||
// passed into the plug-in entry point
|
||||
var defaults = {
|
||||
cancelOn: 'select,input,textarea',
|
||||
direction: 'multi',
|
||||
dragHold: false,
|
||||
hoverThumbs: false,
|
||||
scrollDelta: settings.scrollDelta,
|
||||
showThumbs: true,
|
||||
persistThumbs: false,
|
||||
captureWheel: true,
|
||||
wheelDelta: settings.wheelDelta,
|
||||
wheelDirection: 'multi',
|
||||
zIndex: 999,
|
||||
ignoreSizing: false
|
||||
};
|
||||
|
||||
// Triggers a DOM event on the overscrolled element.
|
||||
// All events are namespaced under the overscroll name
|
||||
function triggerEvent(event, target) {
|
||||
target.trigger('overscroll:' + event);
|
||||
}
|
||||
|
||||
// Utility function to return a timestamp
|
||||
function time() {
|
||||
return (new Date()).getTime();
|
||||
}
|
||||
|
||||
// Captures the position from an event, modifies the properties
|
||||
// of the second argument to persist the position, and then
|
||||
// returns the modified object
|
||||
function capturePosition(event, position, index) {
|
||||
position.x = event.pageX;
|
||||
position.y = event.pageY;
|
||||
position.time = time();
|
||||
position.index = index;
|
||||
return position;
|
||||
}
|
||||
|
||||
// Used to move the thumbs around an overscrolled element
|
||||
function moveThumbs(thumbs, sizing, left, top) {
|
||||
|
||||
var ml, mt;
|
||||
|
||||
if (thumbs && thumbs.added) {
|
||||
if (thumbs.horizontal) {
|
||||
ml = left * (1 + sizing.container.width / sizing.container.scrollWidth);
|
||||
mt = top + sizing.thumbs.horizontal.top;
|
||||
thumbs.horizontal.css('margin', mt + 'px 0 0 ' + ml + 'px');
|
||||
}
|
||||
if (thumbs.vertical) {
|
||||
ml = left + sizing.thumbs.vertical.left;
|
||||
mt = top * (1 + sizing.container.height / sizing.container.scrollHeight);
|
||||
thumbs.vertical.css('margin', mt + 'px 0 0 ' + ml + 'px');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Used to toggle the thumbs on and off
|
||||
// of an overscrolled element
|
||||
function toggleThumbs(thumbs, options, dragging) {
|
||||
if (thumbs && thumbs.added && !options.persistThumbs) {
|
||||
if (dragging) {
|
||||
if (thumbs.vertical) {
|
||||
thumbs.vertical.stop(true, true).fadeTo('fast', settings.thumbOpacity);
|
||||
}
|
||||
if (thumbs.horizontal) {
|
||||
thumbs.horizontal.stop(true, true).fadeTo('fast', settings.thumbOpacity);
|
||||
}
|
||||
} else {
|
||||
if (thumbs.vertical) {
|
||||
thumbs.vertical.fadeTo('fast', 0);
|
||||
}
|
||||
if (thumbs.horizontal) {
|
||||
thumbs.horizontal.fadeTo('fast', 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Defers click event listeners to after a mouseup event.
|
||||
// Used to avoid unintentional clicks
|
||||
function deferClick(target) {
|
||||
var clicks, key = 'events';
|
||||
var events = $._data ? $._data(target[0], key) : target.data(key);
|
||||
if (events && events.click) {
|
||||
clicks = events.click.slice();
|
||||
target.off('click').one('click', function(){
|
||||
$.each(clicks, function(i, click){
|
||||
target.click(click);
|
||||
}); return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Toggles thumbs on hover. This event is only triggered
|
||||
// if the hoverThumbs option is set
|
||||
function hover(event) {
|
||||
var data = event.data,
|
||||
thumbs = data.thumbs,
|
||||
options = data.options,
|
||||
dragging = event.type === 'mouseenter';
|
||||
toggleThumbs(thumbs, options, dragging);
|
||||
}
|
||||
|
||||
// This function is only ever used when the overscrolled element
|
||||
// scrolled outside of the scope of this plugin.
|
||||
function scroll(event) {
|
||||
var data = event.data;
|
||||
if (!data.flags.dragged) {
|
||||
/*jshint validthis:true */
|
||||
moveThumbs(data.thumbs, data.sizing, this.scrollLeft, this.scrollTop);
|
||||
}
|
||||
}
|
||||
|
||||
// handles mouse wheel scroll events
|
||||
function wheel(event) {
|
||||
|
||||
// prevent any default wheel behavior
|
||||
event.preventDefault();
|
||||
|
||||
var data = event.data,
|
||||
options = data.options,
|
||||
sizing = data.sizing,
|
||||
thumbs = data.thumbs,
|
||||
dwheel = data.wheel,
|
||||
flags = data.flags,
|
||||
original = event.originalEvent,
|
||||
delta = 0, deltaX = 0, deltaY = 0;
|
||||
|
||||
// stop any drifts
|
||||
flags.drifting = false;
|
||||
|
||||
// normalize the wheel ticks
|
||||
if (original.detail) {
|
||||
delta = -original.detail;
|
||||
if (original.detailX) {
|
||||
deltaX = -original.detailX;
|
||||
}
|
||||
if (original.detailY) {
|
||||
deltaY = -original.detailY;
|
||||
}
|
||||
} else if (original.wheelDelta) {
|
||||
delta = original.wheelDelta / settings.wheelTicks;
|
||||
if (original.wheelDeltaX) {
|
||||
deltaX = original.wheelDeltaX / settings.wheelTicks;
|
||||
}
|
||||
if (original.wheelDeltaY) {
|
||||
deltaY = original.wheelDeltaY / settings.wheelTicks;
|
||||
}
|
||||
}
|
||||
|
||||
// apply a pixel delta to each tick
|
||||
delta *= options.wheelDelta;
|
||||
deltaX *= options.wheelDelta;
|
||||
deltaY *= options.wheelDelta;
|
||||
|
||||
// initialize flags if this is the first tick
|
||||
if (!dwheel) {
|
||||
data.target.data(datakey).dragging = flags.dragging = true;
|
||||
data.wheel = dwheel = { timeout: null };
|
||||
toggleThumbs(thumbs, options, true);
|
||||
}
|
||||
|
||||
// actually modify scroll offsets
|
||||
if (options.wheelDirection === 'vertical'){
|
||||
/*jshint validthis:true */
|
||||
this.scrollTop -= delta;
|
||||
} else if ( options.wheelDirection === 'horizontal') {
|
||||
this.scrollLeft -= delta;
|
||||
} else {
|
||||
this.scrollLeft -= deltaX;
|
||||
this.scrollTop -= deltaY || delta;
|
||||
}
|
||||
|
||||
if (dwheel.timeout) { cancel(dwheel.timeout); }
|
||||
|
||||
moveThumbs(thumbs, sizing, this.scrollLeft, this.scrollTop);
|
||||
|
||||
dwheel.timeout = wait(function() {
|
||||
data.target.data(datakey).dragging = flags.dragging = false;
|
||||
toggleThumbs(thumbs, options, data.wheel = null);
|
||||
}, settings.thumbTimeout);
|
||||
|
||||
}
|
||||
|
||||
// updates the current scroll offset during a mouse move
|
||||
function drag(event) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
var data = event.data,
|
||||
touches = event.originalEvent.touches,
|
||||
options = data.options,
|
||||
sizing = data.sizing,
|
||||
thumbs = data.thumbs,
|
||||
position = data.position,
|
||||
flags = data.flags,
|
||||
target = data.target.get(0);
|
||||
|
||||
|
||||
// correct page coordinates for touch devices
|
||||
if (touches && touches.length) {
|
||||
event = touches[0];
|
||||
}
|
||||
|
||||
if (!flags.dragged) {
|
||||
toggleThumbs(thumbs, options, true);
|
||||
}
|
||||
|
||||
flags.dragged = true;
|
||||
|
||||
if (options.direction !== 'vertical') {
|
||||
target.scrollLeft -= (event.pageX - position.x);
|
||||
}
|
||||
|
||||
if (data.options.direction !== 'horizontal') {
|
||||
target.scrollTop -= (event.pageY - position.y);
|
||||
}
|
||||
|
||||
capturePosition(event, data.position);
|
||||
|
||||
if (--data.capture.index <= 0) {
|
||||
data.target.data(datakey).dragging = flags.dragging = true;
|
||||
capturePosition(event, data.capture, settings.captureThreshold);
|
||||
}
|
||||
|
||||
moveThumbs(thumbs, sizing, target.scrollLeft, target.scrollTop);
|
||||
|
||||
}
|
||||
|
||||
// sends the overscrolled element into a drift
|
||||
function drift(target, event, callback) {
|
||||
|
||||
var data = event.data, dx, dy, xMod, yMod,
|
||||
capture = data.capture,
|
||||
options = data.options,
|
||||
sizing = data.sizing,
|
||||
thumbs = data.thumbs,
|
||||
elapsed = time() - capture.time,
|
||||
scrollLeft = target.scrollLeft,
|
||||
scrollTop = target.scrollTop,
|
||||
decay = settings.driftDecay;
|
||||
|
||||
// only drift if enough time has passed since
|
||||
// the last capture event
|
||||
if (elapsed > settings.driftTimeout) {
|
||||
callback(data); return;
|
||||
}
|
||||
|
||||
// determine offset between last capture and current time
|
||||
dx = options.scrollDelta * (event.pageX - capture.x);
|
||||
dy = options.scrollDelta * (event.pageY - capture.y);
|
||||
|
||||
// update target scroll offsets
|
||||
if (options.direction !== 'vertical') {
|
||||
scrollLeft -= dx;
|
||||
} if (options.direction !== 'horizontal') {
|
||||
scrollTop -= dy;
|
||||
}
|
||||
|
||||
// split the distance to travel into a set of sequences
|
||||
xMod = dx / settings.driftSequences;
|
||||
yMod = dy / settings.driftSequences;
|
||||
|
||||
triggerEvent('driftstart', data.target);
|
||||
|
||||
data.drifting = true;
|
||||
|
||||
// animate the drift sequence
|
||||
compat.animate(function render() {
|
||||
if (data.drifting) {
|
||||
var min = 1, max = -1;
|
||||
data.drifting = false;
|
||||
if (yMod > min && target.scrollTop > scrollTop || yMod < max && target.scrollTop < scrollTop) {
|
||||
data.drifting = true;
|
||||
target.scrollTop -= yMod;
|
||||
yMod /= decay;
|
||||
}
|
||||
if (xMod > min && target.scrollLeft > scrollLeft || xMod < max && target.scrollLeft < scrollLeft) {
|
||||
data.drifting = true;
|
||||
target.scrollLeft -= xMod;
|
||||
xMod /= decay;
|
||||
}
|
||||
moveThumbs(thumbs, sizing, target.scrollLeft, target.scrollTop);
|
||||
compat.animate(render);
|
||||
} else {
|
||||
triggerEvent('driftend', data.target);
|
||||
callback(data);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// starts the drag operation and binds the mouse move handler
|
||||
function start(event) {
|
||||
|
||||
var data = event.data,
|
||||
touches = event.originalEvent.touches,
|
||||
target = data.target,
|
||||
dstart = data.start = $(event.target),
|
||||
flags = data.flags;
|
||||
|
||||
// stop any drifts
|
||||
flags.drifting = false;
|
||||
|
||||
// only start drag if the user has not explictly banned it.
|
||||
if (dstart.size() && !dstart.is(data.options.cancelOn)) {
|
||||
|
||||
// without this the simple "click" event won't be recognized on touch clients
|
||||
if (!touches) { event.preventDefault(); }
|
||||
|
||||
if (!compat.overflowScrolling) {
|
||||
target.css('cursor', compat.cursor.grabbing);
|
||||
target.data(datakey).dragging = flags.dragging = flags.dragged = false;
|
||||
|
||||
// apply the drag listeners to the doc or target
|
||||
if(data.options.dragHold) {
|
||||
$(document).on(events.drag, data, drag);
|
||||
} else {
|
||||
target.on(events.drag, data, drag);
|
||||
}
|
||||
}
|
||||
|
||||
data.position = capturePosition(event, {});
|
||||
data.capture = capturePosition(event, {}, settings.captureThreshold);
|
||||
triggerEvent('dragstart', target);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ends the drag operation and unbinds the mouse move handler
|
||||
function stop(event) {
|
||||
|
||||
var data = event.data,
|
||||
target = data.target,
|
||||
options = data.options,
|
||||
flags = data.flags,
|
||||
thumbs = data.thumbs,
|
||||
|
||||
// hides the thumbs after the animation is done
|
||||
done = function () {
|
||||
if (thumbs && !options.hoverThumbs) {
|
||||
toggleThumbs(thumbs, options, false);
|
||||
}
|
||||
};
|
||||
|
||||
// remove drag listeners from doc or target
|
||||
if(options.dragHold) {
|
||||
$(document).unbind(events.drag, drag);
|
||||
} else {
|
||||
target.unbind(events.drag, drag);
|
||||
}
|
||||
|
||||
// only fire events and drift if we started with a
|
||||
// valid position
|
||||
if (data.position) {
|
||||
|
||||
triggerEvent('dragend', target);
|
||||
|
||||
// only drift if a drag passed our threshold
|
||||
if (flags.dragging && !compat.overflowScrolling) {
|
||||
drift(target.get(0), event, done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// only if we moved, and the mouse down is the same as
|
||||
// the mouse up target do we defer the event
|
||||
if (flags.dragging && !compat.overflowScrolling && data.start && data.start.is(event.target)) {
|
||||
deferClick(data.start);
|
||||
}
|
||||
|
||||
// clear all internal flags and settings
|
||||
target.data(datakey).dragging =
|
||||
data.start =
|
||||
data.capture =
|
||||
data.position =
|
||||
flags.dragged =
|
||||
flags.dragging = false;
|
||||
|
||||
// set the cursor back to normal
|
||||
target.css('cursor', compat.cursor.grab);
|
||||
|
||||
}
|
||||
|
||||
// Ensures that a full set of options are provided
|
||||
// for the plug-in. Also does some validation
|
||||
function getOptions(options) {
|
||||
|
||||
// fill in missing values with defaults
|
||||
options = $.extend({}, defaults, options);
|
||||
|
||||
// check for inconsistent directional restrictions
|
||||
if (options.direction !== 'multi' && options.direction !== options.wheelDirection) {
|
||||
options.wheelDirection = options.direction;
|
||||
}
|
||||
|
||||
// ensure positive values for deltas
|
||||
options.scrollDelta = math.abs(parseFloat(options.scrollDelta));
|
||||
options.wheelDelta = math.abs(parseFloat(options.wheelDelta));
|
||||
|
||||
// fix values for scroll offset
|
||||
options.scrollLeft = options.scrollLeft === none ? null : math.abs(parseFloat(options.scrollLeft));
|
||||
options.scrollTop = options.scrollTop === none ? null : math.abs(parseFloat(options.scrollTop));
|
||||
|
||||
return options;
|
||||
|
||||
}
|
||||
|
||||
// Returns the sizing information (bounding box) for the
|
||||
// target DOM element
|
||||
function getSizing(target) {
|
||||
|
||||
var $target = $(target),
|
||||
width = $target.width(),
|
||||
height = $target.height(),
|
||||
scrollWidth = width >= target.scrollWidth ? width : target.scrollWidth,
|
||||
scrollHeight = height >= target.scrollHeight ? height : target.scrollHeight,
|
||||
hasScroll = scrollWidth > width || scrollHeight > height;
|
||||
|
||||
return {
|
||||
valid: hasScroll,
|
||||
container: {
|
||||
width: width,
|
||||
height: height,
|
||||
scrollWidth: scrollWidth,
|
||||
scrollHeight: scrollHeight
|
||||
},
|
||||
thumbs: {
|
||||
horizontal: {
|
||||
width: width * width / scrollWidth,
|
||||
height: settings.thumbThickness,
|
||||
corner: settings.thumbThickness / 2,
|
||||
left: 0,
|
||||
top: height - settings.thumbThickness
|
||||
},
|
||||
vertical: {
|
||||
width: settings.thumbThickness,
|
||||
height: height * height / scrollHeight,
|
||||
corner: settings.thumbThickness / 2,
|
||||
left: width - settings.thumbThickness,
|
||||
top: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Attempts to get (or implicitly creates) the
|
||||
// remover function for the target passed
|
||||
// in as an argument
|
||||
function getRemover(target, orCreate) {
|
||||
|
||||
var $target = $(target), thumbs,
|
||||
data = $target.data(datakey) || {},
|
||||
style = $target.attr('style'),
|
||||
fallback = orCreate ? function () {
|
||||
|
||||
data = $target.data(datakey);
|
||||
thumbs = data.thumbs;
|
||||
|
||||
// restore original styles (if any)
|
||||
if (style) {
|
||||
$target.attr('style', style);
|
||||
} else {
|
||||
$target.removeAttr('style');
|
||||
}
|
||||
|
||||
// remove any created thumbs
|
||||
if (thumbs) {
|
||||
if (thumbs.horizontal) { thumbs.horizontal.remove(); }
|
||||
if (thumbs.vertical) { thumbs.vertical.remove(); }
|
||||
}
|
||||
|
||||
// remove any bound overscroll events and data
|
||||
$target
|
||||
.removeData(datakey)
|
||||
.off(events.wheel, wheel)
|
||||
.off(events.start, start)
|
||||
.off(events.end, stop)
|
||||
.off(events.ignored, ignore);
|
||||
|
||||
} : $.noop;
|
||||
|
||||
return $.isFunction(data.remover) ? data.remover : fallback;
|
||||
|
||||
}
|
||||
|
||||
// Genterates CSS specific to a particular thumb.
|
||||
// It requires sizing data and options
|
||||
function getThumbCss(size, options) {
|
||||
return {
|
||||
position: 'absolute',
|
||||
opacity: options.persistThumbs ? settings.thumbOpacity : 0,
|
||||
'background-color': 'black',
|
||||
width: size.width + 'px',
|
||||
height: size.height + 'px',
|
||||
'border-radius': size.corner + 'px',
|
||||
'margin': size.top + 'px 0 0 ' + size.left + 'px',
|
||||
'z-index': options.zIndex
|
||||
};
|
||||
}
|
||||
|
||||
// Creates the DOM elements used as "thumbs" within
|
||||
// the target container.
|
||||
function createThumbs(target, sizing, options) {
|
||||
|
||||
var div = '<div/>',
|
||||
thumbs = {},
|
||||
css = false;
|
||||
|
||||
if (sizing.container.scrollWidth > 0 && options.direction !== 'vertical') {
|
||||
css = getThumbCss(sizing.thumbs.horizontal, options);
|
||||
thumbs.horizontal = $(div).css(css).prependTo(target);
|
||||
}
|
||||
|
||||
if (sizing.container.scrollHeight > 0 && options.direction !== 'horizontal') {
|
||||
css = getThumbCss(sizing.thumbs.vertical, options);
|
||||
thumbs.vertical = $(div).css(css).prependTo(target);
|
||||
}
|
||||
|
||||
thumbs.added = !!css;
|
||||
|
||||
return thumbs;
|
||||
|
||||
}
|
||||
|
||||
// ignores events on the overscroll element
|
||||
function ignore(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// This function takes a jQuery element, some
|
||||
// (optional) options, and sets up event metadata
|
||||
// for each instance the plug-in affects
|
||||
function setup(target, options) {
|
||||
|
||||
// create initial data properties for this instance
|
||||
options = getOptions(options);
|
||||
var sizing = getSizing(target),
|
||||
thumbs, data = {
|
||||
options: options, sizing: sizing,
|
||||
flags: { dragging: false },
|
||||
remover: getRemover(target, true)
|
||||
};
|
||||
|
||||
// only apply handlers if the overscrolled element
|
||||
// actually has an area to scroll
|
||||
if (sizing.valid || options.ignoreSizing) {
|
||||
// provide a circular-reference, enable events, and
|
||||
// apply any required CSS
|
||||
data.target = target = $(target).css({
|
||||
position: 'relative',
|
||||
cursor: compat.cursor.grab
|
||||
}).on(events.start, data, start)
|
||||
.on(events.end, data, stop)
|
||||
.on(events.ignored, data, ignore);
|
||||
|
||||
// apply the stop listeners for drag end
|
||||
if(options.dragHold) {
|
||||
$(document).on(events.end, data, stop);
|
||||
} else {
|
||||
data.target.on(events.end, data, stop);
|
||||
}
|
||||
|
||||
// apply any user-provided scroll offsets
|
||||
if (options.scrollLeft !== null) {
|
||||
target.scrollLeft(options.scrollLeft);
|
||||
} if (options.scrollTop !== null) {
|
||||
target.scrollTop(options.scrollTop);
|
||||
}
|
||||
|
||||
// use native oversroll, if it exists
|
||||
if (compat.overflowScrolling) {
|
||||
target.css(compat.overflowScrolling, 'touch');
|
||||
} else {
|
||||
target.on(events.scroll, data, scroll);
|
||||
}
|
||||
|
||||
// check to see if the user would like mousewheel support
|
||||
if (options.captureWheel) {
|
||||
target.on(events.wheel, data, wheel);
|
||||
}
|
||||
|
||||
// add thumbs and listeners (if we're showing them)
|
||||
if (options.showThumbs) {
|
||||
if (compat.overflowScrolling) {
|
||||
target.css('overflow', 'scroll');
|
||||
} else {
|
||||
target.css('overflow', 'hidden');
|
||||
data.thumbs = thumbs = createThumbs(target, sizing, options);
|
||||
if (thumbs.added) {
|
||||
moveThumbs(thumbs, sizing, target.scrollLeft(), target.scrollTop());
|
||||
if (options.hoverThumbs) {
|
||||
target.on(events.hover, data, hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
target.css('overflow', 'hidden');
|
||||
}
|
||||
|
||||
target.data(datakey, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Removes any event listeners and other instance-specific
|
||||
// data from the target. It attempts to leave the target
|
||||
// at the state it found it.
|
||||
function teardown(target) {
|
||||
getRemover(target)();
|
||||
}
|
||||
|
||||
// This is the entry-point for enabling the plug-in;
|
||||
// You can find it's exposure point at the end
|
||||
// of this closure
|
||||
function overscroll(options) {
|
||||
/*jshint validthis:true */
|
||||
return this.removeOverscroll().each(function() {
|
||||
setup(this, options);
|
||||
});
|
||||
}
|
||||
|
||||
// This is the entry-point for disabling the plug-in;
|
||||
// You can find it's exposure point at the end
|
||||
// of this closure
|
||||
function removeOverscroll() {
|
||||
/*jshint validthis:true */
|
||||
return this.each(function () {
|
||||
teardown(this);
|
||||
});
|
||||
}
|
||||
|
||||
// Extend overscroll to expose settings to the user
|
||||
overscroll.settings = settings;
|
||||
|
||||
// Extend jQuery's prototype to expose the plug-in.
|
||||
// If the supports native overflowScrolling, overscroll will not
|
||||
// attempt to override the browser's built in support
|
||||
$.extend(namespace, {
|
||||
overscroll: overscroll,
|
||||
removeOverscroll: removeOverscroll
|
||||
});
|
||||
|
||||
})(window, document, navigator, Math, setTimeout, clearTimeout, jQuery.fn, jQuery);
|
102
static/lib/loading-bar.css
Executable file
102
static/lib/loading-bar.css
Executable file
|
@ -0,0 +1,102 @@
|
|||
|
||||
/* Make clicks pass-through */
|
||||
#loading-bar,
|
||||
#loading-bar-spinner {
|
||||
pointer-events: none;
|
||||
-webkit-pointer-events: none;
|
||||
-webkit-transition: 0.5s linear all;
|
||||
-moz-transition: 0.5s linear all;
|
||||
-o-transition: 0.5s linear all;
|
||||
transition: 0.5s linear all;
|
||||
}
|
||||
|
||||
#loading-bar.ng-enter,
|
||||
#loading-bar.ng-leave.ng-leave-active,
|
||||
#loading-bar-spinner.ng-enter,
|
||||
#loading-bar-spinner.ng-leave.ng-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#loading-bar.ng-enter.ng-enter-active,
|
||||
#loading-bar.ng-leave,
|
||||
#loading-bar-spinner.ng-enter.ng-enter-active,
|
||||
#loading-bar-spinner.ng-leave {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#loading-bar .bar {
|
||||
-webkit-transition: width 350ms;
|
||||
-moz-transition: width 350ms;
|
||||
-o-transition: width 350ms;
|
||||
transition: width 350ms;
|
||||
|
||||
background: #29d;
|
||||
position: fixed;
|
||||
z-index: 2000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
/* Fancy blur effect */
|
||||
#loading-bar .peg {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
|
||||
opacity: 1.0;
|
||||
|
||||
-webkit-transform: rotate(3deg) translate(0px, -4px);
|
||||
-moz-transform: rotate(3deg) translate(0px, -4px);
|
||||
-ms-transform: rotate(3deg) translate(0px, -4px);
|
||||
-o-transform: rotate(3deg) translate(0px, -4px);
|
||||
transform: rotate(3deg) translate(0px, -4px);
|
||||
}
|
||||
|
||||
#loading-bar-spinner {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
#loading-bar-spinner .spinner-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
||||
border: solid 2px transparent;
|
||||
border-top-color: #29d;
|
||||
border-left-color: #29d;
|
||||
border-radius: 10px;
|
||||
|
||||
-webkit-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-moz-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-ms-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-o-animation: loading-bar-spinner 400ms linear infinite;
|
||||
animation: loading-bar-spinner 400ms linear infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading-bar-spinner {
|
||||
0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }
|
||||
}
|
||||
@-moz-keyframes loading-bar-spinner {
|
||||
0% { -moz-transform: rotate(0deg); transform: rotate(0deg); }
|
||||
100% { -moz-transform: rotate(360deg); transform: rotate(360deg); }
|
||||
}
|
||||
@-o-keyframes loading-bar-spinner {
|
||||
0% { -o-transform: rotate(0deg); transform: rotate(0deg); }
|
||||
100% { -o-transform: rotate(360deg); transform: rotate(360deg); }
|
||||
}
|
||||
@-ms-keyframes loading-bar-spinner {
|
||||
0% { -ms-transform: rotate(0deg); transform: rotate(0deg); }
|
||||
100% { -ms-transform: rotate(360deg); transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes loading-bar-spinner {
|
||||
0% { transform: rotate(0deg); transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); transform: rotate(360deg); }
|
||||
}
|
271
static/lib/loading-bar.js
Executable file
271
static/lib/loading-bar.js
Executable file
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* angular-loading-bar
|
||||
*
|
||||
* intercepts XHR requests and creates a loading bar.
|
||||
* Based on the excellent nprogress work by rstacruz (more info in readme)
|
||||
*
|
||||
* (c) 2013 Wes Cruver
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
// Alias the loading bar so it can be included using a simpler
|
||||
// (and maybe more professional) module name:
|
||||
angular.module('angular-loading-bar', ['chieffancypants.loadingBar']);
|
||||
|
||||
|
||||
/**
|
||||
* loadingBarInterceptor service
|
||||
*
|
||||
* Registers itself as an Angular interceptor and listens for XHR requests.
|
||||
*/
|
||||
angular.module('chieffancypants.loadingBar', [])
|
||||
.config(['$httpProvider', function ($httpProvider) {
|
||||
|
||||
var interceptor = ['$q', '$cacheFactory', 'cfpLoadingBar', function ($q, $cacheFactory, cfpLoadingBar) {
|
||||
|
||||
/**
|
||||
* The total number of requests made
|
||||
*/
|
||||
var reqsTotal = 0;
|
||||
|
||||
/**
|
||||
* The number of requests completed (either successfully or not)
|
||||
*/
|
||||
var reqsCompleted = 0;
|
||||
|
||||
|
||||
/**
|
||||
* calls cfpLoadingBar.complete() which removes the
|
||||
* loading bar from the DOM.
|
||||
*/
|
||||
function setComplete() {
|
||||
cfpLoadingBar.complete();
|
||||
reqsCompleted = 0;
|
||||
reqsTotal = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the response has already been cached
|
||||
* @param {Object} config the config option from the request
|
||||
* @return {Boolean} retrns true if cached, otherwise false
|
||||
*/
|
||||
function isCached(config) {
|
||||
var cache;
|
||||
var defaults = $httpProvider.defaults;
|
||||
|
||||
if (config.method !== 'GET' || config.cache === false) {
|
||||
config.cached = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.cache === true && defaults.cache === undefined) {
|
||||
cache = $cacheFactory.get('$http');
|
||||
} else if (defaults.cache !== undefined) {
|
||||
cache = defaults.cache;
|
||||
} else {
|
||||
cache = config.cache;
|
||||
}
|
||||
|
||||
var cached = cache !== undefined ?
|
||||
cache.get(config.url) !== undefined : false;
|
||||
|
||||
if (config.cached !== undefined && cached !== config.cached) {
|
||||
return config.cached;
|
||||
}
|
||||
config.cached = cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
return {
|
||||
'request': function(config) {
|
||||
// Check to make sure this request hasn't already been cached and that
|
||||
// the requester didn't explicitly ask us to ignore this request:
|
||||
if (!config.ignoreLoadingBar && !isCached(config)) {
|
||||
if (reqsTotal === 0) {
|
||||
cfpLoadingBar.start();
|
||||
}
|
||||
reqsTotal++;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
||||
'response': function(response) {
|
||||
if (!isCached(response.config)) {
|
||||
reqsCompleted++;
|
||||
if (reqsCompleted >= reqsTotal) {
|
||||
setComplete();
|
||||
} else {
|
||||
cfpLoadingBar.set(reqsCompleted / reqsTotal);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
'responseError': function(rejection) {
|
||||
if (!isCached(rejection.config)) {
|
||||
reqsCompleted++;
|
||||
if (reqsCompleted >= reqsTotal) {
|
||||
setComplete();
|
||||
} else {
|
||||
cfpLoadingBar.set(reqsCompleted / reqsTotal);
|
||||
}
|
||||
}
|
||||
return $q.reject(rejection);
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
$httpProvider.interceptors.push(interceptor);
|
||||
}])
|
||||
|
||||
|
||||
/**
|
||||
* Loading Bar
|
||||
*
|
||||
* This service handles adding and removing the actual element in the DOM.
|
||||
* Generally, best practices for DOM manipulation is to take place in a
|
||||
* directive, but because the element itself is injected in the DOM only upon
|
||||
* XHR requests, and it's likely needed on every view, the best option is to
|
||||
* use a service.
|
||||
*/
|
||||
.provider('cfpLoadingBar', function() {
|
||||
|
||||
this.includeSpinner = true;
|
||||
this.includeBar = true;
|
||||
this.parentSelector = 'body';
|
||||
|
||||
this.$get = ['$document', '$timeout', '$animate', '$rootScope', function ($document, $timeout, $animate, $rootScope) {
|
||||
|
||||
var $parentSelector = this.parentSelector,
|
||||
$parent = $document.find($parentSelector),
|
||||
loadingBarContainer = angular.element('<div id="loading-bar"><div class="bar"><div class="peg"></div></div></div>'),
|
||||
loadingBar = loadingBarContainer.find('div').eq(0),
|
||||
spinner = angular.element('<div id="loading-bar-spinner"><div class="spinner-icon"></div></div>');
|
||||
|
||||
var incTimeout,
|
||||
completeTimeout,
|
||||
started = false,
|
||||
status = 0;
|
||||
|
||||
var includeSpinner = this.includeSpinner;
|
||||
var includeBar = this.includeBar;
|
||||
|
||||
/**
|
||||
* Inserts the loading bar element into the dom, and sets it to 2%
|
||||
*/
|
||||
function _start() {
|
||||
$timeout.cancel(completeTimeout);
|
||||
|
||||
// do not continually broadcast the started event:
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rootScope.$broadcast('cfpLoadingBar:started');
|
||||
started = true;
|
||||
|
||||
if (includeBar) {
|
||||
$animate.enter(loadingBarContainer, $parent);
|
||||
}
|
||||
|
||||
if (includeSpinner) {
|
||||
$animate.enter(spinner, $parent);
|
||||
}
|
||||
_set(0.02);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the loading bar's width to a certain percent.
|
||||
*
|
||||
* @param n any value between 0 and 1
|
||||
*/
|
||||
function _set(n) {
|
||||
if (!started) {
|
||||
return;
|
||||
}
|
||||
var pct = (n * 100) + '%';
|
||||
loadingBar.css('width', pct);
|
||||
status = n;
|
||||
|
||||
// increment loadingbar to give the illusion that there is always
|
||||
// progress but make sure to cancel the previous timeouts so we don't
|
||||
// have multiple incs running at the same time.
|
||||
$timeout.cancel(incTimeout);
|
||||
incTimeout = $timeout(function() {
|
||||
_inc();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the loading bar by a random amount
|
||||
* but slows down as it progresses
|
||||
*/
|
||||
function _inc() {
|
||||
if (_status() >= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var rnd = 0;
|
||||
|
||||
// TODO: do this mathmatically instead of through conditions
|
||||
|
||||
var stat = _status();
|
||||
if (stat >= 0 && stat < 0.25) {
|
||||
// Start out between 3 - 6% increments
|
||||
rnd = (Math.random() * (5 - 3 + 1) + 3) / 100;
|
||||
} else if (stat >= 0.25 && stat < 0.65) {
|
||||
// increment between 0 - 3%
|
||||
rnd = (Math.random() * 3) / 100;
|
||||
} else if (stat >= 0.65 && stat < 0.9) {
|
||||
// increment between 0 - 2%
|
||||
rnd = (Math.random() * 2) / 100;
|
||||
} else if (stat >= 0.9 && stat < 0.99) {
|
||||
// finally, increment it .5 %
|
||||
rnd = 0.005;
|
||||
} else {
|
||||
// after 99%, don't increment:
|
||||
rnd = 0;
|
||||
}
|
||||
|
||||
var pct = _status() + rnd;
|
||||
_set(pct);
|
||||
}
|
||||
|
||||
function _status() {
|
||||
return status;
|
||||
}
|
||||
|
||||
function _complete() {
|
||||
$rootScope.$broadcast('cfpLoadingBar:completed');
|
||||
_set(1);
|
||||
|
||||
// Attempt to aggregate any start/complete calls within 500ms:
|
||||
completeTimeout = $timeout(function() {
|
||||
$animate.leave(loadingBarContainer, function() {
|
||||
status = 0;
|
||||
started = false;
|
||||
});
|
||||
$animate.leave(spinner);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
return {
|
||||
start : _start,
|
||||
set : _set,
|
||||
status : _status,
|
||||
inc : _inc,
|
||||
complete : _complete,
|
||||
includeSpinner : this.includeSpinner,
|
||||
parentSelector : this.parentSelector
|
||||
};
|
||||
|
||||
|
||||
|
||||
}]; //
|
||||
}); // wtf javascript. srsly
|
||||
})(); //
|
|
@ -1,12 +1,5 @@
|
|||
<div class="container" ng-show="!loading && !image">
|
||||
No image found
|
||||
</div>
|
||||
|
||||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="container repo repo-image-view" ng-show="!loading && image">
|
||||
<div class="resource-view" resource="image" error-message="'No image found'">
|
||||
<div class="container repo repo-image-view">
|
||||
<div class="header">
|
||||
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name }}" class="back"><i class="fa fa-chevron-left"></i></a>
|
||||
<h3>
|
||||
|
@ -15,13 +8,13 @@
|
|||
<span style="color: #ccc">/</span>
|
||||
<span style="color: #666;">{{repo.name}}</span>
|
||||
<span style="color: #ccc">/</span>
|
||||
<span>{{image.id.substr(0, 12)}}</span>
|
||||
<span>{{image.value.id.substr(0, 12)}}</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<!-- Comment -->
|
||||
<blockquote ng-show="image.comment">
|
||||
<span class="markdown-view" content="image.comment"></span>
|
||||
<blockquote ng-show="image.value.comment">
|
||||
<span class="markdown-view" content="image.value.comment"></span>
|
||||
</blockquote>
|
||||
|
||||
<!-- Information -->
|
||||
|
@ -31,7 +24,7 @@
|
|||
<div>
|
||||
<div class="id-container">
|
||||
<div class="input-group">
|
||||
<input id="full-id" type="text" class="form-control" value="{{ image.id }}" readonly>
|
||||
<input id="full-id" type="text" class="form-control" value="{{ image.value.id }}" readonly>
|
||||
<span id="copyClipboard" class="input-group-addon" title="Copy to Clipboard" data-clipboard-target="full-id">
|
||||
<i class="fa fa-copy"></i>
|
||||
</span>
|
||||
|
@ -92,6 +85,5 @@
|
|||
<div id="changes-tree-container" class="changes-container" onresize="tree && tree.notifyResized()"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,19 +9,21 @@
|
|||
</div>
|
||||
|
||||
<div ng-show="!user.anonymous">
|
||||
<div ng-show="loadingmyrepos">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
<span class="namespace-selector" user="user" namespace="namespace" ng-show="!loadingmyrepos && user.organizations"></span>
|
||||
<div ng-show="!loadingmyrepos && myrepos.length > 0">
|
||||
<span class="namespace-selector" user="user" namespace="namespace" ng-show="user.organizations"></span>
|
||||
|
||||
<div class="resource-view" resource="my_repositories">
|
||||
<!-- Repos -->
|
||||
<div ng-show="my_repositories.value.length > 0">
|
||||
<h2>Top Repositories</h2>
|
||||
<div class="repo-listing" ng-repeat="repository in myrepos">
|
||||
<div class="repo-listing" ng-repeat="repository in my_repositories.value">
|
||||
<span class="repo-circle no-background" repo="repository"></span>
|
||||
<a ng-href="/repository/{{ repository.namespace }}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||
<div class="markdown-view description" content="repository.description" first-line-only="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="!loadingmyrepos && myrepos.length == 0">
|
||||
|
||||
<!-- No Repos -->
|
||||
<div ng-show="my_repositories.value.length == 0">
|
||||
<div class="sub-message" style="margin-top: 20px">
|
||||
<span ng-show="namespace != user.username">You don't have access to any repositories in this organization yet.</span>
|
||||
<span ng-show="namespace == user.username">You don't have any repositories yet!</span>
|
||||
|
@ -32,6 +34,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- col -->
|
||||
|
||||
<div class="col-md-4 col-md-offset-1">
|
||||
|
@ -41,8 +44,8 @@
|
|||
<div ng-show="!user.anonymous" class="user-welcome">
|
||||
<img class="gravatar" src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=128&d=identicon" />
|
||||
<div class="sub-message">Welcome <b>{{ user.username }}</b>!</div>
|
||||
<a ng-show="myrepos" class="btn btn-primary" href="/repository/">Browse all repositories</a>
|
||||
<a ng-show="myrepos" class="btn btn-success" href="/new/">Create a new repository</a>
|
||||
<a ng-show="my_repositories.value" class="btn btn-primary" href="/repository/">Browse all repositories</a>
|
||||
<a class="btn btn-success" href="/new/">Create a new repository</a>
|
||||
</div>
|
||||
</div> <!-- col -->
|
||||
</div> <!-- row -->
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<div class="loading" ng-show="loading || creating">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
<div class="loading" ng-show="creating">
|
||||
<div class="quay-spinner"></div>
|
||||
</div>
|
||||
|
||||
<div class="container create-org" ng-show="!loading && !creating">
|
||||
<div class="container create-org" ng-show="!creating">
|
||||
|
||||
<div class="row header-row">
|
||||
<div class="col-md-8 col-md-offset-1">
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
<div class="container" ng-show="user.anonymous">
|
||||
<h3>Please <a href="/signin/">sign in</a></h3>
|
||||
<div class="col-sm-6 col-sm-offset-3">
|
||||
<div class="user-setup" redirect-url="'/new/'"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container" ng-show="!user.anonymous && building">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
<div class="quay-spinner"></div>
|
||||
</div>
|
||||
|
||||
<div class="container" ng-show="!user.anonymous && creating">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
<div class="quay-spinner"></div>
|
||||
</div>
|
||||
|
||||
<div class="container" ng-show="!user.anonymous && uploading">
|
||||
|
@ -75,7 +77,7 @@
|
|||
In order to make this repository private, you’ll need to upgrade your plan from <b>{{ subscribedPlan.title }}</b> to <b>{{ planRequired.title }}</b>. This will cost $<span>{{ planRequired.price / 100 }}</span>/month.
|
||||
</div>
|
||||
<a class="btn btn-primary" ng-click="upgradePlan()" ng-show="!planChanging">Upgrade now</a>
|
||||
<i class="fa fa-spinner fa-spin fa-3x" ng-show="planChanging"></i>
|
||||
<div class="quay-spinner" ng-show="planChanging"></div>
|
||||
</div>
|
||||
|
||||
<div class="required-plan" ng-show="repo.is_public == '0' && planRequired && !isUserNamespace">
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="loading" ng-show="!loading && !organization">
|
||||
No matching organization found
|
||||
</div>
|
||||
|
||||
<div class="org-admin container" ng-show="!loading && organization">
|
||||
<div class="resource-view" resource="orgResource" error-message="'No organization found'"></div>
|
||||
<div class="org-admin container" ng-show="organization">
|
||||
<div class="organization-header" organization="organization" clickable="true"></div>
|
||||
|
||||
<div class="row">
|
||||
|
@ -48,7 +41,7 @@
|
|||
<!-- Billing History tab -->
|
||||
<div id="billing" class="tab-pane">
|
||||
<div ng-show="invoiceLoading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
<div class="quay-spinner"></div>
|
||||
</div>
|
||||
|
||||
<div ng-show="!invoiceLoading && !invoices">
|
||||
|
@ -105,8 +98,7 @@
|
|||
|
||||
<!-- Members tab -->
|
||||
<div id="members" class="tab-pane">
|
||||
<i class="fa fa-spinner fa-spin fa-3x" ng-show="membersLoading"></i>
|
||||
|
||||
<div class="quay-spinner" ng-show="membersLoading"></div>
|
||||
<div ng-show="!membersLoading">
|
||||
<div class="side-controls">
|
||||
<div class="result-count">
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
<div class="org-member-logs container" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="container" ng-show="!loading && !organization">
|
||||
Organization not found
|
||||
</div>
|
||||
|
||||
<div class="container" ng-show="!loading && !memberInfo">
|
||||
Member not found
|
||||
</div>
|
||||
|
||||
<div class="org-member-logs container" ng-show="!loading && organization && memberInfo">
|
||||
<div class="resource-view" resource="memberResource" error-message="'Member not found'">
|
||||
<div class="org-member-logs container">
|
||||
<div class="organization-header" organization="organization" clickable="true"></div>
|
||||
<div class="logs-view" organization="organization" performer="memberInfo" visible="organization && memberInfo && ready"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="loading" ng-show="!loading && !organization">
|
||||
No matching organization found
|
||||
</div>
|
||||
|
||||
<div class="org-view container" ng-show="!loading && organization">
|
||||
<div class="resource-view" resource="orgResource" error-message="'No matching organization found'">
|
||||
<div class="org-view container">
|
||||
<div class="organization-header" organization="organization">
|
||||
<div class="header-buttons" ng-show="organization.is_admin">
|
||||
|
||||
|
@ -50,6 +43,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="cannotChangeTeamModal">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="container org-list conntent-container">
|
||||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
<div class="loading" ng-show="!user">
|
||||
<div class="quay-spinner"></div>
|
||||
</div>
|
||||
|
||||
<div class="button-bar-right">
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
<div class="user-setup" signed-in="signedIn()" redirect-url="'/plans/'"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" ng-click="cancelNotedPlan()">Close</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
|
||||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="container" ng-show="!loading && (!repo || !permissions)">
|
||||
No repository found
|
||||
</div>
|
||||
|
||||
<div class="container repo repo-admin" ng-show="!loading && repo && permissions">
|
||||
<div class="resource-view" resource="repository" error-message="'No repository found'"></div>
|
||||
<div class="container repo repo-admin" ng-show="repo">
|
||||
<div class="header row">
|
||||
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name }}" class="back"><i class="fa fa-chevron-left"></i></a>
|
||||
<h3>
|
||||
|
@ -156,12 +148,9 @@
|
|||
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="URLs which will be invoked with an HTTP POST and JSON payload when a successful push to the repository occurs."></i>
|
||||
</div>
|
||||
|
||||
<div class="panel-body" ng-show="webhooksLoading">
|
||||
Loading webhooks: <i class="fa fa-spinner fa-spin fa-2x" style="vertical-align: middle; margin-left: 4px"></i>
|
||||
</div>
|
||||
|
||||
<div class="panel-body" ng-show="!webhooksLoading">
|
||||
<table class="permissions" ng-form="newWebhookForm">
|
||||
<div class="panel-body">
|
||||
<div class="resource-view" resource="webhooksResource" error-message="'Could not load webhooks'">
|
||||
<table class="permissions">
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="width: 500px;">Webhook URL</td>
|
||||
|
@ -179,16 +168,24 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<form name="createWebhookForm" ng-submit="createWebhook()">
|
||||
<table class="permissions">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<td style="width: 500px;">
|
||||
<input type="url" class="form-control" placeholder="New webhook url..." ng-model="newWebhook.url" required>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-primary" type="submit" ng-click="createWebhook()">Create</button>
|
||||
<button class="btn btn-primary" type="submit" ng-disabled="createWebhookForm.$invalid">Create</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<div class="right-info">
|
||||
Quay will <b>POST</b> to these webhooks whenever a push occurs. See the <a href="/guide">User Guide</a> for more information.
|
||||
|
@ -240,7 +237,11 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Auth dialog -->
|
||||
<div class="docker-auth-dialog" username="'$token'" token="shownToken.code"
|
||||
shown="!!shownToken" counter="shownTokenCounter">
|
||||
<i class="fa fa-key"></i> {{ shownToken.friendlyName }}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="container" ng-show="!loading">
|
||||
<div class="container">
|
||||
<div class="repo-list" ng-show="!user.anonymous">
|
||||
<div ng-class="user.organizations.length ? 'section-header' : ''">
|
||||
<div class="button-bar-right">
|
||||
|
@ -27,30 +23,36 @@
|
|||
<h3 ng-show="namespace == user.username">Your Repositories</h3>
|
||||
<h3 ng-show="namespace != user.username">Repositories</h3>
|
||||
|
||||
<div ng-show="user_repositories.length > 0">
|
||||
<div class="repo-listing" ng-repeat="repository in user_repositories">
|
||||
<div class="resource-view" resource="user_repositories">
|
||||
<!-- User/Org has repositories -->
|
||||
<div ng-show="user_repositories.value.length > 0">
|
||||
<div class="repo-listing" ng-repeat="repository in user_repositories.value">
|
||||
<span class="repo-circle no-background" repo="repository"></span>
|
||||
<a ng-href="/repository/{{repository.namespace}}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||
<div class="description markdown-view" content="repository.description" first-line-only="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="user_repositories.length == 0" style="padding:20px;">
|
||||
<!-- User/Org has no repositories -->
|
||||
<div ng-show="user_repositories.value.length == 0" style="padding:20px;">
|
||||
<div class="alert alert-info">
|
||||
<h4 ng-show="namespace == user.username">You don't have any repositories yet!</h4>
|
||||
<h4 ng-show="namespace != user.username">This organization doesn't have any repositories, or you have not been provided access.</h4>
|
||||
<a href="/guide"><b>Click here</b> to learn how to create a repository</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="repo-list">
|
||||
<h3>Top Public Repositories</h3>
|
||||
<div class="repo-listing" ng-repeat="repository in public_repositories">
|
||||
<div class="resource-view" resource="public_repositories">
|
||||
<div class="repo-listing" ng-repeat="repository in public_repositories.value">
|
||||
<span class="repo-circle no-background" repo="repository"></span>
|
||||
<a ng-href="/repository/{{repository.namespace}}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||
<div class="description markdown-view" content="repository.description" first-line-only="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="loading" ng-show="!loading && !organization">
|
||||
No matching team found
|
||||
</div>
|
||||
|
||||
<div class="team-view container" ng-show="!loading && organization">
|
||||
<div class="resource-view" resource="orgResource" error-message="'No matching organization'">
|
||||
<div class="team-view container">
|
||||
<div class="organization-header" organization="organization" team-name="teamname"></div>
|
||||
|
||||
<div class="resource-view" resource="membersResource" error-message="'No matching team found'">
|
||||
<div class="description markdown-input" content="team.description" can-write="organization.is_admin"
|
||||
content-changed="updateForDescription" field-title="'team description'"></div>
|
||||
|
||||
|
@ -40,7 +34,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="cannotChangeTeamModal">
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
<div class="loading" ng-show="!user">
|
||||
<div class="quay-spinner"></div>
|
||||
</div>
|
||||
|
||||
<div class="loading" ng-show="!loading && !user">
|
||||
<div class="loading" ng-show="user.anonymous">
|
||||
No matching user found
|
||||
</div>
|
||||
|
||||
<div class="user-admin container" ng-show="!loading && user">
|
||||
<div class="user-admin container" ng-show="!user.anonymous">
|
||||
<div class="row">
|
||||
<div class="organization-header-element">
|
||||
<img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=24&d=identicon">
|
||||
|
@ -45,20 +45,23 @@
|
|||
<!-- Change password tab -->
|
||||
<div id="password" class="tab-pane">
|
||||
<div class="loading" ng-show="updatingUser">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
<div class="quay-spinner 3x"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="help-block" ng-show="changePasswordSuccess">Password changed successfully</span>
|
||||
|
||||
<div ng-show="!updatingUser">
|
||||
<form class="form-change-pw col-md-6" name="changePasswordForm" ng-submit="changePassword()" data-trigger="manual"
|
||||
data-content="{{ changePasswordError }}" data-placement="right" ng-show="!awaitingConfirmation && !registering">
|
||||
<input type="password" class="form-control" placeholder="Your new password" ng-model="user.password" required>
|
||||
<input type="password" class="form-control" placeholder="Verify your new password" ng-model="user.repeatPassword"
|
||||
match="user.password" required>
|
||||
<button class="btn btn-danger" ng-disabled="changePasswordForm.$invalid" type="submit"
|
||||
analytics-on analytics-event="register">Change Password</button>
|
||||
<span class="help-block" ng-show="changePasswordSuccess">Password changed successfully</span>
|
||||
analytics-on analytics-event="change-pass">Change Password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Robot accounts tab -->
|
||||
<div id="robots" class="tab-pane">
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
<div class="container" ng-show="!loading && !repo">
|
||||
No repository found
|
||||
</div>
|
||||
|
||||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="container repo" ng-show="!loading && repo">
|
||||
<div class="resource-view" resource="repository" error-message="'No Repository Found'">
|
||||
<div class="container repo">
|
||||
<!-- Repo Header -->
|
||||
<div class="header">
|
||||
<h3>
|
||||
<span class="repo-circle" repo="repo"></span>
|
||||
|
||||
<span style="color: #aaa;"> {{repo.namespace}}</span> <span style="color: #ccc">/</span> {{repo.name}}
|
||||
|
||||
<span class="settings-cog" ng-show="repo.can_admin" title="Repository Settings" bs-tooltip="tooltip.title" data-placement="bottom">
|
||||
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name + '/admin' }}">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
|
@ -44,7 +35,7 @@
|
|||
<div id="buildInfoBox" class="status-box" ng-show="repo.is_building"
|
||||
bs-popover="'static/partials/build-status-item.html'" data-placement="bottom">
|
||||
<span class="title">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
<span class="quay-spinner"></span>
|
||||
<b>Building Images</b>
|
||||
</span>
|
||||
<span class="count" ng-class="buildsInfo ? 'visible' : ''"><span>{{ buildsInfo ? buildsInfo.length : '-' }}</span></span>
|
||||
|
@ -69,7 +60,9 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
|||
</div>
|
||||
|
||||
<div class="repo-content" ng-show="!currentTag.image && repo.is_building">
|
||||
<div class="empty-message">Your build is currently processing, if this takes longer than an hour, please contact <a href="mailto:support@quay.io">Quay.io support</a></div>
|
||||
<div class="empty-message">
|
||||
Your build is currently processing, if this takes longer than an hour, please contact <a href="mailto:support@quay.io">Quay.io support</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content view -->
|
||||
|
@ -79,7 +72,6 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
|||
<div class="row">
|
||||
<!-- Tree View container -->
|
||||
<div class="col-md-8">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<!-- Tag dropdown -->
|
||||
|
@ -93,18 +85,14 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
|||
</ul>
|
||||
</div>
|
||||
<span class="right-title">Tags</span>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Image history loading -->
|
||||
<div ng-hide="imageHistory" style="padding: 10px; text-align: center;">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<!-- Tree View itself -->
|
||||
<!-- Image history tree -->
|
||||
<div class="resource-view" resource="imageHistory">
|
||||
<div id="image-history-container" onresize="tree.notifyResized()"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Side Panel -->
|
||||
<div class="col-md-4">
|
||||
|
@ -112,10 +100,10 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
|||
<div class="panel-heading">
|
||||
<!-- Image dropdown -->
|
||||
<div class="tag-dropdown dropdown" title="Images" bs-tooltip="tooltip.title" data-placement="top">
|
||||
<i class="fa fa-archive"><span class="tag-count">{{imageHistory.length}}</span></i>
|
||||
<i class="fa fa-archive"><span class="tag-count">{{imageHistory.value.length}}</span></i>
|
||||
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">{{currentImage.id.substr(0, 12)}} <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="image in imageHistory">
|
||||
<li ng-repeat="image in imageHistory.value">
|
||||
<a href="javascript:void(0)" ng-click="setImage(image)">{{image.id.substr(0, 12)}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -139,10 +127,7 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
|||
</dl>
|
||||
|
||||
<!-- Image changes loading -->
|
||||
<div ng-hide="currentImageChanges">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="resource-view" resource="currentImageChangeResource">
|
||||
<div class="changes-container small-changes-container"
|
||||
ng-show="currentImageChanges.changed.length || currentImageChanges.added.length || currentImageChanges.removed.length">
|
||||
<div class="changes-count-container accordion-toggle" data-toggle="collapse" data-parent="#accordion" data-target="#collapseChanges">
|
||||
|
@ -179,7 +164,11 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
|||
</div>
|
||||
</div>
|
||||
<div class="more-changes" ng-show="getMoreCount(currentImageChanges) > 0">
|
||||
<a href="{{'/repository/' + repo.namespace + '/' + repo.name + '/image/' + currentImage.id}}">And {{getMoreCount(currentImageChanges)}} more...</a>
|
||||
<a href="{{'/repository/' + repo.namespace + '/' + repo.name + '/image/' + currentImage.id}}">
|
||||
And {{getMoreCount(currentImageChanges)}} more...
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/static/lib/loading-bar.css">
|
||||
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.0/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.no-icons.min.css">
|
||||
<link href='//fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css'>
|
||||
|
@ -42,8 +44,9 @@
|
|||
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular-route.min.js"></script>
|
||||
|
||||
<script src="//cdn.jsdelivr.net/underscorejs/1.5.2/underscore-min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/restangular/1.1.3/restangular.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/restangular/1.2.0/restangular.min.js"></script>
|
||||
|
||||
<script src="static/lib/loading-bar.js"></script>
|
||||
<script src="static/lib/angular-strap.min.js"></script>
|
||||
<script src="static/lib/angulartics.js"></script>
|
||||
<script src="static/lib/angulartics-mixpanel.js"></script>
|
||||
|
|
Reference in a new issue