<commit_before>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global require, define, Mustache, $, setTimeout, clearTimeout */

define(function (require, exports, module) {
    "use strict";

    var apiUrlPrefix      = "https://api.typekit.com/edge_internal_v1/",
        fontIncludePrefix = "<script src=\"http://use.edgefonts.net/",
        fontIncludeSuffix = ".js\"></script>";

    var fontsByClass = {},
        fontsByName  = {},
        fontsBySlug  = {},
        allFonts     = [],
        allSlugs     = [];

    var pickerHtml     = require("text!htmlContent/ewf-picker.html"),
        resultsHtml    = require("text!htmlContent/ewf-results.html"),
        Strings        = require("strings");

    var $picker  = null,
        $results = null;

    var fontClassifications = ["serif", "sans-serif", "slab-serif", "script", "blackletter", "monospaced", "handmade", "decorative"],
        fontRecommendations = ["headings", "paragraphs"];

    var websafeFonts = ["andale mono", "arial", "arial black", "comic sans ms", "courier new", "georgia", "impact", "times new roman", "trebuchet ms", "verdana", "sans-serif", "serif"];

    var SEARCH_HYSTERESIS = 500; // milliseconds
    var lastSearchTimer = null;

    function getWebsafeFonts() {
        return websafeFonts.concat([]); // make a copy
    }

    function getAllSlugs() {
        return allSlugs.concat([]); // make a copy
    }

    /**
     * Returns font object matching specified slug or 'undefined' if not found
     *
     * @param {!string} slug - the slug of the desired font
     * @return {object} font object, or undefined if not found
     */
    function getFontBySlug(slug) {
        return fontsBySlug[slug.toLowerCase()];
    }

    /**
     * Returns a copy of arr that is a.) all lowercase, b.) sorted lexigraphically,
     * and c.) has all duplicates removed.
     *
     * @param {!Array.<string>}
     * @return {Array.<string>}
     */
    function lowerSortUniqStringArray(arr) {
        var i, last = null, lowerArr = [], result = [];
        // fill up the lowerArr array with lowercase versions of the source array
        for (i = 0; i < arr.length; i++) {
            if (typeof arr[i] === "string") {
                lowerArr.push(arr[i].toLowerCase());
            }
        }

        // sort lowerArr alphabetically
        lowerArr.sort();

        // copy unique elements to result array
        for (i = 0; i < lowerArr.length; i++) {
            if (lowerArr[i] !== last) {
                result.push(lowerArr[i]);
                last = lowerArr[i];
            }
        }

        return result;
    }

    /**
     * Returns a sorted array of all the elements in arr that contain a case-insensitive
     * version of the needle. The array returned is a new array. The original arry is not
     * modified.
     *
     * The results are sorted as follows:
     *   1. All elements with a name that starts with the needle
     *   2. All elements with a word that starts with the needle
     *   3. All elements that contain the needle
     * Within each category, elements are sorted alphabetically
     *
     * TODO: We should eventually move this search algorithm (and probably the lowerSortUniqStringArray)
     * to core brackets code. It would likely be useful for things like Quick Open.
     *
     * @param {!string} needle - the search term
     * @param {Array} arr - the array to filter and sort
     * @param {function} stringGetterFunction - optional function to extract the sort
     *                   term from each element in the array
     * @return {Array.<Object>} Array of font objects that contain the search term.
     */
    function filterAndSortArray(needle, arr, stringGetterFunction) {
        var beginning = [], beginningOfWord = [], contains = [];
        var i, index, currentString;

        var lowerCaseNeedle = needle.toLocaleLowerCase();

        if (!stringGetterFunction) {
            // If a function for getting the string out of each array object
            // isn't provided, just use whatever value is in the array as the string
            stringGetterFunction = function (s) { return String(s); };
        }

        for (i = 0; i < arr.length; i++) {
            currentString = stringGetterFunction(arr[i]).toLocaleLowerCase();
            index = currentString.indexOf(lowerCaseNeedle);
            if (index === 0) {
                beginning.push(arr[i]);
            } else if (index > 0) {
                var previousChar = currentString[index - 1];
                if (!previousChar.isAlpha() && !previousChar.isDigit()) {
                    beginningOfWord.push(arr[i]);
                } else {
                    contains.push(arr[i]);
                }
            }
        }

        return beginning.concat(beginningOfWord).concat(contains);
    }

    function searchByName(needle) {
        return filterAndSortArray(needle, allFonts, function (f) { return f.lowerCaseName; });
    }

    function searchBySlug(needle) {
        return filterAndSortArray(needle, getAllSlugs());
    }

    function _displayResults(families) {
        function fontClickHandler(event) {
            var d = event.target;

            // walk up the dom until we find something with the data-slug attribute
            while (d && !d.attributes.hasOwnProperty("data-slug")) {
                d = d.parentElement;
            }

            if (d) {
                $(".ewf-font.selected").removeClass("selected");
                $(d).addClass("selected");
                $(exports).triggerHandler("ewfFontSelected", d.attributes["data-slug"].value);
            }
        }

        if ($results) {
            $results.empty();
            $results.html(Mustache.render(resultsHtml, {Strings: Strings, families: families}));
            $(".ewf-font").click(fontClickHandler);
        }
    }

    function renderPicker(domElement) {
        var localizedClassifications = [];
        var localizedRecommendations = [];
        var i;

        function classificationClickHandler(event) {
            var $targetButton = $(event.target);
            while ($targetButton.length > 0 && !$targetButton.attr("data-classification")) {
                $targetButton = $targetButton.parent();
            }
            if ($targetButton.length > 0) {
                var classification = $targetButton.attr("data-classification");
                var families = fontsByClass[classification];

                // clear previously selected class
                $(".ewf-tabs button").removeClass("selected");

                // select this class
                $targetButton.addClass("selected");

                _displayResults(families);
            }
        }

        function searchExecutor(query) {
            var fonts = searchByName(query);
            // clear any previously selected class
            $('.ewf-tabs button').removeClass("selected");
            _displayResults(fonts);
        }

        function searchHandler(event) {
            var query = $(event.target).val();
            if (lastSearchTimer) {
                clearTimeout(lastSearchTimer);
            }
            lastSearchTimer = setTimeout(function () { searchExecutor(query); }, SEARCH_HYSTERESIS);

        }

        // map font classifications to their localized names:
        for (i = 0; i < fontClassifications.length; i++) {
            localizedClassifications.push({className: fontClassifications[i], localizedName: Strings[fontClassifications[i]]});
        }

        // map font classifications to their localized names:
        for (i = 0; i < fontRecommendations.length; i++) {
            localizedRecommendations.push({className: fontRecommendations[i], localizedName: Strings[fontRecommendations[i]]});
        }

        $picker = $(Mustache.render(pickerHtml, {Strings: Strings, localizedClassifications: localizedClassifications, localizedRecommendations: localizedRecommendations}));
        $(domElement).append($picker);

        $(".ewf-tabs button", $picker).click(classificationClickHandler);

        $('.ewf-tabs .ewf-search-fonts', $picker).on("keyup", searchHandler);

        $results = $(".ewf-results", $picker);

        $('.font-classifications button:first').trigger('click');
    }

    /**
     * Generates the script tag for including the specified fonts.
     *
     * @param {!Array} fonts - should be an array of objects, and each object
     *      should have the following properties:
     *        slug - string specifying the unique slug of the font (e.g. "droid-sans")
     *        fvds - array of variant strings (e.g. ["n4", "n7"])
     *        subset - string specifying the subset desired (e.g. "default")
     *
     */
    function createInclude(fonts) {
        var i, fontStrings = [];
        for (i = 0; i < fonts.length; i++) {
            fontStrings.push(fonts[i].slug + ":" + fonts[i].fvds.join(",") + ":" + fonts[i].subset);
        }
        return fontIncludePrefix + fontStrings.join(";") + fontIncludeSuffix;
    }

    function init(newApiUrlPrefix) {
        var d = $.Deferred();

        if (newApiUrlPrefix) {
            apiUrlPrefix = newApiUrlPrefix;
        }

        function organizeFamilies(families) {
            allFonts = families.families;
            var i, j;

            // Clean up the fonts in two ways:
            //   1. give all fonts a locale lowercase name (for searching by name)
            //   2. make sure all slugs are lowercase (should be the case already)
            for (i = 0; i < allFonts.length; i++) {
                allFonts[i].lowerCaseName = allFonts[i].name.toLocaleLowerCase();
                allFonts[i].slug = allFonts[i].slug.toLowerCase();
            }

            // We keep allFonts in alphabetical order by name, so that all other
            // lists will also be in order.
            allFonts.sort(function (a, b) {
                if (a.lowerCaseName < b.lowerCaseName) {
                    return -1;
                } else if (a.lowerCaseName > b.lowerCaseName) {
                    return 1;
                } else { // they're equal
                    return 0;
                }
            });

            // setup the allSlugs array;
            for (i = 0; i < allFonts.length; i++) {
                allSlugs.push(allFonts[i].slug);
            }

            fontsByClass = {};
            fontsByName = {};
            fontsBySlug = {};

            for (i = 0; i < allFonts.length; i++) {
                for (j = 0; j < allFonts[i].classifications.length; j++) {
                    if (!fontsByClass.hasOwnProperty(allFonts[i].classifications[j])) {
                        fontsByClass[allFonts[i].classifications[j]] = [];
                    }
                    fontsByClass[allFonts[i].classifications[j]].push(allFonts[i]);
                }
                for (j = 0; j < allFonts[i].recommended_for.length; j++) {
                    if (!fontsByClass.hasOwnProperty(allFonts[i].recommended_for[j])) {
                        fontsByClass[allFonts[i].recommended_for[j]] = [];
                    }
                    fontsByClass[allFonts[i].recommended_for[j]].push(allFonts[i]);
                }

                fontsByName[allFonts[i].name] = allFonts[i];
                fontsBySlug[allFonts[i].slug] = allFonts[i];
            }
        }

        $.ajax({
            url: apiUrlPrefix + "families",
            dataType: 'json',
            success: function (data) {
                organizeFamilies(data);
                d.resolve();
            },
            error: function () {
                d.reject("XHR request to 'families' API failed");
            }
        });

        return d.promise();

    }

    exports.lowerSortUniqStringArray = lowerSortUniqStringArray;
    exports.filterAndSortArray = filterAndSortArray;
    exports.getWebsafeFonts = getWebsafeFonts;
    exports.getAllSlugs = getAllSlugs;
    exports.getFontBySlug = getFontBySlug;
    exports.searchByName = searchByName;
    exports.searchBySlug = searchBySlug;
    exports.renderPicker = renderPicker;
    exports.createInclude = createInclude;
    exports.init = init;

});
<commit_msg>change search "algorithm" to return no fonts for an empty string search. done to prevent downloading 500 font images at once, which can cause crash on lion due to running out of FDs<commit_after>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global require, define, Mustache, $, setTimeout, clearTimeout */

define(function (require, exports, module) {
    "use strict";

    var apiUrlPrefix      = "https://api.typekit.com/edge_internal_v1/",
        fontIncludePrefix = "<script src=\"http://use.edgefonts.net/",
        fontIncludeSuffix = ".js\"></script>";

    var fontsByClass = {},
        fontsByName  = {},
        fontsBySlug  = {},
        allFonts     = [],
        allSlugs     = [];

    var pickerHtml     = require("text!htmlContent/ewf-picker.html"),
        resultsHtml    = require("text!htmlContent/ewf-results.html"),
        Strings        = require("strings");

    var $picker  = null,
        $results = null;

    var fontClassifications = ["serif", "sans-serif", "slab-serif", "script", "blackletter", "monospaced", "handmade", "decorative"],
        fontRecommendations = ["headings", "paragraphs"];

    var websafeFonts = ["andale mono", "arial", "arial black", "comic sans ms", "courier new", "georgia", "impact", "times new roman", "trebuchet ms", "verdana", "sans-serif", "serif"];

    var SEARCH_HYSTERESIS = 500; // milliseconds
    var lastSearchTimer = null;

    function getWebsafeFonts() {
        return websafeFonts.concat([]); // make a copy
    }

    function getAllSlugs() {
        return allSlugs.concat([]); // make a copy
    }

    /**
     * Returns font object matching specified slug or 'undefined' if not found
     *
     * @param {!string} slug - the slug of the desired font
     * @return {object} font object, or undefined if not found
     */
    function getFontBySlug(slug) {
        return fontsBySlug[slug.toLowerCase()];
    }

    /**
     * Returns a copy of arr that is a.) all lowercase, b.) sorted lexigraphically,
     * and c.) has all duplicates removed.
     *
     * @param {!Array.<string>}
     * @return {Array.<string>}
     */
    function lowerSortUniqStringArray(arr) {
        var i, last = null, lowerArr = [], result = [];
        // fill up the lowerArr array with lowercase versions of the source array
        for (i = 0; i < arr.length; i++) {
            if (typeof arr[i] === "string") {
                lowerArr.push(arr[i].toLowerCase());
            }
        }

        // sort lowerArr alphabetically
        lowerArr.sort();

        // copy unique elements to result array
        for (i = 0; i < lowerArr.length; i++) {
            if (lowerArr[i] !== last) {
                result.push(lowerArr[i]);
                last = lowerArr[i];
            }
        }

        return result;
    }

    /**
     * Returns a sorted array of all the elements in arr that contain a case-insensitive
     * version of the needle. The array returned is a new array. The original arry is not
     * modified.
     *
     * The results are sorted as follows:
     *   1. All elements with a name that starts with the needle
     *   2. All elements with a word that starts with the needle
     *   3. All elements that contain the needle
     * Within each category, elements are sorted alphabetically
     *
     * TODO: We should eventually move this search algorithm (and probably the lowerSortUniqStringArray)
     * to core brackets code. It would likely be useful for things like Quick Open.
     *
     * @param {!string} needle - the search term
     * @param {Array} arr - the array to filter and sort
     * @param {function} stringGetterFunction - optional function to extract the sort
     *                   term from each element in the array
     * @return {Array.<Object>} Array of font objects that contain the search term.
     */
    function filterAndSortArray(needle, arr, stringGetterFunction) {
        var beginning = [], beginningOfWord = [], contains = [];
        var i, index, currentString;

        var lowerCaseNeedle = needle.toLocaleLowerCase();

        if (!stringGetterFunction) {
            // If a function for getting the string out of each array object
            // isn't provided, just use whatever value is in the array as the string
            stringGetterFunction = function (s) { return String(s); };
        }

        for (i = 0; i < arr.length; i++) {
            currentString = stringGetterFunction(arr[i]).toLocaleLowerCase();
            index = currentString.indexOf(lowerCaseNeedle);
            if (index === 0) {
                beginning.push(arr[i]);
            } else if (index > 0) {
                var previousChar = currentString[index - 1];
                if (!previousChar.isAlpha() && !previousChar.isDigit()) {
                    beginningOfWord.push(arr[i]);
                } else {
                    contains.push(arr[i]);
                }
            }
        }

        return beginning.concat(beginningOfWord).concat(contains);
    }

    function searchByName(needle) {
        if (needle === "") {
            return [];
        } else {
            return filterAndSortArray(needle, allFonts, function (f) { return f.lowerCaseName; });
        }
    }

    function searchBySlug(needle) {
        if (needle === "") {
            return [];
        } else {
            return filterAndSortArray(needle, getAllSlugs());
        }
    }

    function _displayResults(families) {
        function fontClickHandler(event) {
            var d = event.target;

            // walk up the dom until we find something with the data-slug attribute
            while (d && !d.attributes.hasOwnProperty("data-slug")) {
                d = d.parentElement;
            }

            if (d) {
                $(".ewf-font.selected").removeClass("selected");
                $(d).addClass("selected");
                $(exports).triggerHandler("ewfFontSelected", d.attributes["data-slug"].value);
            }
        }

        if ($results) {
            $results.empty();
            $results.html(Mustache.render(resultsHtml, {Strings: Strings, families: families}));
            $(".ewf-font").click(fontClickHandler);
        }
    }

    function renderPicker(domElement) {
        var localizedClassifications = [];
        var localizedRecommendations = [];
        var i;

        function classificationClickHandler(event) {
            var $targetButton = $(event.target);
            while ($targetButton.length > 0 && !$targetButton.attr("data-classification")) {
                $targetButton = $targetButton.parent();
            }
            if ($targetButton.length > 0) {
                var classification = $targetButton.attr("data-classification");
                var families = fontsByClass[classification];

                // clear previously selected class
                $(".ewf-tabs button").removeClass("selected");

                // clear any previous search
                $(".ewf-search-fonts").val("");

                // select this class
                $targetButton.addClass("selected");

                _displayResults(families);
            }
        }

        function searchExecutor(query) {
            var fonts = searchByName(query);
            // clear any previously selected class
            $('.ewf-tabs button').removeClass("selected");
            _displayResults(fonts);
        }

        function searchHandler(event) {
            var query = $(event.target).val();
            if (lastSearchTimer) {
                clearTimeout(lastSearchTimer);
            }
            lastSearchTimer = setTimeout(function () { searchExecutor(query); }, SEARCH_HYSTERESIS);

        }

        // map font classifications to their localized names:
        for (i = 0; i < fontClassifications.length; i++) {
            localizedClassifications.push({className: fontClassifications[i], localizedName: Strings[fontClassifications[i]]});
        }

        // map font classifications to their localized names:
        for (i = 0; i < fontRecommendations.length; i++) {
            localizedRecommendations.push({className: fontRecommendations[i], localizedName: Strings[fontRecommendations[i]]});
        }

        $picker = $(Mustache.render(pickerHtml, {Strings: Strings, localizedClassifications: localizedClassifications, localizedRecommendations: localizedRecommendations}));
        $(domElement).append($picker);

        $(".ewf-tabs button", $picker).click(classificationClickHandler);

        $('.ewf-tabs .ewf-search-fonts', $picker).on("keyup", searchHandler);

        $results = $(".ewf-results", $picker);

        $('.font-classifications button:first').trigger('click');
    }

    /**
     * Generates the script tag for including the specified fonts.
     *
     * @param {!Array} fonts - should be an array of objects, and each object
     *      should have the following properties:
     *        slug - string specifying the unique slug of the font (e.g. "droid-sans")
     *        fvds - array of variant strings (e.g. ["n4", "n7"])
     *        subset - string specifying the subset desired (e.g. "default")
     *
     */
    function createInclude(fonts) {
        var i, fontStrings = [];
        for (i = 0; i < fonts.length; i++) {
            fontStrings.push(fonts[i].slug + ":" + fonts[i].fvds.join(",") + ":" + fonts[i].subset);
        }
        return fontIncludePrefix + fontStrings.join(";") + fontIncludeSuffix;
    }

    function init(newApiUrlPrefix) {
        var d = $.Deferred();

        if (newApiUrlPrefix) {
            apiUrlPrefix = newApiUrlPrefix;
        }

        function organizeFamilies(families) {
            allFonts = families.families;
            var i, j;

            // Clean up the fonts in two ways:
            //   1. give all fonts a locale lowercase name (for searching by name)
            //   2. make sure all slugs are lowercase (should be the case already)
            for (i = 0; i < allFonts.length; i++) {
                allFonts[i].lowerCaseName = allFonts[i].name.toLocaleLowerCase();
                allFonts[i].slug = allFonts[i].slug.toLowerCase();
            }

            // We keep allFonts in alphabetical order by name, so that all other
            // lists will also be in order.
            allFonts.sort(function (a, b) {
                if (a.lowerCaseName < b.lowerCaseName) {
                    return -1;
                } else if (a.lowerCaseName > b.lowerCaseName) {
                    return 1;
                } else { // they're equal
                    return 0;
                }
            });

            // setup the allSlugs array;
            for (i = 0; i < allFonts.length; i++) {
                allSlugs.push(allFonts[i].slug);
            }

            fontsByClass = {};
            fontsByName = {};
            fontsBySlug = {};

            for (i = 0; i < allFonts.length; i++) {
                for (j = 0; j < allFonts[i].classifications.length; j++) {
                    if (!fontsByClass.hasOwnProperty(allFonts[i].classifications[j])) {
                        fontsByClass[allFonts[i].classifications[j]] = [];
                    }
                    fontsByClass[allFonts[i].classifications[j]].push(allFonts[i]);
                }
                for (j = 0; j < allFonts[i].recommended_for.length; j++) {
                    if (!fontsByClass.hasOwnProperty(allFonts[i].recommended_for[j])) {
                        fontsByClass[allFonts[i].recommended_for[j]] = [];
                    }
                    fontsByClass[allFonts[i].recommended_for[j]].push(allFonts[i]);
                }

                fontsByName[allFonts[i].name] = allFonts[i];
                fontsBySlug[allFonts[i].slug] = allFonts[i];
            }
        }

        $.ajax({
            url: apiUrlPrefix + "families",
            dataType: 'json',
            success: function (data) {
                organizeFamilies(data);
                d.resolve();
            },
            error: function () {
                d.reject("XHR request to 'families' API failed");
            }
        });

        return d.promise();

    }

    exports.lowerSortUniqStringArray = lowerSortUniqStringArray;
    exports.filterAndSortArray = filterAndSortArray;
    exports.getWebsafeFonts = getWebsafeFonts;
    exports.getAllSlugs = getAllSlugs;
    exports.getFontBySlug = getFontBySlug;
    exports.searchByName = searchByName;
    exports.searchBySlug = searchBySlug;
    exports.renderPicker = renderPicker;
    exports.createInclude = createInclude;
    exports.init = init;

});
<|endoftext|>


<commit_before>/**
 *
 * Copyright (c) 2015 Madjid Taha
 * Released under MIT license:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * For more information:
 * https://github.com/madjidtaha/Spritepack.js
 * contact@madjidtaha.fr
 *
 * Spritepack is a fork of Magipack by Keita Kuroki
 * https://github.com/keitakun/Magipack.js
 * code@hellokeita.in
 *
 **/

(function(global) {
	window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
	Spritepack.isIE = Boolean(document.all);
	var hasBlob = false;
	try{
		hasBlob = Boolean(Blob);
	}catch(e)
	{
		hasBlob = false;
	}
	Spritepack.hasBlob = hasBlob;
	Spritepack.version = '1.0.0';
	if(!Spritepack.hasBlob)
	{
		throw new Error('Spritepack need blob support to work.')

	}

	function req()
	{
		if(window.XMLHttpRequest) return new XMLHttpRequest()
		if(window.ActiveXObject) return new ActiveXObject("MSXML2.XMLHTTP.3.0")
	}

	function Spritepack(pack, config) {
		this.progress = 0
		if(pack)
		{
			this.init(pack, config);
		}
	}

	Spritepack.prototype._onProgress = function(e)
	{
		p = e.position / e.totalSize;
		if(isNaN(p)) p = 0;
		if(this._configComplete)
		{
			p = p * 0.9 + 0.1;
		}else{
			p *= 0.1;
		}
		this.progress = p;
		if(this.onProgress) this.onProgress(p, 1);
	}

	Spritepack.prototype._loadFile = function(path, callback, type)
	{
		var xhr = req();
		if(!type)
		{
			type = 'arraybuffer';
			try{
				if(Blob.prototype.slice)
				{
					type = 'blob';
				}
			}catch(e)
			{

			}
		}

		xhr.open('GET', path, true);

		xhr.responseType = type;
		var _callback = callback;
		var _this = this;
		xhr.onprogress = function(e)
		{
			_this._onProgress(e)
		}
		xhr.onreadystatechange = function(e) {
			if (this.readyState == 4) {
				if(_callback) _callback.call(_this, this);
				this.onreadystatechange = null;
			}
		};

		xhr.send(null);
	}

	Spritepack.prototype._configLoaded = function(e)
	{
		this._configComplete = true;
		this.config = eval('(' + e.responseText + ')');
		this._loadFile(this.pack, this._packLoaded);
	}

	Spritepack.prototype._packLoaded = function(e)
	{

		this.init(e.response, this.config);

		if(this.onLoadComplete)
		{
			this.onLoadComplete(this);
		}
	}

	Spritepack.prototype._findFile = function(name)
	{
		var i;
		i = this.config.length;
		while (i-- > 0)
		{
			if(this.config[i][0] == name)
			{
				return this.config[i];
			}
		}
		while (i-- > 0) {
			if (name.indexOf(this.config[i][0]) >= 0) {
				return this.config[i];
			}
		}
	}

	Spritepack.prototype._getRange = function(i, e, type)
	{
		var b;
		if(this.blob.slice)
		{
			b = this.blob.slice(i, e, type);
			return window.URL.createObjectURL(b);
		}else if (this.blob.webkitSlice)
		{
			b = this.blob.webkitSlice(i, e, type);
			return window.URL.createObjectURL(b);
		}else if(this.blob.mozSlice)
		{
			b = this.blob.mozSlice(i, e, type);
			return window.URL.createObjectURL(b);
		}
	}

	Spritepack.prototype.init = function(pack, config)
	{
		this.config = config;
		if(pack != null)
		{

			this.blob = new Blob([pack])

		}
	}

	Spritepack.prototype.load = function(pack, config)
	{
		this.pack = pack;
		if(config)
		{
			this._loadFile(config, this._configLoaded, 'text');
		}else{
			this._loadFile(pack, this._packLoaded);
		}
	}

	Spritepack.prototype.getURI = function()
	{
		if(arguments.length == 0) throw new Error('Not enough arguments.');
		if(isNaN(arguments[0]) && !this.config) throw new Error('No config file loaded.');

		var type;

		if(!isNaN(arguments[0]) && !isNaN(arguments[1]))
		{
			type = arguments[2];
			if(!type) type = 'text/plain';
			return this._getRange(arguments[0], arguments[1], type);
		}

		var file = this._findFile(arguments[0]);
		if(!file) throw new Error('File not found in pack.');
		type = file[3];
		if(!type) type = 'text/plain';

		return this._getRange(file[1], file[2], type);
	}

	Spritepack.init = function(pack, config) {
		if(this.inited) throw new Error('Spritepack static instance already initialized.');
		this._instance = new Spritepack(pack, config);
		if(pack)
		{
			this._instance.init(pack, config);
		}
		this.inited = true;
	};

	Spritepack.load = function(pack, config) {
		if(!this.inited) this.init();
		this._instance.onLoadComplete = this.onLoadComplete;
		this._instance.load(pack, config);
	};

	Spritepack.getURI = function()
	{
		return this._instance.getURI.apply(this._instance, arguments);
	}

	/* global define:true module:true window: true */

	if (typeof define === 'function' && define.amd) {

	 	define(function() { return Spritepack; });

	} else if (typeof module !== 'undefined' && module.exports) {

		module.exports = Spritepack;

	} else if (typeof this !== 'undefined') {

	  	global.Spritepack = Spritepack;

	}

	// return Spritepack;

})(this);
<commit_msg>fix typo<commit_after>/**
 *
 * Copyright (c) 2015 Madjid Taha
 * Released under MIT license:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * For more information:
 * https://github.com/madjidtaha/Spritepack.js
 * contact@madjidtaha.fr
 *
 * Spritepack is a fork of Magipack by Keita Kuroki
 * https://github.com/keitakun/Magipack.js
 * code@hellokeita.in
 *
 **/

(function(global) {
	window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
	Spritepack.isIE = Boolean(document.all);
	var hasBlob = false;
	try{
		hasBlob = Boolean(Blob);
	}catch(e)
	{
		hasBlob = false;
	}
	Spritepack.hasBlob = hasBlob;
	Spritepack.version = '1.0.0';
	if(!Spritepack.hasBlob)
	{
		throw new Error('Spritepack need blob support to work.')

	}

	function req()
	{
		if(window.XMLHttpRequest) return new XMLHttpRequest()
		if(window.ActiveXObject) return new ActiveXObject("MSXML2.XMLHTTP.3.0")
	}

	function Spritepack(pack, config) {
		this.progress = 0
		if(pack)
		{
			this.init(pack, config);
		}
	}

	Spritepack.prototype._onProgress = function(e)
	{
		p = e.position / e.totalSize;
		if(isNaN(p)) p = 0;
		if(this._configComplete)
		{
			p = p * 0.9 + 0.1;
		}else{
			p *= 0.1;
		}
		this.progress = p;
		if(this.onProgress) this.onProgress(p, 1);
	}

	Spritepack.prototype._loadFile = function(path, callback, type)
	{
		var xhr = req();
		if(!type)
		{
			type = 'arraybuffer';
			try{
				if(Blob.prototype.slice)
				{
					type = 'blob';
				}
			}catch(e)
			{

			}
		}

		xhr.open('GET', path, true);

		xhr.responseType = type;
		var _callback = callback;
		var _this = this;
		xhr.onprogress = function(e)
		{
			_this._onProgress(e)
		}
		xhr.onreadystatechange = function(e) {
			if (this.readyState == 4) {
				if(_callback) _callback.call(_this, this);
				this.onreadystatechange = null;
			}
		};

		xhr.send(null);
	}

	Spritepack.prototype._configLoaded = function(e)
	{
		this._configComplete = true;
		this.config = eval('(' + e.responseText + ')');
		this._loadFile(this.pack, this._packLoaded);
	}

	Spritepack.prototype._packLoaded = function(e)
	{

		this.init(e.response, this.config);

		if(this.onLoadComplete)
		{
			this.onLoadComplete(this);
		}
	}

	Spritepack.prototype._findFile = function(name)
	{
		var i;
		i = this.config.length;
		while (i-- > 0)
		{
			if(this.config[i][0] == name)
			{
				return this.config[i];
			}
		}
		while (i-- > 0) {
			if (name.indexOf(this.config[i][0]) >= 0) {
				return this.config[i];
			}
		}
	}

	Spritepack.prototype._getRange = function(i, e, type)
	{
		var b;
		if(this.blob.slice)
		{
			b = this.blob.slice(i, e, type);
			return window.URL.createObjectURL(b);
		}else if (this.blob.webkitSlice)
		{
			b = this.blob.webkitSlice(i, e, type);
			return window.URL.createObjectURL(b);
		}else if(this.blob.mozSlice)
		{
			b = this.blob.mozSlice(i, e, type);
			return window.URL.createObjectURL(b);
		}
	}

	Spritepack.prototype.init = function(pack, config)
	{
		this.config = config;
		if(pack != null)
		{

			this.blob = new Blob([pack])

		}
	}

	Spritepack.prototype.load = function(pack, config)
	{
		this.pack = pack;
		if(config)
		{
			this._loadFile(config, this._configLoaded, 'text');
		}else{
			this._loadFile(pack, this._packLoaded);
		}
	}

	Spritepack.prototype.getURI = function()
	{
		if(arguments.length == 0) throw new Error('Not enough arguments.');
		if(isNaN(arguments[0]) && !this.config) throw new Error('No config file loaded.');

		var type;

		if(!isNaN(arguments[0]) && !isNaN(arguments[1]))
		{
			type = arguments[2];
			if(!type) type = 'text/plain';
			return this._getRange(arguments[0], arguments[1], type);
		}

		var file = this._findFile(arguments[0]);
		if(!file) throw new Error('File not found in pack.');
		type = file[3];
		if(!type) type = 'text/plain';

		return this._getRange(file[1], file[2], type);
	}

	Spritepack.init = function(pack, config) {
		if(this.inited) throw new Error('Spritepack static instance already initialized.');
		this._instance = new Spritepack(pack, config);
		if(pack)
		{
			this._instance.init(pack, config);
		}
		this.inited = true;
	};

	Spritepack.load = function(pack, config) {
		if(!this.inited) this.init();
		this._instance.onLoadComplete = this.onLoadComplete;
		this._instance.load(pack, config);
	};

	Spritepack.getURI = function()
	{
		return this._instance.getURI.apply(this._instance, arguments);
	}

	/* global define:true module:true window:true */

	if (typeof define === 'function' && define.amd) {

	 	define(function() { return Spritepack; });

	} else if (typeof module !== 'undefined' && module.exports) {

		module.exports = Spritepack;

	} else if (typeof this !== 'undefined') {

	  	global.Spritepack = Spritepack;

	}

})(this);
<|endoftext|>


<commit_before>"use strict";

var CityTour = CityTour || {};

CityTour.NeighborhoodGenerator = (function() {
  var MIN_DISTANCE_BETWEEN_NEIGHBORHOODS = 10;

  // The size of the square that is used to calculate the terrain flatness
  // around a given point. Each value should be an odd number.
  var NEIGHBORHOOD_CENTER_WIDTH = 9;
  var NEIGHBORHOOD_CENTER_DEPTH = 9;

  var calculateFlatnessScores = function(terrain) {
    var flatnessScores = [];
    var x, z;

    for (x = terrain.minMapX(); x <= terrain.maxMapX(); x++) {
      flatnessScores[x] = [];

      for (z = terrain.minMapZ(); z <= terrain.maxMapZ(); z++) {
        if (terrain.waterHeightAtCoordinates(x, z) > 0.0) {
          flatnessScores[x][z] = Number.POSITIVE_INFINITY;
        }
        else {
          flatnessScores[x][z] = averageHeightDifferenceAroundPoint(terrain, x, z);
        }
      }
    }

    return flatnessScores;
  };

  var averageHeightDifferenceAroundPoint = function(terrain, centerX, centerZ) {
    var centerHeight = terrain.landHeightAtCoordinates(centerX, centerZ);
    var pointCount = 0;
    var totalHeightDeltas = 0.0;
    var minX = Math.max(terrain.minMapX(), centerX - ((NEIGHBORHOOD_CENTER_WIDTH - 1) / 2));
    var maxX = Math.min(terrain.maxMapX(), centerX + ((NEIGHBORHOOD_CENTER_WIDTH - 1) / 2));
    var minZ = Math.max(terrain.minMapZ(), centerZ - ((NEIGHBORHOOD_CENTER_DEPTH - 1) / 2));
    var maxZ = Math.min(terrain.maxMapZ(), centerZ + ((NEIGHBORHOOD_CENTER_DEPTH - 1) / 2));
    var x, z;

    for (x = minX; x <= maxX; x++) {
      for (z = minZ; z <= maxZ; z++) {
        totalHeightDeltas += Math.abs(centerHeight - terrain.landHeightAtCoordinates(x, z));
        pointCount += 1;
      }
    }

    return totalHeightDeltas / pointCount;
  };

  var closestNeighborhoodDistance = function(neighborhoods, x, z) {
    var minDistanceToNeighborhood = Number.POSITIVE_INFINITY;
    var distanceToClosestNeighborhood;
    var i;

    if (neighborhoods.length === 0) {
      return 0;
    }

    for (i = 0; i < neighborhoods.length; i++) {
      distanceToClosestNeighborhood = CityTour.Math.distanceBetweenPoints(x, z, neighborhoods[i].centerX, neighborhoods[i].centerZ);
      if (distanceToClosestNeighborhood < minDistanceToNeighborhood) {
        minDistanceToNeighborhood = distanceToClosestNeighborhood;
      }
    }

    return minDistanceToNeighborhood;
  };

  var bestNeighborhoodSite = function(terrain, flatnessScores, neighborhoods) {
    var bestSiteCoordinates = { x: terrain.minMapX(), z: terrain.minMapZ() };
    var bestSiteScore = Number.POSITIVE_INFINITY;
    var score, centralityScore, flatnessScore;
    var distanceToClosestNeighborhood;
    var x, z;

    for (x = terrain.minMapX() + 1; x < terrain.maxMapX(); x++) {
      for (z = terrain.minMapX() + 1; z < terrain.maxMapZ(); z++) {
        centralityScore = Math.abs(x) + Math.abs(z);

        flatnessScore = flatnessScores[x][z];

        distanceToClosestNeighborhood = closestNeighborhoodDistance(neighborhoods, x, z);
        if (distanceToClosestNeighborhood < MIN_DISTANCE_BETWEEN_NEIGHBORHOODS) {
          distanceToClosestNeighborhood = 1000;
        }

        score = centralityScore + (flatnessScore * 10) + distanceToClosestNeighborhood;

        if (score < bestSiteScore) {
          bestSiteScore = score;
          bestSiteCoordinates.x = x;
          bestSiteCoordinates.z = z;
        }
      }
    }

    return bestSiteCoordinates;
  };

  var generate = function(terrain, count) {
    var flatnessScores = calculateFlatnessScores(terrain);
    var neighborhoods = [];
    var neighborhoodCenter;
    var i;

    for (i = 0; i < count; i++) {
      neighborhoodCenter = bestNeighborhoodSite(terrain, flatnessScores, neighborhoods);
      neighborhoods.push({ centerX: neighborhoodCenter.x, centerZ: neighborhoodCenter.z });
    }

    return neighborhoods;
  };

  return {
    generate: generate,
  };
})();
<commit_msg>Using correct coordinate<commit_after>"use strict";

var CityTour = CityTour || {};

CityTour.NeighborhoodGenerator = (function() {
  var MIN_DISTANCE_BETWEEN_NEIGHBORHOODS = 10;

  // The size of the square that is used to calculate the terrain flatness
  // around a given point. Each value should be an odd number.
  var NEIGHBORHOOD_CENTER_WIDTH = 9;
  var NEIGHBORHOOD_CENTER_DEPTH = 9;

  var calculateFlatnessScores = function(terrain) {
    var flatnessScores = [];
    var x, z;

    for (x = terrain.minMapX(); x <= terrain.maxMapX(); x++) {
      flatnessScores[x] = [];

      for (z = terrain.minMapZ(); z <= terrain.maxMapZ(); z++) {
        if (terrain.waterHeightAtCoordinates(x, z) > 0.0) {
          flatnessScores[x][z] = Number.POSITIVE_INFINITY;
        }
        else {
          flatnessScores[x][z] = averageHeightDifferenceAroundPoint(terrain, x, z);
        }
      }
    }

    return flatnessScores;
  };

  var averageHeightDifferenceAroundPoint = function(terrain, centerX, centerZ) {
    var centerHeight = terrain.landHeightAtCoordinates(centerX, centerZ);
    var pointCount = 0;
    var totalHeightDeltas = 0.0;
    var minX = Math.max(terrain.minMapX(), centerX - ((NEIGHBORHOOD_CENTER_WIDTH - 1) / 2));
    var maxX = Math.min(terrain.maxMapX(), centerX + ((NEIGHBORHOOD_CENTER_WIDTH - 1) / 2));
    var minZ = Math.max(terrain.minMapZ(), centerZ - ((NEIGHBORHOOD_CENTER_DEPTH - 1) / 2));
    var maxZ = Math.min(terrain.maxMapZ(), centerZ + ((NEIGHBORHOOD_CENTER_DEPTH - 1) / 2));
    var x, z;

    for (x = minX; x <= maxX; x++) {
      for (z = minZ; z <= maxZ; z++) {
        totalHeightDeltas += Math.abs(centerHeight - terrain.landHeightAtCoordinates(x, z));
        pointCount += 1;
      }
    }

    return totalHeightDeltas / pointCount;
  };

  var closestNeighborhoodDistance = function(neighborhoods, x, z) {
    var minDistanceToNeighborhood = Number.POSITIVE_INFINITY;
    var distanceToClosestNeighborhood;
    var i;

    if (neighborhoods.length === 0) {
      return 0;
    }

    for (i = 0; i < neighborhoods.length; i++) {
      distanceToClosestNeighborhood = CityTour.Math.distanceBetweenPoints(x, z, neighborhoods[i].centerX, neighborhoods[i].centerZ);
      if (distanceToClosestNeighborhood < minDistanceToNeighborhood) {
        minDistanceToNeighborhood = distanceToClosestNeighborhood;
      }
    }

    return minDistanceToNeighborhood;
  };

  var bestNeighborhoodSite = function(terrain, flatnessScores, neighborhoods) {
    var bestSiteCoordinates = { x: terrain.minMapX(), z: terrain.minMapZ() };
    var bestSiteScore = Number.POSITIVE_INFINITY;
    var score, centralityScore, flatnessScore;
    var distanceToClosestNeighborhood;
    var x, z;

    for (x = terrain.minMapX() + 1; x < terrain.maxMapX(); x++) {
      for (z = terrain.minMapZ() + 1; z < terrain.maxMapZ(); z++) {
        centralityScore = Math.abs(x) + Math.abs(z);

        flatnessScore = flatnessScores[x][z];

        distanceToClosestNeighborhood = closestNeighborhoodDistance(neighborhoods, x, z);
        if (distanceToClosestNeighborhood < MIN_DISTANCE_BETWEEN_NEIGHBORHOODS) {
          distanceToClosestNeighborhood = 1000;
        }

        score = centralityScore + (flatnessScore * 10) + distanceToClosestNeighborhood;

        if (score < bestSiteScore) {
          bestSiteScore = score;
          bestSiteCoordinates.x = x;
          bestSiteCoordinates.z = z;
        }
      }
    }

    return bestSiteCoordinates;
  };

  var generate = function(terrain, count) {
    var flatnessScores = calculateFlatnessScores(terrain);
    var neighborhoods = [];
    var neighborhoodCenter;
    var i;

    for (i = 0; i < count; i++) {
      neighborhoodCenter = bestNeighborhoodSite(terrain, flatnessScores, neighborhoods);
      neighborhoods.push({ centerX: neighborhoodCenter.x, centerZ: neighborhoodCenter.z });
    }

    return neighborhoods;
  };

  return {
    generate: generate,
  };
})();
<|endoftext|>


<commit_before>var debug = require('debug')('wol:leaderboard');

/* interpret packet and structure match object for easier use */
module.exports = function parse(game, match) {
    /* fail if we're somehow missing players */
    if (!match.players || match.players.length < 1) return;

    /* normalize nicks and remove useless entries */
    var spectators = [];
    match.players.forEach(function(player, index) {
        /* typically Computer */
        if (!player.nam) {
            player.nam = 'Computer'; // stylized (CaMeLcAsE)
            player.name = 'computer'; // computational (lowercase)
            return;
        }

        /* lowercase username for further reference */
        player.name = player.nam.toLowerCase();

        /* remove spectators */
        if (player.spc && player.spc > 0) {
            spectators.push(index);
            return;
        }
    });

    spectators.forEach(function(spectator) {
        match.players.splice(spectator, 1);
    });

    /* if we have ra stats, normalize packet then carry on  */
    if (game == 'ra' || game == 'am') {
        match = require(global.cwd + '/lib/games/lib/ra').normalize(match);
    }

    /* if we have td stats, normalize packet then carry on  */
    if (game == 'td') {
        match = require(global.cwd + '/lib/games/lib/td').normalize(match);
    }

    /* determine win/loss outcome for each player */
    match.players.forEach(function(player) {
        player.won = 0;
        player.loss = 0;
        player.discon = 0;

        /* shortcut: if player lost connection */
        if (player.lcn && player.lcn > 0) {
            player.discon = 1;
        }

        /* evaluate completions */
        if (player.cmp) {
            switch (player.cmp) {
                case 2:
                    player.loss = 1;
                    player.discon = 1;
                    break;

                case 256:
                    player.won = 1;
                    break;

                case 512: /* resigned */
                case 528: /* defeated */
                    player.loss = 1;
                    break;
            }
        }

        /* remove unused info */
        delete player.ipa; /* could be tunnel or p2p (unreliable) */
        delete player.adr; /* same reason but for ra1 */
        delete player.ser; /* serials are not used on CnCNet */
    });

    return match;
};
<commit_msg>fix: preserve non-spectators<commit_after>var debug = require('debug')('wol:leaderboard');

/* interpret packet and structure match object for easier use */
module.exports = function parse(game, match) {
    /* fail if we're somehow missing players */
    if (!match.players || match.players.length < 1) return;

    /* normalize nicks and remove useless entries */
    var players = [];
    match.players.forEach(function(player, index) {
        /* do nothing with spectators */
        if (player.spc && player.spc > 0) {
            return;
        }

        /* typically Computer */
        if (!player.nam) {
            player.nam = 'Computer'; // stylized (CaMeLcAsE)
        }

        /* lowercase username for further reference */
        player.name = player.nam.toLowerCase();

        /* keep sanitized player */
        players.push(player);
    });

    /* reassign players to keep */
    match.players = players;

    /* if we have ra stats, normalize packet then carry on  */
    if (game == 'ra' || game == 'am') {
        match = require(global.cwd + '/lib/games/lib/ra').normalize(match);
    }

    /* if we have td stats, normalize packet then carry on  */
    if (game == 'td') {
        match = require(global.cwd + '/lib/games/lib/td').normalize(match);
    }

    /* determine win/loss outcome for each player */
    match.players.forEach(function(player) {
        player.won = 0;
        player.loss = 0;
        player.discon = 0;

        /* shortcut: if player lost connection */
        if (player.lcn && player.lcn > 0) {
            player.discon = 1;
        }

        /* evaluate completions */
        if (player.cmp) {
            switch (player.cmp) {
                case 2:
                    player.loss = 1;
                    player.discon = 1;
                    break;

                case 256:
                    player.won = 1;
                    break;

                case 512: /* resigned */
                case 528: /* defeated */
                    player.loss = 1;
                    break;
            }
        }

        /* remove unused info */
        delete player.ipa; /* could be tunnel or p2p (unreliable) */
        delete player.adr; /* same reason but for ra1 */
        delete player.ser; /* serials are not used on CnCNet */
    });

    return match;
};
<|endoftext|>


<commit_before>var current_event = 0;

// hashCode and intToARGB borrowed from http://stackoverflow.com/a/3426956
function hashCode(str) { // java String#hashCode
    var hash = 0;
    for (var i = 0; i < str.length; i++) {
	hash = str.charCodeAt(i) + ((hash << 5) - hash);
    }
    return hash;
}

function zeroPad(num,count) {
    var numZeropad = num + '';
    while(numZeropad.length < count) {
	numZeropad = "0" + numZeropad;
    }
    return numZeropad;
}

// Colored nicks
function intToDark(i) {
    r = (i >> 24) & 0xff;
    g = (i >> 16) & 0xff;
    b = (i >> 8) & 0xff;
    if ((r*299 + g*587 + b*114) / 1000 < 140) {
	return zeroPad(r.toString(16), 2) + zeroPad(g.toString(16), 2) + zeroPad(b.toString(16), 2);
    }
    else {
	return intToDark(i * 42);
    }
}

// Wraped around any id string with possible special characters
function eid(myid) {
    return '#' + myid.replace(/([#:|.])/g, '\\$1');
}

function setSessions(val) {
    if (navigator.id) {
	navigator.id.sessions = val ? val : [ ];
    }
}

function loggedIn(email) {
    setSessions([ { email: email } ]);
    $('#new-server').modal('hide');
    $('#logout').bind('click', logout);
    $('#new-server').submit(addServer);
    listServers();
    $('#usermenu .dropdown-toggle').html(email + '');
    $('.dropdown-toggle').dropdown();
    $('.logged-out').hide();
    $('.logged-in').show();
    startPoll();
}

function loggedOut() {
    setSessions();
    $('#browserid').css('opacity', '1.0');
    $('.logged-in').hide();
    $('.logged-out').show();
}

function logout(event) {
    event.preventDefault();
    $.ajax({
	type: 'POST',
	url: '/api/logout',
	success: function() {
	    // and then redraw the UI.
	    loggedOut();
	}
    });
}

function hostnameId(hostname) {
    return hostname.replace(/\./g,'-');
}

function serverControls(hostname) {
    hostname_id = hostnameId(hostname);
    modal_id = hostname_id + '-channel-join';
    modal_url = '/api/networks/'+hostname;
    return '

Join a channel!

<form class="form-inline"><input type="text" class="input channel-name" placeholder="Channel" /> <input type="text" class="input channel-key" placeholder="Key" /></form>

Join

';
}

function handlePoll(data) {
    $.each(data, function(index, msg) {
	if (msg["kind"] == "privmsg" || msg["kind"] == "action") {
	    var nick = msg["args"][0].split('!')[0];
	    var lines = [[msg["event_id"], nick, msg["args"][2], msg["kind"]]];
	    var channel = msg["args"][1];

	    if (channel == msg["identity"]) {
		// Talking to ourselves... private messages
		newChannelMessages(lines, msg["network"], nick);
	    }
	    else {
		newChannelMessages(lines, msg["network"], channel);
	    }
	}
    });
}

function startPoll() {
    // Poll for events forever!
    (function poll(){
	$.ajax({ url: "/api/poll",
		 success: handlePoll,
		 dataType: "json",
		 complete: poll,
		 timeout: 5000
	       });
    })();
}

function channelURL(prefix, channel) {
    return prefix + '/' + encodeURIComponent(channel);
}

function channelMessagesURL(hostname, channel) {
    return channelURL('/api/networks/'+hostname, channel)+'/messages';
}

function channelInput(event) {
    event.preventDefault();
    $.ajax({
	type: 'POST',
	url: event.data.url,
	// Fix this - use real JSON encoding (why does jquery not do that?)
	data: '{"privmsg":"'+$(event.data.id).val()+'"}',
	dataType: 'json',
	contentType: 'application/json',
	success: function () {
	    $(event.data.id).val("");
	}
    });
}

function newChannelMessages(channel_messages, hostname, channel) {
    var hostname_id = hostnameId(hostname);
    var channel_id = hostname_id + '-' + channel;
    // Options passed with the form submit for channel input
    var options = {url:channelMessagesURL(hostname, channel),
		   id:eid(channel_id)+'-input'};

    // If we're just getting a message from somewhere new, add the pills
    if (!$(eid(channel_id)).length) {
	// Fixme: Will break with multiple servers
	$('.channels-nav')
	    .append('
'+channel+'
');
	$('#'+hostname_id).children('.tab-content')
	    .append('

');
	$(eid(channel_id)).append('<form><input class="channel-input" id="'+channel_id+'-input" name="'+channel+'" size="16" type="text" /></form>');
	$(eid(channel_id)).children('form').submit(options, channelInput);
    }

    var irc_body = $(eid(channel_id)+' table.irc-body');

    if ($(window).scrollTop() + $(window).height() == $(document).height()) {
	scrolled_down = true;
    }
    else {
	scrolled_down = false;
    }

    // Stick new message rows in the div
    $.each(channel_messages, function(index, val) {
	row = $(document.createElement('tr'));
	nick_col = $(document.createElement('td'));
	msg_col = $(document.createElement('td'));
	nick_col.addClass('nick');
	msg_col.addClass('privmsg');
	var url_exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
	msg_col.text(String(val[2]));
	msg_col.html(msg_col.html().replace(url_exp, '$1'));
	nick_text = String(val[1]);
	nick_color = "#"+intToDark(hashCode(nick_text));
	row.css("color", nick_color);
	nick_col.text(nick_text);
	if (val[3] == "action") {
	    msg_col.addClass('action');
	}
	row.append(nick_col);
	row.append(msg_col);
	irc_body.append(row);
    });

    // Scroll if new lines in the current channel and scrolled down
    if ($(eid(channel_id)).hasClass("active") && scrolled_down) {
	$('html, body').animate({scrollTop: $(document).height()}, 800);
    }
}

function refreshChannel(hostname, channel) {
    $.getJSON(channelMessagesURL(hostname, channel), function (channel_messages) {
	// Newest message before we reverse it
	current_event = Math.max(channel_messages[0], current_event);
	channel_messages.reverse();
	newChannelMessages(channel_messages, hostname, channel);
    });
}

function joinChannel(event) {
    event.preventDefault();
    var form = $(this).parent().parent()
	.children(".modal-body")
	.children(".form-inline");
    var channel_name = form.children(".channel-name").val();
    var channel_key = form.children(".channel-key").val();
    $.ajax({
	type: 'POST',
	url: channelURL($(this).attr('href'), channel_name),
	// Fix this - use real JSON encoding (why does jquery not do that?)
	data: '{"key":"'+channel_key+'"}',
	dataType: 'json',
	contentType: 'application/json',
	success: function () {
	    console.log("Joining "+channel_name+" with key "+channel_key);
	    $(".modal").modal('hide');
	}
    });
}

function listChannels(hostname) {
    var url = '/api/networks/'+hostname;
    var hostname_id = hostnameId(hostname);
    $('#'+hostname_id).append(serverControls(hostname));
    $('#'+hostname_id+'-channel-join .modal-footer a').click(joinChannel);
    $('#'+hostname_id+' .modal').modal('hide');
    $.getJSON(url, function (channel_list) {
	$.each(channel_list, function(index, val) {
	    refreshChannel(hostname, val);
	});
    });
}

function switchServerTab() {
    var hostname_id = '#'+this.href.split('#')[1];
    $('#servers-nav .active').removeClass('active');
    $(this).parent().addClass('active');
    $('#servers .active').removeClass('active');
    $('#servers').find(hostname_id).addClass('active');
}

function addServerTab(hostname) {
    var hostname_id = hostnameId(hostname);
    $('#servers-nav').append('
'+hostname+'
');
    $('#servers-nav a').click(switchServerTab);
    $('#servers').append('

');
    listChannels(hostname);
}

function listServers() {
    $("#server-list tbody tr").remove();
    $.getJSON('/api/networks', function (server_list) {
	$.each(server_list, function(index, val) {
	    var cols = new Array();
	    // Enabled server
	    if (val[0]) {
		cols[0] = '<input type="checkbox" class="server-disable" checked />	';
	    }
	    else {
		cols[0] = '<input type="checkbox" class="server-enable" />	';
	    }
	    cols[1] = ''+val[1]+'	';
	    cols[2] = ''+val[2]+'	';
	    // SSL configuration
	    if (val[3]) {
		// Remove disabled when this is implemented
		cols[3] = ' Enabled	';
	    }
	    else {
		cols[3] = ' Disabled	';
	    }
	    // Nick configured yet?
	    if (val[4]) {
		cols[4] = '<input type="text" class="disabled" value="'+val[4]+'" />	';
	    }
	    else {
		cols[4] = '<input type="text" class="disabled" />	';
	    }
	    // Server configuration line
	    $('#server-list > tbody:last').append('
'+cols.join()+'
');
	    addServerTab(val[1]);
	});
    });
}

function addServerClear() {
    $('#hostname').parent().parent().removeClass("error");
    $('#hostname').next('.help-inline').empty();
}

function addServer(event) {
    event.preventDefault();
    $.ajax({
	type: 'POST',
	url: '/api/networks',
	data: $(this).serialize(),
	dataType: 'json',
	success: function() {
	    //$('#new-server').modal('hide');
	    // Connect to a new server by default
	    serverSettings($('#hostname').val(), true, $('#nick').val());
	},
	error: function(xhr, status, error) {
	    if (xhr.status == 409) {
		$('#hostname').keypress(addServerClear);
	    }
	    $('#hostname').parent().parent().addClass("error");
	    var error = $.parseJSON(xhr.responseText);
	    $('#hostname').next('.help-inline').text(error);
	}
    });
}

function serverSettings(server, enabled, nick) {
    $.ajax({
	type: 'POST',
	url: '/api/networks/' + server,
	data: {'enabled':enabled, 'nick':nick},
	dataType: 'json',
	success: function() {
	    // Refresh the list on success
	    listServers();
	},
	error: function(res, status, xhr) {
	    alert("toggleServer failed: "+res);
	}
    });
}

function gotVerifiedEmail(assertion) {
    if (assertion !== null) {
	$.ajax({
	    type: 'POST',
	    url: '/api/login',
	    dataType: 'json',
	    data: { assertion: assertion },
	    success: function(res, status, xhr) {
		if (res === null) loggedOut();
		else loggedIn(res);
	    },
	    error: function(res, status, xhr) {
		loggedOut();
		alert("login failure" + res);
	    }
	});
    }
    else {
	loggedOut();
    }
}

$(document).ready(function() {
    if (!navigator.id) {
        $('#browserid').hide();
        return;
    }

    $.get('/api/whoami', function (res) {
	if (res === null) loggedOut();
	else loggedIn(res);
    }, 'json');

    $('#browserid').click(function() {
	$('#browserid').css('opacity', '0.4');
        navigator.id.get(gotVerifiedEmail);
    });

});
<commit_msg>Fix a multi-server display bug.<commit_after>var current_event = 0;

// hashCode and intToARGB borrowed from http://stackoverflow.com/a/3426956
function hashCode(str) { // java String#hashCode
    var hash = 0;
    for (var i = 0; i < str.length; i++) {
	hash = str.charCodeAt(i) + ((hash << 5) - hash);
    }
    return hash;
}

function zeroPad(num,count) {
    var numZeropad = num + '';
    while(numZeropad.length < count) {
	numZeropad = "0" + numZeropad;
    }
    return numZeropad;
}

// Colored nicks
function intToDark(i) {
    r = (i >> 24) & 0xff;
    g = (i >> 16) & 0xff;
    b = (i >> 8) & 0xff;
    if ((r*299 + g*587 + b*114) / 1000 < 140) {
	return zeroPad(r.toString(16), 2) + zeroPad(g.toString(16), 2) + zeroPad(b.toString(16), 2);
    }
    else {
	return intToDark(i * 42);
    }
}

// Wraped around any id string with possible special characters
function eid(myid) {
    return '#' + myid.replace(/([#:|.])/g, '\\$1');
}

function setSessions(val) {
    if (navigator.id) {
	navigator.id.sessions = val ? val : [ ];
    }
}

function loggedIn(email) {
    setSessions([ { email: email } ]);
    $('#new-server').modal('hide');
    $('#logout').bind('click', logout);
    $('#new-server').submit(addServer);
    listServers();
    $('#usermenu .dropdown-toggle').html(email + '');
    $('.dropdown-toggle').dropdown();
    $('.logged-out').hide();
    $('.logged-in').show();
    startPoll();
}

function loggedOut() {
    setSessions();
    $('#browserid').css('opacity', '1.0');
    $('.logged-in').hide();
    $('.logged-out').show();
}

function logout(event) {
    event.preventDefault();
    $.ajax({
	type: 'POST',
	url: '/api/logout',
	success: function() {
	    // and then redraw the UI.
	    loggedOut();
	}
    });
}

function hostnameId(hostname) {
    return hostname.replace(/\./g,'-');
}

function serverControls(hostname) {
    hostname_id = hostnameId(hostname);
    modal_id = hostname_id + '-channel-join';
    modal_url = '/api/networks/'+hostname;
    return '

Join a channel!

<form class="form-inline"><input type="text" class="input channel-name" placeholder="Channel" /> <input type="text" class="input channel-key" placeholder="Key" /></form>

Join

';
}

function handlePoll(data) {
    $.each(data, function(index, msg) {
	if (msg["kind"] == "privmsg" || msg["kind"] == "action") {
	    var nick = msg["args"][0].split('!')[0];
	    var lines = [[msg["event_id"], nick, msg["args"][2], msg["kind"]]];
	    var channel = msg["args"][1];

	    if (channel == msg["identity"]) {
		// Talking to ourselves... private messages
		newChannelMessages(lines, msg["network"], nick);
	    }
	    else {
		newChannelMessages(lines, msg["network"], channel);
	    }
	}
    });
}

function startPoll() {
    // Poll for events forever!
    (function poll(){
	$.ajax({ url: "/api/poll",
		 success: handlePoll,
		 dataType: "json",
		 complete: poll,
		 timeout: 5000
	       });
    })();
}

function channelURL(prefix, channel) {
    return prefix + '/' + encodeURIComponent(channel);
}

function channelMessagesURL(hostname, channel) {
    return channelURL('/api/networks/'+hostname, channel)+'/messages';
}

function channelInput(event) {
    event.preventDefault();
    $.ajax({
	type: 'POST',
	url: event.data.url,
	// Fix this - use real JSON encoding (why does jquery not do that?)
	data: '{"privmsg":"'+$(event.data.id).val()+'"}',
	dataType: 'json',
	contentType: 'application/json',
	success: function () {
	    $(event.data.id).val("");
	}
    });
}

function newChannelMessages(channel_messages, hostname, channel) {
    var hostname_id = hostnameId(hostname);
    var channel_id = hostname_id + '-' + channel;
    // Options passed with the form submit for channel input
    var options = {url:channelMessagesURL(hostname, channel),
		   id:eid(channel_id)+'-input'};

    // If we're just getting a message from somewhere new, add the pills
    if (!$(eid(channel_id)).length) {
	$('#'+hostname_id).find('.channels-nav')
	    .append('
'+channel+'
');
	$('#'+hostname_id).children('.tab-content')
	    .append('

');
	$(eid(channel_id)).append('<form><input class="channel-input" id="'+channel_id+'-input" name="'+channel+'" size="16" type="text" /></form>');
	$(eid(channel_id)).children('form').submit(options, channelInput);
    }

    var irc_body = $(eid(channel_id)+' table.irc-body');

    if ($(window).scrollTop() + $(window).height() == $(document).height()) {
	scrolled_down = true;
    }
    else {
	scrolled_down = false;
    }

    // Stick new message rows in the div
    $.each(channel_messages, function(index, val) {
	row = $(document.createElement('tr'));
	nick_col = $(document.createElement('td'));
	msg_col = $(document.createElement('td'));
	nick_col.addClass('nick');
	msg_col.addClass('privmsg');
	var url_exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
	msg_col.text(String(val[2]));
	msg_col.html(msg_col.html().replace(url_exp, '$1'));
	nick_text = String(val[1]);
	nick_color = "#"+intToDark(hashCode(nick_text));
	row.css("color", nick_color);
	nick_col.text(nick_text);
	if (val[3] == "action") {
	    msg_col.addClass('action');
	}
	row.append(nick_col);
	row.append(msg_col);
	irc_body.append(row);
    });

    // Scroll if new lines in the current channel and scrolled down
    if ($(eid(channel_id)).hasClass("active") && scrolled_down) {
	$('html, body').animate({scrollTop: $(document).height()}, 800);
    }
}

function refreshChannel(hostname, channel) {
    $.getJSON(channelMessagesURL(hostname, channel), function (channel_messages) {
	// Newest message before we reverse it
	current_event = Math.max(channel_messages[0], current_event);
	channel_messages.reverse();
	newChannelMessages(channel_messages, hostname, channel);
    });
}

function joinChannel(event) {
    event.preventDefault();
    var form = $(this).parent().parent()
	.children(".modal-body")
	.children(".form-inline");
    var channel_name = form.children(".channel-name").val();
    var channel_key = form.children(".channel-key").val();
    $.ajax({
	type: 'POST',
	url: channelURL($(this).attr('href'), channel_name),
	// Fix this - use real JSON encoding (why does jquery not do that?)
	data: '{"key":"'+channel_key+'"}',
	dataType: 'json',
	contentType: 'application/json',
	success: function () {
	    console.log("Joining "+channel_name+" with key "+channel_key);
	    $(".modal").modal('hide');
	}
    });
}

function listChannels(hostname) {
    var url = '/api/networks/'+hostname;
    var hostname_id = hostnameId(hostname);
    $('#'+hostname_id).append(serverControls(hostname));
    $('#'+hostname_id+'-channel-join .modal-footer a').click(joinChannel);
    $('#'+hostname_id+' .modal').modal('hide');
    $.getJSON(url, function (channel_list) {
	$.each(channel_list, function(index, val) {
	    refreshChannel(hostname, val);
	});
    });
}

function switchServerTab() {
    var hostname_id = '#'+this.href.split('#')[1];
    $('#servers-nav .active').removeClass('active');
    $(this).parent().addClass('active');
    $('#servers .active').removeClass('active');
    $('#servers').find(hostname_id).addClass('active');
}

function addServerTab(hostname) {
    var hostname_id = hostnameId(hostname);
    $('#servers-nav').append('
'+hostname+'
');
    $('#servers-nav a').click(switchServerTab);
    $('#servers').append('

');
    listChannels(hostname);
}

function listServers() {
    $("#server-list tbody tr").remove();
    $.getJSON('/api/networks', function (server_list) {
	$.each(server_list, function(index, val) {
	    var cols = new Array();
	    // Enabled server
	    if (val[0]) {
		cols[0] = '<input type="checkbox" class="server-disable" checked />	';
	    }
	    else {
		cols[0] = '<input type="checkbox" class="server-enable" />	';
	    }
	    cols[1] = ''+val[1]+'	';
	    cols[2] = ''+val[2]+'	';
	    // SSL configuration
	    if (val[3]) {
		// Remove disabled when this is implemented
		cols[3] = ' Enabled	';
	    }
	    else {
		cols[3] = ' Disabled	';
	    }
	    // Nick configured yet?
	    if (val[4]) {
		cols[4] = '<input type="text" class="disabled" value="'+val[4]+'" />	';
	    }
	    else {
		cols[4] = '<input type="text" class="disabled" />	';
	    }
	    // Server configuration line
	    $('#server-list > tbody:last').append('
'+cols.join()+'
');
	    addServerTab(val[1]);
	});
    });
}

function addServerClear() {
    $('#hostname').parent().parent().removeClass("error");
    $('#hostname').next('.help-inline').empty();
}

function addServer(event) {
    event.preventDefault();
    $.ajax({
	type: 'POST',
	url: '/api/networks',
	data: $(this).serialize(),
	dataType: 'json',
	success: function() {
	    //$('#new-server').modal('hide');
	    // Connect to a new server by default
	    serverSettings($('#hostname').val(), true, $('#nick').val());
	},
	error: function(xhr, status, error) {
	    if (xhr.status == 409) {
		$('#hostname').keypress(addServerClear);
	    }
	    $('#hostname').parent().parent().addClass("error");
	    var error = $.parseJSON(xhr.responseText);
	    $('#hostname').next('.help-inline').text(error);
	}
    });
}

function serverSettings(server, enabled, nick) {
    $.ajax({
	type: 'POST',
	url: '/api/networks/' + server,
	data: {'enabled':enabled, 'nick':nick},
	dataType: 'json',
	success: function() {
	    // Refresh the list on success
	    listServers();
	},
	error: function(res, status, xhr) {
	    alert("toggleServer failed: "+res);
	}
    });
}

function gotVerifiedEmail(assertion) {
    if (assertion !== null) {
	$.ajax({
	    type: 'POST',
	    url: '/api/login',
	    dataType: 'json',
	    data: { assertion: assertion },
	    success: function(res, status, xhr) {
		if (res === null) loggedOut();
		else loggedIn(res);
	    },
	    error: function(res, status, xhr) {
		loggedOut();
		alert("login failure" + res);
	    }
	});
    }
    else {
	loggedOut();
    }
}

$(document).ready(function() {
    if (!navigator.id) {
        $('#browserid').hide();
        return;
    }

    $.get('/api/whoami', function (res) {
	if (res === null) loggedOut();
	else loggedIn(res);
    }, 'json');

    $('#browserid').click(function() {
	$('#browserid').css('opacity', '0.4');
        navigator.id.get(gotVerifiedEmail);
    });

});
<|endoftext|>


<commit_before>var tty = require('tty')
var diff = require('diff')
var ms = require('ms')
var utils = require('../utils')
var supportsColor = process.browser ? null : require('supports-color')

exports = module.exports = Base

/**
 * Save timer references to avoid Sinon interfering.
 * See: https://github.com/mochajs/mocha/issues/237
 */

/* eslint-disable no-unused-vars, no-native-reassign */
var Date = global.Date
var setTimeout = global.setTimeout
var setInterval = global.setInterval
var clearTimeout = global.clearTimeout
var clearInterval = global.clearInterval
/* eslint-enable no-unused-vars, no-native-reassign */

/**
 * Check if both stdio streams are associated with a tty.
 */
var isatty = tty.isatty(1) && tty.isatty(2)

/**
 * Enable coloring by default, except in the browser interface.
 */
exports.useColors = !process.browser && (supportsColor || (process.env.MOCHA_COLORS !== undefined))

/**
 * Inline diffs instead of +/-
 */
exports.inlineDiffs = false

/**
 * Default color map.
 */
exports.colors = {
  pass: 90,
  fail: 31,
  'bright pass': 92,
  'bright fail': 91,
  'bright yellow': 93,
  pending: 36,
  suite: 0,
  'error title': 0,
  'error message': 31,
  'error stack': 90,
  checkmark: 32,
  fast: 90,
  medium: 33,
  slow: 31,
  green: 32,
  light: 90,
  'diff gutter': 90,
  'diff added': 32,
  'diff removed': 31
}

/**
 * Default symbol map.
 */
exports.symbols = {
  ok: '✓',
  err: '✖',
  dot: '․',
  comma: ',',
  bang: '!'
}

// With node.js on Windows: use symbols available in terminal default fonts
if (process.platform === 'win32') {
  exports.symbols.ok = '\u221A'
  exports.symbols.err = '\u00D7'
  exports.symbols.dot = '.'
}

/**
 * Color `str` with the given `type`,
 * allowing colors to be disabled,
 * as well as user-defined color
 * schemes.
 *
 * @param {string} type
 * @param {string} str
 * @return {string}
 * @api private
 */
var color = exports.color = function (type, str) {
  if (!exports.useColors) {
    return String(str)
  }
  return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'
}

/**
 * Expose term window size, with some defaults for when stderr is not a tty.
 */
exports.window = {
  width: 75
}

if (isatty) {
  exports.window.width = process.stdout.getWindowSize
      ? process.stdout.getWindowSize(1)[0]
      : tty.getWindowSize()[1]
}

/**
 * Expose some basic cursor interactions that are common among reporters.
 */
exports.cursor = {
  hide: function () {
    isatty && process.stdout.write('\u001b[?25l')
  },

  show: function () {
    isatty && process.stdout.write('\u001b[?25h')
  },

  deleteLine: function () {
    isatty && process.stdout.write('\u001b[2K')
  },

  beginningOfLine: function () {
    isatty && process.stdout.write('\u001b[0G')
  },

  CR: function () {
    if (isatty) {
      exports.cursor.deleteLine()
      exports.cursor.beginningOfLine()
    } else {
      process.stdout.write('\r')
    }
  }
}

/**
 * Outut the given `failures` as a list.
 *
 * @param {Array} failures
 * @api public
 */
exports.list = function (failures) {
  console.log()
  failures.forEach(function (test, i) {
    // format
    var fmt = color('error title', '  %s) %s:\n') +
      color('error message', '     %s') +
      color('error stack', '\n%s\n')

    // msg
    var msg
    var err = test.err
    var message
    if (err.message && typeof err.message.toString === 'function') {
      message = err.message + ''
    } else if (typeof err.inspect === 'function') {
      message = err.inspect() + ''
    } else {
      message = ''
    }
    var stack = err.stack || message
    var index = message ? stack.indexOf(message) : -1
    var actual = err.actual
    var expected = err.expected
    var escape = true

    if (index === -1) {
      msg = message
    } else {
      index += message.length
      msg = stack.slice(0, index)
      // remove msg from stack
      stack = stack.slice(index + 1)
    }

    // uncaught
    if (err.uncaught) {
      msg = 'Uncaught ' + msg
    }
    // explicitly show diff
    if (err.showDiff !== false && sameType(actual, expected) && expected !== undefined) {
      escape = false
      if (!(typeof actual === 'string' && typeof expected === 'string')) {
        err.actual = actual = utils.stringify(actual)
        err.expected = expected = utils.stringify(expected)
      }

      fmt = color('error title', '  %s) %s:\n%s') + color('error stack', '\n%s\n')
      var match = message.match(/^([^:]+): expected/)
      msg = '\n      ' + color('error message', match ? match[1] : msg)

      if (exports.inlineDiffs) {
        msg += inlineDiff(err, escape)
      } else {
        msg += unifiedDiff(err, escape)
      }
    }

    // indent stack trace
    stack = stack.replace(/^/gm, '  ')

    console.log(fmt, (i + 1), test.fullTitle(), msg, stack)
  })
}

/**
 * Initialize a new `Base` reporter.
 *
 * All other reporters generally
 * inherit from this reporter, providing
 * stats such as test duration, number
 * of tests passed / failed etc.
 *
 * @param {Runner} runner
 * @api public
 */
function Base (runner) {
  var stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }
  var failures = this.failures = []

  if (!runner) {
    return
  }
  this.runner = runner

  runner.stats = stats

  runner.on('start', function () {
    stats.start = new Date()
  })

  runner.on('suite', function (suite) {
    stats.suites = stats.suites || 0
    suite.root || stats.suites++
  })

  runner.on('test end', function () {
    stats.tests = stats.tests || 0
    stats.tests++
  })

  runner.on('pass', function (test) {
    stats.passes = stats.passes || 0

    if (test.duration > test.slow()) {
      test.speed = 'slow'
    } else if (test.duration > test.slow() / 2) {
      test.speed = 'medium'
    } else {
      test.speed = 'fast'
    }

    stats.passes++
  })

  runner.on('fail', function (test, err) {
    stats.failures = stats.failures || 0
    stats.failures++
    test.err = err
    failures.push(test)
  })

  runner.on('end', function () {
    stats.end = new Date()
    stats.duration = new Date() - stats.start
  })

  runner.on('pending', function () {
    stats.pending++
  })
}

/**
 * Output common epilogue used by many of
 * the bundled reporters.
 *
 * @api public
 */
Base.prototype.epilogue = function () {
  var stats = this.stats
  var fmt

  console.log()

  // passes
  fmt = color('bright pass', ' ') +
    color('green', ' %d passing') +
    color('light', ' (%s)')

  console.log(fmt,
    stats.passes || 0,
    ms(stats.duration))

  // pending
  if (stats.pending) {
    fmt = color('pending', ' ') +
      color('pending', ' %d pending')

    console.log(fmt, stats.pending)
  }

  // failures
  if (stats.failures) {
    fmt = color('fail', '  %d failing')

    console.log(fmt, stats.failures)

    Base.list(this.failures)
    console.log()
  }

  console.log()
}

/**
 * Pad the given `str` to `len`.
 *
 * @api private
 * @param {string} str
 * @param {string} len
 * @return {string}
 */
function pad (str, len) {
  str = String(str)
  return Array(len - str.length + 1).join(' ') + str
}

/**
 * Returns an inline diff between 2 strings with coloured ANSI output
 *
 * @api private
 * @param {Error} err with actual/expected
 * @param {boolean} escape
 * @return {string} Diff
 */
function inlineDiff (err, escape) {
  var msg = errorDiff(err, 'WordsWithSpace', escape)

  // linenos
  var lines = msg.split('\n')
  if (lines.length > 4) {
    var width = String(lines.length).length
    msg = lines.map(function (str, i) {
      return pad(++i, width) + ' |' + ' ' + str
    }).join('\n')
  }

  // legend
  msg = '\n' +
    color('diff removed', 'actual') +
    ' ' +
    color('diff added', 'expected') +
    '\n\n' +
    msg +
    '\n'

  // indent
  msg = msg.replace(/^/gm, '      ')
  return msg
}

/**
 * Returns a unified diff between two strings.
 *
 * @api private
 * @param {Error} err with actual/expected
 * @param {boolean} escape
 * @return {string} The diff.
 */
function unifiedDiff (err, escape) {
  var indent = '      '
  function cleanUp (line) {
    if (escape) {
      line = escapeInvisibles(line)
    }
    if (line[0] === '+') {
      return indent + colorLines('diff added', line)
    }
    if (line[0] === '-') {
      return indent + colorLines('diff removed', line)
    }
    if (line.match(/@@/)) {
      return null
    }
    if (line.match(/\\ No newline/)) {
      return null
    }
    return indent + line
  }
  function notBlank (line) {
    return typeof line !== 'undefined' && line !== null
  }
  var msg = diff.createPatch('string', err.actual, err.expected)
  var lines = msg.split('\n').splice(4)
  return '\n      ' +
    colorLines('diff added', '+ expected') + ' ' +
    colorLines('diff removed', '- actual') +
    '\n\n' +
    lines.map(cleanUp).filter(notBlank).join('\n')
}

/**
 * Return a character diff for `err`.
 *
 * @api private
 * @param {Error} err
 * @param {string} type
 * @param {boolean} escape
 * @return {string}
 */
function errorDiff (err, type, escape) {
  var actual = escape ? escapeInvisibles(err.actual) : err.actual
  var expected = escape ? escapeInvisibles(err.expected) : err.expected
  return diff['diff' + type](actual, expected).map(function (str) {
    if (str.added) {
      return colorLines('diff added', str.value)
    }
    if (str.removed) {
      return colorLines('diff removed', str.value)
    }
    return str.value
  }).join('')
}

/**
 * Returns a string with all invisible characters in plain text
 *
 * @api private
 * @param {string} line
 * @return {string}
 */
function escapeInvisibles (line) {
  return line.replace(/\t/g, '<tab>')
    .replace(/\r/g, '<CR>')
    .replace(/\n/g, '<LF>\n')
}

/**
 * Color lines for `str`, using the color `name`.
 *
 * @api private
 * @param {string} name
 * @param {string} str
 * @return {string}
 */
function colorLines (name, str) {
  return str.split('\n').map(function (str) {
    return color(name, str)
  }).join('\n')
}

/**
 * Object#toString reference.
 */
var objToString = Object.prototype.toString

/**
 * Check that a / b have the same type.
 *
 * @api private
 * @param {Object} a
 * @param {Object} b
 * @return {boolean}
 */
function sameType (a, b) {
  return objToString.call(a) === objToString.call(b)
}
<commit_msg>refactor: clean up base reporter<commit_after>const tty = require('tty')
const diff = require('diff')
const ms = require('ms')
const utils = require('../utils')
const supportsColor = process.browser ? null : require('supports-color')

exports = module.exports = Base

/**
 * Save timer references to avoid Sinon interfering.
 * See: https://github.com/mochajs/mocha/issues/237
 */
/* eslint-disable no-unused-vars, no-native-reassign */
const Date = global.Date
const setTimeout = global.setTimeout
const setInterval = global.setInterval
const clearTimeout = global.clearTimeout
const clearInterval = global.clearInterval
/* eslint-enable no-unused-vars, no-native-reassign */

/**
 * Check if both stdio streams are associated with a tty.
 */
const isatty = tty.isatty(1) && tty.isatty(2)

/**
 * Enable coloring by default, except in the browser interface.
 */
exports.useColors = !process.browser && (supportsColor || (process.env.MOCHA_COLORS !== undefined))

/**
 * Default color map.
 */
exports.colors = {
  pass: 90,
  fail: 31,
  'bright pass': 92,
  'bright fail': 91,
  'bright yellow': 93,
  pending: 36,
  suite: 0,
  'error title': 0,
  'error message': 31,
  'error stack': 90,
  checkmark: 32,
  fast: 90,
  medium: 33,
  slow: 31,
  green: 32,
  light: 90,
  'diff gutter': 90,
  'diff added': 32,
  'diff removed': 31
}

/**
 * Default symbol map.
 */
exports.symbols = {
  ok: '✓',
  err: '✖',
  dot: '․',
  comma: ',',
  bang: '!'
}

// With node.js on Windows: use symbols available in terminal default fonts
if (process.platform === 'win32') {
  exports.symbols.ok = '\u221A'
  exports.symbols.err = '\u00D7'
  exports.symbols.dot = '.'
}

/**
 * Color `str` with the given `type`,
 * allowing colors to be disabled,
 * as well as user-defined color
 * schemes.
 *
 * @param {string} type
 * @param {string} str
 * @return {string}
 * @api private
 */
const color = exports.color = function (type, str) {
  if (!exports.useColors) {
    return String(str)
  }
  return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'
}

/**
 * Expose term window size, with some defaults for when stderr is not a tty.
 */
exports.window = {
  width: 75
}

if (isatty) {
  exports.window.width = process.stdout.getWindowSize
      ? process.stdout.getWindowSize(1)[0]
      : tty.getWindowSize()[1]
}

/**
 * Expose some basic cursor interactions that are common among reporters.
 */
exports.cursor = {
  hide: function () {
    isatty && process.stdout.write('\u001b[?25l')
  },

  show: function () {
    isatty && process.stdout.write('\u001b[?25h')
  },

  deleteLine: function () {
    isatty && process.stdout.write('\u001b[2K')
  },

  beginningOfLine: function () {
    isatty && process.stdout.write('\u001b[0G')
  },

  CR: function () {
    if (isatty) {
      exports.cursor.deleteLine()
      exports.cursor.beginningOfLine()
    } else {
      process.stdout.write('\r')
    }
  }
}

/**
 * Outut the given `failures` as a list.
 * @api public
 * @param {Array} failures
 */
exports.list = function (failures) {
  console.log()
  failures.forEach(function (test, i) {
    // format
    var fmt = color('error title', '  %s) %s:\n') +
      color('error message', '     %s') +
      color('error stack', '\n%s\n')

    // msg
    var msg
    var err = test.err
    let message
    if (err.message && typeof err.message.toString === 'function') {
      message = err.message + ''
    } else if (typeof err.inspect === 'function') {
      message = err.inspect() + ''
    } else {
      message = ''
    }
    let stack = err.stack || message
    let index = message ? stack.indexOf(message) : -1
    let actual = err.actual
    let expected = err.expected
    let escape = true

    if (index === -1) {
      msg = message
    } else {
      index += message.length
      msg = stack.slice(0, index)
      // remove msg from stack
      stack = stack.slice(index + 1)
    }

    // uncaught
    if (err.uncaught) {
      msg = 'Uncaught ' + msg
    }
    // explicitly show diff
    if (err.showDiff !== false && sameType(actual, expected) && expected !== undefined) {
      escape = false
      if (!(typeof actual === 'string' && typeof expected === 'string')) {
        err.actual = actual = utils.stringify(actual)
        err.expected = expected = utils.stringify(expected)
      }

      fmt = color('error title', '  %s) %s:\n%s') + color('error stack', '\n%s\n')
      const match = message.match(/^([^:]+): expected/)
      msg = '\n      ' + color('error message', match ? match[1] : msg)

      msg += unifiedDiff(err, escape)
    }

    // indent stack trace
    stack = stack.replace(/^/gm, '  ')

    console.log(fmt, (i + 1), test.fullTitle(), msg, stack)
  })
}

/**
 * Initialize a new `Base` reporter.
 *
 * All other reporters generally
 * inherit from this reporter, providing
 * stats such as test duration, number
 * of tests passed / failed etc.
 *
 * @param {Runner} runner
 * @api public
 */
function Base (runner) {
  const stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }
  const failures = this.failures = []

  if (!runner) {
    return
  }
  this.runner = runner

  runner.stats = stats

  runner.on('start', function () {
    stats.start = new Date()
  })

  runner.on('suite', function (suite) {
    stats.suites = stats.suites || 0
    suite.root || stats.suites++
  })

  runner.on('test end', function () {
    stats.tests = stats.tests || 0
    stats.tests++
  })

  runner.on('pass', function (test) {
    stats.passes = stats.passes || 0

    if (test.duration > test.slow()) {
      test.speed = 'slow'
    } else if (test.duration > test.slow() / 2) {
      test.speed = 'medium'
    } else {
      test.speed = 'fast'
    }

    stats.passes++
  })

  runner.on('fail', function (test, err) {
    stats.failures = stats.failures || 0
    stats.failures++
    test.err = err
    failures.push(test)
  })

  runner.on('end', function () {
    stats.end = new Date()
    stats.duration = new Date() - stats.start
  })

  runner.on('pending', function () {
    stats.pending++
  })
}

/**
 * Output common epilogue used by many of
 * the bundled reporters.
 *
 * @api public
 */
Base.prototype.epilogue = function () {
  const stats = this.stats
  let fmt

  console.log()

  fmt = color('bright pass', ' ') +
    color('green', ' %d passing') +
    color('light', ' (%s)')

  console.log(fmt,
    stats.passes || 0,
    ms(stats.duration))

  if (stats.pending > 0) {
    fmt = color('pending', ' ') +
      color('pending', ' %d pending')

    console.log(fmt, stats.pending)
  }

  if (stats.failures > 0) {
    fmt = color('fail', '  %d failing')

    console.log(fmt, stats.failures)

    Base.list(this.failures)
    console.log()
  }

  console.log()
}

/**
 * Returns a unified diff between two strings.
 *
 * @api private
 * @param {Error} err with actual/expected
 * @param {boolean} escape
 * @return {string} The diff.
 */
function unifiedDiff (err, escape) {
  const indent = '      '
  function cleanUp (line) {
    if (escape) {
      line = escapeInvisibles(line)
    }
    if (line[0] === '+') {
      return indent + colorLines('diff added', line)
    }
    if (line[0] === '-') {
      return indent + colorLines('diff removed', line)
    }
    if (line.match(/@@/)) {
      return null
    }
    if (line.match(/\\ No newline/)) {
      return null
    }
    return indent + line
  }
  function notBlank (line) {
    return typeof line !== 'undefined' && line !== null
  }
  const msg = diff.createPatch('string', err.actual, err.expected)
  const lines = msg.split('\n').splice(4)
  return '\n      ' +
    colorLines('diff added', '+ expected') + ' ' +
    colorLines('diff removed', '- actual') +
    '\n\n' +
    lines.map(cleanUp).filter(notBlank).join('\n')
}

/**
 * Returns a string with all invisible characters in plain text
 *
 * @api private
 * @param {string} line
 * @return {string}
 */
const escapeInvisibles = line => line
  .replace(/\t/g, '<tab>')
  .replace(/\r/g, '<CR>')
  .replace(/\n/g, '<LF>\n')

/**
 * Color lines for `str`, using the color `name`.
 * @api private
 * @param {string} name
 * @param {string} str
 * @return {string}
 */
const colorLines = (name, str) => str.split('\n').map(str => color(name, str)).join('\n')

/**
 * Object#toString reference.
 */
const objToString = Object.prototype.toString

/**
 * Check that a / b have the same type.
 *
 * @api private
 * @param {Object} a
 * @param {Object} b
 * @return {boolean}
 */
const sameType = (a, b) => objToString.call(a) === objToString.call(b)
<|endoftext|>


<commit_before>var _ = require('underscore');
var torque = require('torque.js');
var Backbone = require('backbone');
var GMapsLayerView = require('./gmaps-layer-view');
var TorqueLayerViewBase = require('../torque-layer-view-base');

var GMapsTorqueLayerView = function (layerModel, gmapsMap) {
  GMapsLayerView.call(this, layerModel, this, gmapsMap);

  torque.GMapsTorqueLayer.call(this, this._initialAttrs(layerModel));

  this._init(layerModel); // available due to this model being extended with torque-layer-base
};

_.extend(
  GMapsTorqueLayerView.prototype,
  GMapsLayerView.prototype,
  torque.GMapsTorqueLayer.prototype,
  TorqueLayerViewBase,
  {
    _update: function () {
      var changed = this.model.changedAttributes();
      if (changed === false) return;
      changed.tile_style && this.setCartoCSS(this.model.get('tile_style'));
      if ('query' in changed || 'query_wrapper' in changed) {
        this.setSQL(this._getQuery(this.model));
      }
      if ('visible' in changed) {
        this.model.get('visible') ? this.show() : this.hide();
      }
    },

    refreshView: function () {
      // TODO: update screen
    },

    onAdd: function () {
      torque.GMapsTorqueLayer.prototype.onAdd.apply(this);
    },

    onTilesLoaded: function () {
      // this.trigger('load');
      Backbone.Events.trigger.call(this, 'load');
    },

    onTilesLoading: function () {
      Backbone.Events.trigger.call(this, 'loading');
    }

  });

module.exports = GMapsTorqueLayerView;
<commit_msg>fixed google maps<commit_after>var _ = require('underscore');
var torque = require('torque.js');
var Backbone = require('backbone');
var GMapsLayerView = require('./gmaps-layer-view');
var TorqueLayerViewBase = require('../torque-layer-view-base');

var GMapsTorqueLayerView = function (layerModel, gmapsMap) {
  GMapsLayerView.call(this, layerModel, this, gmapsMap);

  torque.GMapsTorqueLayer.call(this, this._initialAttrs(layerModel));

  this._init(layerModel); // available due to this model being extended with torque-layer-base
};

_.extend(
  GMapsTorqueLayerView.prototype,
  GMapsLayerView.prototype,
  torque.GMapsTorqueLayer.prototype,
  TorqueLayerViewBase,
  {
    _update: function () {
      var changed = this.model.changedAttributes();
      if (changed === false) return;

      if ('visible' in changed) {
        this.model.get('visible') ? this.show() : this.hide();
      }

      if ('urls' in changed) {
        // REAL HACK
        this.provider.templateUrl = this.model.get('urls').tiles[0];
        // set meta
        _.extend(this.provider.options, this.model.get('meta'));
        this.model.set(this.model.get('meta'));
        // this needs to be deferred in order to break the infinite loop
        // of setReady changing keys and keys updating the model
        // If we do this in the next iteration 'urls' will not be in changedAttributes
        // so this will not pass through this code
        setTimeout(function () {
          this.provider._setReady(true);
          this._reloadTiles();
        }.bind(this), 0);
      }
    },

    refreshView: function () {
      // TODO: update screen
    },

    onAdd: function () {
      torque.GMapsTorqueLayer.prototype.onAdd.apply(this);
    },

    onTilesLoaded: function () {
      // this.trigger('load');
      Backbone.Events.trigger.call(this, 'load');
    },

    onTilesLoading: function () {
      Backbone.Events.trigger.call(this, 'loading');
    }

  });

module.exports = GMapsTorqueLayerView;
<|endoftext|>