<commit_before>/**
 * @fileoverview Preloads model/collection instance data and stores it in a
 * defined variable.
 * @author David Parlevliet
 * @version 20130113
 * @preserve Copyright 2013 David Parlevliet.
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Preloader
 * =========
 * Preloads model/collection constants. These models are the most common objects
 * required for views to function without the need to poll the server. For this
 * to function correctly, every constant MUST have the typeOf variable
 * predefined and matching the constant name.
 *
 * These objects should be attached to a Backbone caching plugin such as to save
 * load time for the end user. Backbone.cachingSync is recommended;
 * https://github.com/ggozad/Backbone.cachingSync
 *
 * Example:
 *   var Foo = Backbone.Collection.extend({
 *     typeOf: 'Foo',
 *     url: '/somewhere/',
 *     model: Bar,
 *     sync: Backbone.cachingSync(Backbone.sync, 'Foo')
 *   });
 *
 * Example Preloader Usage:
 *  Preloader.instances = {
 *    'Source': 'Destination',
 *  };
 *  Preloader.on('Source:loaded', function() {
 *    // do stuff
 *  });
 *  Preloader.on('complete', function() {
 *    Backbone.history.start();
 *  });
 *  $(document).ready(function() {
 *    Preloader.start();
 *  });
 */
var Preloader = _.extend({
  /** Time in seconds until the Preloader gives up and triggers complete */
  timeout     : 10,
  /** false to our fetch requests one at a time or true for all at once. */
  parallel    : true,
  /**
   * If true this.off() events will be called. This is required if you are using
   * Backbone.cachingSync, because 'sync' will be triggered twice -- Once when
   * the data is successfully loaded from cache, and then once more when it is
   * successfully pulled from the server.
   */
  untrigger   : true,
  /**
   * Backbone.Events overrides this and becomes a hook for capturing events.
   * Events List:
   *

   *
__variable__:loading - called when fetch() is called.

   *
__variable__:loaded - called when a variable sync is returned

   *
__variable__:error - called with variable error is returned

   *
loaded - called when any loaded event is triggered

   *
error - called when any variable error is returned

   *
complete - called when all variables are loaded

   *
timeout - called when timeout is reached before compelte

   *

   */
  on          : undefined,
  /** Inherited from Backbone.Events */
  off         : undefined,
  /** Inherited from Backbone.Events */
  trigger     : undefined,
  /**
   * Start the preloading
   */
  start: function() {
    if (this.started) return;
    this.started      = true;
    this.loaded       = 0;
    this.loading      = 0;
    this.records      = {};
    for (var key in this.instances) {
      this.records[key] = {
        'destination': key,
        'source': this.instances[key],
        'status': false
      };
      this.loading += 1;
    }
    this.load_next();
    this.timer = setTimeout(function() {
      Preloader.timedout()
    }, this.timeout*1000);
  },
  /**
   * Start the preloading
   */
  load_next: function() {
    for (var key in this.records) {
      if (this.records[key].status===false) {
        window[this.records[key].destination] = new window[key]();
        var model = window[this.records[key].destination];
        model.on('sync', this.load_complete, this);
        model.on('error', this.error, this);
        this.trigger(key+':loading');
        model.fetch();
        this.records[key].status = 'loading';
        if (this.parallel===false)
          break;
      }
    }
  },
  /**
   * When 'sync' event is triggered by fetch() it returns back here to clean
   * up and fire other events.
   */
  load_complete: function(subject, results, jqXHR) {
    if (subject.typeOf in this.records) {
      this.records[subject.typeOf].status = true;
      this.loaded += 1;
    }
    this.trigger('loaded', subject, jqXHR, response);
    this.trigger(subject.typeOf+':loaded', subject, jqXHR, response);
    if (this.loaded>=this.loading)
      this.complete();
    this.load_next();
    if (!this.untrigger) return;
    this.off(subject.typeOf+':loaded');
    this.off(subject.typeOf+':loading');
  },
  /**
   * When preloading is complete
   */
  complete: function() {
    clearTimeout(this.timer);
    this.trigger('complete', true);
    if (!this.untrigger) return;
    this.off('complete');
    this.off('loaded');
    this.off('timeout');
  },
  /**
   * Handles errors thrown from instances and calls other error triggers for the
   * user to intercept.
   */
  error: function(subject, jqXHR, response) {
    this.trigger(arguments[0].typeOf+':error', arguments);
    this.trigger('error', subject, jqXHR, response);
  },
  /**
   * Called after X seconds defined by this.timeout. If this is called it means
   * that the preloader took too long to complete and the user should do
   * something about it so the user doesn't close the browser out of
   * frustration.
   */
  timedout: function() {
    if (this.loaded!=this.loading)
      this.trigger('timeout');
    this.trigger('complete', false);
  }
}, Backbone.Events);
<commit_msg>Fixed typo & fixed up timeout related callbacks<commit_after>/**
 * @fileoverview Preloads model/collection instance data and stores it in a
 * defined variable.
 * @author David Parlevliet
 * @version 20130113
 * @preserve Copyright 2013 David Parlevliet.
 *
 * Preloader
 * =========
 * Preloads model/collection constants. These models are the most common objects
 * required for views to function without the need to poll the server. For this
 * to function correctly, every constant MUST have the typeOf variable
 * predefined and matching the constant name.
 *
 * These objects should be attached to a Backbone caching plugin such as to save
 * load time for the end user. Backbone.cachingSync is recommended;
 * https://github.com/ggozad/Backbone.cachingSync
 *
 * Example:
 *   var Foo = Backbone.Collection.extend({
 *     typeOf: 'Foo',
 *     url: '/somewhere/',
 *     model: Bar,
 *     sync: Backbone.cachingSync(Backbone.sync, 'Foo')
 *   });
 *
 * Example Preloader Usage:
 *  Preloader.instances = {
 *    'Source': 'Destination',
 *  };
 *  Preloader.on('Source:loaded', function() {
 *    // do stuff
 *  });
 *  Preloader.on('complete', function() {
 *    Backbone.history.start();
 *  });
 *  $(document).ready(function() {
 *    Preloader.start();
 *  });
 */
var Preloader = _.extend({
  /** Time in seconds until the Preloader gives up and triggers complete */
  timeout     : 10,
  /** false to our fetch requests one at a time or true for all at once. */
  parallel    : true,
  /**
   * If true this.off() events will be called. This is required if you are using
   * Backbone.cachingSync, because 'sync' will be triggered twice -- Once when
   * the data is successfully loaded from cache, and then once more when it is
   * successfully pulled from the server.
   */
  untrigger   : true,
  /**
   * Backbone.Events overrides this and becomes a hook for capturing events.
   * Events List:
   *

   *
__variable__:loading - called when fetch() is called.

   *
__variable__:loaded - called when a variable sync is returned

   *
__variable__:error - called with variable error is returned

   *
loaded - called when any loaded event is triggered

   *
error - called when any variable error is returned

   *
complete - called when all variables are loaded

   *
timeout - called when timeout is reached before compelte

   *

   */
  on          : undefined,
  /** Inherited from Backbone.Events */
  off         : undefined,
  /** Inherited from Backbone.Events */
  trigger     : undefined,
  /**
   * Start the preloading
   */
  start: function() {
    if (this.started) return;
    this.started      = true;
    this.loaded       = 0;
    this.loading      = 0;
    this.records      = {};
    for (var key in this.instances) {
      this.records[key] = {
        'destination': key,
        'source': this.instances[key],
        'status': false
      };
      this.loading += 1;
    }
    this.load_next();
    this.timer = setTimeout(function() {
      Preloader.timedout()
    }, this.timeout*1000);
  },
  /**
   * Start the preloading
   */
  load_next: function() {
    for (var key in this.records) {
      if (this.records[key].status===false) {
        window[this.records[key].destination] = new window[key]();
        var model = window[this.records[key].destination];
        model.on('sync', this.load_complete, this);
        model.on('error', this.error, this);
        this.trigger(key+':loading');
        model.fetch();
        this.records[key].status = 'loading';
        if (this.parallel===false)
          break;
      }
    }
  },
  /**
   * When 'sync' event is triggered by fetch() it returns back here to clean
   * up and fire other events.
   */
  load_complete: function(subject, results, jqXHR) {
    if (subject.typeOf in this.records) {
      this.records[subject.typeOf].status = true;
      this.loaded += 1;
    }
    this.trigger('loaded', subject, jqXHR, response);
    this.trigger(subject.typeOf+':loaded', subject, jqXHR, response);
    if (this.loaded>=this.loading)
      this.complete();
    this.load_next();
    if (!this.untrigger) return;
    this.off(subject.typeOf+':loaded');
    this.off(subject.typeOf+':loading');
  },
  /**
   * When preloading is complete
   */
  complete: function() {
    this.trigger('complete', true);
    if (!this.untrigger) return;
    clearTimeout(this.timer);
    this.off('timeout');
    this.off('complete');
    this.off('loaded');
  },
  /**
   * Handles errors thrown from instances and calls other error triggers for the
   * user to intercept.
   */
  error: function(subject, jqXHR, response) {
    this.trigger(arguments[0].typeOf+':error', arguments);
    this.trigger('error', subject, jqXHR, response);
  },
  /**
   * Called after X seconds defined by this.timeout. If this is called it means
   * that the preloader took too long to complete and the user should do
   * something about it so the user doesn't close the browser out of
   * frustration.
   */
  timedout: function() {
    if (this.loaded<this.loading)
      this.trigger('timeout');
    this.trigger('complete', false);
  }
}, Backbone.Events);
<|endoftext|>


<commit_before>/**
 * Config
 */

Humble.Config = {};
Humble.Config.DVZ = {};
Humble.Config.DVZ.url = 'http://www.whatwepayfor.com/api/';
Humble.Config.DVZ.budgetAccount = {
    name : 'getBudgetAccount',
    fields : {
        account : {},
        agency : {},
        bureau : {},
        'function' : {},
        subfunction : {},
        spendingType : {},
        amounti : {},
        mycosti : {}
    }
};

var Color = [

    '#f74e7f',
    '#ff0049',
    '#a53455',

    '#ff6238',
    '#fe4817',
    '#ba3815',

    '#cebe0e',
    '#feeb17',
    '#faec53',

    '#2db408',
    '#43ff10',
    '#81ff5f',

    '#0755fb',
    '#0f79fd',
    '#569ffa',

    '#8f09a7',
    '#d80ffc',
    '#e35dfb'
];

/*
var Color  = new Array(
    '#ff0049', //strawberry
    '#fd7100', //red-orange
    '#f9aa00', //orange
    '#f9e502', //yellow
    '#70fb00', //green
    '#00c2ff', //light blue
    '#0f91ff', //blue
    '#b537b6', //purple
    '#e92192'  //fuschia
);
*/

Humble.Config.DVZ.spendingColors = {
    deficit : '#333333',
    other   : '#777777',
    income  : '#eeeeee'
};
Humble.Config.DVZ.budget = {
    name : 'getBudget',
    fields : {
        dimensionID : {
            name : "Dimension ID"
        },
        dimensionName : {
            name : " Dimension Name"
        },
        spendingType : {},
        amounti : {},
        mycosti : {}
    },
    dimensions : {
        _2771 : {
            name  : "National Defense",
            color : Color[0]
        },
        _2772 : {
            name  : "International Affairs",
            color : Color[1]
        },
        _2773 : {
            name  : "General Science, Space, and Technology",
            color : Color[2]
        },
        _2774 : {
            name  : "Energy",
            color : Color[3]
        },
        _2775 : {
            name  : "Natural Resources and Environment",
            color : Color[4]
        },
        _2776 : {
            name  : "Agriculture",
            color : Color[5]
        },
//        _2777 : {
//            name  : "Commerce and Housing Credit",
//            color : '#7B88FD'
//        },
        _2778 : {
            name  : "Transportation",
            color : Color[6]
        },
        _2779 : {
            name  : "Community and Regional Development",
            color : Color[7]
        },
        _2780 : {
            name  : "Education, Training, Employment, and Social Services",
            color : Color[8]
        },
        _2781 : {
            name  : "Health",
            color : Color[9]
        },
        _2782 : {
            name  : "Medicare",
            color : Color[10]
        },
        _2783 : {
            name  : "Income Security",
            color : Color[11]
        },
        _2784 : {
            name  : "Social Security",
            color : Color[12]
        },
        _2785 : {
            name  : "Veterans Benefits and Services",
            color : Color[13]
        },
        _2786 : {
            name  : "Administration of Justice",
            color : Color[14]
        },
        _2787 : {
            name  : "General Government",
            color : Color[15]
        },
        _2788 : {
            name  : "Net Interest",
            color : Color[16]
        },
        _2789 : {
            name  : "Allowances",
            color : Color[17]
        }
//        _2790 : {
//            name  : "Undistributed Offsetting Receipts",
//            color : '#4DB548'
//        }
    }
}
<commit_msg>Colour changes<commit_after>/**
 * Config
 */

Humble.Config = {};
Humble.Config.DVZ = {};
Humble.Config.DVZ.url = 'http://www.whatwepayfor.com/api/';
Humble.Config.DVZ.budgetAccount = {
    name : 'getBudgetAccount',
    fields : {
        account : {},
        agency : {},
        bureau : {},
        'function' : {},
        subfunction : {},
        spendingType : {},
        amounti : {},
        mycosti : {}
    }
};

var Color = [

    '#f74e7f',
    '#ff0049',
    '#a53455',

    '#ff6238',
    '#fe4817',
    '#ba3815',

    '#cebe0e',
    '#feeb17',
    '#f8ed77',

    '#2db408',
    '#43ff10',
    '#81ff5f',

    '#0755fb',
    '#0f79fd',
    '#569ffa',

    '#8f09a7',
    '#d80ffc',
    '#e35dfb'
];

/*
var Color  = new Array(
    '#ff0049', //strawberry
    '#fd7100', //red-orange
    '#f9aa00', //orange
    '#f9e502', //yellow
    '#70fb00', //green
    '#00c2ff', //light blue
    '#0f91ff', //blue
    '#b537b6', //purple
    '#e92192'  //fuschia
);
*/

Humble.Config.DVZ.spendingColors = {
    deficit : '#333333',
    other   : '#777777',
    income  : '#eeeeee'
};
Humble.Config.DVZ.budget = {
    name : 'getBudget',
    fields : {
        dimensionID : {
            name : "Dimension ID"
        },
        dimensionName : {
            name : " Dimension Name"
        },
        spendingType : {},
        amounti : {},
        mycosti : {}
    },
    dimensions : {
        _2771 : {
            name  : "National Defense",
            color : Color[0]
        },
        _2772 : {
            name  : "International Affairs",
            color : Color[1]
        },
        _2773 : {
            name  : "General Science, Space, and Technology",
            color : Color[2]
        },
        _2774 : {
            name  : "Energy",
            color : Color[3]
        },
        _2775 : {
            name  : "Natural Resources and Environment",
            color : Color[4]
        },
        _2776 : {
            name  : "Agriculture",
            color : Color[5]
        },
//        _2777 : {
//            name  : "Commerce and Housing Credit",
//            color : '#7B88FD'
//        },
        _2778 : {
            name  : "Transportation",
            color : Color[6]
        },
        _2779 : {
            name  : "Community and Regional Development",
            color : Color[7]
        },
        _2780 : {
            name  : "Education, Training, Employment, and Social Services",
            color : Color[8]
        },
        _2781 : {
            name  : "Health",
            color : Color[9]
        },
        _2782 : {
            name  : "Medicare",
            color : Color[10]
        },
        _2783 : {
            name  : "Income Security",
            color : Color[11]
        },
        _2784 : {
            name  : "Social Security",
            color : Color[12]
        },
        _2785 : {
            name  : "Veterans Benefits and Services",
            color : Color[13]
        },
        _2786 : {
            name  : "Administration of Justice",
            color : Color[14]
        },
        _2787 : {
            name  : "General Government",
            color : Color[15]
        },
        _2788 : {
            name  : "Net Interest",
            color : Color[16]
        },
        _2789 : {
            name  : "Allowances",
            color : Color[17]
        }
//        _2790 : {
//            name  : "Undistributed Offsetting Receipts",
//            color : '#4DB548'
//        }
    }
}
<|endoftext|>


<commit_before>function setSelectedPage(){

    var currentPage = location.pathname;
    switch (currentPage){
        case "/":
            document.getElementById("homeLink").setAttribute("class","active");
            document.getElementById("homeLinkMob").setAttribute("class","active");
            break;
        case "/index.php":
            document.getElementById("homeLink").setAttribute("class","active");
            document.getElementById("homeLinkMob").setAttribute("class","active");
            break;
        case "/about.php":
            document.getElementById("aboutLink").setAttribute("class","active");
            document.getElementById("aboutLinkMob").setAttribute("class","active");
            break;
    }

}
<commit_msg>modifed url check to work with new url scheme<commit_after>function setSelectedPage(){

    var currentPage = location.pathname;
    prompt(currentPage);
    switch (currentPage){
        case "/":
            document.getElementById("homeLink").setAttribute("class","active");
            document.getElementById("homeLinkMob").setAttribute("class","active");
            break;
        case "/index.php":
            document.getElementById("homeLink").setAttribute("class","active");
            document.getElementById("homeLinkMob").setAttribute("class","active");
            break;
        case "/about/":
            document.getElementById("aboutLink").setAttribute("class","active");
            document.getElementById("aboutLinkMob").setAttribute("class","active");
            break;
    }

}
<|endoftext|>


<commit_before>const express = require('express');
const path = require('path');

const Router = require('./controllers/Router');

class Hopin {
  constructor({relativePath, publicPath = 'public'}) {
    this._relativePath = relativePath;
    this._publicPath = path.join(relativePath, publicPath);

    this._app = express();
    this._server = null;

    this._router = new Router({
      relativePath,
      publicPath,
    });
  }

  startServer(port) {
    this._app.use(express.static(this._publicPath));

    this._app.use('/', this._router.routes);

    return new Promise((resolve, reject) => {
      this._server = this._app.listen(port, () => {
        resolve(this._server.address());
      });
    });
  }

  stopServer() {
    return new Promise((resolve) => this._server.close(resolve));
  }
}

module.exports = Hopin;
<commit_msg>Fix public path (#11)<commit_after>const express = require('express');
const path = require('path');

const Router = require('./controllers/Router');

class Hopin {
  constructor({relativePath, publicPath = 'public'}) {
    this._relativePath = relativePath;
    this._publicPath = path.join(relativePath, publicPath);

    this._app = express();
    this._server = null;

    this._router = new Router({
      relativePath: this._relativePath,
      publicPath: this._publicPath,
    });
  }

  startServer(port) {
    this._app.use(express.static(this._publicPath));

    this._app.use('/', this._router.routes);

    return new Promise((resolve, reject) => {
      this._server = this._app.listen(port, () => {
        resolve(this._server.address());
      });
    });
  }

  stopServer() {
    return new Promise((resolve) => this._server.close(resolve));
  }
}

module.exports = Hopin;
<|endoftext|>


<commit_before>/*!
 * wq.app 0.7.4-dev - wq/map.js
 * Leaflet integration for app.js list & detail views
 * (c) 2013-2015, S. Andrew Sheppard
 * https://wq.io/license
 */

define(['leaflet', 'jquery', './app', './pages', './json', './spinner',
        './template', './console', 'es5-shim'],
function(L, $, app, pages, json, spin, tmpl, console) {

/* global require */

// module variable
var map = {};

// module configuration
map.config = {
    'maps': {}, // Auto-populated from app.config.pages where map == true
    'defaults': {
        'zoom': 0,
        'center': [0, 0],
        'autoZoom': {
            'wait': 0.5, // How long to wait before triggering autoZoom
            'sticky': true, // Start new maps in same location as old maps

            // Settings for fitBounds
            'maxZoom': 13,
            'animate': true
        },

        // Defaults to simplify creation of new icons of the same dimensions
        // as L.Icon.Default
        'icon': {
            'iconSize':    [25, 41],
            'iconAnchor':  [12, 41],
            'popupAnchor': [1, -34],
            'shadowSize':  [41, 41],
            'shadowUrl':  L.Icon.Default.imagePath + '/marker-shadow.png'
        },

        'owl': false
    }
};

// References to generated map objects
map.maps = {};

// References to generated icons
map.icons = {
    'default': new L.Icon.Default()
};

// This should be called after app.init()
map.init = function(defaults) {
    // Auto-detect whether CRS-aware GeoJSON parser is available
    map.geoJson = L.Proj ? L.Proj.geoJson : L.geoJson;

    if (defaults)
        L.extend(map.config.defaults, defaults);

    // Define map configuration for all app pages with map=True
    for (var page in app.config.pages) {
        var pconf = L.extend({}, app.config.pages[page]);
        if (!pconf.map)
            continue;
        pconf.layers = [];
        map.config.maps[page] = pconf;

        // Register onshow events
        if (pconf.list) {
            pconf.autoLayers = true;
            _registerList(page);
            _registerDetail(page);
        } else {
            _registerOther(page);
        }
    }
};

// Register an onshow event for list views
function _registerList(page) {
    var mapconf = _getConf(page);
    var url = mapconf.url ? mapconf.url + '/' : '';
    pages.addRoute(url, 's', function() {
        map.createMap(page);
    });

    // Special handling for /[parent_list_url]/[parent_id]/[url]
    for (var ppage in app.getParents(page)) {
        var pconf = app.config.pages[ppage];
        var purl = pconf.url;
        if (url)
            purl += '/';
        purl += '<slug>/' + mapconf.url;
        pages.addRoute(purl, 's', goUrl);
        pages.addRoute(purl + '/', 's', goUrl);
    }

    function goUrl(match, ui, params) {
        // Override URL for this map's main layer to enable filter by parent
        var override = {};
        var url = match[0].substring(1).split("?")[0];
        if (params)
            url += ".geojson" + L.Util.getParamString(params);
        override[mapconf.name] = {'url': url};
        map.createMap(page, undefined, override);
    }
}

// Register an onshow event for item detail views
function _registerDetail(page) {
    var mapconf = _getConf(page);
    var url = mapconf.url ? mapconf.url + '/' : '';
    pages.addRoute(url + '<slug>', 's', function(match) {
        var itemid = match[1];
        if (itemid == 'new')
            return;
        map.createMap(page, itemid);
    });
}

// Register an onshow event for non-list single pages
function _registerOther(page) {
    var mapconf = _getConf(page);
    var url = mapconf.url;
    pages.addRoute(url, 's', function() {
        map.createMap(page);
    });
}

// Add a layer configuration (layerconf) to a map configuration
map.addLayerConf = function(page, layerconf) {
    var mapconf = _getConf(page);
    mapconf.layers.push(layerconf);
};

// Define an icon for use by list items displayed as points
map.createIcon = function(name, options) {
    options = L.extend({}, map.config.defaults.icon, options);
    map.icons[name] = L.icon(options);
};

// Load map configuration for the given page
map.getLayerConfs = function(page, itemid) {
    var mapconf = _getConf(page);
    var layers = mapconf.layers.slice();

    if (!mapconf.autoLayers)
        return layers;

    if (itemid) {
        // Automatically load geojson for the current item
        layers.push({
            'name': mapconf.name,
            'url': mapconf.url + '/' + itemid,
            'oneach': map.renderPopup(page)
        });
    } else {
        // Automatically load geojson for the entire list
        layers.push({
            'name': mapconf.name,
            'url': mapconf.url,
            'oneach': map.renderPopup(page),
            'cluster': true
        });
    }
    return layers;
};

// Internal layer loading function - override to customize
var _cache = {};
map.loadLayer = function(url, callback) {
    url = app.service + '/' + url;
    if (url.indexOf('.geojson') == -1)
        url += '.geojson';
    if (_cache[url]) {
        setTimeout(function() {
            callback(_cache[url]);
        }, 100);
        return;
    }
    spin.start();
    json.get(url, function(geojson) {
        spin.stop();
        _cache[url] = geojson;
        callback(geojson);
    });
};

// Default base maps - override to customize
map.createBaseMaps = function() {
    /* jshint maxlen: false */
    var mqcdn = "http://otile{s}.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.png";

    // Attribution (https://gist.github.com/mourner/1804938)
    var osmAttr = 'Map data © OpenStreetMap contributors, CC-BY-SA';
    var aerialAttr = 'Imagery © NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency';
    var mqTilesAttr = 'Tiles © MapQuest ';

    return {
        "Street": L.tileLayer(mqcdn, {
            'subdomains': '1234',
            'type': 'map',
            'attribution': osmAttr + ', ' + mqTilesAttr
        }),
        "Aerial": L.tileLayer(mqcdn, {
            'subdomains': '1234',
            'type': 'sat',
            'attribution': aerialAttr + ', ' + mqTilesAttr
        })
    };
};

map.createLayerControl = function(basemaps, layers) {
    return L.control.layers(basemaps, layers);
};

// Default popup renderer for items - override to customize
// (assumes template called [page]_popup)
map.renderPopup = function(page) {
    var owl = map.config.defaults.owl && require('wq/owl');
    return function(feat, layer) {
        var attrs = L.extend({'id': feat.id}, feat.properties);
        layer.bindPopup(
            tmpl.render(page + '_popup', attrs)
        );
        if (owl) {
            layer.on('click', function() {
                owl('map:layerclick', {'page': page, 'id': feat.id});
            });
        }
    };
};

// Primary map routine
map.createMap = function(page, itemid, override) {
    var mapid, divid, mapconf, m, defaults,
        layerConfs, remaining, layers,
        basemaps, basemap, div, owl;

    // Load configuration and div id
    mapconf = _getConf(page);
    defaults = map.config.defaults;
    // If defaults.owl, assume wq/owl has been async-loaded already
    owl = defaults.owl && require('wq/owl');

    if (mapconf.list && itemid)
        mapid = page + '-' + itemid;
    else
        mapid = page;

    if (mapconf.div)
        divid = mapconf.div;
    else
        divid = mapid + '-map';

    div = L.DomUtil.get(divid);
    if (!div) {
        // Skip map creation if the expected div doesn't exist
        console.log(divid + ' not found; skipping map creation');
        return;
    }

    // Make sure leaflet hasn't already been initialized for this map
    if (div._leaflet) {
        // This is probably an onshow event for a page that was rendered
        // and then went offscreen before coming back; refresh layout.
        m = map.maps[mapid];
        m.invalidateSize();
        if (defaults.autoZoom.sticky) {
            m.setView(defaults.center, defaults.zoom);
        }
        return;
    }

    // Create map, set default zoom and basemap
    m = map.maps[mapid] = L.map(divid);
    m.setView(defaults.center, defaults.zoom);
    basemaps = map.createBaseMaps();
    basemap = Object.keys(basemaps)[0];
    basemaps[basemap].addTo(m);

    // Load layerconfs and add empty layer groups to map
    layers = {};
    layerConfs = map.getLayerConfs(page, itemid);
    remaining = layerConfs.length;
    layerConfs.forEach(function(layerconf, i) {
        layerconf = L.extend({}, layerconf,
                             override && override[layerconf.name]);
        if (layerconf.cluster && L.MarkerClusterGroup) {
            var options = {};
            if (layerconf.clusterIcon)
                options.iconCreateFunction = layerconf.clusterIcon;
            layerconf.layer = new L.MarkerClusterGroup(options).addTo(m);
        } else {
            layerconf.layer = L.featureGroup().addTo(m);
        }
        loadLayer(layerconf);
        layers[layerconf.name] = layerconf.layer;
        layerConfs[i] = layerconf;
    });

    map.createLayerControl(basemaps, layers).addTo(m);

    // Async-load geojson for layers and add to layergroups
    function loadLayer(layerconf) {
        map.loadLayer(layerconf.url, function(geojson) {
            var options = {};
            if (layerconf.oneach)
                options.onEachFeature = layerconf.oneach;
            if (layerconf.icon)
                options.pointToLayer = _makeMarker(layerconf.icon);
            if (layerconf.style)
                options.style = layerconf.style;
            map.geoJson(geojson, options).addTo(layerconf.layer);
            remaining--;
            if (!remaining)
                autoZoom();
        });
    }

    function autoZoom() {
        if (mapconf.autoZoom !== undefined && !mapconf.autoZoom)
            return;
        if (!map.config.defaults.autoZoom)
            return;
        var bounds = layerConfs[0].layer.getBounds();
        if (layerConfs.length > 1) {
            layerConfs.slice(1).forEach(function(layerconf) {
                bounds.extend(layerconf.layer.getBounds());
            });
        }
        if (mapconf.minBounds) {
            bounds.extend(mapconf.minBounds);
        }
        setTimeout(function() {
            m.fitBounds(bounds, map.config.defaults.autoZoom);
        }, map.config.defaults.autoZoom.wait * 1000);
    }

    if (map.config.defaults.autoZoom.sticky) {
        m.on('moveend', function() {
            map.config.defaults.zoom = m.getZoom();
            map.config.defaults.center = m.getCenter();
        });
    }
    if (owl) {
        m.on('moveend', function() {
            owl('map:moveend', {
                'zoom': m.getZoom(),
                'center': m.getCenter(),
                'bounds': m.getBounds()
            });
        });
        [
            'baselayerchange',
            'overlayadd',
            'overlayremove'
        ].forEach(_layerEvent);
    }
    function _layerEvent(name) {
        m.on(name, function(evt) {
            owl('map:' + name, {'layer': evt.name});
        });
    }

    // Ensure valid layout on screen
    setTimeout(function() {
        m.invalidateSize();
    }, 100);

    // Try to ensure no Leaflet widgets are enhanced by jQuery Mobile
    var $controls = $(div).find(".leaflet-control-container");
    $controls.find("input").attr("data-role", "none");

    if (mapconf.onshow)
        mapconf.onshow(m);

    return m;
};

// Internal function for creating markers (used with layerconf.icon)
function _makeMarker(icon) {
    return function pointToLayer(geojson, latlng) {
        // Define icon as a function to customize per-feature
        var key;
        if (typeof icon == 'function')
            key = icon(geojson.properties);
        else
            key = icon;
        return L.marker(latlng, {'icon': map.icons[key]});
    };
}

// Load map configuration for a given page
function _getConf(page) {
    var mapconf = map.config.maps[page];
    if (!mapconf)
        throw 'Configuration for "' + page + '" not found!';
    return mapconf;
}

return map;

});
<commit_msg>export cache for customization<commit_after>/*!
 * wq.app 0.7.4-dev - wq/map.js
 * Leaflet integration for app.js list & detail views
 * (c) 2013-2015, S. Andrew Sheppard
 * https://wq.io/license
 */

define(['leaflet', 'jquery', './app', './pages', './json', './spinner',
        './template', './console', 'es5-shim'],
function(L, $, app, pages, json, spin, tmpl, console) {

/* global require */

// module variable
var map = {};

// module configuration
map.config = {
    'maps': {}, // Auto-populated from app.config.pages where map == true
    'defaults': {
        'zoom': 0,
        'center': [0, 0],
        'autoZoom': {
            'wait': 0.5, // How long to wait before triggering autoZoom
            'sticky': true, // Start new maps in same location as old maps

            // Settings for fitBounds
            'maxZoom': 13,
            'animate': true
        },

        // Defaults to simplify creation of new icons of the same dimensions
        // as L.Icon.Default
        'icon': {
            'iconSize':    [25, 41],
            'iconAnchor':  [12, 41],
            'popupAnchor': [1, -34],
            'shadowSize':  [41, 41],
            'shadowUrl':  L.Icon.Default.imagePath + '/marker-shadow.png'
        },

        'owl': false
    }
};

// References to generated map objects
map.maps = {};

// References to generated icons
map.icons = {
    'default': new L.Icon.Default()
};

// This should be called after app.init()
map.init = function(defaults) {
    // Auto-detect whether CRS-aware GeoJSON parser is available
    map.geoJson = L.Proj ? L.Proj.geoJson : L.geoJson;

    if (defaults)
        L.extend(map.config.defaults, defaults);

    // Define map configuration for all app pages with map=True
    for (var page in app.config.pages) {
        var pconf = L.extend({}, app.config.pages[page]);
        if (!pconf.map)
            continue;
        pconf.layers = [];
        map.config.maps[page] = pconf;

        // Register onshow events
        if (pconf.list) {
            pconf.autoLayers = true;
            _registerList(page);
            _registerDetail(page);
        } else {
            _registerOther(page);
        }
    }
};

// Register an onshow event for list views
function _registerList(page) {
    var mapconf = _getConf(page);
    var url = mapconf.url ? mapconf.url + '/' : '';
    pages.addRoute(url, 's', function() {
        map.createMap(page);
    });

    // Special handling for /[parent_list_url]/[parent_id]/[url]
    for (var ppage in app.getParents(page)) {
        var pconf = app.config.pages[ppage];
        var purl = pconf.url;
        if (url)
            purl += '/';
        purl += '<slug>/' + mapconf.url;
        pages.addRoute(purl, 's', goUrl);
        pages.addRoute(purl + '/', 's', goUrl);
    }

    function goUrl(match, ui, params) {
        // Override URL for this map's main layer to enable filter by parent
        var override = {};
        var url = match[0].substring(1).split("?")[0];
        if (params)
            url += ".geojson" + L.Util.getParamString(params);
        override[mapconf.name] = {'url': url};
        map.createMap(page, undefined, override);
    }
}

// Register an onshow event for item detail views
function _registerDetail(page) {
    var mapconf = _getConf(page);
    var url = mapconf.url ? mapconf.url + '/' : '';
    pages.addRoute(url + '<slug>', 's', function(match) {
        var itemid = match[1];
        if (itemid == 'new')
            return;
        map.createMap(page, itemid);
    });
}

// Register an onshow event for non-list single pages
function _registerOther(page) {
    var mapconf = _getConf(page);
    var url = mapconf.url;
    pages.addRoute(url, 's', function() {
        map.createMap(page);
    });
}

// Add a layer configuration (layerconf) to a map configuration
map.addLayerConf = function(page, layerconf) {
    var mapconf = _getConf(page);
    mapconf.layers.push(layerconf);
};

// Define an icon for use by list items displayed as points
map.createIcon = function(name, options) {
    options = L.extend({}, map.config.defaults.icon, options);
    map.icons[name] = L.icon(options);
};

// Load map configuration for the given page
map.getLayerConfs = function(page, itemid) {
    var mapconf = _getConf(page);
    var layers = mapconf.layers.slice();

    if (!mapconf.autoLayers)
        return layers;

    if (itemid) {
        // Automatically load geojson for the current item
        layers.push({
            'name': mapconf.name,
            'url': mapconf.url + '/' + itemid,
            'oneach': map.renderPopup(page)
        });
    } else {
        // Automatically load geojson for the entire list
        layers.push({
            'name': mapconf.name,
            'url': mapconf.url,
            'oneach': map.renderPopup(page),
            'cluster': true
        });
    }
    return layers;
};

// Internal layer loading function - override to customize
map.cache = {};
map.loadLayer = function(url, callback) {
    url = app.service + '/' + url;
    if (url.indexOf('.geojson') == -1)
        url += '.geojson';
    if (map.cache[url]) {
        setTimeout(function() {
            callback(map.cache[url]);
        }, 100);
        return;
    }
    spin.start();
    json.get(url, function(geojson) {
        spin.stop();
        map.cache[url] = geojson;
        callback(geojson);
    });
};

// Default base maps - override to customize
map.createBaseMaps = function() {
    /* jshint maxlen: false */
    var mqcdn = "http://otile{s}.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.png";

    // Attribution (https://gist.github.com/mourner/1804938)
    var osmAttr = 'Map data © OpenStreetMap contributors, CC-BY-SA';
    var aerialAttr = 'Imagery © NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency';
    var mqTilesAttr = 'Tiles © MapQuest ';

    return {
        "Street": L.tileLayer(mqcdn, {
            'subdomains': '1234',
            'type': 'map',
            'attribution': osmAttr + ', ' + mqTilesAttr
        }),
        "Aerial": L.tileLayer(mqcdn, {
            'subdomains': '1234',
            'type': 'sat',
            'attribution': aerialAttr + ', ' + mqTilesAttr
        })
    };
};

map.createLayerControl = function(basemaps, layers) {
    return L.control.layers(basemaps, layers);
};

// Default popup renderer for items - override to customize
// (assumes template called [page]_popup)
map.renderPopup = function(page) {
    var owl = map.config.defaults.owl && require('wq/owl');
    return function(feat, layer) {
        var attrs = L.extend({'id': feat.id}, feat.properties);
        layer.bindPopup(
            tmpl.render(page + '_popup', attrs)
        );
        if (owl) {
            layer.on('click', function() {
                owl('map:layerclick', {'page': page, 'id': feat.id});
            });
        }
    };
};

// Primary map routine
map.createMap = function(page, itemid, override) {
    var mapid, divid, mapconf, m, defaults,
        layerConfs, remaining, layers,
        basemaps, basemap, div, owl;

    // Load configuration and div id
    mapconf = _getConf(page);
    defaults = map.config.defaults;
    // If defaults.owl, assume wq/owl has been async-loaded already
    owl = defaults.owl && require('wq/owl');

    if (mapconf.list && itemid)
        mapid = page + '-' + itemid;
    else
        mapid = page;

    if (mapconf.div)
        divid = mapconf.div;
    else
        divid = mapid + '-map';

    div = L.DomUtil.get(divid);
    if (!div) {
        // Skip map creation if the expected div doesn't exist
        console.log(divid + ' not found; skipping map creation');
        return;
    }

    // Make sure leaflet hasn't already been initialized for this map
    if (div._leaflet) {
        // This is probably an onshow event for a page that was rendered
        // and then went offscreen before coming back; refresh layout.
        m = map.maps[mapid];
        m.invalidateSize();
        if (defaults.autoZoom.sticky) {
            m.setView(defaults.center, defaults.zoom);
        }
        return;
    }

    // Create map, set default zoom and basemap
    m = map.maps[mapid] = L.map(divid);
    m.setView(defaults.center, defaults.zoom);
    basemaps = map.createBaseMaps();
    basemap = Object.keys(basemaps)[0];
    basemaps[basemap].addTo(m);

    // Load layerconfs and add empty layer groups to map
    layers = {};
    layerConfs = map.getLayerConfs(page, itemid);
    remaining = layerConfs.length;
    layerConfs.forEach(function(layerconf, i) {
        layerconf = L.extend({}, layerconf,
                             override && override[layerconf.name]);
        if (layerconf.cluster && L.MarkerClusterGroup) {
            var options = {};
            if (layerconf.clusterIcon)
                options.iconCreateFunction = layerconf.clusterIcon;
            layerconf.layer = new L.MarkerClusterGroup(options).addTo(m);
        } else {
            layerconf.layer = L.featureGroup().addTo(m);
        }
        loadLayer(layerconf);
        layers[layerconf.name] = layerconf.layer;
        layerConfs[i] = layerconf;
    });

    map.createLayerControl(basemaps, layers).addTo(m);

    // Async-load geojson for layers and add to layergroups
    function loadLayer(layerconf) {
        map.loadLayer(layerconf.url, function(geojson) {
            var options = {};
            if (layerconf.oneach)
                options.onEachFeature = layerconf.oneach;
            if (layerconf.icon)
                options.pointToLayer = _makeMarker(layerconf.icon);
            if (layerconf.style)
                options.style = layerconf.style;
            map.geoJson(geojson, options).addTo(layerconf.layer);
            remaining--;
            if (!remaining)
                autoZoom();
        });
    }

    function autoZoom() {
        if (mapconf.autoZoom !== undefined && !mapconf.autoZoom)
            return;
        if (!map.config.defaults.autoZoom)
            return;
        var bounds = layerConfs[0].layer.getBounds();
        if (layerConfs.length > 1) {
            layerConfs.slice(1).forEach(function(layerconf) {
                bounds.extend(layerconf.layer.getBounds());
            });
        }
        if (mapconf.minBounds) {
            bounds.extend(mapconf.minBounds);
        }
        setTimeout(function() {
            m.fitBounds(bounds, map.config.defaults.autoZoom);
        }, map.config.defaults.autoZoom.wait * 1000);
    }

    if (map.config.defaults.autoZoom.sticky) {
        m.on('moveend', function() {
            map.config.defaults.zoom = m.getZoom();
            map.config.defaults.center = m.getCenter();
        });
    }
    if (owl) {
        m.on('moveend', function() {
            owl('map:moveend', {
                'zoom': m.getZoom(),
                'center': m.getCenter(),
                'bounds': m.getBounds()
            });
        });
        [
            'baselayerchange',
            'overlayadd',
            'overlayremove'
        ].forEach(_layerEvent);
    }
    function _layerEvent(name) {
        m.on(name, function(evt) {
            owl('map:' + name, {'layer': evt.name});
        });
    }

    // Ensure valid layout on screen
    setTimeout(function() {
        m.invalidateSize();
    }, 100);

    // Try to ensure no Leaflet widgets are enhanced by jQuery Mobile
    var $controls = $(div).find(".leaflet-control-container");
    $controls.find("input").attr("data-role", "none");

    if (mapconf.onshow)
        mapconf.onshow(m);

    return m;
};

// Internal function for creating markers (used with layerconf.icon)
function _makeMarker(icon) {
    return function pointToLayer(geojson, latlng) {
        // Define icon as a function to customize per-feature
        var key;
        if (typeof icon == 'function')
            key = icon(geojson.properties);
        else
            key = icon;
        return L.marker(latlng, {'icon': map.icons[key]});
    };
}

// Load map configuration for a given page
function _getConf(page) {
    var mapconf = map.config.maps[page];
    if (!mapconf)
        throw 'Configuration for "' + page + '" not found!';
    return mapconf;
}

return map;

});
<|endoftext|>


<commit_before>export const UPDATE_TOKEN = 'auth/UPDATE_TOKEN';

export function updateToken(token) {
  return { type: UPDATE_TOKEN, token };
}

const initialState = {
  token: ''
};

export function authReducer(state = initialState, action) {
  const { token, type } = action;
  switch (type) {
  case UPDATE_TOKEN:
    return { token };
  default:
    return state;
  }
}

export default store => next => action => {
  if (!action.withToken) return next(action);

  const state = store.getState();
  next(action.withToken(state.token));
}
<commit_msg>Add withToken helper method<commit_after>export const UPDATE_TOKEN = 'auth/UPDATE_TOKEN';

export function updateToken(token) {
  return { type: UPDATE_TOKEN, token };
}

const initialState = {
  token: ''
};

export function authReducer(state = initialState, action) {
  const { token, type } = action;
  switch (type) {
  case UPDATE_TOKEN:
    return { token };
  default:
    return state;
  }
}

export function withToken(fn) {
  return {
    withToken: fn
  };
}

export default store => next => action => {
  if (!action.withToken) return next(action);

  const state = store.getState();
  next(action.withToken(state.token));
}
<|endoftext|>


<commit_before>/*
 * wq.app 0.8.1-dev - wq/owl.js
 * Client for the Offline Web Log service.
 * (c) 2014-2015, S. Andrew Sheppard
 * https://wq.io/license
 */

define(['wq/store', 'jquery', 'jquery.mobile'],
function(ds, $) {

var _referer = document.referrer;

// Module object is main logging function
function owl(action, data, path) {
    if (!owl.config.key) {
        throw "Missing log key (hint: try owl.init)!";
    }
    if (!action) {
        action = "view";
    }
    if (!path) {
        path = owl.getPath();
    }
    if (path.length > 255) {
        if (!data) {
            data = {};
        }
        data.path = path;
        path = path.substring(0, 255);
    }
    var post = {
        "path": path,
        "action": action,
        "client_date": Date.now() / 1000
    };
    if (action == "view" || action == "init") {
        post.referer = _referer;
        if (action == "view") {
            _referer = document.location.href;
        }
    }
    if (data) {
        post.data = JSON.stringify(data);
    }
    var queue = owl.ds.get(owl.config.key) || [];
    queue.push(post);
    owl.ds.set(owl.config.key, queue);
}

owl.config = {};
owl.ds = ds.getStore('owl');

// Initiallize configuration and set up event listeners
owl.init = function init(config) {
    owl.config = $.extend({
        'key': "" + Math.round(1e10 * Math.random()),
        'url': "/owl",
        'ping': 30,
        'sync': 1,
        'scroll': true,
        'maxFailures': 5,
        'wait': 60
    }, config);
    owl("init", owl.stats());
    owl.syncAll();

    $('body').on('pagecontainershow', function() {
        owl("view");
    });

    $('body').on('click', 'a[rel="external"]', function(evt) {
        owl("link", {'url': evt.target.href});
    });

    Object.keys(owl.events).forEach(function(evt) {
        $(window).on(evt, function() {
            var data;
            if (owl.events[evt]) {
                data = owl.events[evt]();
            }
            owl("event:" + evt, data);
        });
    });

    $(window).on('unload', function() {
        owl('event:unload');
        owl.sync(owl.config.key, true);
    });

    if (owl.config.sync) {
        owl._syncInterval = setInterval(owl.sync, owl.config.sync * 1000);
    }
    if (owl.config.ping) {
        owl._pingInterval = setInterval(function() {
            owl("ping");
        }, owl.config.ping * 1000);
    }
};

// Sync log to server (with logic to handle errors & prevent simultaneous sync)
var _syncing = {};
var _waiting = {};
owl.sync = function sync(key, forceSync) {
    if (!key) {
        key = owl.config.key;
    }
    if (!key) {
        throw "Missing log key (hint: try owl.init)!";
    }
    if (!owl.config.url) {
        throw "Missing server URL (hint: try owl.init)!";
    }

    if (_syncing[key] && !forceSync) {
        return;
    }

    var queue = owl.ds.get(key);
    if (!queue) {
        return;
    }

    var failkey = key + '-fail';
    var isOldQueue = (key != owl.config.key);
    if (!queue.length) {
        if (isOldQueue) {
            owl.ds.set(key, null);
            owl.ds.set(failkey, null);
        }
        return;
    }

    var failures = owl.ds.get(failkey) || 0;
    if (failures > owl.config.maxFailures && !forceSync) {
        if (!_waiting[key]) {
            _waiting[key] = setTimeout(function() {
                owl.ds.set(failkey, 0);
                _waiting[key] = false;
            }, owl.config.wait * 1000);
        }
        return;
    }

    _syncing[key] = true;
    queue.forEach(function(d) {
        d.client_key = key;
    });

    var async = !forceSync;

    $.ajax(owl.config.url, {
        'type': 'POST',
        'async': async,
        'data': JSON.stringify(queue),
        'contentType': "application/json",
        'beforeSend': function(xhr) {
            var cookie = document.cookie.split(';');
            var csrftoken = null;
            cookie.forEach(function(c) {
                var vals = c.trim().split('=');
                if (vals[0].trim() == "csrftoken") {
                    csrftoken = vals[1].trim();
                }
            });
            if (!csrftoken) {
                return;
            }
            xhr.setRequestHeader('X-CSRFToken', csrftoken);
        },
        'success': function success() {
            _syncing[key] = false;
            // Don't wipe out newly queued items that haven't been synced yet
            var newqueue = owl.ds.get(key).filter(function(d){
                return !d.client_key;
            });

            if (!newqueue.length && isOldQueue) {
                // Delete old queues when they are no longer needed
                owl.ds.set(key, null);
                owl.ds.set(failkey, null);
            } else {
                owl.ds.set(key, newqueue);
                owl.ds.set(failkey, 0);
            }
        },
        'error': function error(jqxhr, text, err) {
            _syncing[key] = false;
            owl('sendfail', {
                'message': text,
                'error': err
            });
            failures++;
            owl.ds.set(failkey, failures);
        }
    });
};

owl.syncAll = function() {
    owl.ds.keys().forEach(function(key){
        if (key.indexOf('-fail') == -1) {
            owl.sync(key);
        }
    });
};

owl.events = {
    'throttledresize': function() {
        var $win = $(window);
        return {
            'width': $win.width(),
            'height': $win.height()
        };
    },
    'scrollstop': function() {
        var $win = $(window);
        return {
            'top': $win.scrollTop(),
            'left': $win.scrollLeft()
        };
    }
};

// Utility to get the current URL path
owl.getPath = function getPath() {
    // $('.ui-page-active').data("url");
    var loc = window.location;
    return loc.pathname + loc.search;
};

// Utility to get various browser & app information
owl.stats = function() {
    return {
        // App info
        'version': owl.config.version,

        // Browser info
        'standalone': navigator.standalone,
        'size': owl.events.throttledresize(),
        'scroll': owl.events.scrollstop(),

        // Cache status
        'localstorage': ds.localStorageUsage(),
        'appcache': (
            window.applicationCache ? window.applicationCache.status : null
        )
    };
};

return owl;

});
<commit_msg>capture more external links<commit_after>/*
 * wq.app 0.8.1-dev - wq/owl.js
 * Client for the Offline Web Log service.
 * (c) 2014-2015, S. Andrew Sheppard
 * https://wq.io/license
 */

define(['wq/store', 'jquery', 'jquery.mobile'],
function(ds, $) {

var _referer = document.referrer;

// Module object is main logging function
function owl(action, data, path) {
    if (!owl.config.key) {
        throw "Missing log key (hint: try owl.init)!";
    }
    if (!action) {
        action = "view";
    }
    if (!path) {
        path = owl.getPath();
    }
    if (path.length > 255) {
        if (!data) {
            data = {};
        }
        data.path = path;
        path = path.substring(0, 255);
    }
    var post = {
        "path": path,
        "action": action,
        "client_date": Date.now() / 1000
    };
    if (action == "view" || action == "init") {
        post.referer = _referer;
        if (action == "view") {
            _referer = document.location.href;
        }
    }
    if (data) {
        post.data = JSON.stringify(data);
    }
    var queue = owl.ds.get(owl.config.key) || [];
    queue.push(post);
    owl.ds.set(owl.config.key, queue);
}

owl.config = {};
owl.ds = ds.getStore('owl');

// Initiallize configuration and set up event listeners
owl.init = function init(config) {
    owl.config = $.extend({
        'key': "" + Math.round(1e10 * Math.random()),
        'url': "/owl",
        'ping': 30,
        'sync': 1,
        'scroll': true,
        'maxFailures': 5,
        'wait': 60
    }, config);
    owl("init", owl.stats());
    owl.syncAll();

    $('body').on('pagecontainershow', function() {
        owl("view");
    });

    $('body').on('click', 'a', function(evt) {
        var url = evt.target.href;
        if (!url || url.indexOf(window.location.origin) === 0) {
            return;
        }
        owl("link", {'url': url});
    });

    Object.keys(owl.events).forEach(function(evt) {
        $(window).on(evt, function() {
            var data;
            if (owl.events[evt]) {
                data = owl.events[evt]();
            }
            owl("event:" + evt, data);
        });
    });

    $(window).on('unload', function() {
        owl('event:unload');
        owl.sync(owl.config.key, true);
    });

    if (owl.config.sync) {
        owl._syncInterval = setInterval(owl.sync, owl.config.sync * 1000);
    }
    if (owl.config.ping) {
        owl._pingInterval = setInterval(function() {
            owl("ping");
        }, owl.config.ping * 1000);
    }
};

// Sync log to server (with logic to handle errors & prevent simultaneous sync)
var _syncing = {};
var _waiting = {};
owl.sync = function sync(key, forceSync) {
    if (!key) {
        key = owl.config.key;
    }
    if (!key) {
        throw "Missing log key (hint: try owl.init)!";
    }
    if (!owl.config.url) {
        throw "Missing server URL (hint: try owl.init)!";
    }

    if (_syncing[key] && !forceSync) {
        return;
    }

    var queue = owl.ds.get(key);
    if (!queue) {
        return;
    }

    var failkey = key + '-fail';
    var isOldQueue = (key != owl.config.key);
    if (!queue.length) {
        if (isOldQueue) {
            owl.ds.set(key, null);
            owl.ds.set(failkey, null);
        }
        return;
    }

    var failures = owl.ds.get(failkey) || 0;
    if (failures > owl.config.maxFailures && !forceSync) {
        if (!_waiting[key]) {
            _waiting[key] = setTimeout(function() {
                owl.ds.set(failkey, 0);
                _waiting[key] = false;
            }, owl.config.wait * 1000);
        }
        return;
    }

    _syncing[key] = true;
    queue.forEach(function(d) {
        d.client_key = key;
    });

    var async = !forceSync;

    $.ajax(owl.config.url, {
        'type': 'POST',
        'async': async,
        'data': JSON.stringify(queue),
        'contentType': "application/json",
        'beforeSend': function(xhr) {
            var cookie = document.cookie.split(';');
            var csrftoken = null;
            cookie.forEach(function(c) {
                var vals = c.trim().split('=');
                if (vals[0].trim() == "csrftoken") {
                    csrftoken = vals[1].trim();
                }
            });
            if (!csrftoken) {
                return;
            }
            xhr.setRequestHeader('X-CSRFToken', csrftoken);
        },
        'success': function success() {
            _syncing[key] = false;
            // Don't wipe out newly queued items that haven't been synced yet
            var newqueue = owl.ds.get(key).filter(function(d){
                return !d.client_key;
            });

            if (!newqueue.length && isOldQueue) {
                // Delete old queues when they are no longer needed
                owl.ds.set(key, null);
                owl.ds.set(failkey, null);
            } else {
                owl.ds.set(key, newqueue);
                owl.ds.set(failkey, 0);
            }
        },
        'error': function error(jqxhr, text, err) {
            _syncing[key] = false;
            owl('sendfail', {
                'message': text,
                'error': err
            });
            failures++;
            owl.ds.set(failkey, failures);
        }
    });
};

owl.syncAll = function() {
    owl.ds.keys().forEach(function(key){
        if (key.indexOf('-fail') == -1) {
            owl.sync(key);
        }
    });
};

owl.events = {
    'throttledresize': function() {
        var $win = $(window);
        return {
            'width': $win.width(),
            'height': $win.height()
        };
    },
    'scrollstop': function() {
        var $win = $(window);
        return {
            'top': $win.scrollTop(),
            'left': $win.scrollLeft()
        };
    }
};

// Utility to get the current URL path
owl.getPath = function getPath() {
    // $('.ui-page-active').data("url");
    var loc = window.location;
    return loc.pathname + loc.search;
};

// Utility to get various browser & app information
owl.stats = function() {
    return {
        // App info
        'version': owl.config.version,

        // Browser info
        'standalone': navigator.standalone,
        'size': owl.events.throttledresize(),
        'scroll': owl.events.scrollstop(),

        // Cache status
        'localstorage': ds.localStorageUsage(),
        'appcache': (
            window.applicationCache ? window.applicationCache.status : null
        )
    };
};

return owl;

});
<|endoftext|>


<commit_before>'use strict';

(function () {

  function setupRequestAnimationFrame () {
    var lastTime = 0;
    var vendors = ['webkit', 'moz', 'ms', 'o'];

    for (var i = 0; !window.requestAnimationFrame && i < vendors.length; i += 1) {
      var vendor = vendors[i];
      window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame'];
      window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] ||
        window[vendor + 'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame) {
      window.requestAnimationFrame = function (callback) {
        var now = new Date().getTime();
        var nextTime = Math.max(0, 16 - (now - lastTime));

        var id = window.setTimeout(function () {
          callback(now + nextTime);
        }, nextTime);

        lastTime = now + nextTime;
        return id;
      };
    }

    if (!window.cancelAnimationFrame) {
      window.cancelAnimationFrame = function (id) {
        clearTimeout(id);
      };
    }
  }

  setupRequestAnimationFrame();

  function setupPerformanceNow () {
    if (!('performance' in window)) {
      window.performance = {};
    }

    if (!('now' in window.performance)) {
      var dateNow = Date.now || function () {
        return new Date().getTime();
      };

      var nowOffset = dateNow();

      if (performance.timing && performance.timing.navigationStart) {
        nowOffset = performance.timing.navigationStart;
      }

      window.performance.now = function now () {
        return dateNow() - nowOffset;
      };
    }
  }

  setupPerformanceNow();

  function getSlik (Immutable) {

    var animationIndex = 0;

    function getDefault (value, defaultValue) {
      return typeof value === 'undefined' ? defaultValue : value;
    }

    /* @group loop */

    var raf;
    var animations = Immutable.Map();

    function doLoop () {
      var now = performance.now();

      animations.forEach(function (animationLoop) {
        animationLoop(now);
      });

      if (animations.count()) {
        raf = requestAnimationFrame(doLoop);
      }
    }

    function startLoop (animationId, animationLoop) {
      var previousAnimationCount = animations.count();

      if (!animations.has(animationId)) {
        animations = animations.set(animationId, animationLoop);
      }

      if (!previousAnimationCount && animations.count()) {
        raf = requestAnimationFrame(doLoop);
      }
    }

    function stopLoop (animationId) {
      var previousAnimationCount = animations.count();

      if (animations.has(animationId)) {
        animations = animations.delete(animationId);
      }

      if (previousAnimationCount && !animations.count()) {
        cancelAnimationFrame(raf);
      }
    }

    /* @end loop */

    /* @group easing */

    function multiply (fromValue, toValue, multiplier) {
      return fromValue * (1 - multiplier) + toValue * multiplier;
    }

    function Linear (progress) {
      return progress;
    }

    function Ease (progress) {
      return (1 - Math.cos(progress * Math.PI)) / 2;
    }

    function EaseIn (progress) {
      return 1 - Math.cos(progress * Math.PI / 2);
    }

    function EaseOut (progress) {
      return Math.sin(progress * Math.PI / 2);
    }

    function Dip (progress) {
      return (progress >= 0.5 ? 1 : 0) + (Math.sin(progress * Math.PI) / 2) * (progress >= 0.5 ? -1 : 1);
    }

    function Spring (progress) {
      return 1 - (Math.cos(progress * progress * Math.PI * 10) * (1 - progress));
    }

    var Easing = {
      Linear: Linear,
      Ease: Ease,
      EaseIn: EaseIn,
      EaseOut: EaseOut,
      Dip: Dip,
      Spring: Spring
    };

    /* @end easing */

    function Animation (initial) {
      var self = this;

      var startTime, lastTime, pausedAfter;

      var animationId = animationIndex;
      animationIndex += 1;

      var fromValues = Immutable.fromJS(getDefault(initial.from, {}));
      var toValues = Immutable.fromJS(getDefault(initial.to, {}));
      var durationMillis = getDefault(initial.duration, 500);
      var frameRate = 1000 / getDefault(initial.fps, 120);
      var easing = getDefault(initial.ease, Linear);
      var shouldLoop = getDefault(initial.loop, false);

      var currentValues = fromValues;

      var events = Immutable.Map({
        all: Immutable.List(),
        start: Immutable.List(),
        stop: Immutable.List(),
        pause: Immutable.List(),
        end: Immutable.List(),
        update: Immutable.List(),
        loop: Immutable.List()
      });

      function triggerEvent (type) {
        events.get(type, Immutable.List()).forEach(function (callback) {
          callback(currentValues);
        });

        events.get('all', Immutable.List()).forEach(function (callback) {
          callback(currentValues);
        });
      }

      function mapValues (progress, parentPath, now, fromValue, key) {
        var path = parentPath.push(key);

        if (Immutable.Iterable.isIterable(fromValue)) {
          // Ease nested immutable objects
          return fromValue.map(mapValues.bind(null, progress, path, now));
        }

        var toValue = toValues.getIn(path);

        // Only ease values if the toValue exists
        if (typeof toValue !== 'undefined') {
          var multiplier = easing(progress, startTime, now, durationMillis, fromValue, toValue);
          return multiply(fromValue, toValue, multiplier);
        }

        return fromValue;
      }

      function animationLoop (now) {
        var progress = Math.min(Math.max((now - startTime) / durationMillis, 0), 1);

        // Limit frame rate
        if (now - lastTime >= frameRate) {
          if (Immutable.Iterable.isIterable(fromValues)) {
            // Ease immutable objects
            currentValues = fromValues.map(mapValues.bind(null, progress, Immutable.List(), now));
          } else {
            // Ease individual value
            var multiplier = easing(progress, startTime, now, durationMillis, fromValues, toValues);
            currentValues = multiply(fromValues, toValues, multiplier);
          }

          triggerEvent('update');
        }

        lastTime = now;

        if (progress >= 1) {
          if (shouldLoop) {
            startTime = now;
            triggerEvent('loop');
          } else {
            stopLoop(animationId);
            triggerEvent('end');
          }
        }
      }

      // Set the frameRate
      function fps (input) {
        frameRate = 1000 / input;
        return self;
      }

      // Set from values
      function from (input) {
        fromValues = Immutable.fromJS(input);
        return self;
      }

      // Set to values
      function to (input) {
        toValues = Immutable.fromJS(input);
        return self;
      }

      // Set animation duration
      function duration (input) {
        durationMillis = input;
        return self;
      }

      // Set easing
      function ease (input) {
        if (typeof input !== 'function') {
          throw new Error('Ease must be a function, instead got: ' + (typeof input));
        }

        easing = input;
        return self;
      }

      // Set whether the animation should loop
      function loop (input) {
        shouldLoop = input;
        return self;
      }

      function mapInvertValues (value) {
        if (Immutable.Iterable.isIterable(value)) {
          return value.map(mapInvertValues);
        }

        return value * -1;
      }

      // Invert animation values
      function invert () {
        if (Immutable.Iterable.isIterable(toValues)) {
          toValues = toValues.map(mapInvertValues);
        } else {
          toValues = mapInvertValues(toValues);
        }

        return self;
      }

      // Play animation in reverse
      function reverse () {
        var temp = fromValues;
        fromValues = toValues;
        toValues = temp;
        return self;
      }

      // Start or resume animation
      function start () {
        if (typeof pausedAfter !== 'undefined') {
          startTime = performance.now() - pausedAfter;
        } else {
          startTime = performance.now();
        }

        lastTime = startTime;
        pausedAfter = undefined;

        triggerEvent('start');

        startLoop(animationId, animationLoop);

        return self;
      }

      // Stop animation and resume from beginning
      function stop () {
        triggerEvent('stop');
        stopLoop(animationId);
        pausedAfter = undefined;
        startTime = undefined;
        return self;
      }

      // Pause animation and resume from this point
      function pause () {
        triggerEvent('pause');
        stopLoop(animationId);
        pausedAfter = startTime - performance.now();
        startTime = undefined;
        return self;
      }

      // Add event listener
      function bind (type, callback) {
        if (!events.has(type)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var eventsOfType = events.get(type, Immutable.List());

        if (!eventsOfType.includes(callback)) {
          events = events.set(type, eventsOfType.push(callback));
        }

        return self;
      }

      // Remove event listener
      function unbind (type, callback) {
        if (!events.has(type)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var eventsOfType = events.get(type, Immutable.List());

        if (eventsOfType.includes(callback)) {
          events = events.set(type, eventsOfType.delete(eventsOfType.indexOf(callback)));
        }

        return self;
      }

      // Run on start and automatically unbind
      function first (callback) {
        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        function firstCallback (result) {
          callback(result);
          unbind('start', firstCallback);
        }

        bind('start', firstCallback);

        return self;
      }

      // Run on complete and automatically unbind
      function then (callback) {
        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        function thenCallback (result) {
          callback(result);
          unbind('end', thenCallback);
        }

        bind('end', thenCallback);

        return self;
      }

      self.fps = fps;
      self.from = from;
      self.to = to;
      self.duration = duration;
      self.ease = ease;
      self.loop = loop;
      self.invert = invert;
      self.reverse = reverse;
      self.start = start;
      self.stop = self.reset = stop;
      self.pause = pause;
      self.bind = self.on = bind;
      self.unbind = self.off = unbind;
      self.first = first;
      self.then = then;
    }

    return {
      Animation: Animation,
      Easing: Easing
    };

  }

  // Export for commonjs / browserify
  if (typeof exports === 'object' && typeof module !== 'undefined') {
    module.exports = getSlik(require('immutable')); // eslint-disable-line no-undef
  // Export for amd / require
  } else if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef
    define(['immutable'], function (Immutable) { // eslint-disable-line no-undef
      return getSlik(Immutable);
    });
  // Export globally
  } else {
    var root;

    if (typeof window !== 'undefined') {
      root = window;
    } else if (typeof global !== 'undefined') {
      root = global; // eslint-disable-line no-undef
    } else if (typeof self !== 'undefined') {
      root = self; // eslint-disable-line no-undef
    } else {
      root = this;
    }

    root.Slik = getSlik(root.Immutable);
  }

})();
<commit_msg>Remove stop alias (reset)<commit_after>'use strict';

(function () {

  function setupRequestAnimationFrame () {
    var lastTime = 0;
    var vendors = ['webkit', 'moz', 'ms', 'o'];

    for (var i = 0; !window.requestAnimationFrame && i < vendors.length; i += 1) {
      var vendor = vendors[i];
      window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame'];
      window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] ||
        window[vendor + 'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame) {
      window.requestAnimationFrame = function (callback) {
        var now = new Date().getTime();
        var nextTime = Math.max(0, 16 - (now - lastTime));

        var id = window.setTimeout(function () {
          callback(now + nextTime);
        }, nextTime);

        lastTime = now + nextTime;
        return id;
      };
    }

    if (!window.cancelAnimationFrame) {
      window.cancelAnimationFrame = function (id) {
        clearTimeout(id);
      };
    }
  }

  setupRequestAnimationFrame();

  function setupPerformanceNow () {
    if (!('performance' in window)) {
      window.performance = {};
    }

    if (!('now' in window.performance)) {
      var dateNow = Date.now || function () {
        return new Date().getTime();
      };

      var nowOffset = dateNow();

      if (performance.timing && performance.timing.navigationStart) {
        nowOffset = performance.timing.navigationStart;
      }

      window.performance.now = function now () {
        return dateNow() - nowOffset;
      };
    }
  }

  setupPerformanceNow();

  function getSlik (Immutable) {

    var animationIndex = 0;

    function getDefault (value, defaultValue) {
      return typeof value === 'undefined' ? defaultValue : value;
    }

    /* @group loop */

    var raf;
    var animations = Immutable.Map();

    function doLoop () {
      var now = performance.now();

      animations.forEach(function (animationLoop) {
        animationLoop(now);
      });

      if (animations.count()) {
        raf = requestAnimationFrame(doLoop);
      }
    }

    function startLoop (animationId, animationLoop) {
      var previousAnimationCount = animations.count();

      if (!animations.has(animationId)) {
        animations = animations.set(animationId, animationLoop);
      }

      if (!previousAnimationCount && animations.count()) {
        raf = requestAnimationFrame(doLoop);
      }
    }

    function stopLoop (animationId) {
      var previousAnimationCount = animations.count();

      if (animations.has(animationId)) {
        animations = animations.delete(animationId);
      }

      if (previousAnimationCount && !animations.count()) {
        cancelAnimationFrame(raf);
      }
    }

    /* @end loop */

    /* @group easing */

    function multiply (fromValue, toValue, multiplier) {
      return fromValue * (1 - multiplier) + toValue * multiplier;
    }

    function Linear (progress) {
      return progress;
    }

    function Ease (progress) {
      return (1 - Math.cos(progress * Math.PI)) / 2;
    }

    function EaseIn (progress) {
      return 1 - Math.cos(progress * Math.PI / 2);
    }

    function EaseOut (progress) {
      return Math.sin(progress * Math.PI / 2);
    }

    function Dip (progress) {
      return (progress >= 0.5 ? 1 : 0) + (Math.sin(progress * Math.PI) / 2) * (progress >= 0.5 ? -1 : 1);
    }

    function Spring (progress) {
      return 1 - (Math.cos(progress * progress * Math.PI * 10) * (1 - progress));
    }

    var Easing = {
      Linear: Linear,
      Ease: Ease,
      EaseIn: EaseIn,
      EaseOut: EaseOut,
      Dip: Dip,
      Spring: Spring
    };

    /* @end easing */

    function Animation (initial) {
      var self = this;

      var startTime, lastTime, pausedAfter;

      var animationId = animationIndex;
      animationIndex += 1;

      var fromValues = Immutable.fromJS(getDefault(initial.from, {}));
      var toValues = Immutable.fromJS(getDefault(initial.to, {}));
      var durationMillis = getDefault(initial.duration, 500);
      var frameRate = 1000 / getDefault(initial.fps, 120);
      var easing = getDefault(initial.ease, Linear);
      var shouldLoop = getDefault(initial.loop, false);

      var currentValues = fromValues;

      var events = Immutable.Map({
        all: Immutable.List(),
        start: Immutable.List(),
        stop: Immutable.List(),
        pause: Immutable.List(),
        end: Immutable.List(),
        update: Immutable.List(),
        loop: Immutable.List()
      });

      function triggerEvent (type) {
        events.get(type, Immutable.List()).forEach(function (callback) {
          callback(currentValues);
        });

        events.get('all', Immutable.List()).forEach(function (callback) {
          callback(currentValues);
        });
      }

      function mapValues (progress, parentPath, now, fromValue, key) {
        var path = parentPath.push(key);

        if (Immutable.Iterable.isIterable(fromValue)) {
          // Ease nested immutable objects
          return fromValue.map(mapValues.bind(null, progress, path, now));
        }

        var toValue = toValues.getIn(path);

        // Only ease values if the toValue exists
        if (typeof toValue !== 'undefined') {
          var multiplier = easing(progress, startTime, now, durationMillis, fromValue, toValue);
          return multiply(fromValue, toValue, multiplier);
        }

        return fromValue;
      }

      function animationLoop (now) {
        var progress = Math.min(Math.max((now - startTime) / durationMillis, 0), 1);

        // Limit frame rate
        if (now - lastTime >= frameRate) {
          if (Immutable.Iterable.isIterable(fromValues)) {
            // Ease immutable objects
            currentValues = fromValues.map(mapValues.bind(null, progress, Immutable.List(), now));
          } else {
            // Ease individual value
            var multiplier = easing(progress, startTime, now, durationMillis, fromValues, toValues);
            currentValues = multiply(fromValues, toValues, multiplier);
          }

          triggerEvent('update');
        }

        lastTime = now;

        if (progress >= 1) {
          if (shouldLoop) {
            startTime = now;
            triggerEvent('loop');
          } else {
            stopLoop(animationId);
            triggerEvent('end');
          }
        }
      }

      // Set the frameRate
      function fps (input) {
        frameRate = 1000 / input;
        return self;
      }

      // Set from values
      function from (input) {
        fromValues = Immutable.fromJS(input);
        return self;
      }

      // Set to values
      function to (input) {
        toValues = Immutable.fromJS(input);
        return self;
      }

      // Set animation duration
      function duration (input) {
        durationMillis = input;
        return self;
      }

      // Set easing
      function ease (input) {
        if (typeof input !== 'function') {
          throw new Error('Ease must be a function, instead got: ' + (typeof input));
        }

        easing = input;
        return self;
      }

      // Set whether the animation should loop
      function loop (input) {
        shouldLoop = input;
        return self;
      }

      function mapInvertValues (value) {
        if (Immutable.Iterable.isIterable(value)) {
          return value.map(mapInvertValues);
        }

        return value * -1;
      }

      // Invert animation values
      function invert () {
        if (Immutable.Iterable.isIterable(toValues)) {
          toValues = toValues.map(mapInvertValues);
        } else {
          toValues = mapInvertValues(toValues);
        }

        return self;
      }

      // Play animation in reverse
      function reverse () {
        var temp = fromValues;
        fromValues = toValues;
        toValues = temp;
        return self;
      }

      // Start or resume animation
      function start () {
        if (typeof pausedAfter !== 'undefined') {
          startTime = performance.now() - pausedAfter;
        } else {
          startTime = performance.now();
        }

        lastTime = startTime;
        pausedAfter = undefined;

        triggerEvent('start');

        startLoop(animationId, animationLoop);

        return self;
      }

      // Stop animation and resume from beginning
      function stop () {
        triggerEvent('stop');
        stopLoop(animationId);
        pausedAfter = undefined;
        startTime = undefined;
        return self;
      }

      // Pause animation and resume from this point
      function pause () {
        triggerEvent('pause');
        stopLoop(animationId);
        pausedAfter = startTime - performance.now();
        startTime = undefined;
        return self;
      }

      // Add event listener
      function bind (type, callback) {
        if (!events.has(type)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var eventsOfType = events.get(type, Immutable.List());

        if (!eventsOfType.includes(callback)) {
          events = events.set(type, eventsOfType.push(callback));
        }

        return self;
      }

      // Remove event listener
      function unbind (type, callback) {
        if (!events.has(type)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var eventsOfType = events.get(type, Immutable.List());

        if (eventsOfType.includes(callback)) {
          events = events.set(type, eventsOfType.delete(eventsOfType.indexOf(callback)));
        }

        return self;
      }

      // Run on start and automatically unbind
      function first (callback) {
        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        function firstCallback (result) {
          callback(result);
          unbind('start', firstCallback);
        }

        bind('start', firstCallback);

        return self;
      }

      // Run on complete and automatically unbind
      function then (callback) {
        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        function thenCallback (result) {
          callback(result);
          unbind('end', thenCallback);
        }

        bind('end', thenCallback);

        return self;
      }

      self.fps = fps;
      self.from = from;
      self.to = to;
      self.duration = duration;
      self.ease = ease;
      self.loop = loop;
      self.invert = invert;
      self.reverse = reverse;
      self.start = start;
      self.stop = stop;
      self.pause = pause;
      self.bind = self.on = bind;
      self.unbind = self.off = unbind;
      self.first = first;
      self.then = then;
    }

    return {
      Animation: Animation,
      Easing: Easing
    };

  }

  // Export for commonjs / browserify
  if (typeof exports === 'object' && typeof module !== 'undefined') {
    module.exports = getSlik(require('immutable')); // eslint-disable-line no-undef
  // Export for amd / require
  } else if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef
    define(['immutable'], function (Immutable) { // eslint-disable-line no-undef
      return getSlik(Immutable);
    });
  // Export globally
  } else {
    var root;

    if (typeof window !== 'undefined') {
      root = window;
    } else if (typeof global !== 'undefined') {
      root = global; // eslint-disable-line no-undef
    } else if (typeof self !== 'undefined') {
      root = self; // eslint-disable-line no-undef
    } else {
      root = this;
    }

    root.Slik = getSlik(root.Immutable);
  }

})();
<|endoftext|>