<commit_before>
var lib = require('./lib');
var r = require('./runtime');

var filters = {
    abs: function(n) {
        return Math.abs(n);
    },

    batch: function(arr, linecount, fill_with) {
        var res = [];
        var tmp = [];

        for(var i=0; i<arr.length; i++) {
            if(i % linecount === 0 && tmp.length) {
                res.push(tmp);
                tmp = [];
            }

            tmp.push(arr[i]);
        }

        if(tmp.length) {
            if(fill_with) {
                for(var i=tmp.length; i<linecount; i++) {
                    tmp.push(fill_with);
                }
            }

            res.push(tmp);
        }

        return res;
    },

    capitalize: function(str) {
        var ret = str.toLowerCase();
        return r.copySafeness(str, ret.charAt(0).toUpperCase() + ret.slice(1));
    },

    center: function(str, width) {
        width = width || 80;

        if(str.length >= width) {
            return str;
        }

        var spaces = width - str.length;
        var pre = lib.repeat(" ", spaces/2 - spaces % 2);
        var post = lib.repeat(" ", spaces/2);
        return r.copySafeness(str, pre + str + post);
    },

    'default': function(val, def) {
        return val ? val : def;
    },

    dictsort: function(val, case_sensitive, by) {
        if (!lib.isObject(val)) {
            throw new lib.TemplateError("dictsort filter: val must be an object");
        }

        var array = [];
        for (var k in val) {
            // deliberately include properties from the object's prototype
            array.push([k,val[k]]);
        }

        var si;
        if (by === undefined || by === "key") {
            si = 0;
        } else if (by === "value") {
            si = 1;
        } else {
            throw new lib.TemplateError(
                "dictsort filter: You can only sort by either key or value");
        }

        array.sort(function(t1, t2) {
            var a = t1[si];
            var b = t2[si];

            if (!case_sensitive) {
                if (lib.isString(a)) {
                    a = a.toUpperCase();
                }
                if (lib.isString(b)) {
                    b = b.toUpperCase();
                }
            }

            return a > b ? 1 : (a == b ? 0 : -1);
        });

        return array;
    },

    escape: function(str) {
        if(typeof str == 'string' ||
           str instanceof r.SafeString) {
            return lib.escape(str);
        }
        return str;
    },

    safe: function(str) {
        return r.markSafe(str);
    },

    first: function(arr) {
        return arr[0];
    },

    groupby: function(arr, attr) {
        return lib.groupBy(arr, attr);
    },

    indent: function(str, width, indentfirst) {
        width = width || 4;
        var res = '';
        var lines = str.split('\n');
        var sp = lib.repeat(' ', width);

        for(var i=0; i<lines.length; i++) {
            if(i == 0 && !indentfirst) {
                res += lines[i] + '\n';
            }
            else {
                res += sp + lines[i] + '\n';
            }
        }

        return r.copySafeness(str, res);
    },

    join: function(arr, del, attr) {
        del = del || '';

        if(attr) {
            arr = lib.map(arr, function(v) {
                return v[attr];
            });
        }

        return arr.join(del);
    },

    last: function(arr) {
        return arr[arr.length-1];
    },

    length: function(arr) {
        return arr.length;
    },

    list: function(val) {
        if(lib.isString(val)) {
            return val.split('');
        }
        else if(lib.isObject(val)) {
            var keys = [];

            if(Object.keys) {
                keys = Object.keys(val);
            }
            else {
                for(var k in val) {
                    keys.push(k);
                }
            }

            return lib.map(keys, function(k) {
                return { key: k,
                         value: val[k] };
            });
        }
        else {
            throw new lib.TemplateError("list filter: type not iterable");
        }
    },

    lower: function(str) {
        return str.toLowerCase();
    },

    random: function(arr) {
        return arr[Math.floor(Math.random() * arr.length)];
    },

    replace: function(str, old, new_, maxCount) {
        var res = str;
        var last = res;
        var count = 1;
        res = res.replace(old, new_);

        while(last != res) {
            if(count >= maxCount) {
                break;
            }

            last = res;
            res = res.replace(old, new_);
            count++;
        }

        return r.copySafeness(str, res);
    },

    reverse: function(val) {
        var arr;
        if(lib.isString(val)) {
            arr = filters.list(val);
        }
        else {
            // Copy it
            arr = lib.map(val, function(v) { return v; });
        }

        arr.reverse();

        if(lib.isString(val)) {
            return r.copySafeness(val, arr.join(''));
        }
        return arr;
    },

    round: function(val, precision, method) {
        precision = precision || 0;
        var factor = Math.pow(10, precision);
        var rounder;

        if(method == 'ceil') {
            rounder = Math.ceil;
        }
        else if(method == 'floor') {
            rounder = Math.floor;
        }
        else {
            rounder = Math.round;
        }

        return rounder(val * factor) / factor;
    },

    slice: function(arr, slices, fillWith) {
        var sliceLength = Math.floor(arr.length / slices);
        var extra = arr.length % slices;
        var offset = 0;
        var res = [];

        for(var i=0; i<slices; i++) {
            var start = offset + i * sliceLength;
            if(i < extra) {
                offset++;
            }
            var end = offset + (i + 1) * sliceLength;

            var slice = arr.slice(start, end);
            if(fillWith && i >= extra) {
                slice.push(fillWith);
            }
            res.push(slice);
        }

        return res;
    },

    sort: function(arr, reverse, caseSens, attr) {
        // Copy it
        arr = lib.map(arr, function(v) { return v; });

        arr.sort(function(a, b) {
            var x, y;

            if(attr) {
                x = a[attr];
                y = b[attr];
            }
            else {
                x = a;
                y = b;
            }

            if(!caseSens && lib.isString(x) && lib.isString(y)) {
                x = x.toLowerCase();
                y = y.toLowerCase();
            }

            if(x < y) {
                return reverse ? 1 : -1;
            }
            else if(x > y) {
                return reverse ? -1: 1;
            }
            else {
                return 0;
            }
        });

        return arr;
    },

    string: function(obj) {
        return r.copySafeness(obj, obj);
    },

    title: function(str) {
        var words = str.split(' ');
        for(var i = 0; i < words.length; i++) {
            words[i] = filters.capitalize(words[i]);
        }
        return r.copySafeness(str, words.join(' '));
    },

    trim: function(str) {
        return r.copySafeness(str, str.replace(/^\s*|\s*$/g, ''));
    },

    truncate: function(input, length, killwords, end) {
        var orig = input;
        length = length || 255;

        if (input.length <= length)
            return input;

        if (killwords) {
            input = input.substring(0, length);
        } else {
            var idx = input.lastIndexOf(' ', length);
            if(idx === -1) {
                idx = length;
            }

            input = input.substring(0, idx);
        }

        input += (end !== undefined && end !== null) ? end : '...';
        return r.copySafeness(orig, input);
    },

    upper: function(str) {
        return str.toUpperCase();
    },

    urlencode: function(obj) {
        var enc = encodeURIComponent;
        if (lib.isString(obj)) {
            return enc(obj);
        } else {
            var parts;
            if (lib.isArray(obj)) {
                parts = obj.map(function(item) {
                    return enc(item[0]) + '=' + enc(item[1]);
                })
            } else {
                parts = [];
                for (var k in obj) {
                    if (obj.hasOwnProperty(k)) {
                        parts.push(enc(k) + '=' + enc(obj[k]));
                    }
                }
            }
            return parts.join('&');
        }
    },

    urlize: function(str, length, nofollow) {
        if (isNaN(length)) length = Infinity;

        var noFollowAttr = (nofollow === true ? ' rel="nofollow"' : '');

        // For the jinja regexp, see
        // https://github.com/mitsuhiko/jinja2/blob/f15b814dcba6aa12bc74d1f7d0c881d55f7126be/jinja2/utils.py#L20-L23
        var puncRE = /^(?:\(|<|<)?(.*?)(?:\.|,|\)|\n|>)?$/;
        // from http://blog.gerv.net/2011/05/html5_email_address_regexp/
        var emailRE = /^[\w.!#$%&'*+\-\/=?\^`{|}~]+@[a-z\d\-]+(\.[a-z\d\-]+)+$/i;
        var httpHttpsRE = /^https?:\/\/.*$/;
        var wwwRE = /^www\./;
        var tldRE = /\.(?:org|net|com)(?:\:|\/|$)/;

        var words = str.split(/\s+/).filter(function(word) {
          // If the word has no length, bail. This can happen for str with
          // trailing whitespace.
          return word && word.length;
        }).map(function(word) {
          var matches = word.match(puncRE);

          var possibleUrl = matches && matches[1] || word;

          // url that starts with http or https
          if (httpHttpsRE.test(possibleUrl))
            return '' + possibleUrl.substr(0, length) + '';

          // url that starts with www.
          if (wwwRE.test(possibleUrl))
            return '' + possibleUrl.substr(0, length) + '';

          // an email address of the form username@domain.tld
          if (emailRE.test(possibleUrl))
            return '' + possibleUrl + '';

          // url that ends in .com, .org or .net that is not an email address
          if (tldRE.test(possibleUrl))
            return '' + possibleUrl.substr(0, length) + '';

          return possibleUrl;

        });

        return words.join(' ');
    },

    wordcount: function(str) {
        return str.match(/\w+/g).length;
    },

    'float': function(val, def) {
        var res = parseFloat(val);
        return isNaN(res) ? def : res;
    },

    'int': function(val, def) {
        var res = parseInt(val, 10);
        return isNaN(res) ? def : res;
    }
};

// Aliases
filters.d = filters['default'];
filters.e = filters.escape;

module.exports = filters;
<commit_msg>Update word count filter<commit_after>var lib = require('./lib');
var r = require('./runtime');

var filters = {
    abs: function(n) {
        return Math.abs(n);
    },

    batch: function(arr, linecount, fill_with) {
        var res = [];
        var tmp = [];

        for(var i=0; i<arr.length; i++) {
            if(i % linecount === 0 && tmp.length) {
                res.push(tmp);
                tmp = [];
            }

            tmp.push(arr[i]);
        }

        if(tmp.length) {
            if(fill_with) {
                for(var i=tmp.length; i<linecount; i++) {
                    tmp.push(fill_with);
                }
            }

            res.push(tmp);
        }

        return res;
    },

    capitalize: function(str) {
        var ret = str.toLowerCase();
        return r.copySafeness(str, ret.charAt(0).toUpperCase() + ret.slice(1));
    },

    center: function(str, width) {
        width = width || 80;

        if(str.length >= width) {
            return str;
        }

        var spaces = width - str.length;
        var pre = lib.repeat(" ", spaces/2 - spaces % 2);
        var post = lib.repeat(" ", spaces/2);
        return r.copySafeness(str, pre + str + post);
    },

    'default': function(val, def) {
        return val ? val : def;
    },

    dictsort: function(val, case_sensitive, by) {
        if (!lib.isObject(val)) {
            throw new lib.TemplateError("dictsort filter: val must be an object");
        }

        var array = [];
        for (var k in val) {
            // deliberately include properties from the object's prototype
            array.push([k,val[k]]);
        }

        var si;
        if (by === undefined || by === "key") {
            si = 0;
        } else if (by === "value") {
            si = 1;
        } else {
            throw new lib.TemplateError(
                "dictsort filter: You can only sort by either key or value");
        }

        array.sort(function(t1, t2) {
            var a = t1[si];
            var b = t2[si];

            if (!case_sensitive) {
                if (lib.isString(a)) {
                    a = a.toUpperCase();
                }
                if (lib.isString(b)) {
                    b = b.toUpperCase();
                }
            }

            return a > b ? 1 : (a == b ? 0 : -1);
        });

        return array;
    },

    escape: function(str) {
        if(typeof str == 'string' ||
           str instanceof r.SafeString) {
            return lib.escape(str);
        }
        return str;
    },

    safe: function(str) {
        return r.markSafe(str);
    },

    first: function(arr) {
        return arr[0];
    },

    groupby: function(arr, attr) {
        return lib.groupBy(arr, attr);
    },

    indent: function(str, width, indentfirst) {
        width = width || 4;
        var res = '';
        var lines = str.split('\n');
        var sp = lib.repeat(' ', width);

        for(var i=0; i<lines.length; i++) {
            if(i == 0 && !indentfirst) {
                res += lines[i] + '\n';
            }
            else {
                res += sp + lines[i] + '\n';
            }
        }

        return r.copySafeness(str, res);
    },

    join: function(arr, del, attr) {
        del = del || '';

        if(attr) {
            arr = lib.map(arr, function(v) {
                return v[attr];
            });
        }

        return arr.join(del);
    },

    last: function(arr) {
        return arr[arr.length-1];
    },

    length: function(arr) {
        return arr.length;
    },

    list: function(val) {
        if(lib.isString(val)) {
            return val.split('');
        }
        else if(lib.isObject(val)) {
            var keys = [];

            if(Object.keys) {
                keys = Object.keys(val);
            }
            else {
                for(var k in val) {
                    keys.push(k);
                }
            }

            return lib.map(keys, function(k) {
                return { key: k,
                         value: val[k] };
            });
        }
        else {
            throw new lib.TemplateError("list filter: type not iterable");
        }
    },

    lower: function(str) {
        return str.toLowerCase();
    },

    random: function(arr) {
        return arr[Math.floor(Math.random() * arr.length)];
    },

    replace: function(str, old, new_, maxCount) {
        var res = str;
        var last = res;
        var count = 1;
        res = res.replace(old, new_);

        while(last != res) {
            if(count >= maxCount) {
                break;
            }

            last = res;
            res = res.replace(old, new_);
            count++;
        }

        return r.copySafeness(str, res);
    },

    reverse: function(val) {
        var arr;
        if(lib.isString(val)) {
            arr = filters.list(val);
        }
        else {
            // Copy it
            arr = lib.map(val, function(v) { return v; });
        }

        arr.reverse();

        if(lib.isString(val)) {
            return r.copySafeness(val, arr.join(''));
        }
        return arr;
    },

    round: function(val, precision, method) {
        precision = precision || 0;
        var factor = Math.pow(10, precision);
        var rounder;

        if(method == 'ceil') {
            rounder = Math.ceil;
        }
        else if(method == 'floor') {
            rounder = Math.floor;
        }
        else {
            rounder = Math.round;
        }

        return rounder(val * factor) / factor;
    },

    slice: function(arr, slices, fillWith) {
        var sliceLength = Math.floor(arr.length / slices);
        var extra = arr.length % slices;
        var offset = 0;
        var res = [];

        for(var i=0; i<slices; i++) {
            var start = offset + i * sliceLength;
            if(i < extra) {
                offset++;
            }
            var end = offset + (i + 1) * sliceLength;

            var slice = arr.slice(start, end);
            if(fillWith && i >= extra) {
                slice.push(fillWith);
            }
            res.push(slice);
        }

        return res;
    },

    sort: function(arr, reverse, caseSens, attr) {
        // Copy it
        arr = lib.map(arr, function(v) { return v; });

        arr.sort(function(a, b) {
            var x, y;

            if(attr) {
                x = a[attr];
                y = b[attr];
            }
            else {
                x = a;
                y = b;
            }

            if(!caseSens && lib.isString(x) && lib.isString(y)) {
                x = x.toLowerCase();
                y = y.toLowerCase();
            }

            if(x < y) {
                return reverse ? 1 : -1;
            }
            else if(x > y) {
                return reverse ? -1: 1;
            }
            else {
                return 0;
            }
        });

        return arr;
    },

    string: function(obj) {
        return r.copySafeness(obj, obj);
    },

    title: function(str) {
        var words = str.split(' ');
        for(var i = 0; i < words.length; i++) {
            words[i] = filters.capitalize(words[i]);
        }
        return r.copySafeness(str, words.join(' '));
    },

    trim: function(str) {
        return r.copySafeness(str, str.replace(/^\s*|\s*$/g, ''));
    },

    truncate: function(input, length, killwords, end) {
        var orig = input;
        length = length || 255;

        if (input.length <= length)
            return input;

        if (killwords) {
            input = input.substring(0, length);
        } else {
            var idx = input.lastIndexOf(' ', length);
            if(idx === -1) {
                idx = length;
            }

            input = input.substring(0, idx);
        }

        input += (end !== undefined && end !== null) ? end : '...';
        return r.copySafeness(orig, input);
    },

    upper: function(str) {
        return str.toUpperCase();
    },

    urlencode: function(obj) {
        var enc = encodeURIComponent;
        if (lib.isString(obj)) {
            return enc(obj);
        } else {
            var parts;
            if (lib.isArray(obj)) {
                parts = obj.map(function(item) {
                    return enc(item[0]) + '=' + enc(item[1]);
                })
            } else {
                parts = [];
                for (var k in obj) {
                    if (obj.hasOwnProperty(k)) {
                        parts.push(enc(k) + '=' + enc(obj[k]));
                    }
                }
            }
            return parts.join('&');
        }
    },

    urlize: function(str, length, nofollow) {
        if (isNaN(length)) length = Infinity;

        var noFollowAttr = (nofollow === true ? ' rel="nofollow"' : '');

        // For the jinja regexp, see
        // https://github.com/mitsuhiko/jinja2/blob/f15b814dcba6aa12bc74d1f7d0c881d55f7126be/jinja2/utils.py#L20-L23
        var puncRE = /^(?:\(|<|<)?(.*?)(?:\.|,|\)|\n|>)?$/;
        // from http://blog.gerv.net/2011/05/html5_email_address_regexp/
        var emailRE = /^[\w.!#$%&'*+\-\/=?\^`{|}~]+@[a-z\d\-]+(\.[a-z\d\-]+)+$/i;
        var httpHttpsRE = /^https?:\/\/.*$/;
        var wwwRE = /^www\./;
        var tldRE = /\.(?:org|net|com)(?:\:|\/|$)/;

        var words = str.split(/\s+/).filter(function(word) {
          // If the word has no length, bail. This can happen for str with
          // trailing whitespace.
          return word && word.length;
        }).map(function(word) {
          var matches = word.match(puncRE);

          var possibleUrl = matches && matches[1] || word;

          // url that starts with http or https
          if (httpHttpsRE.test(possibleUrl))
            return '' + possibleUrl.substr(0, length) + '';

          // url that starts with www.
          if (wwwRE.test(possibleUrl))
            return '' + possibleUrl.substr(0, length) + '';

          // an email address of the form username@domain.tld
          if (emailRE.test(possibleUrl))
            return '' + possibleUrl + '';

          // url that ends in .com, .org or .net that is not an email address
          if (tldRE.test(possibleUrl))
            return '' + possibleUrl.substr(0, length) + '';

          return possibleUrl;

        });

        return words.join(' ');
    },

    wordcount: function(str) {
        var words = (str) ? str.match(/\w+/g) : null;
        return (words) ? words.length : null;
    },

    'float': function(val, def) {
        var res = parseFloat(val);
        return isNaN(res) ? def : res;
    },

    'int': function(val, def) {
        var res = parseInt(val, 10);
        return isNaN(res) ? def : res;
    }
};

// Aliases
filters.d = filters['default'];
filters.e = filters.escape;

module.exports = filters;
<|endoftext|>


<commit_before>/**
 * @file Object enumerator
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfIsArrayLike*/ // Return true if the parameter looks like an array
/*exported _gpfArrayForEach*/ // Almost like [].forEach (undefined are also enumerated)
/*exported _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*#endif*/

/**
 * Callback function executed on each array / dictionary item
 *
 * @callback gpfCallbackForEach
 *
 * @param {*} value The current item
 * @param {String} index The index of the current item
 * @param {Object} container The container currently being enumerated (array or dictionary)
 * @return {undefined} No return expected
 */

/**
 * Similar to [].forEach but for array-like
 *
 * @param {Array} array Array-like object
 * @param {gpfForEachCallback} callback Callback function executed on each array item
 * @param {*} [thisArg] thisArg Value to use as this when executing callback
 */
function _gpfArrayForEach (array, callback, thisArg) {
    var index,
        length = array.length;
    for (index = 0; index < length; ++index) {
        callback.call(thisArg, array[index], index, array);
    }
}

/**
 * Similar to [].forEach but for objects
 *
 * @param {Object} object Object
 * @param {gpfForEachCallback} callback Callback function executed on each own property
 * @param {*} [thisArg] thisArg Value to use as this when executing callback
 */
function _gpfObjectForEach (object, callback, thisArg) {
    for (var property in object) {
        /* istanbul ignore else */
        if (object.hasOwnProperty(property)) {
            callback.call(thisArg, object[property], property, object);
        }
    }
}

/**
 * Executes a provided function once per structure element.
 * NOTE: unlike [].forEach, non own properties are also enumerated
 *
 * @param {Array|Object} container Container to enumerate
 * @param {gpfForEachCallback} callback Callback function executed on each item or own property
 * @param {*} [thisArg=undefined] thisArg Value to use as this when executing callback
 */
gpf.forEach = function (container, callback, thisArg) {
    if (_gpfIsArrayLike(container)) {
        _gpfArrayForEach(container, callback, thisArg);
        return;
    }
    _gpfObjectForEach(container, callback, thisArg); /*gpf:inline(object)*/
};

/*#ifndef(UMD)*/

gpf.internals._gpfObjectForEach = _gpfObjectForEach;

/*#endif*/
<commit_msg>Removes unused comments<commit_after>/**
 * @file Object enumerator
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfIsArrayLike*/ // Return true if the parameter looks like an array
/*exported _gpfArrayForEach*/ // Almost like [].forEach (undefined are also enumerated)
/*exported _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*#endif*/

/**
 * Callback function executed on each array / dictionary item
 *
 * @callback gpfCallbackForEach
 *
 * @param {*} value The current item
 * @param {String} index The index of the current item
 * @param {Object} container The container currently being enumerated (array or dictionary)
 * @return {undefined} No return expected
 */

/**
 * Similar to [].forEach but for array-like
 *
 * @param {Array} array Array-like object
 * @param {gpfForEachCallback} callback Callback function executed on each array item
 * @param {*} [thisArg] thisArg Value to use as this when executing callback
 */
function _gpfArrayForEach (array, callback, thisArg) {
    var index,
        length = array.length;
    for (index = 0; index < length; ++index) {
        callback.call(thisArg, array[index], index, array);
    }
}

/**
 * Similar to [].forEach but for objects
 *
 * @param {Object} object Object
 * @param {gpfForEachCallback} callback Callback function executed on each own property
 * @param {*} [thisArg] thisArg Value to use as this when executing callback
 */
function _gpfObjectForEach (object, callback, thisArg) {
    for (var property in object) {
        /* istanbul ignore else */
        if (object.hasOwnProperty(property)) {
            callback.call(thisArg, object[property], property, object);
        }
    }
}

/**
 * Executes a provided function once per structure element.
 * NOTE: unlike [].forEach, non own properties are also enumerated
 *
 * @param {Array|Object} container Container to enumerate
 * @param {gpfForEachCallback} callback Callback function executed on each item or own property
 * @param {*} [thisArg=undefined] thisArg Value to use as this when executing callback
 */
gpf.forEach = function (container, callback, thisArg) {
    if (_gpfIsArrayLike(container)) {
        _gpfArrayForEach(container, callback, thisArg);
        return;
    }
    _gpfObjectForEach(container, callback, thisArg);
};

/*#ifndef(UMD)*/

gpf.internals._gpfObjectForEach = _gpfObjectForEach;

/*#endif*/
<|endoftext|>


<commit_before>/**
 * gamepad.js - HumanInput Gamepad Plugin: Adds support for gamepads and joysticks to HumanInput.
 * Copyright (c) 2016, Dan McDougall
 * @link https://github.com/liftoff/HumanInput/src/gamepad.js
 * @license Apache-2.0
 */

import { noop } from './utils';
import HumanInput from './humaninput';

const gpadPresent = function(index) {
    // Returns true if the gamepad with *index* is detected
    var gamepads = navigator.getGamepads();
    for (let i = 0; i < gamepads.length; i++) {
        if (gamepads[i] && gamepads[i].index == index) {
            return true;
        }
    }
};

export class GamepadPlugin {
    /**:GamePadPlugin

    The HumanInput Gamepad plugin adds support for gamepads and joysticks allowing the use of the following event types:

    ========================= =============================     =======================================
    Event                     Description                       Arguments
    ========================= =============================     =======================================
    ``gpad:connected``        A gamepad was connected           (<Gamepad object>)
    ``gpad:disconnected``     A gamepad was connected           (<Gamepad object>)
    ``gpad:button:<n>``       State of button *n* changed       (<Button Value>, <Gamepad object>)
    ``gpad:button:<n>:down``  Button *n* was pressed (down)     (<Button Value>, <Gamepad object>)
    ``gpad:button:<n>:up``    Button *n* was released (up)      (<Button Value>, <Gamepad object>)
    ``gpad:button:<n>:value`` Button *n* value has changed      (<Button Value>, <Gamepad object>)
    ``gpad:axis:<n>``         Gamepad axis *n* changed          (<Button axis value>, <Gamepad object>)
    ========================= =============================     =======================================

    Detection Events
    ----------------
    Whenever a new gamepad is detected the 'gpad:connected' event will fire with the Gamepad object as the only argument.

    Button Events
    -------------
    When triggered, gpad:button events are called like so::

        trigger(event, buttonValue, gamepadObj);

    You can listen for button events using :js:func:`HumanInput.on` like so::

        // Ensure 'gamepad' is included in listenEvents if not calling gamepadUpdate() in your own loop:
        var settings = {addEvents: ['gamepad']};
        var HI = new HumanInput(window, settings);
        var shoot = function(buttonValue, gamepadObj) {
            console.log('Fire! Button value:', buttonValue, 'Gamepad object:', gamepadObj);
        };
        HI.on('gpad:button:1:down', shoot); // Call shoot(buttonValue, gamepadObj) when gamepad button 1 is down
        var stopShooting = function(buttonValue, gamepadObj) {
            console.log('Cease fire! Button value:', buttonValue, 'Gamepad object:', gamepadObj);
        };
        HI.on('gpad:button:1:up', stopShooting); // Call stopShooting(buttonValue, gamepadObj) when gamepad button 1 is released (up)

    For more detail with button events (e.g. you want fine-grained control with pressure-sensitive buttons) just neglect to add ':down' or ':up' to the event::

        HI.on('gpad:button:6', shoot);

    .. note:: The given buttonValue can be any value between 0 (up) and 1 (down).  Pressure sensitive buttons (like L2 and R2 on a DualShock controller) will often have floating point values representing how far down the button is pressed such as ``0.8762931823730469``.

    Button Combo Events
    -------------------
    When multiple gamepad buttons are held down a button combo event will be fired like so::

        trigger("gpad:button:0-gpad:button:1", gamepadObj);

    In the above example gamepad button 0 and button 1 were both held down simultaneously.  This works with as many buttons as the gamepad supports and can be extremely useful for capturing diagonal movement on a dpad.  For example, if you know that button 14 is left and button 13 is right you can use them to define diagonal movement like so::

        on("gpad:button:13-gpad:button:14", downLeft);

    Events triggered in this way will be passed the Gamepad object as the only argument.

    .. note:: Button combo events will always trigger *before* other button events.

    Axis Events
    -----------

    When triggered, gpad:axis events are called like so::

        trigger(event, axisValue, GamepadObj);

    You can listen for axis events using :js:func:`HumanInput.on` like so::

        var moveBackAndForth = function(axisValue, gamepadObj) {
            if (axisValue < 0) {
                console.log('Moving forward at speed: ' + axisValue);
            } else if (axisValue > 0) {
                console.log('Moving backward at speed: ' + axisValue);
            }
        };
        HI.on('gpad:axis:1', moveBackAndForth);

    .. topic:: Game and Application Loops

        If your game or application has its own event loop that runs at least once every ~100ms or so then it may be beneficial to call :js:func:`HumanInput.gamepadUpdate` inside your own loop *instead* of passing 'gamepad' via the 'listenEvents' (or 'addEvents') setting.  Calling :js:func:`HumanInput.gamepadUpdate` is very low overhead (takes less than a millisecond) but HumanInput's default gamepad update loop is only once every 100ms. If you don't want to use your own loop but want HumanInput to update the gamepad events more rapidly you can reduce the 'gpadInterval' setting.  Just note that if you set it too low it will increase CPU utilization which may have negative consequences for your application.

    .. note:: The update interval timer will be disabled if the page is no longer visible (i.e. the user switched tabs).  The interval timer will be restored when the page becomes visible again.  This is handled via the Page Visibility API (the 'document:visibile' and 'document:hidden' events triggered by HumanInput).

    Gamepad State
    -------------
    The state of all buttons and axes on all connected gamepads/joysticks can be read at any time via the `HumanInput.gamepads` property::

        var HI = HumanInput();
        for (var i=0; i < HI.gamepads.length; i++) {
            console.log('Gamepad ' + i + ':', HI.gamepads[i]);
        });

    .. note:: The index position of a gamepad in the `HumanInput.gamepads` array will always match the Gamepad object's 'index' property.
    */

    constructor(HI) { // HI == current instance of HumanInput
        this.HI = HI;
        this.log = new HI.Logger(HI.settings.logLevel || 'INFO', '[HI Gamepad]');
        this.gamepads = [];
        this._gamepadTimer = null;
        // Exports (these will be applied to the current instance of HumanInput)
        this.exports = {
            gamepads: this.gamepads,
            _gamepadTimer: this._gamepadTimer,
            gamepadUpdate: this.gamepadUpdate,
            loadController: this.loadController,
            stopGamepadUpdates: this.stopGamepadUpdates,
            startGamepadUpdates: this.startGamepadUpdates
        };
        return this;
    }

    init(HI) {
        /**:GamepadPlugin.init(HI)

        Initializes the Gamepad Plugin by performing the following:

            * Checks for the presence of the 'gpadInterval' and 'gpadCheckInterval' settings and applies defaults if not found.
            * Sets up an interval timer using 'gpadInterval' or 'gpadCheckInterval' that runs :js:func:`GamepadPlugin.gamepadUpdate` if a gamepad is found or not found, respectively *if* 'gamepad' is set in `HI.settings.listenEvents`.
            * Exports `GamepadPlugin.gamepads`, `GamepadPlugin._gamepadTimer`, and :js:func:`GamepadPlugin.gamepadUpdate` to the current instance of HumanInput.
            * Attaches to the 'visibilitychange' event so that we can disable/enable the interval timer that calls :js:func:`GamepadPlugin.gamepadUpdate` (`GamepadPlugin._gamepadTimer`).
        */
        var settings = HI.settings;
        // Hopefully this timing is fast enough to remain responsive without wasting too much CPU:
        settings.gpadInterval = settings.gpadInterval || 100; // .1s
        settings.gpadCheckInterval = settings.gpadCheckInterval || 3000; // 3s
        clearInterval(this._gamepadTimer); // In case it's already set
        if (settings.listenEvents.includes('gamepad')) {
            this.gamepadUpdate();
            this.startGamepadUpdates();
            // Make sure we play nice and disable our interval timer when the user changes tabs
            HI.on('document:hidden', this.stopGamepadUpdates);
            HI.on('document:visibile', this.startGamepadUpdates);
            // This ensures the gpadCheckInterval is replaced with the gpadInterval
            HI.on('gpad:connected', this.startGamepadUpdates);
        }
        return this;
    }

    gamepadUpdate() {
        /**:GamepadPlugin.gamepadUpdate()

        .. note:: This method needs to be called in a loop.  See the 'Game and Application Loops' topic for how you can optimize gamepad performance in your own game or application.

        Updates the state of `HumanInput.gamepads` and triggers 'gpad:button' or 'gamepad:axes' events if the state of any buttons or axes has changed, respectively.

        This method will also trigger a 'gpad:connected' event when a new Gamepad is detected (i.e. the user plugged it in or the first time the page is loaded).
        */
        var i, j, index, prevState, gp, buttonState, event, bChanged,
            pseudoEvent = {'type': 'gamepad', 'target': HI.elem},
            gamepads = navigator.getGamepads();
        // Check for disconnected gamepads
        for (i = 0; i < this.gamepads.length; i++) {
            if (this.gamepads[i] && !gpadPresent(i)) {
                HI.trigger('gpad:disconnected', this.gamepads[i]);
                this.gamepads[i] = null;
            }
        }
        for (i = 0; i < gamepads.length; ++i) {
            if (gamepads[i]) {
                index = gamepads[i].index,
                gp = this.gamepads[index];
                if (!gp) {
                    // TODO: Add controller layout detection here
                    this.log.debug('Gamepad ' + index + ' detected:', gamepads[i]);
                    HI.trigger('gpad:connected', gamepads[i]);
                    this.gamepads[index] = {
                        axes: [],
                        buttons: [],
                        timestamp: gamepads[i].timestamp,
                        id: gamepads[i].id
                    };
                    gp = this.gamepads[index];
                    // Prepopulate the axes and buttons arrays so the comparisons below will work:
                    for (j=0; j < gamepads[i].buttons.length; j++) {
                        gp.buttons[j] = {value: 0, pressed: false};
                    }
                    for (j=0; j < gamepads[i].axes.length; j++) {
                        gp.axes[j] = 0;
                    }
                    continue;
                } else {
                    if (gp.timestamp == gamepads[i].timestamp) {
                        continue; // Nothing changed
                    }
// NOTE: We we have to make value-by-value copy of the previous gamepad state because Gamepad objects retain references to their internal state (i.e. button and axes values) when copied using traditional methods.  Benchmarking has shown the JSON.parse/JSON.stringify method to be the fastest so far (0.3-0.5ms per call to gamepadUpdate() VS 0.7-1.2ms per call when creating a new object literal, looping over the axes and buttons to copy their values).
                    prevState = JSON.parse(JSON.stringify(gp)); // This should be slower but I think the JS engine has an optimization for this specific parse(stringify()) situation resulting in it being the fastest method
                    gp.timestamp = gamepads[i].timestamp;
                    gp.axes = gamepads[i].axes.slice(0);
                    for (j=0; j < prevState.buttons.length; j++) {
                        gp.buttons[j].pressed = gamepads[i].buttons[j].pressed;
                        gp.buttons[j].value = gamepads[i].buttons[j].value;
                    }
                }
                if (HI.filter(pseudoEvent)) {
                    // Update the state of all down buttons (axes stand alone)
                    for (j=0; j < gp.buttons.length; j++) {
                        buttonState = 'up';
                        if (gp.buttons[j].pressed) {
                            buttonState = 'down';
                        }
                        event = 'gpad:button:' + j;
                        if (buttonState == 'down') {
                            if (!HI.isDown(event)) {
                                HI._addDown(event);
                            }
                        } else {
                            if (HI.isDown(event)) {
                                HI._handleSeqEvents();
                                HI._removeDown(event);
                            }
                        }
                        if (gp.buttons[j].pressed != prevState.buttons[j].pressed) {
                            HI.trigger(HI.scope + event, gp.buttons[j].value, gamepads[i]);
                            HI.trigger(HI.scope + 'gpad:button:' + buttonState, gp.buttons[j].value, gamepads[i]);
                            HI.trigger(HI.scope + event + ':' + buttonState, gp.buttons[j].value, gamepads[i]);
                            bChanged = true;
                        } else if (gp.buttons[j].value != prevState.buttons[j].value) {
                            HI.trigger(HI.scope + event, gp.buttons[j].value, gamepads[i]);
                        }
                    }
                    for (j=0; j < prevState.axes.length; j++) {
                        if (gp.axes[j] != prevState.axes[j]) {
                            event = 'gpad:axis:' + j;
                            HI.trigger(HI.scope + event, gp.axes[j], gamepads[i]);
                        }
                    }
                    if (bChanged) {
                        HI._handleDownEvents(gamepads[i]);
                    }
                }
            }
        }
    }

    loadController(controller) {
        // Loads the given controller (object)
        for (var alias in controller) {
            HI.aliases[alias] = controller[alias];
        }
    }

    stopGamepadUpdates() {
        clearInterval(this._gamepadTimer);
    }

    startGamepadUpdates() {
        clearInterval(this._gamepadTimer);
        if (this.gamepads.length) { // At least one gamepad is connected
            this._gamepadTimer = setInterval(this.gamepadUpdate, HI.settings.gpadInterval);
        } else {
            // Check for a new gamepad every few seconds in case the user plugs one in later
            this._gamepadTimer = setInterval(this.gamepadUpdate, HI.settings.gpadCheckInterval);
        }
    }
}

// The following is a WIP for adding aliases automatically depending on the detected gamepad type:

// The default controller layout.  The keys of this object represent alias names
// that will be assigned to HumanInput.aliases:
// GamepadPlugin.prototype.standardLayout = {
//     // NOTE: This layout should cover DualShock, Xbox controllers, and similar
//     'gpad:up': 'gpad:button:12',
//     'gpad:down': 'gpad:button:13',
//     'gpad:left': 'gpad:button:14',
//     'gpad:right': 'gpad:button:15',
//     'gpad:select': 'gpad:button:8',
//     'gpad:share': 'gpad:button:8',
//     'gpad:start': 'gpad:button:9',
//     'gpad:options': 'gpad:button:9',
//     'gpad:l1': 'gpad:button:4',
//     'gpad:l2': 'gpad:button:6',
//     'gpad:r1': 'gpad:button:5',
//     'gpad:r2': 'gpad:button:7'
// }

HumanInput.plugins.push(GamepadPlugin);
<commit_msg>Gamepad Plugin:  Fixed a few places where HI should have been this.HI.<commit_after>/**
 * gamepad.js - HumanInput Gamepad Plugin: Adds support for gamepads and joysticks to HumanInput.
 * Copyright (c) 2016, Dan McDougall
 * @link https://github.com/liftoff/HumanInput/src/gamepad.js
 * @license Apache-2.0
 */

import { noop } from './utils';
import HumanInput from './humaninput';

const gpadPresent = function(index) {
    // Returns true if the gamepad with *index* is detected
    var gamepads = navigator.getGamepads();
    for (let i = 0; i < gamepads.length; i++) {
        if (gamepads[i] && gamepads[i].index == index) {
            return true;
        }
    }
};

export class GamepadPlugin {
    /**:GamePadPlugin

    The HumanInput Gamepad plugin adds support for gamepads and joysticks allowing the use of the following event types:

    ========================= =============================     =======================================
    Event                     Description                       Arguments
    ========================= =============================     =======================================
    ``gpad:connected``        A gamepad was connected           (<Gamepad object>)
    ``gpad:disconnected``     A gamepad was connected           (<Gamepad object>)
    ``gpad:button:<n>``       State of button *n* changed       (<Button Value>, <Gamepad object>)
    ``gpad:button:<n>:down``  Button *n* was pressed (down)     (<Button Value>, <Gamepad object>)
    ``gpad:button:<n>:up``    Button *n* was released (up)      (<Button Value>, <Gamepad object>)
    ``gpad:button:<n>:value`` Button *n* value has changed      (<Button Value>, <Gamepad object>)
    ``gpad:axis:<n>``         Gamepad axis *n* changed          (<Button axis value>, <Gamepad object>)
    ========================= =============================     =======================================

    Detection Events
    ----------------
    Whenever a new gamepad is detected the 'gpad:connected' event will fire with the Gamepad object as the only argument.

    Button Events
    -------------
    When triggered, gpad:button events are called like so::

        trigger(event, buttonValue, gamepadObj);

    You can listen for button events using :js:func:`HumanInput.on` like so::

        // Ensure 'gamepad' is included in listenEvents if not calling gamepadUpdate() in your own loop:
        var settings = {addEvents: ['gamepad']};
        var HI = new HumanInput(window, settings);
        var shoot = function(buttonValue, gamepadObj) {
            console.log('Fire! Button value:', buttonValue, 'Gamepad object:', gamepadObj);
        };
        HI.on('gpad:button:1:down', shoot); // Call shoot(buttonValue, gamepadObj) when gamepad button 1 is down
        var stopShooting = function(buttonValue, gamepadObj) {
            console.log('Cease fire! Button value:', buttonValue, 'Gamepad object:', gamepadObj);
        };
        HI.on('gpad:button:1:up', stopShooting); // Call stopShooting(buttonValue, gamepadObj) when gamepad button 1 is released (up)

    For more detail with button events (e.g. you want fine-grained control with pressure-sensitive buttons) just neglect to add ':down' or ':up' to the event::

        HI.on('gpad:button:6', shoot);

    .. note:: The given buttonValue can be any value between 0 (up) and 1 (down).  Pressure sensitive buttons (like L2 and R2 on a DualShock controller) will often have floating point values representing how far down the button is pressed such as ``0.8762931823730469``.

    Button Combo Events
    -------------------
    When multiple gamepad buttons are held down a button combo event will be fired like so::

        trigger("gpad:button:0-gpad:button:1", gamepadObj);

    In the above example gamepad button 0 and button 1 were both held down simultaneously.  This works with as many buttons as the gamepad supports and can be extremely useful for capturing diagonal movement on a dpad.  For example, if you know that button 14 is left and button 13 is right you can use them to define diagonal movement like so::

        on("gpad:button:13-gpad:button:14", downLeft);

    Events triggered in this way will be passed the Gamepad object as the only argument.

    .. note:: Button combo events will always trigger *before* other button events.

    Axis Events
    -----------

    When triggered, gpad:axis events are called like so::

        trigger(event, axisValue, GamepadObj);

    You can listen for axis events using :js:func:`HumanInput.on` like so::

        var moveBackAndForth = function(axisValue, gamepadObj) {
            if (axisValue < 0) {
                console.log('Moving forward at speed: ' + axisValue);
            } else if (axisValue > 0) {
                console.log('Moving backward at speed: ' + axisValue);
            }
        };
        HI.on('gpad:axis:1', moveBackAndForth);

    .. topic:: Game and Application Loops

        If your game or application has its own event loop that runs at least once every ~100ms or so then it may be beneficial to call :js:func:`HumanInput.gamepadUpdate` inside your own loop *instead* of passing 'gamepad' via the 'listenEvents' (or 'addEvents') setting.  Calling :js:func:`HumanInput.gamepadUpdate` is very low overhead (takes less than a millisecond) but HumanInput's default gamepad update loop is only once every 100ms. If you don't want to use your own loop but want HumanInput to update the gamepad events more rapidly you can reduce the 'gpadInterval' setting.  Just note that if you set it too low it will increase CPU utilization which may have negative consequences for your application.

    .. note:: The update interval timer will be disabled if the page is no longer visible (i.e. the user switched tabs).  The interval timer will be restored when the page becomes visible again.  This is handled via the Page Visibility API (the 'document:visibile' and 'document:hidden' events triggered by HumanInput).

    Gamepad State
    -------------
    The state of all buttons and axes on all connected gamepads/joysticks can be read at any time via the `HumanInput.gamepads` property::

        var HI = HumanInput();
        for (var i=0; i < HI.gamepads.length; i++) {
            console.log('Gamepad ' + i + ':', HI.gamepads[i]);
        });

    .. note:: The index position of a gamepad in the `HumanInput.gamepads` array will always match the Gamepad object's 'index' property.
    */

    constructor(HI) { // HI == current instance of HumanInput
        this.HI = HI;
        this.log = new HI.Logger(HI.settings.logLevel || 'INFO', '[HI Gamepad]');
        this.gamepads = [];
        this._gamepadTimer = null;
        // Exports (these will be applied to the current instance of HumanInput)
        this.exports = {
            gamepads: this.gamepads,
            _gamepadTimer: this._gamepadTimer,
            gamepadUpdate: this.gamepadUpdate,
            loadController: this.loadController,
            stopGamepadUpdates: this.stopGamepadUpdates,
            startGamepadUpdates: this.startGamepadUpdates
        };
        return this;
    }

    init(HI) {
        /**:GamepadPlugin.init(HI)

        Initializes the Gamepad Plugin by performing the following:

            * Checks for the presence of the 'gpadInterval' and 'gpadCheckInterval' settings and applies defaults if not found.
            * Sets up an interval timer using 'gpadInterval' or 'gpadCheckInterval' that runs :js:func:`GamepadPlugin.gamepadUpdate` if a gamepad is found or not found, respectively *if* 'gamepad' is set in `HI.settings.listenEvents`.
            * Exports `GamepadPlugin.gamepads`, `GamepadPlugin._gamepadTimer`, and :js:func:`GamepadPlugin.gamepadUpdate` to the current instance of HumanInput.
            * Attaches to the 'visibilitychange' event so that we can disable/enable the interval timer that calls :js:func:`GamepadPlugin.gamepadUpdate` (`GamepadPlugin._gamepadTimer`).
        */
        var settings = HI.settings;
        // Hopefully this timing is fast enough to remain responsive without wasting too much CPU:
        settings.gpadInterval = settings.gpadInterval || 100; // .1s
        settings.gpadCheckInterval = settings.gpadCheckInterval || 3000; // 3s
        clearInterval(this._gamepadTimer); // In case it's already set
        if (settings.listenEvents.includes('gamepad')) {
            this.gamepadUpdate();
            this.startGamepadUpdates();
            // Make sure we play nice and disable our interval timer when the user changes tabs
            HI.on('document:hidden', this.stopGamepadUpdates);
            HI.on('document:visibile', this.startGamepadUpdates);
            // This ensures the gpadCheckInterval is replaced with the gpadInterval
            HI.on('gpad:connected', this.startGamepadUpdates);
        }
        return this;
    }

    gamepadUpdate() {
        /**:GamepadPlugin.gamepadUpdate()

        .. note:: This method needs to be called in a loop.  See the 'Game and Application Loops' topic for how you can optimize gamepad performance in your own game or application.

        Updates the state of `HumanInput.gamepads` and triggers 'gpad:button' or 'gamepad:axes' events if the state of any buttons or axes has changed, respectively.

        This method will also trigger a 'gpad:connected' event when a new Gamepad is detected (i.e. the user plugged it in or the first time the page is loaded).
        */
        var i, j, index, prevState, gp, buttonState, event, bChanged,
            pseudoEvent = {'type': 'gamepad', 'target': this.HI.elem},
            gamepads = navigator.getGamepads();
        // Check for disconnected gamepads
        for (i = 0; i < this.gamepads.length; i++) {
            if (this.gamepads[i] && !gpadPresent(i)) {
                this.HI.trigger('gpad:disconnected', this.gamepads[i]);
                this.gamepads[i] = null;
            }
        }
        for (i = 0; i < gamepads.length; ++i) {
            if (gamepads[i]) {
                index = gamepads[i].index,
                gp = this.gamepads[index];
                if (!gp) {
                    // TODO: Add controller layout detection here
                    this.log.debug('Gamepad ' + index + ' detected:', gamepads[i]);
                    this.HI.trigger('gpad:connected', gamepads[i]);
                    this.gamepads[index] = {
                        axes: [],
                        buttons: [],
                        timestamp: gamepads[i].timestamp,
                        id: gamepads[i].id
                    };
                    gp = this.gamepads[index];
                    // Prepopulate the axes and buttons arrays so the comparisons below will work:
                    for (j=0; j < gamepads[i].buttons.length; j++) {
                        gp.buttons[j] = {value: 0, pressed: false};
                    }
                    for (j=0; j < gamepads[i].axes.length; j++) {
                        gp.axes[j] = 0;
                    }
                    continue;
                } else {
                    if (gp.timestamp == gamepads[i].timestamp) {
                        continue; // Nothing changed
                    }
// NOTE: We we have to make value-by-value copy of the previous gamepad state because Gamepad objects retain references to their internal state (i.e. button and axes values) when copied using traditional methods.  Benchmarking has shown the JSON.parse/JSON.stringify method to be the fastest so far (0.3-0.5ms per call to gamepadUpdate() VS 0.7-1.2ms per call when creating a new object literal, looping over the axes and buttons to copy their values).
                    prevState = JSON.parse(JSON.stringify(gp)); // This should be slower but I think the JS engine has an optimization for this specific parse(stringify()) situation resulting in it being the fastest method
                    gp.timestamp = gamepads[i].timestamp;
                    gp.axes = gamepads[i].axes.slice(0);
                    for (j=0; j < prevState.buttons.length; j++) {
                        gp.buttons[j].pressed = gamepads[i].buttons[j].pressed;
                        gp.buttons[j].value = gamepads[i].buttons[j].value;
                    }
                }
                if (this.HI.filter(pseudoEvent)) {
                    // Update the state of all down buttons (axes stand alone)
                    for (j=0; j < gp.buttons.length; j++) {
                        buttonState = 'up';
                        if (gp.buttons[j].pressed) {
                            buttonState = 'down';
                        }
                        event = 'gpad:button:' + j;
                        if (buttonState == 'down') {
                            if (!this.HI.isDown(event)) {
                                this.HI._addDown(event);
                            }
                        } else {
                            if (this.HI.isDown(event)) {
                                this.HI._handleSeqEvents();
                                this.HI._removeDown(event);
                            }
                        }
                        if (gp.buttons[j].pressed != prevState.buttons[j].pressed) {
                            this.HI.trigger(this.HI.scope + event, gp.buttons[j].value, gamepads[i]);
                            this.HI.trigger(this.HI.scope + 'gpad:button:' + buttonState, gp.buttons[j].value, gamepads[i]);
                            this.HI.trigger(this.HI.scope + event + ':' + buttonState, gp.buttons[j].value, gamepads[i]);
                            bChanged = true;
                        } else if (gp.buttons[j].value != prevState.buttons[j].value) {
                            this.HI.trigger(HI.scope + event, gp.buttons[j].value, gamepads[i]);
                        }
                    }
                    for (j=0; j < prevState.axes.length; j++) {
                        if (gp.axes[j] != prevState.axes[j]) {
                            event = 'gpad:axis:' + j;
                            this.HI.trigger(HI.scope + event, gp.axes[j], gamepads[i]);
                        }
                    }
                    if (bChanged) {
                        this.HI._handleDownEvents(gamepads[i]);
                    }
                }
            }
        }
    }

    loadController(controller) {
        // Loads the given controller (object)
        for (var alias in controller) {
            this.HI.aliases[alias] = controller[alias];
        }
    }

    stopGamepadUpdates() {
        clearInterval(this._gamepadTimer);
    }

    startGamepadUpdates() {
        clearInterval(this._gamepadTimer);
        if (this.gamepads.length) { // At least one gamepad is connected
            this._gamepadTimer = setInterval(this.gamepadUpdate, this.HI.settings.gpadInterval);
        } else {
            // Check for a new gamepad every few seconds in case the user plugs one in later
            this._gamepadTimer = setInterval(this.gamepadUpdate, this.HI.settings.gpadCheckInterval);
        }
    }
}

// The following is a WIP for adding aliases automatically depending on the detected gamepad type:

// The default controller layout.  The keys of this object represent alias names
// that will be assigned to HumanInput.aliases:
// GamepadPlugin.prototype.standardLayout = {
//     // NOTE: This layout should cover DualShock, Xbox controllers, and similar
//     'gpad:up': 'gpad:button:12',
//     'gpad:down': 'gpad:button:13',
//     'gpad:left': 'gpad:button:14',
//     'gpad:right': 'gpad:button:15',
//     'gpad:select': 'gpad:button:8',
//     'gpad:share': 'gpad:button:8',
//     'gpad:start': 'gpad:button:9',
//     'gpad:options': 'gpad:button:9',
//     'gpad:l1': 'gpad:button:4',
//     'gpad:l2': 'gpad:button:6',
//     'gpad:r1': 'gpad:button:5',
//     'gpad:r2': 'gpad:button:7'
// }

HumanInput.plugins.push(GamepadPlugin);
<|endoftext|>


<commit_before>// Global variables
var G = {
  isTouch: 'ontouchstart' in window,
  cssTransition: cssTest('transition'),
  cssTransform: cssTest('transform'),
  cssTransformDashed: '',
  trackTransitions: 'height 0.2s ease 0s, width 0.2s ease 0s, opacity 0.2s ease 0s',
  nativeScrollbarSize: getScrollbarWidth(),

  instances: [],
  checkTimer: null,
  pauseCheck: false
};

G.cssTransformDashed = (G.cssTransform == 'transform') ? G.cssTransform : '-'+G.cssTransform.replace('T','-t').toLowerCase();

var getTime = Date.now || function() { return new Date().getTime(); };

var animationTimeout = (function () {
  return window.requestAnimationFrame
    || window.webkitRequestAnimationFrame
    || window.mozRequestAnimationFrame
    || window.msRequestAnimationFrame
    || window.oRequestAnimationFrame
    || function(callback){ window.setTimeout(callback, 1000/60); };
})();

// Get scrollbars width, thanks Google Closure Library
function getScrollbarWidth () {
  var htmlEl = document.documentElement,
      outerEl, innerEl, width = 0;

  outerEl = document.createElement('div');
  outerEl.style.cssText = 'overflow:auto;width:50px;height:50px;' + 'position:absolute;left:-100px';

  innerEl = document.createElement('div');
  innerEl.style.cssText = 'width:100px;height:100px';

  outerEl.appendChild(innerEl);
  htmlEl.appendChild(outerEl);
  width = outerEl.offsetWidth - outerEl.clientWidth;
  htmlEl.removeChild(outerEl);

  return width;
}

// Detect css3 support, thanks Modernizr
function cssTest (prop) {
  var ucProp  = prop.charAt(0).toUpperCase() + prop.slice(1),
      el = document.createElement( 'test' ),
      props   = (prop + ' ' + ['Webkit','Moz','O','ms'].join(ucProp + ' ') + ucProp).split(' ');

  for ( var i in props ) {
    if ( el.style[ props[i] ] !== undefined ) return props[i];
  }
  return false;
}

function _extend (dest, src, merge) {
  for(var key in src) {
    if(!src.hasOwnProperty(key) || dest[key] !== undefined && merge) {
      continue;
    }
    dest[key] = src[key];
  }
  return dest;
}

function _invoke (collection, fn, args) {
  var i, j, key;
  if(collection.length) {
    for(i = 0, j = collection.length; i < j; i++) {
      if(collection[i][fn])
        collection[i][fn].apply(collection[i], args);
    }
  } else {
    for (key in collection) {
      if(collection.hasOwnProperty(key) && collection[key][fn])
        collection[key][fn].apply(collection[key], args);
    }
  }
}<commit_msg>Removed code no longer needed<commit_after>// Global variables
var G = {
  isTouch: 'ontouchstart' in window,
  cssTransition: cssTest('transition'),
  cssTransform: cssTest('transform'),
  nativeScrollbarSize: getScrollbarWidth(),

  instances: [],
  checkTimer: null,
  pauseCheck: false
};

G.cssTransformDashed = (G.cssTransform == 'transform') ? G.cssTransform : '-'+G.cssTransform.replace('T','-t').toLowerCase();

var getTime = Date.now || function() { return new Date().getTime(); };

var animationTimeout = (function () {
  return window.requestAnimationFrame
    || window.webkitRequestAnimationFrame
    || window.mozRequestAnimationFrame
    || window.msRequestAnimationFrame
    || function(callback){ window.setTimeout(callback, 1000/60); };
})();

// Get scrollbars width, thanks Google Closure Library
function getScrollbarWidth () {
  var htmlEl = document.documentElement,
      outerEl, innerEl, width = 0;

  outerEl = document.createElement('div');
  outerEl.style.cssText = 'overflow:auto;width:50px;height:50px;' + 'position:absolute;left:-100px';

  innerEl = document.createElement('div');
  innerEl.style.cssText = 'width:100px;height:100px';

  outerEl.appendChild(innerEl);
  htmlEl.appendChild(outerEl);
  width = outerEl.offsetWidth - outerEl.clientWidth;
  htmlEl.removeChild(outerEl);

  return width;
}

// Detect css3 support, thanks Modernizr
function cssTest (prop) {
  var ucProp  = prop.charAt(0).toUpperCase() + prop.slice(1),
      el = document.createElement( 'test' ),
      props   = (prop + ' ' + ['Webkit','Moz','O','ms'].join(ucProp + ' ') + ucProp).split(' ');

  for ( var i in props ) {
    if ( el.style[ props[i] ] !== undefined ) return props[i];
  }
  return false;
}

function _extend (dest, src, merge) {
  for(var key in src) {
    if(!src.hasOwnProperty(key) || dest[key] !== undefined && merge) {
      continue;
    }
    dest[key] = src[key];
  }
  return dest;
}

function _invoke (collection, fn, args) {
  var i, j;
  if(collection.length) {
    for(i = 0, j = collection.length; i < j; i++) {
      collection[i][fn].apply(collection[i], args);
    }
  } else {
    for (i in collection) {
      collection[i][fn].apply(collection[i], args);
    }
  }
}<|endoftext|>


<commit_before>

chrome.webRequest.onBeforeSendHeaders.addListener(
	function(info) {

		// remove referer header if exists
		for (var i = 0; i < info.requestHeaders.length; ++i) {
			if (info.requestHeaders[i].name === "Referer") {
				info.requestHeaders.splice(i, 1);
				break;
			}
		}
		//console.log(info.requestHeaders);

		// choose referer url
		// TODO: add more URLs that Haaretz support
		// TODO: Do we need those URLs or can we stick with one only?
		// var list	= ["http://www.facebook.com/", "http://www.twitter.com/", "http://www.google.com"];
		// var url		= list[ Math.round(Math.random() * 2) ];

		var url		= "http://www.facebook.com/";

		// add referer
		info.requestHeaders.push({
			name	: "Referer",
			value	: url
		});
		//console.log(info.requestHeaders);

		return { requestHeaders: info.requestHeaders };
	},
	{
		urls: ["*://*.haaretz.co.il/*"],
		types: ["main_frame"]
	},
	["blocking", "requestHeaders"]);<commit_msg>remove unused code and documentation<commit_after>
// Add a listener to any Haaretz request
chrome.webRequest.onBeforeSendHeaders.addListener(
	function(info) {

		// remove referer header if exists
		for (var i = 0; i < info.requestHeaders.length; ++i) {
			if (info.requestHeaders[i].name === "Referer") {
				info.requestHeaders.splice(i, 1);
				break;
			}
		}

		// add referer (facebook in this case, but can be any social network or search engine that Haaretz suports)
		info.requestHeaders.push({
			name	: "Referer",
			value	: "http://www.facebook.com/"
		});

		return { requestHeaders: info.requestHeaders };
	},
	{
		// permisions
		urls: ["*://*.haaretz.co.il/*"],
		types: ["main_frame"]
	},
	// this is a blocking function, to change the request
	["blocking", "requestHeaders"]);
<|endoftext|>


<commit_before>'use strict';

export function createHandler() {
  const data = {
    M1A: 'M1 → Kabaty',
    M1B: 'M1 → Młociny',
    M2A: 'M2 → Dworzec Wileński',
    M2B: 'M2 → Rondo Daszyńskiego',
    X0A: '666 → Plac Testerów'
  };

  return {
    onGetBusStopsGroups: function(res) {
      for (let stop of res) {
        const name = stop.cityCode === '--' ?
          stop.name : stop.name + ', ' + stop.cityName;
        Object.assign(data, {
          [stop.id.toString()]: {
            name: name
          }
        });
      }
    },
    onGetBusStops: function(res) {
      for (let id in res) {
        Object.assign(data[id], getCentroid(res[id]), getLines(res[id]));
        // console.log(data[id]);
      }
    },
    onGetSchedules: function(res) {
      for (let line of res.schedule) {
        // console.log(data[line.routes[0].stops[0].id.slice(0,4)]);
        Object.assign(data, ...(line.routes.map(
          route => getDirectionsForLine(line.id, route))));
      }
    },
    get data() {
      return data;
    }
  };
};

function getDirectionsForLine(name, route) {
  return {
    [name + route.dir]: {
      name: name + ' → ' + route.end.name
    }
  };
}

function getCentroid(stops) {
  // approximate the center of mass of the polygon given by all the stops
  // in the stop gropu by calculating the centroid of vertices
  const sum = stops.reduce(
    (centroid, stop) => Object.assign(centroid, {
      x: centroid.x + parseFloat(stop.xGPS),
      y: centroid.y + parseFloat(stop.yGPS),
    }), { x: 0, y: 0 });
  return {
    x: sum.x / stops.length,
    y: sum.y / stops.length,
  };
}

function getLines(stops) {
  return {
    lines: stops.reduce(
      (seq, cur) => seq.concat(
        flattenLines(cur.lines, cur.destination)), [])
  };
}

function flattenLines(lines, dest) {
  const flat = new Set();
  for (let type in lines) {
    lines[type].forEach(
      line => flat.add(makeName(line, dest)));
  }
  return Array.from(flat);
}

function makeName(line, dest) {
  return line.replace('^', '') + ' → ' + dest.replace('Kier.: ', '');
}
<commit_msg>group lines per destination<commit_after>'use strict';

export function createHandler() {
  const data = {
    M1A: 'M1 → Kabaty',
    M1B: 'M1 → Młociny',
    M2A: 'M2 → Dworzec Wileński',
    M2B: 'M2 → Rondo Daszyńskiego',
    X0A: '666 → Plac Testerów'
  };

  return {
    onGetBusStopsGroups: function(res) {
      for (let stop of res) {
        const name = stop.cityCode === '--' ?
          stop.name : stop.name + ', ' + stop.cityName;
        Object.assign(data, {
          [stop.id.toString()]: {
            name: name
          }
        });
      }
    },
    onGetBusStops: function(res) {
      for (let id in res) {
        Object.assign(
          data[id], getCentroid(res[id]), getDirs(res[id]));
        // console.log(data[id]);
      }
    },
    onGetSchedules: function(res) {
      for (let line of res.schedule) {
        // console.log(data[line.routes[0].stops[0].id.slice(0,4)]);
        Object.assign(data, ...(line.routes.map(
          route => getDirectionsForLine(line.id, route))));
      }
    },
    get data() {
      return data;
    }
  };
};

function getDirectionsForLine(name, route) {
  return {
    [name + route.dir]: {
      name: name + ' → ' + route.end.name
    }
  };
}

function getCentroid(stops) {
  // approximate the center of mass of the polygon given by all the stops
  // in the stop gropu by calculating the centroid of vertices
  const sum = stops.reduce(
    (centroid, stop) => Object.assign(centroid, {
      x: centroid.x + parseFloat(stop.xGPS),
      y: centroid.y + parseFloat(stop.yGPS),
    }), { x: 0, y: 0 });
  return {
    x: sum.x / stops.length,
    y: sum.y / stops.length,
  };
}

function getDirs(stops) {
  return {
    dirs: stops.reduce(groupByDestination, {})
  };
}

function groupByDestination(dests, stop) {
  const dest = nameDestination(stop.destination);
  const prev = dests[dest] || [];
  return Object.assign(dests, {
    [dest]: prev.concat(flattenLines(stop.lines, dest))
  });
}

function flattenLines(lines, dest) {
  const flat = new Set();
  for (let type in lines) {
    lines[type].forEach(
      line => flat.add(nameLine(line)));
  }
  return Array.from(flat);
}

function nameLine(line) {
  return line.replace('^', '');
}

function nameDestination(dest) {
  return dest.replace('Kier.: ', '');
}
<|endoftext|>


<commit_before>const _ = require('lodash');
const { HttpStatusError } = require('./errors');
const { parse } = require('./parser');
const { distinct, raw } = require('./transforms');

class ModelHandler {
    constructor(model, defaults = { limit: 50, offset: 0 }) {
        this.model = model;
        this.defaults = defaults;
    }

    create() {
        return [
            raw,
            handle
        ];

        function handle(req, res, next) {
            this.model
                .create(req.body)
                .then(respond)
                .catch(next);

            function respond(row) {
                res.status(201);
                res.send(res.transform(row));
            }
        }
    }

    get() {
        return [
            raw,
            handle
        ];

        function handle(req, res, next) {
            this
                .findOne(req.params)
                .then(respond)
                .catch(next);

            function respond(row) {
                if (!row) {
                    throw new HttpStatusError(404, 'Not Found');
                }

                res.send(res.transform(row));
            }
        }
    }

    query() {
        return [
            raw,
            handle
        ];

        function handle(req, res, next) {
            this
                .findAndCountAll(req.query)
                .then(respond)
                .catch(next);

            function respond({ rows, start, end, count }) {
                res.set('Content-Range', `${start}-${end}/${count}`);

                if (count > end) {
                    res.status(206);
                } else {
                    res.status(200);
                }

                res.send(res.transform(rows));
            }
        }
    }

    remove() {
        return [
            handle
        ];

        function handle(req, res, next) {
            this
                .findOne(req.params)
                .then(destroy)
                .then(respond)
                .catch(next);

            function destroy(row) {
                if (!row) {
                    throw new HttpStatusError(404, 'Not Found');
                }

                return row.destroy();
            }

            function respond() {
                res.sendStatus(204);
            }
        }
    }

    update() {
        return [
            raw,
            handle
        ];

        function handle(req, res, next) {
            this
                .findOne(req.params)
                .then(updateAttributes)
                .then(respond)
                .catch(next);

            function updateAttributes(row) {
                if (!row) {
                    throw new HttpStatusError(404, 'Not Found');
                }

                return row.updateAttributes(req.body);
            }

            function respond(row) {
                res.send(res.transform(row));
            }
        }
    }

    findOne(params, options) {
        options = _.merge(parse(params, this.model), options);

        return this.model.findOne(options);
    }

    findAndCountAll(params, options) {
        let parsed = parse(params, this.model);

        options = _(parsed)
            .defaults(this.defaults)
            .merge(options)
            .value();

        return this.model
            .findAndCountAll(options)
            .then(extract);

        function extract({ count, rows }) {
            const start = options.offset;
            const end = Math.min(count, (options.offset + options.limit) || count);

            return { rows, start, end, count };
        }
    }
}

module.exports = ModelHandler;<commit_msg>Added self reference for this context.<commit_after>const _ = require('lodash');
const { HttpStatusError } = require('./errors');
const { parse } = require('./parser');
const { distinct, raw } = require('./transforms');

class ModelHandler {
    constructor(model, defaults = { limit: 50, offset: 0 }) {
        this.model = model;
        this.defaults = defaults;
    }

    create() {
        const self = this;

        return [
            raw,
            handle
        ];

        function handle(req, res, next) {
            self.model
                .create(req.body)
                .then(respond)
                .catch(next);

            function respond(row) {
                res.status(201);
                res.send(res.transform(row));
            }
        }
    }

    get() {
        const self = this;

        return [
            raw,
            handle
        ];

        function handle(req, res, next) {
            self
                .findOne(req.params)
                .then(respond)
                .catch(next);

            function respond(row) {
                if (!row) {
                    throw new HttpStatusError(404, 'Not Found');
                }

                res.send(res.transform(row));
            }
        }
    }

    query() {
        const self = this;

        return [
            raw,
            handle
        ];

        function handle(req, res, next) {
            self
                .findAndCountAll(req.query)
                .then(respond)
                .catch(next);

            function respond({ rows, start, end, count }) {
                res.set('Content-Range', `${start}-${end}/${count}`);

                if (count > end) {
                    res.status(206);
                } else {
                    res.status(200);
                }

                res.send(res.transform(rows));
            }
        }
    }

    remove() {
        const self = this;

        return [
            handle
        ];

        function handle(req, res, next) {
            self
                .findOne(req.params)
                .then(destroy)
                .then(respond)
                .catch(next);

            function destroy(row) {
                if (!row) {
                    throw new HttpStatusError(404, 'Not Found');
                }

                return row.destroy();
            }

            function respond() {
                res.sendStatus(204);
            }
        }
    }

    update() {
        const self = this;

        return [
            raw,
            handle
        ];

        function handle(req, res, next) {
            self
                .findOne(req.params)
                .then(updateAttributes)
                .then(respond)
                .catch(next);

            function updateAttributes(row) {
                if (!row) {
                    throw new HttpStatusError(404, 'Not Found');
                }

                return row.updateAttributes(req.body);
            }

            function respond(row) {
                res.send(res.transform(row));
            }
        }
    }

    findOne(params, options) {
        options = _.merge(parse(params, this.model), options);

        return this.model.findOne(options);
    }

    findAndCountAll(params, options) {
        let parsed = parse(params, this.model);

        options = _(parsed)
            .defaults(this.defaults)
            .merge(options)
            .value();

        return this.model
            .findAndCountAll(options)
            .then(extract);

        function extract({ count, rows }) {
            const start = options.offset;
            const end = Math.min(count, (options.offset + options.limit) || count);

            return { rows, start, end, count };
        }
    }
}

module.exports = ModelHandler;<|endoftext|>


<commit_before>import angular from 'angular';
import $ from 'jquery';
import moment from 'moment';
import _ from 'lodash';
import kbn from 'app/core/utils/kbn';
import './bower_components/d3/d3.min.js';
import './bower_components/epoch/dist/js/epoch.min.js';

angular.module('grafana.directives').directive('grafanaHeatmapEpoch', function($rootScope, timeSrv) {
  return {
    restrict: 'A',
    template: '<div> </div>',
    link: function(scope, elem) {
      var ctrl = scope.ctrl;
      var dashboard = ctrl.dashboard;
      var panel = ctrl.panel;
      var data;
      var sortedSeries;
      var legendSideLastValue = null;
      var rootScope = scope.$root;
      var epoch = null;
      var firstDraw = true;
      var delta = true;
      var labelToModelIndexMap = {};
      var currentDatasource = '';
      var currentTimeRange = [0, 0];
      var currentSize = { width: null, height: null };

      // Receive render events
      ctrl.events.on('render', function(renderData) {
        data = renderData || data;
        if (!data) {
          ctrl.refresh();
          return;
        }
        render_panel();
      });

      function getLegendHeight(panelHeight) {
        if (!panel.legend.show || panel.legend.rightSide) {
          return 2;
        }

        if (panel.legend.alignAsTable) {
          var legendSeries = _.filter(data, function(series) {
            return series.hideFromLegend(panel.legend) === false;
          });
          var total = 23 + (22 * legendSeries.length);
          return Math.min(total, Math.floor(panelHeight/2));
        } else {
          return 26;
        }
      }

      function setElementHeight() {
        try {
          var height = ctrl.height - getLegendHeight(ctrl.height);
          elem.css('height', height + 'px');

          return true;
        } catch(e) { // IE throws errors sometimes
          console.log(e);
          return false;
        }
      }

      function shouldAbortRender() {
        if (!data) {
          return true;
        }

        if (!setElementHeight()) { return true; }

        if (elem.width() === 0) {
          return true;
        }
      }

      function processOffsetHook(plot, gridMargin) {
        var left = panel.yaxes[0];
        var right = panel.yaxes[1];
        if (left.show && left.label) { gridMargin.left = 20; }
        if (right.show && right.label) { gridMargin.right = 20; }
      }

      function getEpoch() {
        if (epoch) {
          return epoch;
        }

        epoch = elem.epoch(panel.heatmapOptions);
        scope.$watch('ctrl.panel.heatmapOptions.windowSize', function(newVal, oldVal) {
          epoch.option('windowSize', newVal);
          epoch.option('historySize', newVal * 3);
          epoch.redraw();
        });
        scope.$watch('ctrl.panel.heatmapOptions.buckets', function(newVal, oldVal) {
          epoch.option('buckets', newVal);
        });
        scope.$watch('ctrl.panel.heatmapOptions.bucketRange[0]', function(newVal, oldVal) {
          epoch.option('bucketRange', panel.heatmapOptions.bucketRange);
        });
        scope.$watch('ctrl.panel.heatmapOptions.bucketRange[1]', function(newVal, oldVal) {
          epoch.option('bucketRange', panel.heatmapOptions.bucketRange);
        });
        scope.$watch('ctrl.panel.heatmapOptions.startTime', function(newVal, oldVal) {
          epoch.option('startTime', newVal);
          epoch.redraw();
        });

        return epoch;
      }

      function getHeatmapData(datapoints) {
        var windowInterval = Math.floor((ctrl.range.to - ctrl.range.from) / panel.heatmapOptions.windowSize);
        var data = _.chain(datapoints)
        .reject(function(dp) {
          return dp[0] === null;
        })
        .groupBy(function(dp) {
          return Math.floor((dp[1] - ctrl.range.from) / windowInterval) + 1;
        })
        .map(function(values, timeKey) {
          return {
            time: Math.floor((timeKey * windowInterval + ctrl.range.from) / 1000),
            histogram: _.countBy(values, function(value) {
              return value[0];
            })
          };
        })
        .sortBy(function(dp) {
          return dp.time;
        }).value();

        if (data.length > panel.heatmapOptions.windowSize) {
          data[panel.heatmapOptions.windowSize].histogram = {};
        }

        return data;
      }

      function callPlot(incrementRenderCounter, data) {
        try {
          epoch.setData(data);

          if (ctrl.range.from !== currentTimeRange[0] || ctrl.range.to !== currentTimeRange[1]) {
            var ticks = Math.ceil(panel.heatmapOptions.windowSize / panel.heatmapOptions.ticks.time);
            var min = _.isUndefined(ctrl.range.from) ? null : ctrl.range.from.valueOf();
            var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf();
            epoch.option('tickFormats.bottom', function (d) {
              var timeFormat = time_format(ticks, min, max);
              return moment.unix(d).format(timeFormat);
            });
            epoch.ticksChanged();
          }
          currentTimeRange = [ctrl.range.from, ctrl.range.to];

          epoch.redraw();
        } catch (e) {
          console.log('epoch error', e);
        }

        if (incrementRenderCounter) {
          ctrl.renderingCompleted();
        }
      }

      // Function for rendering panel
      function render_panel() {
        if (shouldAbortRender()) {
          return;
        }

        // reset startTime if datasource is changed
        if (panel.datasource !== currentDatasource) {
          panel.heatmapOptions.startTime = Math.floor(ctrl.range.from.valueOf() / 1000);
          firstDraw = true;
        }
        currentDatasource = panel.datasource;

        // replace the characters which is not allowed to use for label
        _.each(data, function (series) {
          series.epochLabel = series.label.replace(/[ !"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, ' ');
        });

        var epoch = getEpoch();

        // check panel size change
        var width = elem.parent().width();
        var height = elem.parent().height();
        if (width !== currentSize.width) {
          epoch.option('width', width);
          var ticksTime = (width > 700) ? 5 : 10;
          epoch.option('ticks.time', ticksTime);
          epoch.ticksChanged();
        }
        if (height !== currentSize.height) {
          epoch.option('height', height - getLegendHeight(height));
        }
        currentSize.width = width;
        currentSize.height = height;

        if (firstDraw) {
          delta = true;
          var seriesData = _.map(data, function (series, i) {
            delta = delta && series.color; // use color as delta temporaly, if all series is delta, enable realtime chart

            // if hidden remove points
            if (ctrl.hiddenSeries[series.alias]) {
              return {};
            }

            return {
              label: series.epochLabel,
              values: getHeatmapData(series.datapoints)
            };
          });

          if (shouldDelayDraw(panel)) {
            // temp fix for legends on the side, need to render twice to get dimensions right
            callPlot(false, seriesData);
            setTimeout(function() { callPlot(true, seriesData); }, 50);
            legendSideLastValue = panel.legend.rightSide;
          } else {
            callPlot(true, seriesData);
          }

          if (delta) {
            firstDraw = false;

            labelToModelIndexMap = {};
            _.each(data, function (series, i) {
              labelToModelIndexMap[series.epochLabel] = i;
            });
          }
        } else if (delta) {
          var indexedData = [];
          var dataLength = 0;
          _.each(data, function (series) {
            if (_.isUndefined(labelToModelIndexMap[series.epochLabel])) {
              return;
            }

            var values = getHeatmapData(series.datapoints);
            indexedData[labelToModelIndexMap[series.epochLabel]] = values;

            if (dataLength < values.length) {
              dataLength = values.length;
            }
          });

          if (!_.isEmpty(indexedData)) {
            var now = new Date() / 1000;
            for (var n = 0; n < dataLength; n++) {
              var pushData = indexedData.map(function (d) {
                return d[n] || {
                  time: now,
                  histogram: {
                    0: 0
                  }
                };
              });
              epoch.push(pushData);
            }
          }
          ctrl.renderingCompleted();
        }
      }

      function time_format(ticks, min, max) {
        if (min && max && ticks) {
          var range = max - min;
          var secPerTick = (range/ticks) / 1000;
          var oneDay = 86400000;
          var oneYear = 31536000000;

          if (secPerTick <= 45) {
            return "HH:mm:ss";
          }
          if (secPerTick <= 7200 || range <= oneDay) {
            return "HH:mm";
          }
          if (secPerTick <= 80000) {
            return "M/D HH:mm";
          }
          if (secPerTick <= 2419200 || range <= oneYear) {
            return "M/D";
          }
          return "YYYY-M";
        }

        return "HH:mm";
      }

      function shouldDelayDraw(panel) {
        if (panel.legend.rightSide) {
          return true;
        }
        if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) {
          return true;
        }
      }

      elem.bind("plotselected", function (event, ranges) {
        scope.$apply(function() {
          timeSrv.setTime({
            from  : moment.utc(ranges.xaxis.from),
            to    : moment.utc(ranges.xaxis.to),
          });
        });
      });
    }
  };
});
<commit_msg>minor fix<commit_after>import angular from 'angular';
import $ from 'jquery';
import moment from 'moment';
import _ from 'lodash';
import kbn from 'app/core/utils/kbn';
import './bower_components/d3/d3.min.js';
import './bower_components/epoch/dist/js/epoch.min.js';

angular.module('grafana.directives').directive('grafanaHeatmapEpoch', function($rootScope, timeSrv) {
  return {
    restrict: 'A',
    template: '<div> </div>',
    link: function(scope, elem) {
      var ctrl = scope.ctrl;
      var dashboard = ctrl.dashboard;
      var panel = ctrl.panel;
      var data;
      var sortedSeries;
      var legendSideLastValue = null;
      var rootScope = scope.$root;
      var epoch = null;
      var firstDraw = true;
      var delta = true;
      var labelToModelIndexMap = {};
      var currentDatasource = '';
      var currentTimeRange = [0, 0];
      var currentSize = { width: null, height: null };

      // Receive render events
      ctrl.events.on('render', function(renderData) {
        data = renderData || data;
        if (!data) {
          ctrl.refresh();
          return;
        }
        render_panel();
      });

      function getLegendHeight(panelHeight) {
        if (!panel.legend.show || panel.legend.rightSide) {
          return 2;
        }

        if (panel.legend.alignAsTable) {
          var legendSeries = _.filter(data, function(series) {
            return series.hideFromLegend(panel.legend) === false;
          });
          var total = 23 + (22 * legendSeries.length);
          return Math.min(total, Math.floor(panelHeight/2));
        } else {
          return 26;
        }
      }

      function setElementHeight() {
        try {
          var height = ctrl.height - getLegendHeight(ctrl.height);
          elem.css('height', height + 'px');

          return true;
        } catch(e) { // IE throws errors sometimes
          console.log(e);
          return false;
        }
      }

      function shouldAbortRender() {
        if (!data) {
          return true;
        }

        if (!setElementHeight()) { return true; }

        if (elem.width() === 0) {
          return true;
        }
      }

      function processOffsetHook(plot, gridMargin) {
        var left = panel.yaxes[0];
        var right = panel.yaxes[1];
        if (left.show && left.label) { gridMargin.left = 20; }
        if (right.show && right.label) { gridMargin.right = 20; }
      }

      function getEpoch() {
        if (epoch) {
          return epoch;
        }

        epoch = elem.epoch(panel.heatmapOptions);
        scope.$watch('ctrl.panel.heatmapOptions.windowSize', function(newVal, oldVal) {
          epoch.option('windowSize', newVal);
          epoch.option('historySize', newVal * 3);
          epoch.redraw();
        });
        scope.$watch('ctrl.panel.heatmapOptions.buckets', function(newVal, oldVal) {
          epoch.option('buckets', newVal);
        });
        scope.$watch('ctrl.panel.heatmapOptions.bucketRange[0]', function(newVal, oldVal) {
          epoch.option('bucketRange', panel.heatmapOptions.bucketRange);
        });
        scope.$watch('ctrl.panel.heatmapOptions.bucketRange[1]', function(newVal, oldVal) {
          epoch.option('bucketRange', panel.heatmapOptions.bucketRange);
        });
        scope.$watch('ctrl.panel.heatmapOptions.startTime', function(newVal, oldVal) {
          epoch.option('startTime', newVal);
          epoch.redraw();
        });

        return epoch;
      }

      function getHeatmapData(datapoints) {
        var windowInterval = Math.floor((ctrl.range.to - ctrl.range.from) / panel.heatmapOptions.windowSize);
        var data = _.chain(datapoints)
        .reject(function(dp) {
          return dp[0] === null;
        })
        .groupBy(function(dp) {
          return Math.floor((dp[1] - ctrl.range.from) / windowInterval) + 1;
        })
        .map(function(values, timeKey) {
          return {
            time: Math.floor((timeKey * windowInterval + ctrl.range.from) / 1000),
            histogram: _.countBy(values, function(value) {
              return value[0];
            })
          };
        })
        .sortBy(function(dp) {
          return dp.time;
        }).value();

        if (data.length > panel.heatmapOptions.windowSize) {
          data[panel.heatmapOptions.windowSize].histogram = {};
        }

        return data;
      }

      function callPlot(incrementRenderCounter, data) {
        try {
          epoch.setData(data);

          if (ctrl.range.from !== currentTimeRange[0] || ctrl.range.to !== currentTimeRange[1]) {
            var ticks = Math.ceil(panel.heatmapOptions.windowSize / panel.heatmapOptions.ticks.time);
            var min = _.isUndefined(ctrl.range.from) ? null : ctrl.range.from.valueOf();
            var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf();
            epoch.option('tickFormats.bottom', function (d) {
              var timeFormat = time_format(ticks, min, max);
              return moment.unix(d).format(timeFormat);
            });
            epoch.ticksChanged();
          }
          currentTimeRange = [ctrl.range.from, ctrl.range.to];

          epoch.redraw();
        } catch (e) {
          console.log('epoch error', e);
        }

        if (incrementRenderCounter) {
          ctrl.renderingCompleted();
        }
      }

      // Function for rendering panel
      function render_panel() {
        if (shouldAbortRender()) {
          return;
        }

        // reset startTime if datasource is changed
        if (panel.datasource !== currentDatasource) {
          panel.heatmapOptions.startTime = Math.floor(ctrl.range.from.valueOf() / 1000);
          firstDraw = true;
        }
        currentDatasource = panel.datasource;

        // replace the characters which is not allowed to use for label
        _.each(data, function (series) {
          series.epochLabel = series.label.replace(/[ !"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, ' ');
        });

        var epoch = getEpoch();

        // check panel size change
        var width = elem.parent().width();
        var height = elem.parent().height();
        if (width !== currentSize.width) {
          epoch.option('width', width);
          var ticksTime = (width > 700) ? 5 : 10;
          epoch.option('ticks.time', ticksTime);
          epoch.ticksChanged();
        }
        if (height !== currentSize.height) {
          epoch.option('height', height - getLegendHeight(height));
        }
        currentSize.width = width;
        currentSize.height = height;

        if (firstDraw) {
          delta = true;
          var seriesData = _.map(data, function (series, i) {
            delta = delta && series.color; // use color as delta temporaly, if all series is delta, enable realtime chart

            // if hidden remove points
            if (ctrl.hiddenSeries[series.alias]) {
              return {};
            }

            return {
              label: series.epochLabel,
              values: getHeatmapData(series.datapoints)
            };
          });

          if (shouldDelayDraw(panel)) {
            // temp fix for legends on the side, need to render twice to get dimensions right
            callPlot(false, seriesData);
            setTimeout(function() { callPlot(true, seriesData); }, 50);
            legendSideLastValue = panel.legend.rightSide;
          } else {
            callPlot(true, seriesData);
          }

          // create model index for realtime graph
          if (delta) {
            firstDraw = false;

            labelToModelIndexMap = {};
            _.each(data, function (series, i) {
              labelToModelIndexMap[series.epochLabel] = i;
            });
          }
        } else if (delta) { // realtime graph
          var indexedData = [];
          var dataLength = 0;
          _.each(data, function (series) {
            if (_.isUndefined(labelToModelIndexMap[series.epochLabel])) {
              return;
            }

            var values = getHeatmapData(series.datapoints);
            indexedData[labelToModelIndexMap[series.epochLabel]] = values;

            if (dataLength < values.length) {
              dataLength = values.length;
            }
          });

          if (!_.isEmpty(indexedData)) {
            var now = new Date() / 1000;
            for (var n = 0; n < dataLength; n++) {
              var pushData = indexedData.map(function (d) {
                return d[n] || {
                  time: now,
                  histogram: {
                    0: 0
                  }
                };
              });
              epoch.push(pushData);
            }
          }
          ctrl.renderingCompleted();
        }
      }

      function time_format(ticks, min, max) {
        if (min && max && ticks) {
          var range = max - min;
          var secPerTick = (range/ticks) / 1000;
          var oneDay = 86400000;
          var oneYear = 31536000000;

          if (secPerTick <= 45) {
            return "HH:mm:ss";
          }
          if (secPerTick <= 7200 || range <= oneDay) {
            return "HH:mm";
          }
          if (secPerTick <= 80000) {
            return "M/D HH:mm";
          }
          if (secPerTick <= 2419200 || range <= oneYear) {
            return "M/D";
          }
          return "YYYY-M";
        }

        return "HH:mm";
      }

      function shouldDelayDraw(panel) {
        if (panel.legend.rightSide) {
          return true;
        }
        if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) {
          return true;
        }
      }

      elem.bind("plotselected", function (event, ranges) {
        scope.$apply(function() {
          timeSrv.setTime({
            from  : moment.utc(ranges.xaxis.from),
            to    : moment.utc(ranges.xaxis.to),
          });
        });
      });
    }
  };
});
<|endoftext|>


<commit_before>import MutationObserver from '@sheerun/mutationobserver-shim'

const globalObj = typeof window === 'undefined' ? global : window

// we only run our tests in node, and setImmediate is supported in node.
// istanbul ignore next
function setImmediatePolyfill(fn) {
  return globalObj.setTimeout(fn, 0)
}

// istanbul ignore next
const clearTimeoutFn = globalObj.clearTimeout
const setImmediateFn = globalObj.setImmediate || setImmediatePolyfill
const setTimeoutFn = globalObj.setTimeout

function newMutationObserver(onMutation) {
  const MutationObserverConstructor =
    typeof window !== 'undefined' &&
    typeof window.MutationObserver !== 'undefined'
      ? window.MutationObserver
      : MutationObserver

  return new MutationObserverConstructor(onMutation)
}

function getDocument() {
  /* istanbul ignore if */
  if (typeof window === 'undefined') {
    throw new Error('Could not find default container')
  }
  return window.document
}

export {
  getDocument,
  newMutationObserver,
  clearTimeoutFn as clearTimeout,
  setImmediateFn as setImmediate,
  setTimeoutFn as setTimeout,
}
<commit_msg>chore: fix coverage report. (#318)<commit_after>import MutationObserver from '@sheerun/mutationobserver-shim'

const globalObj = typeof window === 'undefined' ? global : window

// we only run our tests in node, and setImmediate is supported in node.
// istanbul ignore next
function setImmediatePolyfill(fn) {
  return globalObj.setTimeout(fn, 0)
}

const clearTimeoutFn = globalObj.clearTimeout
// istanbul ignore next
const setImmediateFn = globalObj.setImmediate || setImmediatePolyfill
const setTimeoutFn = globalObj.setTimeout

function newMutationObserver(onMutation) {
  const MutationObserverConstructor =
    typeof window !== 'undefined' &&
    typeof window.MutationObserver !== 'undefined'
      ? window.MutationObserver
      : MutationObserver

  return new MutationObserverConstructor(onMutation)
}

function getDocument() {
  /* istanbul ignore if */
  if (typeof window === 'undefined') {
    throw new Error('Could not find default container')
  }
  return window.document
}

export {
  getDocument,
  newMutationObserver,
  clearTimeoutFn as clearTimeout,
  setImmediateFn as setImmediate,
  setTimeoutFn as setTimeout,
}
<|endoftext|>


<commit_before>import __ from "lodash";
import _ from "lodash-fp";

export const split = _.curry((delimiter, str) => str.split(delimiter));
export const join = _.curry((delimiter, str) => str.join(delimiter));

export function log(x) {
    console.log(x);

    return x;
}

export const groupBy = _.curryRight(__.groupBy, 2);

// A less annoying _.fill function.
export const fill = _.curry((x, arr) => _.map(() => x, arr));

export const doWhile = _.curry((f, g, x) => {
    let y = f(x);

    while (g(y))
        y = f(y);

    return y;
});
<commit_msg>Added a reverse helper.<commit_after>import __ from "lodash";
import _ from "lodash-fp";

export const split = _.curry((delimiter, str) => str.split(delimiter));

export const join = _.curry((delimiter, str) => str.join(delimiter));

export const reverse = (x) => x.reverse();

export function log(x) {
    console.log(x);

    return x;
}

export const groupBy = _.curryRight(__.groupBy, 2);

// A less annoying _.fill function.
export const fill = _.curry((x, arr) => _.map(() => x, arr));

export const doWhile = _.curry((f, g, x) => {
    let y = f(x);

    while (g(y))
        y = f(y);

    return y;
});
<|endoftext|>