diff options
author | Marvin Borner | 2018-07-13 19:06:45 +0200 |
---|---|---|
committer | Marvin Borner | 2018-07-13 19:06:45 +0200 |
commit | 6fcfb7c04d32e1c8b26a312295bf7ac3ec2d2ad7 (patch) | |
tree | dbc87ef16fa01d5d99116de283592b8fe5e02944 /public/bower_components/PACE/pace.coffee | |
parent | dfd839f27146df0ad0494e11734fc7d310c70ebf (diff) |
Fixed many permissions and began admin interface
Diffstat (limited to 'public/bower_components/PACE/pace.coffee')
-rw-r--r-- | public/bower_components/PACE/pace.coffee | 755 |
1 files changed, 755 insertions, 0 deletions
diff --git a/public/bower_components/PACE/pace.coffee b/public/bower_components/PACE/pace.coffee new file mode 100644 index 0000000..219e1d6 --- /dev/null +++ b/public/bower_components/PACE/pace.coffee @@ -0,0 +1,755 @@ +defaultOptions = + # How long should it take for the bar to animate to a new + # point after receiving it + catchupTime: 100 + + # How quickly should the bar be moving before it has any progress + # info from a new source in %/ms + initialRate: .03 + + # What is the minimum amount of time the bar should be on the + # screen. Irrespective of this number, the bar will always be on screen for + # 33 * (100 / maxProgressPerFrame) + ghostTime ms. + minTime: 250 + + # What is the minimum amount of time the bar should sit after the last + # update before disappearing + ghostTime: 100 + + # Its easy for a bunch of the bar to be eaten in the first few frames + # before we know how much there is to load. This limits how much of + # the bar can be used per frame + maxProgressPerFrame: 20 + + # This tweaks the animation easing + easeFactor: 1.25 + + # Should pace automatically start when the page is loaded, or should it wait for `start` to + # be called? Always false if pace is loaded with AMD or CommonJS. + startOnPageLoad: true + + # Should we restart the browser when pushState or replaceState is called? (Generally + # means ajax navigation has occured) + restartOnPushState: true + + # Should we show the progress bar for every ajax request (not just regular or ajax-y page + # navigation)? Set to false to disable. + # + # If so, how many ms does the request have to be running for before we show the progress? + restartOnRequestAfter: 500 + + # What element should the pace element be appended to on the page? + target: 'body' + + elements: + # How frequently in ms should we check for the elements being tested for + # using the element monitor? + checkInterval: 100 + + # What elements should we wait for before deciding the page is fully loaded (not required) + selectors: ['body'] + + eventLag: + # When we first start measuring event lag, not much is going on in the browser yet, so it's + # not uncommon for the numbers to be abnormally low for the first few samples. This configures + # how many samples we need before we consider a low number to mean completion. + minSamples: 10 + + # How many samples should we average to decide what the current lag is? + sampleCount: 3 + + # Above how many ms of lag is the CPU considered busy? + lagThreshold: 3 + + ajax: + # Which HTTP methods should we track? + trackMethods: ['GET'] + + # Should we track web socket connections? + trackWebSockets: true + + # A list of regular expressions or substrings of URLS we should ignore (for both tracking and restarting) + ignoreURLs: [] + +now = -> + performance?.now?() ? +new Date + +requestAnimationFrame = window.requestAnimationFrame or window.mozRequestAnimationFrame or + window.webkitRequestAnimationFrame or window.msRequestAnimationFrame + +cancelAnimationFrame = window.cancelAnimationFrame or window.mozCancelAnimationFrame + +if not requestAnimationFrame? + requestAnimationFrame = (fn) -> + setTimeout fn, 50 + + cancelAnimationFrame = (id) -> + clearTimeout id + +runAnimation = (fn) -> + last = now() + tick = -> + diff = now() - last + + if diff >= 33 + # Don't run faster than 30 fps + + last = now() + fn diff, -> + requestAnimationFrame tick + else + setTimeout tick, (33 - diff) + + tick() + +result = (obj, key, args...) -> + if typeof obj[key] is 'function' + obj[key](args...) + else + obj[key] + +extend = (out, sources...) -> + for source in sources when source + for own key, val of source + if out[key]? and typeof out[key] is 'object' and val? and typeof val is 'object' + extend(out[key], val) + else + out[key] = val + out + +avgAmplitude = (arr) -> + sum = count = 0 + for v in arr + sum += Math.abs(v) + count++ + + sum / count + +getFromDOM = (key='options', json=true) -> + el = document.querySelector "[data-pace-#{ key }]" + + return unless el + + data = el.getAttribute "data-pace-#{ key }" + + return data if not json + + try + return JSON.parse data + catch e + console?.error "Error parsing inline pace options", e + +class Evented + on: (event, handler, ctx, once=false) -> + @bindings ?= {} + @bindings[event] ?= [] + @bindings[event].push {handler, ctx, once} + + once: (event, handler, ctx) -> + @on(event, handler, ctx, true) + + off: (event, handler) -> + return unless @bindings?[event]? + + if not handler? + delete @bindings[event] + else + i = 0 + while i < @bindings[event].length + if @bindings[event][i].handler is handler + @bindings[event].splice i, 1 + else + i++ + + trigger: (event, args...) -> + if @bindings?[event] + i = 0 + while i < @bindings[event].length + {handler, ctx, once} = @bindings[event][i] + + handler.apply(ctx ? @, args) + + if once + @bindings[event].splice i, 1 + else + i++ + +Pace = window.Pace or {} +window.Pace = Pace + +extend Pace, Evented:: + +options = Pace.options = extend {}, defaultOptions, window.paceOptions, getFromDOM() + +for source in ['ajax', 'document', 'eventLag', 'elements'] + # true enables them without configuration, so we grab the config from the defaults + if options[source] is true + options[source] = defaultOptions[source] + +class NoTargetError extends Error + +class Bar + constructor: -> + @progress = 0 + + getElement: -> + if not @el? + targetElement = document.querySelector options.target + + if not targetElement + throw new NoTargetError + + @el = document.createElement 'div' + @el.className = "pace pace-active" + + document.body.className = document.body.className.replace /pace-done/g, '' + document.body.className += ' pace-running' + + @el.innerHTML = ''' + <div class="pace-progress"> + <div class="pace-progress-inner"></div> + </div> + <div class="pace-activity"></div> + ''' + if targetElement.firstChild? + targetElement.insertBefore @el, targetElement.firstChild + else + targetElement.appendChild @el + + @el + + finish: -> + el = @getElement() + + el.className = el.className.replace 'pace-active', '' + el.className += ' pace-inactive' + + document.body.className = document.body.className.replace 'pace-running', '' + document.body.className += ' pace-done' + + update: (prog) -> + @progress = prog + + do @render + + destroy: -> + try + @getElement().parentNode.removeChild(@getElement()) + catch NoTargetError + + @el = undefined + + render: -> + if not document.querySelector(options.target)? + return false + + el = @getElement() + + transform = "translate3d(#{ @progress }%, 0, 0)" + for key in ['webkitTransform', 'msTransform', 'transform'] + el.children[0].style[key] = transform + + if not @lastRenderedProgress or @lastRenderedProgress|0 != @progress|0 + # The whole-part of the number has changed + + el.children[0].setAttribute 'data-progress-text', "#{ @progress|0 }%" + + if @progress >= 100 + # We cap it at 99 so we can use prefix-based attribute selectors + progressStr = '99' + else + progressStr = if @progress < 10 then "0" else "" + progressStr += @progress|0 + + el.children[0].setAttribute 'data-progress', "#{ progressStr }" + + @lastRenderedProgress = @progress + + done: -> + @progress >= 100 + +class Events + constructor: -> + @bindings = {} + + trigger: (name, val) -> + if @bindings[name]? + for binding in @bindings[name] + binding.call @, val + + on: (name, fn) -> + @bindings[name] ?= [] + @bindings[name].push fn + +_XMLHttpRequest = window.XMLHttpRequest +_XDomainRequest = window.XDomainRequest +_WebSocket = window.WebSocket + +extendNative = (to, from) -> + for key of from:: + try + if not to[key]? and typeof from[key] isnt 'function' + if typeof Object.defineProperty is 'function' + Object.defineProperty(to, key, { + get: -> + return from::[key]; + , + configurable: true, + enumerable: true }) + else + to[key] = from::[key] + catch e + +ignoreStack = [] + +Pace.ignore = (fn, args...) -> + ignoreStack.unshift 'ignore' + ret = fn(args...) + ignoreStack.shift() + ret + +Pace.track = (fn, args...) -> + ignoreStack.unshift 'track' + ret = fn(args...) + ignoreStack.shift() + ret + +shouldTrack = (method='GET') -> + if ignoreStack[0] is 'track' + return 'force' + + if not ignoreStack.length and options.ajax + if method is 'socket' and options.ajax.trackWebSockets + return true + else if method.toUpperCase() in options.ajax.trackMethods + return true + + return false + +# We should only ever instantiate one of these +class RequestIntercept extends Events + constructor: -> + super + + monitorXHR = (req) => + _open = req.open + req.open = (type, url, async) => + if shouldTrack(type) + @trigger 'request', {type, url, request: req} + + _open.apply req, arguments + + window.XMLHttpRequest = (flags) -> + req = new _XMLHttpRequest(flags) + + monitorXHR req + + req + + try + extendNative window.XMLHttpRequest, _XMLHttpRequest + + if _XDomainRequest? + window.XDomainRequest = -> + req = new _XDomainRequest + + monitorXHR req + + req + + try + extendNative window.XDomainRequest, _XDomainRequest + + if _WebSocket? and options.ajax.trackWebSockets + window.WebSocket = (url, protocols) => + if protocols? + req = new _WebSocket(url, protocols) + else + req = new _WebSocket(url) + + if shouldTrack('socket') + @trigger 'request', {type: 'socket', url, protocols, request: req} + + req + + try + extendNative window.WebSocket, _WebSocket + +_intercept = null +getIntercept = -> + if not _intercept? + _intercept = new RequestIntercept + _intercept + +shouldIgnoreURL = (url) -> + for pattern in options.ajax.ignoreURLs + if typeof pattern is 'string' + if url.indexOf(pattern) isnt -1 + return true + + else + if pattern.test(url) + return true + + return false + +# If we want to start the progress bar +# on every request, we need to hear the request +# and then inject it into the new ajax monitor +# start will have created. + +getIntercept().on 'request', ({type, request, url}) -> + return if shouldIgnoreURL(url) + + if not Pace.running and (options.restartOnRequestAfter isnt false or shouldTrack(type) is 'force') + args = arguments + + after = options.restartOnRequestAfter or 0 + if typeof after is 'boolean' + after = 0 + + setTimeout -> + if type is 'socket' + stillActive = request.readyState < 2 + else + stillActive = 0 < request.readyState < 4 + + if stillActive + Pace.restart() + + for source in Pace.sources + if source instanceof AjaxMonitor + source.watch args... + break + , after + +class AjaxMonitor + constructor: -> + @elements = [] + + getIntercept().on 'request', => @watch arguments... + + watch: ({type, request, url}) -> + return if shouldIgnoreURL(url) + + if type is 'socket' + tracker = new SocketRequestTracker(request) + else + tracker = new XHRRequestTracker(request) + + @elements.push tracker + +class XHRRequestTracker + constructor: (request) -> + @progress = 0 + + if window.ProgressEvent? + # We're dealing with a modern browser with progress event support + + size = null + request.addEventListener 'progress', (evt) => + if evt.lengthComputable + @progress = 100 * evt.loaded / evt.total + else + # If it's chunked encoding, we have no way of knowing the total length of the + # response, all we can do is increment the progress with backoff such that we + # never hit 100% until it's done. + @progress = @progress + (100 - @progress) / 2 + , false + + for event in ['load', 'abort', 'timeout', 'error'] + request.addEventListener event, => + @progress = 100 + , false + + else + _onreadystatechange = request.onreadystatechange + request.onreadystatechange = => + if request.readyState in [0, 4] + @progress = 100 + else if request.readyState is 3 + @progress = 50 + + _onreadystatechange?(arguments...) + +class SocketRequestTracker + constructor: (request) -> + @progress = 0 + + for event in ['error', 'open'] + request.addEventListener event, => + @progress = 100 + , false + +class ElementMonitor + constructor: (options={}) -> + @elements = [] + + options.selectors ?= [] + for selector in options.selectors + @elements.push new ElementTracker selector + +class ElementTracker + constructor: (@selector) -> + @progress = 0 + + @check() + + check: -> + if document.querySelector(@selector) + @done() + else + setTimeout (=> @check()), + options.elements.checkInterval + + done: -> + @progress = 100 + +class DocumentMonitor + states: + loading: 0 + interactive: 50 + complete: 100 + + constructor: -> + @progress = @states[document.readyState] ? 100 + + _onreadystatechange = document.onreadystatechange + document.onreadystatechange = => + if @states[document.readyState]? + @progress = @states[document.readyState] + + _onreadystatechange?(arguments...) + +class EventLagMonitor + constructor: -> + @progress = 0 + + avg = 0 + + samples = [] + + points = 0 + last = now() + interval = setInterval => + diff = now() - last - 50 + last = now() + + samples.push diff + + if samples.length > options.eventLag.sampleCount + samples.shift() + + avg = avgAmplitude samples + + if ++points >= options.eventLag.minSamples and avg < options.eventLag.lagThreshold + @progress = 100 + + clearInterval interval + else + @progress = 100 * (3 / (avg + 3)) + + , 50 + +class Scaler + constructor: (@source) -> + @last = @sinceLastUpdate = 0 + @rate = options.initialRate + @catchup = 0 + @progress = @lastProgress = 0 + + if @source? + @progress = result(@source, 'progress') + + tick: (frameTime, val) -> + val ?= result(@source, 'progress') + + if val >= 100 + @done = true + + if val == @last + @sinceLastUpdate += frameTime + else + if @sinceLastUpdate + @rate = (val - @last) / @sinceLastUpdate + + @catchup = (val - @progress) / options.catchupTime + + @sinceLastUpdate = 0 + @last = val + + if val > @progress + # After we've got a datapoint, we have catchupTime to + # get the progress bar to reflect that new data + @progress += @catchup * frameTime + + scaling = (1 - Math.pow(@progress / 100, options.easeFactor)) + + # Based on the rate of the last update, we preemptively update + # the progress bar, scaling it so it can never hit 100% until we + # know it's done. + @progress += scaling * @rate * frameTime + + @progress = Math.min(@lastProgress + options.maxProgressPerFrame, @progress) + + @progress = Math.max(0, @progress) + @progress = Math.min(100, @progress) + + @lastProgress = @progress + + @progress + +sources = null +scalers = null +bar = null +uniScaler = null +animation = null +cancelAnimation = null +Pace.running = false + +handlePushState = -> + if options.restartOnPushState + Pace.restart() + +# We reset the bar whenever it looks like an ajax navigation has occured. +if window.history.pushState? + _pushState = window.history.pushState + window.history.pushState = -> + handlePushState() + + _pushState.apply window.history, arguments + +if window.history.replaceState? + _replaceState = window.history.replaceState + window.history.replaceState = -> + handlePushState() + + _replaceState.apply window.history, arguments + +SOURCE_KEYS = + ajax: AjaxMonitor + elements: ElementMonitor + document: DocumentMonitor + eventLag: EventLagMonitor + +do init = -> + Pace.sources = sources = [] + + for type in ['ajax', 'elements', 'document', 'eventLag'] + if options[type] isnt false + sources.push new SOURCE_KEYS[type](options[type]) + + for source in options.extraSources ? [] + sources.push new source(options) + + Pace.bar = bar = new Bar + + # Each source of progress data has it's own scaler to smooth its output + scalers = [] + + # We have an extra scaler for the final output to keep things looking nice as we add and + # remove sources + uniScaler = new Scaler + +Pace.stop = -> + Pace.trigger 'stop' + Pace.running = false + + bar.destroy() + + # Not all browsers support cancelAnimationFrame + cancelAnimation = true + + if animation? + cancelAnimationFrame? animation + animation = null + + init() + +Pace.restart = -> + Pace.trigger 'restart' + Pace.stop() + Pace.start() + +Pace.go = -> + Pace.running = true + + bar.render() + + start = now() + + cancelAnimation = false + animation = runAnimation (frameTime, enqueueNextFrame) -> + # Every source gives us a progress number from 0 - 100 + # It's up to us to figure out how to turn that into a smoothly moving bar + # + # Their progress numbers can only increment. We try to interpolate + # between the numbers. + + remaining = 100 - bar.progress + + count = sum = 0 + done = true + # A source is composed of a bunch of elements, each with a raw, unscaled progress + for source, i in sources + scalerList = scalers[i] ?= [] + + elements = source.elements ? [source] + + # Each element is given it's own scaler, which turns its value into something + # smoothed for display + for element, j in elements + scaler = scalerList[j] ?= new Scaler element + + done &= scaler.done + + continue if scaler.done + + count++ + sum += scaler.tick(frameTime) + + avg = sum / count + + bar.update uniScaler.tick(frameTime, avg) + + if bar.done() or done or cancelAnimation + bar.update 100 + + Pace.trigger 'done' + + setTimeout -> + bar.finish() + + Pace.running = false + + Pace.trigger 'hide' + , Math.max(options.ghostTime, Math.max(options.minTime - (now() - start), 0)) + else + enqueueNextFrame() + +Pace.start = (_options) -> + extend options, _options + + Pace.running = true + + try + bar.render() + catch NoTargetError + + # It's usually possible to render a bit before the document declares itself ready + if not document.querySelector('.pace') + setTimeout Pace.start, 50 + else + Pace.trigger 'start' + Pace.go() + +if typeof define is 'function' and define.amd + # AMD + define ['pace'], -> Pace +else if typeof exports is 'object' + # CommonJS + module.exports = Pace +else + # Global + if options.startOnPageLoad + Pace.start() |