Merge remote-tracking branch 'origin/loading-bar'

Conflicts:
	static/js/app.js
This commit is contained in:
yackob03 2013-12-18 17:26:56 -05:00
commit ec01b47608
33 changed files with 2349 additions and 1113 deletions

View 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
View 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
View 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
})(); //