<commit_before>/**
 * @fileoverview
 * HTTP logger view code.
 * Defines views for request list, header info etc.
 */

/**
  * @constructor
  * @extends ViewBase
  * This view implements update chocking. It will not do the update more often
  * than every minUpdateInterval milliseconds
  */
cls.RequestListView = function(id, name, container_class)
{
    var self = this;

    // The list will never be updated more often than this:
    this.minUpdateInterval = 500; // in milliseconds.
    var lastUpdateTime = null;
    var updateTimer = null; // timer id so we can cancel a timeout
    var nextToRendereIndex = null;  // index in log of the next element to render
    var keyEntryId = null; // first entry of log. If log[0] is different, view is invalid
    var expandedItems = []; // IDs of items that are expanded
    var tableBodyEle = null;
    var viewMap = {}; // mapping between ID and active part of detail view

    new ToolbarConfig
    (
      id,
      [],
      [
        {
          handler: 'http-text-search',
          title: ui_strings.S_INPUT_DEFAULT_TEXT_SEARCH
        }
    ]

    );

    /**
     *  Called by the framework through update()
     *  Check if we should redraw the view or not. If we shall, call
     *  doCreateView. The reason for not doing so is if there is less
     *  than minUpdateInterval millis since the last time we did.
     **/
    this.createView = function(container)
    {
        if (lastUpdateTime &&
            (new Date().getTime() - lastUpdateTime) < this.minUpdateInterval) {
            // Haven't waited long enough, so do nothing, but queue it for
            // later if it isn't allready so.
            if (!updateTimer) {
                updateTimer = window.setTimeout(
                        function() { self.createView(container) },
                        this.minUpdateInterval);
            }
            return;
        }

        if (updateTimer) {
            window.clearTimeout(updateTimer);
            updateTimer = null;
        }
        lastUpdateTime = new Date().getTime();
        this.doCreateView(container);
    }

    /**
     * Check if the current view represents reality. This is
     * done by checking the first element in the log. Its id needs to be the
     * same as keyEntryId
     */
    this.viewIsValid = function(log)
    {
        return false;

        // The invalidation code is currently disabled because there is now
        // no chance of a ginourmous list of requests. Hence this optimization
        // is probably not needed

        if (log.length && log[0].id==keyEntryId) {
            return true;
        } else {
            if (log.length) { keyEntryId = log[0].id }
            return false;
        }
    }

    /**
     * Do the actual rendering of the request table, including expanded and
     * collapsed details pages.
     */
    this.doCreateView = function(container)
    {
        var log = HTTPLoggerData.getLog();

        if (!this.viewIsValid(log)) {
            container.clearAndRender(['table',['tbody'], 'class', 'request-table']);
            nextToRendereIndex = 0;
        }
        tableBodyEle = container.getElementsByTagName('tbody')[0];

        if (log.length) {
            var times = log.map(function(e) {
                return e.response ? e.response.time : e.request.time
            })
            var times = times.sort();
            lastTime = times[times.length-1];
        } else {
            lastTime = new Date().getTime();
        }

        var firstTime = log.length ? log[0].request.time : lastTime;

        // partial function invocation that closes over expandedItems
        var fun = function(e) {
            return window.templates.request_list_row(e, expandedItems, firstTime, lastTime, viewMap);
        }

        var tpls = log.slice(nextToRendereIndex).map(fun);
        tableBodyEle.render(tpls);
        nextToRendereIndex = log.length;
    }

    this._getRowForId = function(id) {
        if (!tableBodyEle) { return null }
        for (var n=0,e; e=tableBodyEle.childNodes[n]; n++)
        {
            if (e.getAttribute('data-requestid') == id)
            {
                return e;
            }
        }
        return null;
    }

    /**
     * Collapse the detail view of an entry in the log.
     * Does not invalidate the view and re-render the table.
     * It's fairly slow if the entry is far down in the log though.
     */
    this.collapseEntry = function(id)
    {
        if (!tableBodyEle) { return }
        for (var n=0,e; e=tableBodyEle.childNodes[n]; n++)
        {
            if (e.getAttribute('data-requestid') == id)
            {
                e.className = e.className.replace("expanded", "collapsed");
                tableBodyEle.removeChild(e.nextSibling);
                return
            }
        }
    }

    /**
     * Show the detail view of an entry in the log.
     * Does not invalidate the view and re-render the table.
     * It's fairly slow if the entry is far down in the log though.
     */
    this.expandEntry = function(id) {
        if (!tableBodyEle) { return }
        for (var n=0,e; e=tableBodyEle.childNodes[n]; n++)
        {
            if (e.getAttribute('data-requestid') == id)
            {
                var req = HTTPLoggerData.getRequestById(id);
                var tpl = window.templates.request_details_box(req);
                var ele = document.render(tpl);
                if (e.nextSibling) {
                    tableBodyEle.insertBefore(ele, e.nextSibling);
                }
                else {
                    tableBodyEle.appendChild(ele);
                }
                e.className = e.className.replace("collapsed", "expanded");
                return; // or loop forever since you just made list longer.
            }
        }
    }

    /**
     * Called to toggle request with id. If it's expanded it gets collapsed
     * and vice-versa
     */
    this.toggleDetails = function(id)
    {
        if (expandedItems.indexOf(id)==-1) {
            expandedItems.push(id);
            //this.expandEntry(id);
        } else {
            expandedItems.splice(expandedItems.indexOf(id), 1);
            //this.collapseEntry(id);
        }
        this.update();
    }

    this.selectDetailView = function(id, name) {
        viewMap[id] = name;
        this.update();
        return;

         //the bits under here could be used if we decide to do something
         //smarter than just invalidating the view and redrawing.

        var row = this._getRowForId(id);
        if (!row) { return }

        row = row.nextSibling;
        var req = HTTPLoggerData.getRequestById(id);
        var div = row.getElementsByTagName("div")[0];
        div.clearAndRender(window.templates.parsed_request_headers(req, "headers"));

    }

    this.ondestroy = function()
    {
        keyEntryId = null;
    }

    this.init(id, name, container_class);
}

cls.RequestListView.prototype = ViewBase;
new cls.RequestListView('request_list', ui_strings.M_VIEW_LABEL_REQUEST_LOG, 'scroll');

eventHandlers.click['request-list-expand-collapse'] = function(event, target)
{
    window.views['request_list'].toggleDetails(target.getAttribute("data-requestid"));
};

eventHandlers.click['select-http-detail-view'] = function(event, target)
{
    window.views['request_list'].selectDetailView(target.getAttribute("data-requestid"), target.getAttribute("data-viewname"));
};

/**
 * This sets up the handlers for the search box
 */

(function()
{
    var textSearch = new TextSearch();

    var onViewCreated = function(msg)
    {
        if (msg.id == 'request_list') {
            textSearch.setContainer(msg.container);
            textSearch.setFormInput(views.request_list.getToolbarControl(msg.container, 'http-text-search'));
        }
    }

    var onViewDestroyed = function(msg)
    {
        if (msg.id == 'request_list') {
            textSearch.cleanup();
            topCell.statusbar.updateInfo();
        }
    }

    messages.addListener('view-created', onViewCreated);
    messages.addListener('view-destroyed', onViewDestroyed);

    eventHandlers.input['http-text-search'] = function(event, target)
    {
        textSearch.searchDelayed(target.value);
    }

    eventHandlers.keyup['http-text-search'] = function(event, target)
    {
        if (event.keyCode == 13) {
            textSearch.highlight();
        }
    }

})();

<commit_msg>Fixes DFL-470 for http logger ( The scrollbar should automatically scroll down on clickink the last request in network view )<commit_after>/**
 * @fileoverview
 * HTTP logger view code.
 * Defines views for request list, header info etc.
 */

/**
  * @constructor
  * @extends ViewBase
  * This view implements update chocking. It will not do the update more often
  * than every minUpdateInterval milliseconds
  */
cls.RequestListView = function(id, name, container_class)
{
    var self = this;

    // The list will never be updated more often than this:
    this.minUpdateInterval = 500; // in milliseconds.
    var lastUpdateTime = null;
    var updateTimer = null; // timer id so we can cancel a timeout
    var nextToRendereIndex = null;  // index in log of the next element to render
    var keyEntryId = null; // first entry of log. If log[0] is different, view is invalid
    var expandedItems = []; // IDs of items that are expanded
    var tableBodyEle = null;
    var viewMap = {}; // mapping between ID and active part of detail view

    new ToolbarConfig
    (
      id,
      [],
      [
        {
          handler: 'http-text-search',
          title: ui_strings.S_INPUT_DEFAULT_TEXT_SEARCH
        }
    ]

    );

    /**
     *  Called by the framework through update()
     *  Check if we should redraw the view or not. If we shall, call
     *  doCreateView. The reason for not doing so is if there is less
     *  than minUpdateInterval millis since the last time we did.
     **/
    this.createView = function(container)
    {
        if (lastUpdateTime &&
            (new Date().getTime() - lastUpdateTime) < this.minUpdateInterval) {
            // Haven't waited long enough, so do nothing, but queue it for
            // later if it isn't allready so.
            if (!updateTimer) {
                updateTimer = window.setTimeout(
                        function() { self.createView(container) },
                        this.minUpdateInterval);
            }
            return;
        }

        if (updateTimer) {
            window.clearTimeout(updateTimer);
            updateTimer = null;
        }
        lastUpdateTime = new Date().getTime();
        this.doCreateView(container);
    }

    /**
     * Check if the current view represents reality. This is
     * done by checking the first element in the log. Its id needs to be the
     * same as keyEntryId
     */
    this.viewIsValid = function(log)
    {
        return false;

        // The invalidation code is currently disabled because there is now
        // no chance of a ginourmous list of requests. Hence this optimization
        // is probably not needed

        if (log.length && log[0].id==keyEntryId) {
            return true;
        } else {
            if (log.length) { keyEntryId = log[0].id }
            return false;
        }
    }

    /**
     * Do the actual rendering of the request table, including expanded and
     * collapsed details pages.
     */
    this.doCreateView = function(container)
    {
        var log = HTTPLoggerData.getLog();

        if (!this.viewIsValid(log)) {
            container.clearAndRender(['table',['tbody'], 'class', 'request-table']);
            nextToRendereIndex = 0;
        }
        tableBodyEle = container.getElementsByTagName('tbody')[0];

        if (log.length) {
            var times = log.map(function(e) {
                return e.response ? e.response.time : e.request.time
            })
            var times = times.sort();
            lastTime = times[times.length-1];
        } else {
            lastTime = new Date().getTime();
        }

        var firstTime = log.length ? log[0].request.time : lastTime;

        // partial function invocation that closes over expandedItems
        var fun = function(e) {
            return window.templates.request_list_row(e, expandedItems, firstTime, lastTime, viewMap);
        }

        var tpls = log.slice(nextToRendereIndex).map(fun);
        tableBodyEle.render(tpls);
        nextToRendereIndex = log.length;
    }

    this._getRowForId = function(id) {
        if (!tableBodyEle) { return null }
        for (var n=0,e; e=tableBodyEle.childNodes[n]; n++)
        {
            if (e.getAttribute('data-requestid') == id)
            {
                return e;
            }
        }
        return null;
    }

    /**
     * Collapse the detail view of an entry in the log.
     * Does not invalidate the view and re-render the table.
     * It's fairly slow if the entry is far down in the log though.
     */
    this.collapseEntry = function(id)
    {
        if (!tableBodyEle) { return }
        for (var n=0,e; e=tableBodyEle.childNodes[n]; n++)
        {
            if (e.getAttribute('data-requestid') == id)
            {
                e.className = e.className.replace("expanded", "collapsed");
                tableBodyEle.removeChild(e.nextSibling);
                return
            }
        }
    }

    /**
     * Show the detail view of an entry in the log.
     * Does not invalidate the view and re-render the table.
     * It's fairly slow if the entry is far down in the log though.
     */
    this.expandEntry = function(id) {
        if (!tableBodyEle) { return }
        for (var n=0,e; e=tableBodyEle.childNodes[n]; n++)
        {
            if (e.getAttribute('data-requestid') == id)
            {
                var req = HTTPLoggerData.getRequestById(id);
                var tpl = window.templates.request_details_box(req);
                var ele = document.render(tpl);
                if (e.nextSibling) {
                    tableBodyEle.insertBefore(ele, e.nextSibling);
                }
                else {
                    tableBodyEle.appendChild(ele);
                }
                e.className = e.className.replace("collapsed", "expanded");
                return; // or loop forever since you just made list longer.
            }
        }
    }

    /**
     * Called to toggle request with id. If it's expanded it gets collapsed
     * and vice-versa
     */
    this.toggleDetails = function(id)
    {
        var dirty = false;
        if (expandedItems.indexOf(id)==-1) {
            expandedItems.push(id);
            dirty = true;
        } else {
            expandedItems.splice(expandedItems.indexOf(id), 1);
        }
        this.update();
        if (dirty) {
            var row = this._getRowForId(id).nextSibling
            row.scrollSoftIntoContainerView();
        }

    }

    this.selectDetailView = function(id, name) {
        viewMap[id] = name;
        this.update();
        return;

         //the bits under here could be used if we decide to do something
         //smarter than just invalidating the view and redrawing.

        var row = this._getRowForId(id);
        if (!row) { return }

        row = row.nextSibling;
        var req = HTTPLoggerData.getRequestById(id);
        var div = row.getElementsByTagName("div")[0];
        div.clearAndRender(window.templates.parsed_request_headers(req, "headers"));
    }

    this.ondestroy = function()
    {
        keyEntryId = null;
    }

    this.init(id, name, container_class);
}

cls.RequestListView.prototype = ViewBase;
new cls.RequestListView('request_list', ui_strings.M_VIEW_LABEL_REQUEST_LOG, 'scroll');

eventHandlers.click['request-list-expand-collapse'] = function(event, target)
{
    window.views['request_list'].toggleDetails(target.getAttribute("data-requestid"));
};

eventHandlers.click['select-http-detail-view'] = function(event, target)
{
    window.views['request_list'].selectDetailView(target.getAttribute("data-requestid"), target.getAttribute("data-viewname"));
};

/**
 * This sets up the handlers for the search box
 */

(function()
{
    var textSearch = new TextSearch();

    var onViewCreated = function(msg)
    {
        if (msg.id == 'request_list') {
            textSearch.setContainer(msg.container);
            textSearch.setFormInput(views.request_list.getToolbarControl(msg.container, 'http-text-search'));
        }
    }

    var onViewDestroyed = function(msg)
    {
        if (msg.id == 'request_list') {
            textSearch.cleanup();
            topCell.statusbar.updateInfo();
        }
    }

    messages.addListener('view-created', onViewCreated);
    messages.addListener('view-destroyed', onViewDestroyed);

    eventHandlers.input['http-text-search'] = function(event, target)
    {
        textSearch.searchDelayed(target.value);
    }

    eventHandlers.keyup['http-text-search'] = function(event, target)
    {
        if (event.keyCode == 13) {
            textSearch.highlight();
        }
    }

})();

<|endoftext|>


<commit_before>// ==UserScript==
// @id             iitc-plugin-keys-list@isnot
// @name           IITC plugin: Keys List
// @category       Keys
// @version        0.3.20160818
// @namespace      https://github.com/jonatkins/ingress-intel-total-conversion
// @author         isnot
// @updateURL      none
// @downloadURL    none
// @description    [iitc-plugins] Export the manually entered key counts from the 'keys' plugin.
// @include        https://www.ingress.com/intel*
// @include        http://www.ingress.com/intel*
// @match          https://www.ingress.com/intel*
// @match          http://www.ingress.com/intel*
// @include        https://www.ingress.com/mission/*
// @include        http://www.ingress.com/mission/*
// @match          https://www.ingress.com/mission/*
// @match          http://www.ingress.com/mission/*
// @grant          none
// ==/UserScript==

function wrapper(plugin_info) {
  // ensure plugin framework is there, even if iitc is not yet loaded
  if (typeof window.plugin !== 'function') window.plugin = function() {};

  // PLUGIN START ////////////////////////////////////////////////////////

  // if the Keys/Cache plugin is not available, quit now
  if (!window.plugin.keys) {
    return console.warn("[KeysList] This plugin is dependent on the Keys plugin being present.");
  }
  if (!window.plugin.cachePortalDetailsOnMap) {
    return console.warn("[KeysList] This plugin is dependent on the Cache plugin being present.");
  }

  // Expand Cache BEGIN //////////////////////////////////////////////////////////
  var cache = window.plugin.cachePortalDetailsOnMap;
  cache.KEY_LOCALSTRAGE = 'plugin-cache-local-v1';

  cache.getPortalByGuid = function (guid) {
    var portal_cache = cache.cache[guid];
    if (typeof portal_cache === 'function') {
      console.log('==KeysList gpbg ' + portal_cache);
      return JSON.parse(portal_cache.ent);
    }
  };

  cache.storeToLocal = function () {
    localStorage.setItem(cache.KEY_LOCALSTRAGE, JSON.stringify(cache.cache));
  };

  cache.loadFromLocal = function () {
    // if an existing portal cache, load it
    var raw = window.localStorage[cache.KEY_LOCALSTRAGE];
    if (typeof raw === 'function') {
      cache.merge(JSON.parse(raw));
    } else {
      // make a new cache
      window.localStorage[cache.KEY_LOCALSTRAGE] = "{}";
    }
  };

  cache.merge = function (inbound) {
    if (cache.cache.length < 1) {
      cache.cache = inbound;
      return inbound.length;
    }

    for (var x in Object.keys(inbound)) {
      cache.cache{x} = inbound{x};
    }
    return cache.cache.length;
  };

  // Expand Cache END //////////////////////////////////////////////////////////

  // use own namespace for plugin
  window.plugin.keysList = function() {};
  var self = window.plugin.keysList;
  self.listAll = [];

  // fetch a portal name from a cached list if available
  self.getPortalDetails = function getPortalDetails(key) {
    var name = "(not detected)";
    var latLng = '0,0';
    var imageUrl = '';

    // try plugin cache
    var portal_cache = cache.getPortalByGuid(key.guid);
    if (typeof portal_cache === 'function') {
      //name = portal_cache.name;
      console.log('==KsysList pc ' + portal_cache + Object.keys(portal_cache).join(' '));
    }

    var portal = window.portals[key.guid];
    if (portal) {
      name     = portal.options.data.title;
      imageUrl = portal.options.data.imageUrl;
    }

    var hLatLng = window.findPortalLatLng(key.guid);
    if (hLatLng) {
      latLng = hLatLng.lat + ',' + hLatLng.lng;

    }

    key.name = name;
    key.latLng = latLng;
    key.imageUrl = imageUrl;
    if (latLng === '0,0') {
      key.intelMapUrl = '';
    } else {
      key.intelMapUrl = 'https://www.ingress.com/intel?ll=' + latLng + '&z=17&pll=' + latLng;
    }

    return key;
  };

  self.csvValue = function(str) {
    if (str) {
      return '"' + str.replace("/\"/g", '""') + '"';
    } else {
      return '';
    }
  };

  self.eachKey = function(key) {
    if (key.count > 0) {
      key = self.getPortalDetails(key);
      var csvline = [
        self.csvValue(key.name),
        parseInt(key.count, 10),
        self.csvValue(key.latLng),
        self.csvValue(key.intelMapUrl),
        self.csvValue(key.imageUrl),
        key.guid
      ];
      self.listAll.push(csvline.join(","));
    }
  };

  self.renderList = function() {
    self.listAll = [];

    $.each(plugin.keys.keys, function(key, count) {
      self.eachKey({"guid": key, "count": count});
    });

    var html = '
KeysList for ' + window.PLAYER.nickname + ' ' + self.listAll.length + 'portals of keys. ' + new Date().toISOString() +
        '
<textarea cols="78" rows="20">name,count,latlng,url,image,guid' + "\n" + self.listAll.join("\n") + '</textarea>';
    dialog({
      title: 'KeysList',
      html: html,
      width: 600,
      position: {my: 'right center', at: 'center-60 center', of: window, collision: 'fit'}
    });
    console.log("==KeysList " + window.PLAYER.nickname + " " + self.listAll.length + " " + new Date().toISOString());

    cache.storeToLocal();
  };

  var setup = function() {
    // console.log("==KeysList setup ");
    $('#toolbox').append('KeysListCsv');
    cache.loadFromLocal();
  };

  // PLUGIN END //////////////////////////////////////////////////////////

  setup.info = plugin_info; //add the script info data to the function as a property
  if(!window.bootPlugins) window.bootPlugins = [];
  window.bootPlugins.push(setup);
  // if IITC has already booted, immediately run the 'setup' function
  if(window.iitcLoaded && typeof setup === 'function') setup();
} // wrapper end
// inject code into site context
var script = document.createElement('script');
var info = {};
if (typeof GM_info !== 'undefined' && GM_info && GM_info.script) info.script = { version: GM_info.script.version, name: GM_info.script.name, description: GM_info.script.description };
script.appendChild(document.createTextNode('('+ wrapper +')('+JSON.stringify(info)+');'));
(document.body || document.head || document.documentElement).appendChild(script);
<commit_msg>bugfix / update key info<commit_after>// ==UserScript==
// @id             iitc-plugin-keys-list@isnot
// @name           IITC plugin: Keys List
// @category       Keys
// @version        0.3.20160818
// @namespace      https://github.com/jonatkins/ingress-intel-total-conversion
// @author         isnot
// @updateURL      none
// @downloadURL    none
// @description    [iitc-plugins] Export the manually entered key counts from the 'keys' plugin.
// @include        https://www.ingress.com/intel*
// @include        http://www.ingress.com/intel*
// @match          https://www.ingress.com/intel*
// @match          http://www.ingress.com/intel*
// @include        https://www.ingress.com/mission/*
// @include        http://www.ingress.com/mission/*
// @match          https://www.ingress.com/mission/*
// @match          http://www.ingress.com/mission/*
// @grant          none
// ==/UserScript==

function wrapper(plugin_info) {
  // ensure plugin framework is there, even if iitc is not yet loaded
  if (typeof window.plugin !== 'function') window.plugin = function() {};

  // PLUGIN START ////////////////////////////////////////////////////////

  // if the Keys/Cache plugin is not available, quit now
  if (!window.plugin.keys) {
    return console.warn("[KeysList] This plugin is dependent on the Keys plugin being present.");
  }
  if (!window.plugin.cachePortalDetailsOnMap) {
    return console.warn("[KeysList] This plugin is dependent on the Cache plugin being present.");
  }

  // Expand Cache BEGIN //////////////////////////////////////////////////////////
  var cache = window.plugin.cachePortalDetailsOnMap;
  cache.KEY_LOCALSTRAGE = 'plugin-cache-local-v1';

  cache.getPortalByGuid = function (guid) {
    var portal_cache = cache.cache[guid];
    if (typeof portal_cache === 'function') {
      console.log('==KeysList gpbg ' + portal_cache);
      return JSON.parse(portal_cache.ent);
    }
  };

  cache.storeToLocal = function () {
    localStorage.setItem(cache.KEY_LOCALSTRAGE, JSON.stringify(cache.cache));
  };

  cache.loadFromLocal = function () {
    // if an existing portal cache, load it
    var raw = window.localStorage[cache.KEY_LOCALSTRAGE];
    if (typeof raw === 'function') {
      cache.merge(JSON.parse(raw));
    } else {
      // make a new cache
      window.localStorage[cache.KEY_LOCALSTRAGE] = "{}";
    }
  };

  cache.merge = function (inbound) {
    if (cache.cache.length < 1) {
      cache.cache = inbound;
      return inbound.length;
    }

    for (var x in Object.keys(inbound)) {
      cache.cache[x] = inbound[x];
    }
    return cache.cache.length;
  };

  // Expand Cache END //////////////////////////////////////////////////////////

  // use own namespace for plugin
  window.plugin.keysList = function() {};
  var self = window.plugin.keysList;
  self.listAll = [];

  // fetch a portal name from a cached list if available
  self.getPortalDetails = function getPortalDetails(key) {
    var name = "(not detected)";
    var latLng = '0,0';
    var imageUrl = '';

    // try plugin cache
    var portal_cache = cache.getPortalByGuid(key.guid);
    if (typeof portal_cache === 'function') {
      //name = portal_cache.name;
      console.log('==KsysList pc ' + portal_cache + Object.keys(portal_cache).join(' '));
    }

    var data = (window.portals[key.guid] && window.portals[key.guid].options.data) || window.portalDetail.get(key.guid) || null;
    if (data) {
      name     = data.title;
      imageUrl = window.fixPortalImageUrl(data.image);
      console.log('==KsysList pd ' + data + Object.keys(data).join(' '));
    }

    var hLatLng = window.findPortalLatLng(key.guid);
    if (hLatLng) {
      latLng = hLatLng.lat + ',' + hLatLng.lng;

    }

    key.name = name;
    key.latLng = latLng;
    key.imageUrl = imageUrl;
    if (latLng === '0,0') {
      key.intelMapUrl = '';
    } else {
      key.intelMapUrl = 'https://www.ingress.com/intel?ll=' + latLng + '&z=17&pll=' + latLng;
    }

    return key;
  };

  self.csvValue = function(str) {
    if (str) {
      return '"' + str.replace("/\"/g", '""') + '"';
    } else {
      return '';
    }
  };

  self.eachKey = function(key) {
    if (key.count > 0) {
      key = self.getPortalDetails(key);
      var csvline = [
        self.csvValue(key.name),
        parseInt(key.count, 10),
        self.csvValue(key.latLng),
        self.csvValue(key.intelMapUrl),
        self.csvValue(key.imageUrl),
        key.guid
      ];
      self.listAll.push(csvline.join(","));
    }
  };

  self.renderList = function() {
    self.listAll = [];

    $.each(plugin.keys.keys, function(key, count) {
      self.eachKey({"guid": key, "count": count});
    });

    var html = '
KeysList for ' + window.PLAYER.nickname + ' ' + self.listAll.length + 'portals of keys. ' + new Date().toISOString() +
        '
<textarea cols="78" rows="20">name,count,latlng,url,image,guid' + "\n" + self.listAll.join("\n") + '</textarea>';
    dialog({
      title: 'KeysList',
      html: html,
      width: 600,
      position: {my: 'right center', at: 'center-60 center', of: window, collision: 'fit'}
    });
    console.log("==KeysList " + window.PLAYER.nickname + " " + self.listAll.length + " " + new Date().toISOString());

    cache.storeToLocal();
  };

  var setup = function() {
    // console.log("==KeysList setup ");
    $('#toolbox').append('KeysListCsv');
    cache.loadFromLocal();
  };

  // PLUGIN END //////////////////////////////////////////////////////////

  setup.info = plugin_info; //add the script info data to the function as a property
  if(!window.bootPlugins) window.bootPlugins = [];
  window.bootPlugins.push(setup);
  // if IITC has already booted, immediately run the 'setup' function
  if(window.iitcLoaded && typeof setup === 'function') setup();
} // wrapper end
// inject code into site context
var script = document.createElement('script');
var info = {};
if (typeof GM_info !== 'undefined' && GM_info && GM_info.script) info.script = { version: GM_info.script.version, name: GM_info.script.name, description: GM_info.script.description };
script.appendChild(document.createTextNode('('+ wrapper +')('+JSON.stringify(info)+');'));
(document.body || document.head || document.documentElement).appendChild(script);
<|endoftext|>


<commit_before>// ==UserScript==
// @id             max-links@boombuler
// @name           iitc: Max-Links-Plugin
// @version        0.1
// @description    Calculates how to link the portals to create the maximum number of fields.
// @include        http://www.ingress.com/intel*
// @match          http://www.ingress.com/intel*
// ==/UserScript==

// The algorithm is taken from https://github.com/ironwallaby/delaunay

function wrapper() {
    // ensure plugin framework is there, even if iitc is not yet loaded
    if(typeof window.plugin !== 'function')
        window.plugin = function() {};

    // PLUGIN START ////////////////////////////////////////////////////////

    // use own namespace for plugin
    window.plugin.portalfolding = function() {};

    var MAX_LINK_COLOR = '#FF0000';

    var Triangle = function (a, b, c) {
        this.a = a
        this.b = b
        this.c = c

        var A = b.x - a.x,
            B = b.y - a.y,
            C = c.x - a.x,
            D = c.y - a.y,
            E = A * (a.x + b.x) + B * (a.y + b.y),
            F = C * (a.x + c.x) + D * (a.y + c.y),
            G = 2 * (A * (c.y - b.y) - B * (c.x - b.x)),
            minx, miny, dx, dy

        /* If the points of the triangle are collinear, then just find the
         * extremes and use the midpoint as the center of the circumcircle. */
        if(Math.abs(G) < 0.000001) {
          minx = Math.min(a.x, b.x, c.x)
          miny = Math.min(a.y, b.y, c.y)
          dx   = (Math.max(a.x, b.x, c.x) - minx) * 0.5
          dy   = (Math.max(a.y, b.y, c.y) - miny) * 0.5

          this.x = minx + dx
          this.y = miny + dy
          this.r = dx * dx + dy * dy
        }

        else {
          this.x = (D*E - B*F) / G
          this.y = (A*F - C*E) / G
          dx = this.x - a.x
          dy = this.y - a.y
          this.r = dx * dx + dy * dy
        }
      }

    Triangle.prototype.draw = function(layer) {
        var drawLine = function(src, dest) {
            var poly = L.polyline([[src.y, src.x], [dest.y, dest.x]], {
                color: MAX_LINK_COLOR,
                opacity: 1,
                weight:2,
                clickable: false,
                smoothFactor: 10
              });
            poly.addTo(layer);
        };

        drawLine(this.a, this.b);
        drawLine(this.b, this.c);
        drawLine(this.c, this.a);
    }

    var dedup = function (edges) {
        var j = edges.length,
            a, b, i, m, n

        outer: while(j) {
          b = edges[--j]
          a = edges[--j]
          i = j
          while(i) {
            n = edges[--i]
            m = edges[--i]
            if((a === m && b === n) || (a === n && b === m)) {
              edges.splice(j, 2)
              edges.splice(i, 2)
              j -= 2
              continue outer
            }
          }
        }
    }

    var triangulate = function (vertices) {
        /* Bail if there aren't enough vertices to form any triangles. */
        if(vertices.length < 3)
          return []

        /* Ensure the vertex array is in order of descending X coordinate
         * (which is needed to ensure a subquadratic runtime), and then find
         * the bounding box around the points. */
        vertices.sort(function (a, b) { return b.x - a.x });

        var i    = vertices.length - 1,
            xmin = vertices[i].x,
            xmax = vertices[0].x,
            ymin = vertices[i].y,
            ymax = ymin

        while(i--) {
          if(vertices[i].y < ymin) ymin = vertices[i].y
          if(vertices[i].y > ymax) ymax = vertices[i].y
        }

        /* Find a supertriangle, which is a triangle that surrounds all the
         * vertices. This is used like something of a sentinel value to remove
         * cases in the main algorithm, and is removed before we return any
         * results.
         *
         * Once found, put it in the "open" list. (The "open" list is for
         * triangles who may still need to be considered; the "closed" list is
         * for triangles which do not.) */
        var dx     = xmax - xmin,
            dy     = ymax - ymin,
            dmax   = (dx > dy) ? dx : dy,
            xmid   = (xmax + xmin) * 0.5,
            ymid   = (ymax + ymin) * 0.5,
            open   = [
              new Triangle(
                {x: xmid - 20 * dmax, y: ymid -      dmax, __sentinel: true},
                {x: xmid            , y: ymid + 20 * dmax, __sentinel: true},
                {x: xmid + 20 * dmax, y: ymid -      dmax, __sentinel: true}
              )
            ],
            closed = [],
            edges = [],
            j, a, b

        /* Incrementally add each vertex to the mesh. */
        i = vertices.length
        while(i--) {
          /* For each open triangle, check to see if the current point is
           * inside it's circumcircle. If it is, remove the triangle and add
           * it's edges to an edge list. */
          edges.length = 0
          j = open.length
          while(j--) {
            /* If this point is to the right of this triangle's circumcircle,
             * then this triangle should never get checked again. Remove it
             * from the open list, add it to the closed list, and skip. */
            dx = vertices[i].x - open[j].x
            if(dx > 0 && dx * dx > open[j].r) {
              closed.push(open[j])
              open.splice(j, 1)
              continue
            }

            /* If not, skip this triangle. */
            dy = vertices[i].y - open[j].y
            if(dx * dx + dy * dy > open[j].r)
              continue

            /* Remove the triangle and add it's edges to the edge list. */
            edges.push(
              open[j].a, open[j].b,
              open[j].b, open[j].c,
              open[j].c, open[j].a
            )
            open.splice(j, 1)
          }

          /* Remove any doubled edges. */
          dedup(edges)

          /* Add a new triangle for each edge. */
          j = edges.length
          while(j) {
            b = edges[--j]
            a = edges[--j]
            open.push(new Triangle(a, b, vertices[i]))
          }
        }

        /* Copy any remaining open triangles to the closed list, and then
         * remove any triangles that share a vertex with the supertriangle. */
        Array.prototype.push.apply(closed, open)

        i = closed.length
        while(i--)
          if(closed[i].a.__sentinel ||
             closed[i].b.__sentinel ||
             closed[i].c.__sentinel)
            closed.splice(i, 1)

        /* Yay, we're done! */
        return closed
      }

    var layer = null;

    window.plugin.portalfolding.toggle = function() {
        if (layer) {
                // toggle off
                window.map.removeLayer(layer);
                layer = null;
                return;
        }

        var locations = [];
        for (var key in window.portals) {
            var loc = window.portals[key].options.details.locationE6;
            var nloc = { x: loc.lngE6/1E6, y: loc.latE6/1E6 };
            locations.push(nloc);
        }

       layer = L.layerGroup([])

        var triangles = triangulate(locations);
        var i = triangles.length
        while(i) {
            triangles[--i].draw(layer)
        }

        window.map.addLayer(layer);
        return layer;
     }

    var setup =  function() {
        $('#toolbox').append('toggle MaxLinks ');
    }

    // PLUGIN END //////////////////////////////////////////////////////////
    if(window.iitcLoaded && typeof setup === 'function') {
      setup();
    } else {
      if(window.bootPlugins)
        window.bootPlugins.push(setup);
      else
        window.bootPlugins = [setup];
    }
} // wrapper end

// inject code into site context
var script = document.createElement('script');
script.appendChild(document.createTextNode('('+ wrapper +')();'));
(document.body || document.head || document.documentElement).appendChild(script);<commit_msg>fixed overlapping links<commit_after>// ==UserScript==
// @id             max-links@boombuler
// @name           iitc: Max-Links-Plugin
// @version        0.1
// @description    Calculates how to link the portals to create the maximum number of fields.
// @include        http://www.ingress.com/intel*
// @match          http://www.ingress.com/intel*
// ==/UserScript==

// The algorithm is taken from https://github.com/ironwallaby/delaunay

function wrapper() {
    // ensure plugin framework is there, even if iitc is not yet loaded
    if(typeof window.plugin !== 'function')
        window.plugin = function() {};

    // PLUGIN START ////////////////////////////////////////////////////////

    // use own namespace for plugin
    window.plugin.portalfolding = function() {};

    var MAX_LINK_COLOR = '#FF0000';

    var Triangle = function (a, b, c) {
        this.a = a
        this.b = b
        this.c = c

        var A = b.x - a.x,
            B = b.y - a.y,
            C = c.x - a.x,
            D = c.y - a.y,
            E = A * (a.x + b.x) + B * (a.y + b.y),
            F = C * (a.x + c.x) + D * (a.y + c.y),
            G = 2 * (A * (c.y - b.y) - B * (c.x - b.x)),
            minx, miny, dx, dy

        /* If the points of the triangle are collinear, then just find the
         * extremes and use the midpoint as the center of the circumcircle. */
        if(Math.abs(G) < 0.000001) {
          minx = Math.min(a.x, b.x, c.x)
          miny = Math.min(a.y, b.y, c.y)
          dx   = (Math.max(a.x, b.x, c.x) - minx) * 0.5
          dy   = (Math.max(a.y, b.y, c.y) - miny) * 0.5

          this.x = minx + dx
          this.y = miny + dy
          this.r = dx * dx + dy * dy
        }

        else {
          this.x = (D*E - B*F) / G
          this.y = (A*F - C*E) / G
          dx = this.x - a.x
          dy = this.y - a.y
          this.r = dx * dx + dy * dy
        }
      }

    Triangle.prototype.draw = function(layer, divX, divY) {
        var drawLine = function(src, dest) {
            var poly = L.polyline([[(src.y + divY)/1E6, (src.x + divX)/1E6],
                                   [(dest.y + divY)/1E6, (dest.x + divX)/1E6]], {
                color: MAX_LINK_COLOR,
                opacity: 1,
                weight:2,
                clickable: false,
                smoothFactor: 10
              });
            poly.addTo(layer);
        };

        drawLine(this.a, this.b);
        drawLine(this.b, this.c);
        drawLine(this.c, this.a);
    }

    var dedup = function (edges) {
        var j = edges.length,
            a, b, i, m, n

        outer: while(j) {
          b = edges[--j]
          a = edges[--j]
          i = j
          while(i) {
            n = edges[--i]
            m = edges[--i]
            if((a === m && b === n) || (a === n && b === m)) {
              edges.splice(j, 2)
              edges.splice(i, 2)
              j -= 2
              continue outer
            }
          }
        }
    }

    var triangulate = function (vertices) {
        /* Bail if there aren't enough vertices to form any triangles. */
        if(vertices.length < 3)
          return []

        /* Ensure the vertex array is in order of descending X coordinate
         * (which is needed to ensure a subquadratic runtime), and then find
         * the bounding box around the points. */
        vertices.sort(function (a, b) { return b.x - a.x });

        var i    = vertices.length - 1,
            xmin = vertices[i].x,
            xmax = vertices[0].x,
            ymin = vertices[i].y,
            ymax = ymin

        while(i--) {
          if(vertices[i].y < ymin) ymin = vertices[i].y
          if(vertices[i].y > ymax) ymax = vertices[i].y
        }

        /* Find a supertriangle, which is a triangle that surrounds all the
         * vertices. This is used like something of a sentinel value to remove
         * cases in the main algorithm, and is removed before we return any
         * results.
         *
         * Once found, put it in the "open" list. (The "open" list is for
         * triangles who may still need to be considered; the "closed" list is
         * for triangles which do not.) */
        var dx     = xmax - xmin,
            dy     = ymax - ymin,
            dmax   = (dx > dy) ? dx : dy,
            xmid   = (xmax + xmin) * 0.5,
            ymid   = (ymax + ymin) * 0.5,
            open   = [
              new Triangle(
                {x: xmid - 20 * dmax, y: ymid -      dmax, __sentinel: true},
                {x: xmid            , y: ymid + 20 * dmax, __sentinel: true},
                {x: xmid + 20 * dmax, y: ymid -      dmax, __sentinel: true}
              )
            ],
            closed = [],
            edges = [],
            j, a, b

        /* Incrementally add each vertex to the mesh. */
        i = vertices.length
        while(i--) {
          /* For each open triangle, check to see if the current point is
           * inside it's circumcircle. If it is, remove the triangle and add
           * it's edges to an edge list. */
          edges.length = 0
          j = open.length
          while(j--) {
            /* If this point is to the right of this triangle's circumcircle,
             * then this triangle should never get checked again. Remove it
             * from the open list, add it to the closed list, and skip. */
            dx = vertices[i].x - open[j].x
            if(dx > 0 && dx * dx > open[j].r) {
              closed.push(open[j])
              open.splice(j, 1)
              continue
            }

            /* If not, skip this triangle. */
            dy = vertices[i].y - open[j].y
            if(dx * dx + dy * dy > open[j].r)
              continue

            /* Remove the triangle and add it's edges to the edge list. */
            edges.push(
              open[j].a, open[j].b,
              open[j].b, open[j].c,
              open[j].c, open[j].a
            )
            open.splice(j, 1)
          }

          /* Remove any doubled edges. */
          dedup(edges)

          /* Add a new triangle for each edge. */
          j = edges.length
          while(j) {
            b = edges[--j]
            a = edges[--j]
            open.push(new Triangle(a, b, vertices[i]))
          }
        }

        /* Copy any remaining open triangles to the closed list, and then
         * remove any triangles that share a vertex with the supertriangle. */
        Array.prototype.push.apply(closed, open)

        i = closed.length
        while(i--)
          if(closed[i].a.__sentinel ||
             closed[i].b.__sentinel ||
             closed[i].c.__sentinel)
            closed.splice(i, 1)

        /* Yay, we're done! */
        return closed
      }

    var layer = null;

    window.plugin.portalfolding.toggle = function() {
        if (layer) {
                // toggle off
                window.map.removeLayer(layer);
                layer = null;
                return;
        }

        var locations = [];
        var minX = 0;
        var minY = 0;

        for (var key in window.portals) {
            var loc = window.portals[key].options.details.locationE6;
            var nloc = { x: loc.lngE6, y: loc.latE6 };
            if (nloc.x < minX) minX = nloc.x;
            if (nloc.y < minX) minX = nloc.y;
            locations.push(nloc);
        }

        var i = locations.length;
        while(i) {
            var nloc = locations[--i];
            nloc.x += Math.abs(minX);
            nloc.y += Math.abs(minY);
        }

        layer = L.layerGroup([])

        var triangles = triangulate(locations);
        i = triangles.length;
        while(i) {
            var triangle = triangles[--i];
            triangle.draw(layer, minX, minY)
        }

        window.map.addLayer(layer);
        return layer;
     }

    var setup =  function() {
        $('#toolbox').append('toggle MaxLinks ');
    }

    // PLUGIN END //////////////////////////////////////////////////////////
    if(window.iitcLoaded && typeof setup === 'function') {
      setup();
    } else {
      if(window.bootPlugins)
        window.bootPlugins.push(setup);
      else
        window.bootPlugins = [setup];
    }
} // wrapper end

// inject code into site context
var script = document.createElement('script');
script.appendChild(document.createTextNode('('+ wrapper +')();'));
(document.body || document.head || document.documentElement).appendChild(script);<|endoftext|>


<commit_before><commit_msg>bug fix<commit_after><|endoftext|>


<commit_before>'use strict';

const StyleSheet = require('react-style');

const red = '#da2100';
const green = '#159600';

const styles = {
  card: StyleSheet.create({
    margin: 0,
    height: 'auto',
    width: '100%',
    display: 'flex',
    flexDirection: 'column'
  }),
  changeFolderButton: {
    position: 'absolute',
    top: -28,
    margin: 0,
    right: 19,
    left: 'auto',
    transform: 'none'
  },
  fileTempIndicator: {
    backgroundColor: green,
    height: 10,
    width: 10,
    borderRadius: '100%',
    marginRight: 5,
    display: 'inline-block'
  },
  fileHasTemp: {
    backgroundColor: red
  },
  errorToast: {
    backgroundColor: red
  },
  successToast: {
    backgroundColor: green
  }
};

module.exports = styles;
<commit_msg>cleanup<commit_after>'use strict';

const StyleSheet = require('react-style');

const red = '#da2100';
const green = '#159600';

const styles = {
  card: StyleSheet.create({
    margin: 0,
    height: 'auto',
    width: '100%',
    display: 'flex',
    flexDirection: 'column'
  }),
  changeFolderButton: {
    position: 'absolute',
    top: -28,
    margin: 0,
    right: 19,
    left: 'auto',
    transform: 'none'
  },
  fileTempIndicator: {
    backgroundColor: green,
    height: 10,
    width: 10,
    borderRadius: '100%',
    marginRight: 5,
    display: 'inline-block'
  },
  fileHasTemp: {
    backgroundColor: red
  }
};

module.exports = styles;
<|endoftext|>


<commit_before>/**
 * EventTarget.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

define('moxie/core/EventTarget', [
	'moxie/core/utils/Env',
	'moxie/core/Exceptions',
	'moxie/core/utils/Basic'
], function(Env, x, Basic) {
	/**
	Parent object for all event dispatching components and objects

	@class moxie/core/EventTarget
	@constructor EventTarget
	*/
	function EventTarget() {
		// hash of event listeners by object uid
		var eventpool = {};

		Basic.extend(this, {

			/**
			Unique id of the event dispatcher, usually overriden by children

			@property uid
			@type String
			*/
			uid: null,

			/**
			Can be called from within a child  in order to acquire uniqie id in automated manner

			@method init
			*/
			init: function() {
				if (!this.uid) {
					this.uid = Basic.guid('uid_');
				}
			},

			/**
			Register a handler to a specific event dispatched by the object

			@method addEventListener
			@param {String} type Type or basically a name of the event to subscribe to
			@param {Function} fn Callback function that will be called when event happens
			@param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first
			@param {Object} [scope=this] A scope to invoke event handler in
			*/
			addEventListener: function(type, fn, priority, scope) {
				var self = this, list;

				// without uid no event handlers can be added, so make sure we got one
				if (!this.hasOwnProperty('uid')) {
					this.uid = Basic.guid('uid_');
				}

				type = Basic.trim(type);

				if (/\s/.test(type)) {
					// multiple event types were passed for one handler
					Basic.each(type.split(/\s+/), function(type) {
						self.addEventListener(type, fn, priority, scope);
					});
					return;
				}

				type = type.toLowerCase();
				priority = parseInt(priority, 10) || 0;

				list = eventpool[this.uid] && eventpool[this.uid][type] || [];
				list.push({fn : fn, priority : priority, scope : scope || this});

				if (!eventpool[this.uid]) {
					eventpool[this.uid] = {};
				}
				eventpool[this.uid][type] = list;
			},

			/**
			Check if any handlers were registered to the specified event

			@method hasEventListener
			@param {String} type Type or basically a name of the event to check
			@return {Mixed} Returns a handler if it was found and false, if - not
			*/
			hasEventListener: function(type) {
				var list = type ? eventpool[this.uid] && eventpool[this.uid][type] : eventpool[this.uid];
				return list ? list : false;
			},

			/**
			Unregister the handler from the event, or if former was not specified - unregister all handlers

			@method removeEventListener
			@param {String} type Type or basically a name of the event
			@param {Function} [fn] Handler to unregister
			*/
			removeEventListener: function(type, fn) {
				type = type.toLowerCase();

				var list = eventpool[this.uid] && eventpool[this.uid][type], i;

				if (list) {
					if (fn) {
						for (i = list.length - 1; i >= 0; i--) {
							if (list[i].fn === fn) {
								list.splice(i, 1);
								break;
							}
						}
					} else {
						list = [];
					}

					// delete event list if it has become empty
					if (!list.length) {
						delete eventpool[this.uid][type];

						// and object specific entry in a hash if it has no more listeners attached
						if (Basic.isEmptyObj(eventpool[this.uid])) {
							delete eventpool[this.uid];
						}
					}
				}
			},

			/**
			Remove all event handlers from the object

			@method removeAllEventListeners
			*/
			removeAllEventListeners: function() {
				if (eventpool[this.uid]) {
					delete eventpool[this.uid];
				}
			},

			/**
			Dispatch the event

			@method dispatchEvent
			@param {String/Object} Type of event or event object to dispatch
			@param {Mixed} [...] Variable number of arguments to be passed to a handlers
			@return {Boolean} true by default and false if any handler returned false
			*/
			dispatchEvent: function(type) {
				var uid, list, args, tmpEvt, evt = {}, result = true, undef;

				if (Basic.typeOf(type) !== 'string') {
					// we can't use original object directly (because of Silverlight)
					tmpEvt = type;

					if (Basic.typeOf(tmpEvt.type) === 'string') {
						type = tmpEvt.type;

						if (tmpEvt.total !== undef && tmpEvt.loaded !== undef) { // progress event
							evt.total = tmpEvt.total;
							evt.loaded = tmpEvt.loaded;
						}
						evt.async = tmpEvt.async || false;
					} else {
						throw new x.EventException(x.EventException.UNSPECIFIED_EVENT_TYPE_ERR);
					}
				}

				// check if event is meant to be dispatched on an object having specific uid
				if (type.indexOf('::') !== -1) {
					(function(arr) {
						uid = arr[0];
						type = arr[1];
					}(type.split('::')));
				} else {
					uid = this.uid;
				}

				type = type.toLowerCase();

				list = eventpool[uid] && eventpool[uid][type];

				if (list) {
					// sort event list by prority
					list.sort(function(a, b) { return b.priority - a.priority; });

					args = [].slice.call(arguments);

					// first argument will be pseudo-event object
					args.shift();
					evt.type = type;
					args.unshift(evt);

					if (MXI_DEBUG && Env.debug.events) {
						Env.log("Event '%s' fired on %u", evt.type, uid);
					}

					// Dispatch event to all listeners
					var queue = [];
					Basic.each(list, function(handler) {
						// explicitly set the target, otherwise events fired from shims do not get it
						args[0].target = handler.scope;
						// if event is marked as async, detach the handler
						if (evt.async) {
							queue.push(function(cb) {
								setTimeout(function() {
									cb(handler.fn.apply(handler.scope, args) === false);
								}, 1);
							});
						} else {
							queue.push(function(cb) {
								cb(handler.fn.apply(handler.scope, args) === false); // if handler returns false stop propagation
							});
						}
					});
					if (queue.length) {
						Basic.inSeries(queue, function(err) {
							result = !err;
						});
					}
				}
				return result;
			},

			/**
			Alias for addEventListener

			@method bind
			@protected
			*/
			bind: function() {
				this.addEventListener.apply(this, arguments);
			},

			/**
			Alias for removeEventListener

			@method unbind
			@protected
			*/
			unbind: function() {
				this.removeEventListener.apply(this, arguments);
			},

			/**
			Alias for removeAllEventListeners

			@method unbindAll
			@protected
			*/
			unbindAll: function() {
				this.removeAllEventListeners.apply(this, arguments);
			},

			/**
			Alias for dispatchEvent

			@method trigger
			@protected
			*/
			trigger: function() {
				return this.dispatchEvent.apply(this, arguments);
			},

			/**
			Handle properties of on[event] type.

			@method handleEventProps
			@private
			*/
			handleEventProps: function(dispatches) {
				var self = this;

				this.bind(dispatches.join(' '), function(e) {
					var prop = 'on' + e.type.toLowerCase();
					if (Basic.typeOf(this[prop]) === 'function') {
						this[prop].apply(this, arguments);
					}
				});

				// object must have defined event properties, even if it doesn't make use of them
				Basic.each(dispatches, function(prop) {
					prop = 'on' + prop.toLowerCase(prop);
					if (Basic.typeOf(self[prop]) === 'undefined') {
						self[prop] = null;
					}
				});
			}

		});
	}

	EventTarget.instance = new EventTarget();

	return EventTarget;
});<commit_msg>EventTarget: Add bindOnce() method.<commit_after>/**
 * EventTarget.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

define('moxie/core/EventTarget', [
	'moxie/core/utils/Env',
	'moxie/core/Exceptions',
	'moxie/core/utils/Basic'
], function(Env, x, Basic) {
	/**
	Parent object for all event dispatching components and objects

	@class moxie/core/EventTarget
	@constructor EventTarget
	*/
	function EventTarget() {
		// hash of event listeners by object uid
		var eventpool = {};

		Basic.extend(this, {

			/**
			Unique id of the event dispatcher, usually overriden by children

			@property uid
			@type String
			*/
			uid: null,

			/**
			Can be called from within a child  in order to acquire uniqie id in automated manner

			@method init
			*/
			init: function() {
				if (!this.uid) {
					this.uid = Basic.guid('uid_');
				}
			},

			/**
			Register a handler to a specific event dispatched by the object

			@method addEventListener
			@param {String} type Type or basically a name of the event to subscribe to
			@param {Function} fn Callback function that will be called when event happens
			@param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first
			@param {Object} [scope=this] A scope to invoke event handler in
			*/
			addEventListener: function(type, fn, priority, scope) {
				var self = this, list;

				// without uid no event handlers can be added, so make sure we got one
				if (!this.hasOwnProperty('uid')) {
					this.uid = Basic.guid('uid_');
				}

				type = Basic.trim(type);

				if (/\s/.test(type)) {
					// multiple event types were passed for one handler
					Basic.each(type.split(/\s+/), function(type) {
						self.addEventListener(type, fn, priority, scope);
					});
					return;
				}

				type = type.toLowerCase();
				priority = parseInt(priority, 10) || 0;

				list = eventpool[this.uid] && eventpool[this.uid][type] || [];
				list.push({fn : fn, priority : priority, scope : scope || this});

				if (!eventpool[this.uid]) {
					eventpool[this.uid] = {};
				}
				eventpool[this.uid][type] = list;
			},

			/**
			Check if any handlers were registered to the specified event

			@method hasEventListener
			@param {String} type Type or basically a name of the event to check
			@return {Mixed} Returns a handler if it was found and false, if - not
			*/
			hasEventListener: function(type) {
				var list = type ? eventpool[this.uid] && eventpool[this.uid][type] : eventpool[this.uid];
				return list ? list : false;
			},

			/**
			Unregister the handler from the event, or if former was not specified - unregister all handlers

			@method removeEventListener
			@param {String} type Type or basically a name of the event
			@param {Function} [fn] Handler to unregister
			*/
			removeEventListener: function(type, fn) {
				type = type.toLowerCase();

				var list = eventpool[this.uid] && eventpool[this.uid][type], i;

				if (list) {
					if (fn) {
						for (i = list.length - 1; i >= 0; i--) {
							if (list[i].fn === fn) {
								list.splice(i, 1);
								break;
							}
						}
					} else {
						list = [];
					}

					// delete event list if it has become empty
					if (!list.length) {
						delete eventpool[this.uid][type];

						// and object specific entry in a hash if it has no more listeners attached
						if (Basic.isEmptyObj(eventpool[this.uid])) {
							delete eventpool[this.uid];
						}
					}
				}
			},

			/**
			Remove all event handlers from the object

			@method removeAllEventListeners
			*/
			removeAllEventListeners: function() {
				if (eventpool[this.uid]) {
					delete eventpool[this.uid];
				}
			},

			/**
			Dispatch the event

			@method dispatchEvent
			@param {String/Object} Type of event or event object to dispatch
			@param {Mixed} [...] Variable number of arguments to be passed to a handlers
			@return {Boolean} true by default and false if any handler returned false
			*/
			dispatchEvent: function(type) {
				var uid, list, args, tmpEvt, evt = {}, result = true, undef;

				if (Basic.typeOf(type) !== 'string') {
					// we can't use original object directly (because of Silverlight)
					tmpEvt = type;

					if (Basic.typeOf(tmpEvt.type) === 'string') {
						type = tmpEvt.type;

						if (tmpEvt.total !== undef && tmpEvt.loaded !== undef) { // progress event
							evt.total = tmpEvt.total;
							evt.loaded = tmpEvt.loaded;
						}
						evt.async = tmpEvt.async || false;
					} else {
						throw new x.EventException(x.EventException.UNSPECIFIED_EVENT_TYPE_ERR);
					}
				}

				// check if event is meant to be dispatched on an object having specific uid
				if (type.indexOf('::') !== -1) {
					(function(arr) {
						uid = arr[0];
						type = arr[1];
					}(type.split('::')));
				} else {
					uid = this.uid;
				}

				type = type.toLowerCase();

				list = eventpool[uid] && eventpool[uid][type];

				if (list) {
					// sort event list by prority
					list.sort(function(a, b) { return b.priority - a.priority; });

					args = [].slice.call(arguments);

					// first argument will be pseudo-event object
					args.shift();
					evt.type = type;
					args.unshift(evt);

					if (MXI_DEBUG && Env.debug.events) {
						Env.log("Event '%s' fired on %u", evt.type, uid);
					}

					// Dispatch event to all listeners
					var queue = [];
					Basic.each(list, function(handler) {
						// explicitly set the target, otherwise events fired from shims do not get it
						args[0].target = handler.scope;
						// if event is marked as async, detach the handler
						if (evt.async) {
							queue.push(function(cb) {
								setTimeout(function() {
									cb(handler.fn.apply(handler.scope, args) === false);
								}, 1);
							});
						} else {
							queue.push(function(cb) {
								cb(handler.fn.apply(handler.scope, args) === false); // if handler returns false stop propagation
							});
						}
					});
					if (queue.length) {
						Basic.inSeries(queue, function(err) {
							result = !err;
						});
					}
				}
				return result;
			},

			/**
			Register a handler to the event type that will run only once

			@method bindOnce
			@since >1.4.1
			@param {String} type Type or basically a name of the event to subscribe to
			@param {Function} fn Callback function that will be called when event happens
			@param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first
			@param {Object} [scope=this] A scope to invoke event handler in
			*/
			bindOnce: function(type, fn, priority, scope) {
				var self = this;
				self.bind.call(this, type, function cb() {
					self.unbind(type, cb);
					return fn.apply(this, arguments);
				}, priority, scope);
			},

			/**
			Alias for addEventListener

			@method bind
			@protected
			*/
			bind: function() {
				this.addEventListener.apply(this, arguments);
			},

			/**
			Alias for removeEventListener

			@method unbind
			@protected
			*/
			unbind: function() {
				this.removeEventListener.apply(this, arguments);
			},

			/**
			Alias for removeAllEventListeners

			@method unbindAll
			@protected
			*/
			unbindAll: function() {
				this.removeAllEventListeners.apply(this, arguments);
			},

			/**
			Alias for dispatchEvent

			@method trigger
			@protected
			*/
			trigger: function() {
				return this.dispatchEvent.apply(this, arguments);
			},

			/**
			Handle properties of on[event] type.

			@method handleEventProps
			@private
			*/
			handleEventProps: function(dispatches) {
				var self = this;

				this.bind(dispatches.join(' '), function(e) {
					var prop = 'on' + e.type.toLowerCase();
					if (Basic.typeOf(this[prop]) === 'function') {
						this[prop].apply(this, arguments);
					}
				});

				// object must have defined event properties, even if it doesn't make use of them
				Basic.each(dispatches, function(prop) {
					prop = 'on' + prop.toLowerCase(prop);
					if (Basic.typeOf(self[prop]) === 'undefined') {
						self[prop] = null;
					}
				});
			}

		});
	}

	EventTarget.instance = new EventTarget();

	return EventTarget;
});<|endoftext|>


<commit_before>/*!
 * jquery-time-duration-picker v1.0.7
 *
 * https://github.com/digaev/jquery-time-duration-picker
 *
 * Copyright (c) 2015-2016 Nikolay Digaev
 * Released under the MIT license
 */

( function( $ ) {
  $.timeDurationPicker = {
    setDefaults: function( options ) {
      $.timeDurationPicker.defaults = $.extend( true, {}, {
          lang: "en",
          position: "fixed", // https://github.com/digaev/jquery-time-duration-picker/issues/1
          years: true,
          months: true,
          days: true,
          hours: true,
          minutes: true,
          seconds: false
        }, options
      );
    }
  };
  $.timeDurationPicker.setDefaults();
} )( jQuery );

( function( $ ) {
  var instances = [];

  $( document ).focusin( function( e ) {
    for ( var i = 0, c = instances.length; i < c; ++i ) {
      var inst = instances[ i ];
      for ( var j = 0, l = inst.element.length; j < l; ++j ) {
        if ( inst.element[ j ] === e.target ) {
          var offset = $( e.target ).offset();
          offset.top += $( e.target ).outerHeight();
          offset.top -= $( window ).scrollTop();
          offset.left -= $( window ).scrollLeft();
          inst._content.div.css( offset ).fadeIn();
        }
      }
    }
  } ).focusout( function() {

    // FIXME: how to correctly detect that the element has lost focus?
    setTimeout( function() {
      var el = document.activeElement;
      if ( $( el ).parents( ".time-duration-picker-content" ).length === 0 ) {
        for ( var i = 0, c = instances.length; i < c; ++i ) {
          var hide = true;
          var inst = instances[ i ];
          for ( var j = 0, l = inst.element.length; j < l; ++j ) {
            if ( inst.element[ j ] === el ) {
              hide = false;
              break;
            }
          }
          if ( hide ) {
            inst._content.div.fadeOut();
          }
        }
      }
    }, 10 );
  } );

  var YEAR = 12 * 30 * 24 * 60 * 60;
  var MONTH = 30 * 24 * 60 * 60;
  var DAY = 24 * 60 * 60;
  var HOUR = 60 * 60;
  var MINUTE = 60;

  $.widget( "custom.timeDurationPicker", {
    options: {
    },
    _langs: {
      en: {
        seconds: "Seconds",
        minutes: "Minutes",
        hours: "Hours",
        days: "Days",
        months: "Months",
        years: "Years",
        human_years: "years",
        human_months: "months",
        human_days: "days",
        human_hours: "hours",
        human_minutes: "minutes",
        human_seconds: "seconds",
        and: "and",
        button_ok: "Done"
      },
      ru: {
        seconds: "Секунды",
        minutes: "Минуты",
        hours: "Часы",
        days: "Дни",
        months: "Месяцы",
        years: "Годы",
        human_years: "лет",
        human_months: "месяцев",
        human_days: "дней",
        human_hours: "часов",
        human_minutes: "минут",
        human_seconds: "секунд",
        and: "и",
        button_ok: "Выбрать"
      },
      ru_short: {
        seconds: "Секунды",
        minutes: "Минуты",
        hours: "Часы",
        days: "Дни",
        months: "Месяцы",
        years: "Годы",
        human_years: "г.",
        human_months: "мес.",
        human_days: "д.",
        human_hours: "час.",
        human_minutes: "мин.",
        human_seconds: "сек.",
        and: "и",
        button_ok: "Выбрать"
      }
    },
    _create: function() {
      var self = this;

      this.options = $.extend(
        {}, $.timeDurationPicker.defaults, this.options
      );

      this._content = {};
      this._content.div = $( "
" );
      this._content.div.addClass( "ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" );
      this._content.div.addClass( "time-duration-picker-content" );
      this._content.div.css( {
          display: "none",
          position: this.options.position,
          "z-index": 401
      } );
      this._content.div.appendTo( document.body );

      this._content.table = $(
        "

"
      ).appendTo( this._content.div );
      this._content.tableBody = $( "tbody", this._content.table );

      this._content.button = $( "<input type='button' />" )
        .val( this._tr( "button_ok" ) );
      this._content.button.addClass(
        "ui-button ui-widget ui-state-default ui-corner-all"
      );
      this._content.button.css( {
        display: "block",
        margin: "0.5em auto",
        padding: "0.5em 1em"
      } );
      this._content.button.hover( function() {
        $( this ).addClass( "ui-state-hover" );
      }, function() {
        $( this ).removeClass( "ui-state-hover" );
      } );
      this._content.button.on( "click", function() {
        self._content.div.fadeOut();
        if ( self.options.onSelect ) {
          self.options.onSelect(
            self.element, self.getDuration(), self.getHumanDuration()
          );
        }
      } );
      this._content.button.appendTo( this._content.div );

      this._initUnits();

      instances.push( this );
    },
    _initUnits: function() {
      if ( this.options.seconds ) {
        this._content.seconds = this._createSelectWithOptions( 0, 59 );
        this._appendRow( this._tr( "seconds" ), this._content.seconds );
      }
      if ( this.options.minutes ) {
        this._content.minutes = this._createSelectWithOptions( 0, 59 );
        this._appendRow( this._tr( "minutes" ), this._content.minutes );
      }
      if ( this.options.hours ) {
        this._content.hours = this._createSelectWithOptions( 0, 23 );
        this._appendRow( this._tr( "hours" ), this._content.hours );
      }
      if ( this.options.days ) {
        this._content.days = this._createSelectWithOptions( 0, 29 );
        this._appendRow( this._tr( "days" ), this._content.days );
      }
      if ( this.options.months ) {
        this._content.months = this._createSelectWithOptions( 0, 11 );
        this._appendRow( this._tr( "months" ), this._content.months );
      }
      if ( this.options.years ) {
        this._content.years = this._createSelectWithOptions( 0, 10 );
        this._appendRow( this._tr( "years" ), this._content.years );
      }
      if ( this.options.defaultValue ) {
        var value;
        if ( typeof this.options.defaultValue === "function" ) {
          value = this.options.defaultValue.call( this );
        } else {
          value = this.options.defaultValue;
        }
        if ( this.setDuration( value ) ) {
          this.element.val( this.getHumanDuration() );
        }
      }
    },
    _createSelectWithOptions: function( min, max ) {
      var select = $( "<select />" ).addClass(
        "ui-widget ui-state-default ui-corner-all"
      );

      select.hover( function() {
        $( this ).addClass( "ui-state-hover" );
      }, function() {
        $( this ).removeClass( "ui-state-hover" );
      } );

      this._createOptionsForSelect( select, min, max );
      return select;
    },
    _createOptionsForSelect: function( select, min, max ) {
      for ( var i = min; i <= max; ++i ) {
        select.append( $( "<option />" ).val( i ).text(
          i < 10 ? ( "0" + i ) : i
        ) );
      }
    },
    _appendRow: function( text, el ) {
      var row = $( "
" ).appendTo( this._content.tableBody );
      $( "" ).css( {
        width: "50%",
        padding: ".5em 1em",
        "text-align": "right",
        "vertical-align": "middle"
      } ).append( $( "" )
        .text( text ) )
        .appendTo( row );
      $( "" ).css( {
        width: "50%",
        padding: ".5em 1em",
        "text-align": "right",
        "vertical-align": "middle"
      } ).append( el )
        .appendTo( row );
    },
    _tr: function( key ) {
      return this._langs[ this.options.lang ][ key ];
    },
    getSeconds: function() {
      return parseInt( this._content.seconds.val() );
    },
    getMinutes: function() {
      return parseInt( this._content.minutes.val() );
    },
    getHours: function() {
      return parseInt( this._content.hours.val() );
    },
    getDays: function() {
      return parseInt( this._content.days.val() );
    },
    getMonths: function() {
      return parseInt( this._content.months.val() );
    },
    getYears: function() {
      return parseInt( this._content.years.val() );
    },
    setDuration: function( value ) {
      value = parseInt( value );
      if ( isNaN( value ) ) {
        return false;
      }

      var i;
      if ( this.options.years ) {
        i = Math.floor( value / YEAR );
        value -= i * YEAR;
        this._content.years.val( i );
      }
      if ( this.options.months ) {
        i = Math.floor( value / MONTH );
        if ( i >= 12 ) {
          i = 0;
        }
        value -= i * MONTH;
        this._content.months.val( i );
      }
      if ( this.options.days ) {
        i = Math.floor( value / DAY );
        if ( i >= 30 ) {
          i = 0;
        }
        value -= i * DAY;
        this._content.days.val( i );
      }
      if ( this.options.hours ) {
        i = Math.floor( value / HOUR );
        if ( i >= 24 ) {
          i = 0;
        }
        value -= i * HOUR;
        this._content.hours.val( i );
      }
      if ( this.options.minutes ) {
        i = Math.floor( value / MINUTE );
        if ( i >= 60 ) {
          i = 0;
        }
        value -= i * MINUTE;
        this._content.minutes.val( i );
      }
      if ( this.options.seconds ) {
        i = Math.floor( value );
        if ( i >= 60 ) {
          i = 0;
        }
        this._content.seconds.val( i );
      }
      return value;
    },
    getDuration: function() {
      var seconds = 0;
      if ( this.options.seconds ) {
        seconds += this.getSeconds();
      }
      if ( this.options.minutes ) {
        seconds += this.getMinutes() * MINUTE;
      }
      if ( this.options.hours ) {
        seconds += this.getHours() * HOUR;
      }
      if ( this.options.days ) {
        seconds += this.getDays() * DAY;
      }
      if ( this.options.months ) {
        seconds += this.getMonths() * MONTH;
      }
      if ( this.options.years ) {
        seconds += this.getYears() * YEAR;
      }
      return seconds;
    },
    getHumanDuration: function() {
      var units = [];
      var duration = "";

      if ( this.options.years && this.getYears() > 0 ) {
        units.push( { value: this.getYears(), name: this._tr( "human_years" ) } );
      }
      if ( this.options.months && this.getMonths() > 0 ) {
        units.push( { value: this.getMonths(), name: this._tr( "human_months" ) } );
      }
      if ( this.options.days && this.getDays() > 0 ) {
        units.push( { value: this.getDays(), name: this._tr( "human_days" ) } );
      }
      if ( this.options.hours && this.getHours() > 0 ) {
        units.push( { value: this.getHours(), name: this._tr( "human_hours" ) } );
      }
      if ( this.options.minutes && this.getMinutes() > 0 ) {
        units.push( { value: this.getMinutes(), name: this._tr( "human_minutes" ) } );
      }
      if ( this.options.seconds && this.getSeconds() > 0 ) {
        units.push( { value: this.getSeconds(), name: this._tr( "human_seconds" ) } );
      }

      for ( var i = 0, l = units.length; i < l; ++i ) {
        var unit = units[ i ];
        var end = ( i === l - 2 ? " " + this._tr( "and" ) + " " : ", " );
        end = i === l - 1 ? "" : end;

        duration += unit.value + " " + unit.name + end;
      }
      return duration;
    }
  } );
} )( jQuery );
<commit_msg>Add `css` option & remove `position` option<commit_after>/*!
 * jquery-time-duration-picker v1.0.7
 *
 * https://github.com/digaev/jquery-time-duration-picker
 *
 * Copyright (c) 2015-2016 Nikolay Digaev
 * Released under the MIT license
 */

( function( $ ) {
  $.timeDurationPicker = {
    setDefaults: function( options ) {
      $.timeDurationPicker.defaults = $.extend( true, {}, {
        lang: "en",
        css: {
          position: "fixed" // https://github.com/digaev/jquery-time-duration-picker/issues/1
        },
        years: true,
        months: true,
        days: true,
        hours: true,
        minutes: true,
        seconds: false
        }, options
      );
    }
  };
  $.timeDurationPicker.setDefaults();
} )( jQuery );

( function( $ ) {
  var instances = [];

  $( document ).focusin( function( e ) {
    for ( var i = 0, c = instances.length; i < c; ++i ) {
      var inst = instances[ i ];
      for ( var j = 0, l = inst.element.length; j < l; ++j ) {
        if ( inst.element[ j ] === e.target ) {
          var offset = $( e.target ).offset();
          offset.top += $( e.target ).outerHeight();
          offset.top -= $( window ).scrollTop();
          offset.left -= $( window ).scrollLeft();
          inst._content.div.css( offset ).fadeIn();
        }
      }
    }
  } ).focusout( function() {

    // FIXME: how to correctly detect that the element has lost focus?
    setTimeout( function() {
      var el = document.activeElement;
      if ( $( el ).parents( ".time-duration-picker-content" ).length === 0 ) {
        for ( var i = 0, c = instances.length; i < c; ++i ) {
          var hide = true;
          var inst = instances[ i ];
          for ( var j = 0, l = inst.element.length; j < l; ++j ) {
            if ( inst.element[ j ] === el ) {
              hide = false;
              break;
            }
          }
          if ( hide ) {
            inst._content.div.fadeOut();
          }
        }
      }
    }, 10 );
  } );

  var YEAR = 12 * 30 * 24 * 60 * 60;
  var MONTH = 30 * 24 * 60 * 60;
  var DAY = 24 * 60 * 60;
  var HOUR = 60 * 60;
  var MINUTE = 60;

  $.widget( "custom.timeDurationPicker", {
    options: {
    },
    _langs: {
      en: {
        seconds: "Seconds",
        minutes: "Minutes",
        hours: "Hours",
        days: "Days",
        months: "Months",
        years: "Years",
        human_years: "years",
        human_months: "months",
        human_days: "days",
        human_hours: "hours",
        human_minutes: "minutes",
        human_seconds: "seconds",
        and: "and",
        button_ok: "Done"
      },
      ru: {
        seconds: "Секунды",
        minutes: "Минуты",
        hours: "Часы",
        days: "Дни",
        months: "Месяцы",
        years: "Годы",
        human_years: "лет",
        human_months: "месяцев",
        human_days: "дней",
        human_hours: "часов",
        human_minutes: "минут",
        human_seconds: "секунд",
        and: "и",
        button_ok: "Выбрать"
      },
      ru_short: {
        seconds: "Секунды",
        minutes: "Минуты",
        hours: "Часы",
        days: "Дни",
        months: "Месяцы",
        years: "Годы",
        human_years: "г.",
        human_months: "мес.",
        human_days: "д.",
        human_hours: "час.",
        human_minutes: "мин.",
        human_seconds: "сек.",
        and: "и",
        button_ok: "Выбрать"
      }
    },
    _create: function() {
      var self = this;

      this.options = $.extend(
        true, {}, $.timeDurationPicker.defaults, this.options
      );

      this._content = {};
      this._content.div = $( "
" );
      this._content.div.addClass( "ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" );
      this._content.div.addClass( "time-duration-picker-content" );
      this._content.div.css( $.extend( {
          display: "none",
          "z-index": 401
      }, this.options.css ) );
      this._content.div.appendTo( document.body );

      this._content.table = $(
        "

"
      ).appendTo( this._content.div );
      this._content.tableBody = $( "tbody", this._content.table );

      this._content.button = $( "<input type='button' />" )
        .val( this._tr( "button_ok" ) );
      this._content.button.addClass(
        "ui-button ui-widget ui-state-default ui-corner-all"
      );
      this._content.button.css( {
        display: "block",
        margin: "0.5em auto",
        padding: "0.5em 1em"
      } );
      this._content.button.hover( function() {
        $( this ).addClass( "ui-state-hover" );
      }, function() {
        $( this ).removeClass( "ui-state-hover" );
      } );
      this._content.button.on( "click", function() {
        self._content.div.fadeOut();
        if ( self.options.onSelect ) {
          self.options.onSelect(
            self.element, self.getDuration(), self.getHumanDuration()
          );
        }
      } );
      this._content.button.appendTo( this._content.div );

      this._initUnits();

      instances.push( this );
    },
    _initUnits: function() {
      if ( this.options.seconds ) {
        this._content.seconds = this._createSelectWithOptions( 0, 59 );
        this._appendRow( this._tr( "seconds" ), this._content.seconds );
      }
      if ( this.options.minutes ) {
        this._content.minutes = this._createSelectWithOptions( 0, 59 );
        this._appendRow( this._tr( "minutes" ), this._content.minutes );
      }
      if ( this.options.hours ) {
        this._content.hours = this._createSelectWithOptions( 0, 23 );
        this._appendRow( this._tr( "hours" ), this._content.hours );
      }
      if ( this.options.days ) {
        this._content.days = this._createSelectWithOptions( 0, 29 );
        this._appendRow( this._tr( "days" ), this._content.days );
      }
      if ( this.options.months ) {
        this._content.months = this._createSelectWithOptions( 0, 11 );
        this._appendRow( this._tr( "months" ), this._content.months );
      }
      if ( this.options.years ) {
        this._content.years = this._createSelectWithOptions( 0, 10 );
        this._appendRow( this._tr( "years" ), this._content.years );
      }
      if ( this.options.defaultValue ) {
        var value;
        if ( typeof this.options.defaultValue === "function" ) {
          value = this.options.defaultValue.call( this );
        } else {
          value = this.options.defaultValue;
        }
        if ( this.setDuration( value ) ) {
          this.element.val( this.getHumanDuration() );
        }
      }
    },
    _createSelectWithOptions: function( min, max ) {
      var select = $( "<select />" ).addClass(
        "ui-widget ui-state-default ui-corner-all"
      );

      select.hover( function() {
        $( this ).addClass( "ui-state-hover" );
      }, function() {
        $( this ).removeClass( "ui-state-hover" );
      } );

      this._createOptionsForSelect( select, min, max );
      return select;
    },
    _createOptionsForSelect: function( select, min, max ) {
      for ( var i = min; i <= max; ++i ) {
        select.append( $( "<option />" ).val( i ).text(
          i < 10 ? ( "0" + i ) : i
        ) );
      }
    },
    _appendRow: function( text, el ) {
      var row = $( "
" ).appendTo( this._content.tableBody );
      $( "" ).css( {
        width: "50%",
        padding: ".5em 1em",
        "text-align": "right",
        "vertical-align": "middle"
      } ).append( $( "" )
        .text( text ) )
        .appendTo( row );
      $( "" ).css( {
        width: "50%",
        padding: ".5em 1em",
        "text-align": "right",
        "vertical-align": "middle"
      } ).append( el )
        .appendTo( row );
    },
    _tr: function( key ) {
      return this._langs[ this.options.lang ][ key ];
    },
    getSeconds: function() {
      return parseInt( this._content.seconds.val() );
    },
    getMinutes: function() {
      return parseInt( this._content.minutes.val() );
    },
    getHours: function() {
      return parseInt( this._content.hours.val() );
    },
    getDays: function() {
      return parseInt( this._content.days.val() );
    },
    getMonths: function() {
      return parseInt( this._content.months.val() );
    },
    getYears: function() {
      return parseInt( this._content.years.val() );
    },
    setDuration: function( value ) {
      value = parseInt( value );
      if ( isNaN( value ) ) {
        return false;
      }

      var i;
      if ( this.options.years ) {
        i = Math.floor( value / YEAR );
        value -= i * YEAR;
        this._content.years.val( i );
      }
      if ( this.options.months ) {
        i = Math.floor( value / MONTH );
        if ( i >= 12 ) {
          i = 0;
        }
        value -= i * MONTH;
        this._content.months.val( i );
      }
      if ( this.options.days ) {
        i = Math.floor( value / DAY );
        if ( i >= 30 ) {
          i = 0;
        }
        value -= i * DAY;
        this._content.days.val( i );
      }
      if ( this.options.hours ) {
        i = Math.floor( value / HOUR );
        if ( i >= 24 ) {
          i = 0;
        }
        value -= i * HOUR;
        this._content.hours.val( i );
      }
      if ( this.options.minutes ) {
        i = Math.floor( value / MINUTE );
        if ( i >= 60 ) {
          i = 0;
        }
        value -= i * MINUTE;
        this._content.minutes.val( i );
      }
      if ( this.options.seconds ) {
        i = Math.floor( value );
        if ( i >= 60 ) {
          i = 0;
        }
        this._content.seconds.val( i );
      }
      return value;
    },
    getDuration: function() {
      var seconds = 0;
      if ( this.options.seconds ) {
        seconds += this.getSeconds();
      }
      if ( this.options.minutes ) {
        seconds += this.getMinutes() * MINUTE;
      }
      if ( this.options.hours ) {
        seconds += this.getHours() * HOUR;
      }
      if ( this.options.days ) {
        seconds += this.getDays() * DAY;
      }
      if ( this.options.months ) {
        seconds += this.getMonths() * MONTH;
      }
      if ( this.options.years ) {
        seconds += this.getYears() * YEAR;
      }
      return seconds;
    },
    getHumanDuration: function() {
      var units = [];
      var duration = "";

      if ( this.options.years && this.getYears() > 0 ) {
        units.push( { value: this.getYears(), name: this._tr( "human_years" ) } );
      }
      if ( this.options.months && this.getMonths() > 0 ) {
        units.push( { value: this.getMonths(), name: this._tr( "human_months" ) } );
      }
      if ( this.options.days && this.getDays() > 0 ) {
        units.push( { value: this.getDays(), name: this._tr( "human_days" ) } );
      }
      if ( this.options.hours && this.getHours() > 0 ) {
        units.push( { value: this.getHours(), name: this._tr( "human_hours" ) } );
      }
      if ( this.options.minutes && this.getMinutes() > 0 ) {
        units.push( { value: this.getMinutes(), name: this._tr( "human_minutes" ) } );
      }
      if ( this.options.seconds && this.getSeconds() > 0 ) {
        units.push( { value: this.getSeconds(), name: this._tr( "human_seconds" ) } );
      }

      for ( var i = 0, l = units.length; i < l; ++i ) {
        var unit = units[ i ];
        var end = ( i === l - 2 ? " " + this._tr( "and" ) + " " : ", " );
        end = i === l - 1 ? "" : end;

        duration += unit.value + " " + unit.name + end;
      }
      return duration;
    }
  } );
} )( jQuery );
<|endoftext|>