<commit_before>var coverRoot = apiRoot + "/cover";
var libraryRoot = apiRoot + "/library";
var librarySpinner;
var libraryWrapper;
var libraryTabs;

function updateLibrary() {
    showSpinner();
    var xhr = new XMLHttpRequest();
    xhr.open("GET", libraryRoot, true);
    xhr.onload = function() {
        try {
            var parsed = JSON.parse(xhr.responseText);
            updateLibraryUI(parsed);
        }
        catch (e) {
            libraryUpdateError();
        }
        hideSpinner();
    };
    xhr.onerror = function() {
        libraryUpdateError();
        hideSpinner();
    };
    xhr.send();
}

function showSpinner() {
    librarySpinner.css("opacity", 1);
}

function hideSpinner() {
    librarySpinner.css("opacity", 0);
}

function buildCoverUrl(mangaId) {
    return coverRoot + "/" + mangaId;
}

function showTabs() {

}

function hideTabs() {

}

function updateLibraryUI(mangas) {
    //Construct categories
    var categories = {};
    for (var i = 0; i < mangas.length; i++) {
        var manga = mangas[i];
        for (var a = 0; a < manga.categories.length; a++) {
            var categoryName = manga.categories[a];
            var category = categories[categoryName];
            if (!valid(category)) {
                category = [];
                categories[categoryName] = category;
            }
            category.push(manga);
        }
    }
    //Remove old entries
    clearElement(libraryWrapper[0]);
    clearElement(libraryTabs[0]);
    var categoryKeys = Object.keys(categories);
    if (categoryKeys.length <= 1) {
        //Append directly if we don't have tabs
        appendMangas(mangas, libraryWrapper[0]);
    } else {
        for(i = 0; i < categoryKeys.length; i++) {
            categoryName = categoryKeys[i];
            category = categories[categoryName];
            var categoryId = "category-tab-" + i;
            //Generate tabs
            var categoryTab = document.createElement("a");
            categoryTab.className = "mdl-layout__tab";
            //First tab is active
            if(i === 0) {
                categoryTab.className += " is-active";
            }
            categoryTab.href = "#" + categoryId;
            categoryTab.textContent = categoryName;
            libraryTabs[0].appendChild(categoryTab);
            //Generate content
            var categoryPanel = document.createElement("div");
            categoryPanel.className = "mdl-tabs__panel";
            //First panel is active
            if(i === 0) {
                categoryPanel.className += " is-active";
            }
            categoryPanel.id = categoryId;
            libraryWrapper[0].appendChild(categoryPanel);
            //Actually append mangas
            appendMangas(category, categoryPanel);
        }
        //Make sure MDL gets tab changes
        componentHandler.upgradeElement(libraryTabs[0]);
    }
    //Make sure MDL gets content changes
    componentHandler.upgradeElement(libraryWrapper[0]);
}

function appendMangas(mangas, rootElement) {
    var libraryGrid = createGrid();
    rootElement.appendChild(libraryGrid);
    for (var i = 0; i < mangas.length; i++) {
        appendManga(mangas[i], libraryGrid);
    }
}

function createGrid() {
    var grid = document.createElement("div");
    grid.className = "mdl-grid";
    return grid;
}

function appendManga(manga, element) {
    var card = document.createElement("div");
    card.className = "mdl-card mdl-shadow--4dp manga_card mdl-cell mdl-cell--2-col mdl-button mdl-js-button mdl-js-ripple-effect";
    var img = document.createElement("img");
    img.className = "manga_card_img";
    img.src = buildCoverUrl(manga.id);
    img.alt = manga.title;
    card.appendChild(img);
    var label = document.createElement("div");
    label.className = "manga_card_label";
    label.textContent = manga.title;
    card.appendChild(label);
    element.appendChild(card);
}

function libraryUpdateError() {
    snackbar.showSnackbar({
        message: "Error getting library!",
        timeout: 2000,
        actionText: "Retry",
        actionHandler: function() {
            updateLibrary();
        }
    });
}

function onLoad() {
    librarySpinner = $(".loading_spinner");
    libraryWrapper = $("#library_wrapper");
    libraryTabs = $("#library_tabs");
    updateLibrary();
}
<commit_msg>Add showing and hiding tabs when there is only one catgory.<commit_after>var coverRoot = apiRoot + "/cover";
var libraryRoot = apiRoot + "/library";
var librarySpinner;
var libraryWrapper;
var libraryTabsParent;
var libraryTabs;

function updateLibrary() {
    showSpinner();
    var xhr = new XMLHttpRequest();
    xhr.open("GET", libraryRoot, true);
    xhr.onload = function() {
        try {
            var parsed = JSON.parse(xhr.responseText);
            updateLibraryUI(parsed);
        }
        catch (e) {
            libraryUpdateError();
        }
        hideSpinner();
    };
    xhr.onerror = function() {
        libraryUpdateError();
        hideSpinner();
    };
    xhr.send();
}

function showSpinner() {
    librarySpinner.css("opacity", 1);
}

function hideSpinner() {
    librarySpinner.css("opacity", 0);
}

function buildCoverUrl(mangaId) {
    return coverRoot + "/" + mangaId;
}

function showTabs() {
    if(!libraryTabsParent[0].contains(libraryTabs[0])) {
        libraryTabsParent[0].appendChild(libraryTabs[0]);
    }
}

function hideTabs() {
    if(libraryTabsParent[0].contains(libraryTabs[0])) {
        libraryTabsParent[0].removeChild(libraryTabs[0]);
    }
}

function updateLibraryUI(mangas) {
    //Construct categories
    var categories = {};
    for (var i = 0; i < mangas.length; i++) {
        var manga = mangas[i];
        for (var a = 0; a < manga.categories.length; a++) {
            var categoryName = manga.categories[a];
            var category = categories[categoryName];
            if (!valid(category)) {
                category = [];
                categories[categoryName] = category;
            }
            category.push(manga);
        }
    }
    //Remove old entries
    clearElement(libraryWrapper[0]);
    clearElement(libraryTabs[0]);
    var categoryKeys = Object.keys(categories);
    if (categoryKeys.length <= 1) {
        //Append directly if we don't have tabs
        appendMangas(mangas, libraryWrapper[0]);
    } else {
        for(i = 0; i < categoryKeys.length; i++) {
            categoryName = categoryKeys[i];
            category = categories[categoryName];
            var categoryId = "category-tab-" + i;
            //Generate tabs
            var categoryTab = document.createElement("a");
            categoryTab.className = "mdl-layout__tab";
            //First tab is active
            if(i === 0) {
                categoryTab.className += " is-active";
            }
            categoryTab.href = "#" + categoryId;
            categoryTab.textContent = categoryName;
            libraryTabs[0].appendChild(categoryTab);
            //Generate content
            var categoryPanel = document.createElement("div");
            categoryPanel.className = "mdl-tabs__panel";
            //First panel is active
            if(i === 0) {
                categoryPanel.className += " is-active";
            }
            categoryPanel.id = categoryId;
            libraryWrapper[0].appendChild(categoryPanel);
            //Actually append mangas
            appendMangas(category, categoryPanel);
        }
        //Make sure MDL gets tab changes
        componentHandler.upgradeElement(libraryTabs[0]);
    }
    //Make sure MDL gets content changes
    componentHandler.upgradeElement(libraryWrapper[0]);
}

function appendMangas(mangas, rootElement) {
    var libraryGrid = createGrid();
    rootElement.appendChild(libraryGrid);
    for (var i = 0; i < mangas.length; i++) {
        appendManga(mangas[i], libraryGrid);
    }
}

function createGrid() {
    var grid = document.createElement("div");
    grid.className = "mdl-grid";
    return grid;
}

function appendManga(manga, element) {
    var card = document.createElement("div");
    card.className = "mdl-card mdl-shadow--4dp manga_card mdl-cell mdl-cell--2-col mdl-button mdl-js-button mdl-js-ripple-effect";
    var img = document.createElement("img");
    img.className = "manga_card_img";
    img.src = buildCoverUrl(manga.id);
    img.alt = manga.title;
    card.appendChild(img);
    var label = document.createElement("div");
    label.className = "manga_card_label";
    label.textContent = manga.title;
    card.appendChild(label);
    element.appendChild(card);
}

function libraryUpdateError() {
    snackbar.showSnackbar({
        message: "Error getting library!",
        timeout: 2000,
        actionText: "Retry",
        actionHandler: function() {
            updateLibrary();
        }
    });
}

function onLoad() {
    librarySpinner = $(".loading_spinner");
    libraryWrapper = $("#library_wrapper");
    libraryTabs = $("#library_tabs");
    libraryTabsParent = libraryTabs.parent();
    updateLibrary();
}
<|endoftext|>


<commit_before>ProLicenceUsersTable = {};
ProLicenceUsersTable = {
    "ProLicenceUsers": {
        "1ad344c0-40c4-4ffe-b1b0-142f6918d437": {
            "reason": "LMauthor",
            "notes": "Jimboy3100"
        },
        "b4c70ce6-602a-4fb6-9196-01d890c66d0e": {
            "reason": "Moderator",
            "notes": "ddevilOWO"
        },
        "32254495-c62d-4d0c-b66e-3d81010d401d": {
            "reason": "℄🌀idris",
            "notes": "℄🌀"
        },
        "7bea1cb9-7e14-4e14-824c-c2682036b0a6": {
            "reason": "℄🌀βurNouT",
            "notes": "℄🌀"
        },
        "e18af28b-d1de-4645-967f-9b95736e8817": {
            "reason": "Hendrik Schönfelder",
            "notes": "Youtuber"
        },
        "a3742f28-059a-4ea1-abf7-566e69ec97f4": {
            "reason": "Moderator",
            "notes": "Snez"
        },
        "5b26499a-ccd0-4cdb-9c0e-9375b9d7cd4c": {
            "reason": "Shiro",
            "notes": "Advertiser-Japan-Fb"
        },
        "81831afa-0a11-4fce-82b3-81067f6cc9bc": {
            "reason": "Shiro",
            "notes": "Advertiser-Japan-Google"
        },
        "4220bddd-7bd6-4f93-89cf-579903eff2bc": {
            "reason": "Donate",
            "notes": "Cla"
        },
        "17914d3b-6b8f-468a-bb6b-4d91d3c3f670": {
            "reason": "GoodWillForSon",
            "notes": "jebak"
        },
        "bbf24317-be3f-4dac-a4d8-f1770434ace5": {
            "reason": "Donate",
            "notes": "Meelis"
        },
        "84222701-39e7-454e-9e41-bc8fd4f468d2": {
            "reason": "Donate",
            "notes": "Siah"
        },
        "87b8a4f8-0cb4-448d-bedc-358a3010c7e6": {
            "reason": "Donate",
            "notes": "Fiku"
        },
        "83df7615-1c6a-4a9d-9d86-1eba11d9589c": {
            "reason": "BeggerJapan",
            "notes": "padelsendpie"
        },
        "7aa2d964-ac82-497c-aa34-25cc342299bd": {
            "reason": "Donate",
            "notes": "CentralAsia"
        },
        "84cfb97a-03ef-4625-a6cb-913b23d9dc69": {
            "reason": "Give@20200521",
            "notes": "testJimboy"
        },
        "3361c3f4-3d94-49d2-b19c-c1b8241d4574": {
            "reason": "Give@20200612",
            "notes": "AngelDeltaServer"
        },
        "e8a49e32-7f71-4449-b1ae-1bf2fe4dc3b": {
            "reason": "Give@20200612",
            "notes": "MaximumDeltaServer"
        },
        "d7385406-7a71-498d-a613-3faf090f6ccb": {
            "reason": "Give@20200612",
            "notes": "VictoriaAiroFbGoogleaccount"
        },
        "40252edd-4ec4-41a0-865f-aa9d967a3706": {
            "reason": "Give@20200612",
            "notes": "VictoriaAiroFbFacebookaccount"
        },
        "cfff71aa-efa6-45c2-bcaa-4e68ce796a58": {
            "reason": "Give@20200612",
            "notes": "MuhAdits"
        },
        "3361c3f4-3d94-49d2-b19c-c1b8241d4574": {
            "reason": "Give@20200612",
            "notes": "GioBartolo"
        },
        "3caec878-4009-4349-9bb8-9ce16cebbe98": {
            "reason": "Give@20200612",
            "notes": "ZainMohammed"
        },
        "ba288e41-27b2-4da6-b97c-56409e073894": {
            "reason": "Give@20200612",
            "notes": "RavagerCanos"
        },
        "693edb8d-427c-4453-a92d-2d8bd6254750": {
            "reason": "Give@20200612",
            "notes": "イイヅカマユミ"
        },
        "b4dc260f-f14d-479b-a373-63beb131a122": {
            "reason": "Give@20200612",
            "notes": "LamPhạm"
        },
        "5dc487ab-f8bb-4834-a311-fff3fc924948": {
            "reason": "Give@20200612",
            "Chosenbyddevil": "抹茶"
        },
        "a9d42b13-df83-4a4d-b175-ec9dedaaaf33": {
            "reason": "Give@20200612",
            "Chosenbyddevilowo": "wenlong"
        },
        "7f350142-5c90-473f-8eff-9352680ba8e6": {
            "reason": "Give@20200612",
            "Chosenbyddevilowo": "kaizen"
        },
        "8ba00967-ef20-4314-b451-d802e4817eb7": {
            "reason": "Give@20200612",
            "Chosenbyddevilowo": "綾辻芙月渚"
        },
        "204c1426-c4c9-46af-b29d-5fa9d4d8704a": {
            "reason": "Give@20200612",
            "Chosenbyddevilowo": "LCilyes"
        },
        "4e50ef3d-626e-4bf6-a376-2a0fc0629864": {
            "reason": "Donate",
            "notes": "Lizzie"
        }
    },
    "versionsInfo": ["v-0.3", "LM-v2.5"]
};
<commit_msg>Update ProLicenceUsersTableJS.js<commit_after>ProLicenceUsersTable = {};
ProLicenceUsersTable = {
    "ProLicenceUsers": {
        "1ad344c0-40c4-4ffe-b1b0-142f6918d437": {
            "reason": "LMauthor",
            "notes": "Jimboy3100"
        },
        "b4c70ce6-602a-4fb6-9196-01d890c66d0e": {
            "reason": "Moderator",
            "notes": "ddevilOWO"
        },
        "32254495-c62d-4d0c-b66e-3d81010d401d": {
            "reason": "℄🌀idris",
            "notes": "℄🌀"
        },
        "7bea1cb9-7e14-4e14-824c-c2682036b0a6": {
            "reason": "℄🌀βurNouT",
            "notes": "℄🌀"
        },
        "e18af28b-d1de-4645-967f-9b95736e8817": {
            "reason": "Hendrik Schönfelder",
            "notes": "Youtuber"
        },
        "a3742f28-059a-4ea1-abf7-566e69ec97f4": {
            "reason": "Moderator",
            "notes": "Snez"
        },
        "5b26499a-ccd0-4cdb-9c0e-9375b9d7cd4c": {
            "reason": "Shiro",
            "notes": "Advertiser-Japan-Fb"
        },
        "81831afa-0a11-4fce-82b3-81067f6cc9bc": {
            "reason": "Shiro",
            "notes": "Advertiser-Japan-Google"
        },
        "4220bddd-7bd6-4f93-89cf-579903eff2bc": {
            "reason": "Donate",
            "notes": "Cla"
        },
        "17914d3b-6b8f-468a-bb6b-4d91d3c3f670": {
            "reason": "GoodWillForSon",
            "notes": "jebak"
        },
        "bbf24317-be3f-4dac-a4d8-f1770434ace5": {
            "reason": "Donate",
            "notes": "Meelis"
        },
        "84222701-39e7-454e-9e41-bc8fd4f468d2": {
            "reason": "Donate",
            "notes": "Siah"
        },
        "87b8a4f8-0cb4-448d-bedc-358a3010c7e6": {
            "reason": "Donate",
            "notes": "Fiku"
        },
        "83df7615-1c6a-4a9d-9d86-1eba11d9589c": {
            "reason": "BeggerJapan",
            "notes": "padelsendpie"
        },
        "7aa2d964-ac82-497c-aa34-25cc342299bd": {
            "reason": "Donate",
            "notes": "CentralAsia"
        },
        "84cfb97a-03ef-4625-a6cb-913b23d9dc69": {
            "reason": "Give@20200521",
            "notes": "testJimboy"
        },
        "3361c3f4-3d94-49d2-b19c-c1b8241d4574": {
            "reason": "Give@20200612",
            "notes": "AngelDeltaServer"
        },
        "e8a49e32-7f71-4449-b1ae-1bf2fe4dc3b": {
            "reason": "Give@20200612",
            "notes": "MaximumDeltaServer"
        },
        "d7385406-7a71-498d-a613-3faf090f6ccb": {
            "reason": "Give@20200612",
            "notes": "VictoriaAiroFbGoogleaccount"
        },
        "40252edd-4ec4-41a0-865f-aa9d967a3706": {
            "reason": "Give@20200612",
            "notes": "VictoriaAiroFbFacebookaccount"
        },
        "cfff71aa-efa6-45c2-bcaa-4e68ce796a58": {
            "reason": "Give@20200612",
            "notes": "MuhAdits"
        },
        "3361c3f4-3d94-49d2-b19c-c1b8241d4574": {
            "reason": "Give@20200612",
            "notes": "GioBartolo"
        },
        "3caec878-4009-4349-9bb8-9ce16cebbe98": {
            "reason": "Give@20200612",
            "notes": "ZainMohammed"
        },
        "ba288e41-27b2-4da6-b97c-56409e073894": {
            "reason": "Give@20200612",
            "notes": "RavagerCanos"
        },
        "693edb8d-427c-4453-a92d-2d8bd6254750": {
            "reason": "Give@20200612",
            "notes": "イイヅカマユミ"
        },
        "b4dc260f-f14d-479b-a373-63beb131a122": {
            "reason": "Give@20200612",
            "notes": "LamPhạm"
        },
        "5dc487ab-f8bb-4834-a311-fff3fc924948": {
            "reason": "Give@20200612",
            "Chosenbyddevil": "抹茶"
        },
        "a9d42b13-df83-4a4d-b175-ec9dedaaaf33": {
            "reason": "Give@20200612",
            "Chosenbyddevilowo": "wenlong"
        },
        "7f350142-5c90-473f-8eff-9352680ba8e6": {
            "reason": "Give@20200612",
            "Chosenbyddevilowo": "kaizen"
        },
        "8ba00967-ef20-4314-b451-d802e4817eb7": {
            "reason": "Give@20200612",
            "Chosenbyddevilowo": "綾辻芙月渚"
        },
        "204c1426-c4c9-46af-b29d-5fa9d4d8704a": {
            "reason": "Give@20200612",
            "Chosenbyddevilowo": "LCilyes"
        },
        "f27c8c1a-77d4-4641-9160-cf0d6f7281da": {
            "reason": "Give@20200620",
            "Chosenbyddevilowo": "ALI cicineLC"
        },
        "4e50ef3d-626e-4bf6-a376-2a0fc0629864": {
            "reason": "Donate",
            "notes": "Lizzie"
        }
    },
    "versionsInfo": ["v-0.3", "LM-v2.5"]
};
<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var tape = require( 'tape' );
var incrmmin = require( './../lib' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof incrmmin, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function throws an error if not provided a positive integer', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		-5.0,
		0.0,
		3.14,
		true,
		false,
		null,
		void 0,
		NaN,
		[],
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			incrmmin( value );
		};
	}
});

tape( 'the function returns an accumulator function', function test( t ) {
	t.equal( typeof incrmmin( 3 ), 'function', 'returns a function' );
	t.end();
});

tape( 'the accumulator function computes a moving minimum incrementally', function test( t ) {
	var expected;
	var actual;
	var data;
	var acc;
	var N;
	var i;

	data = [ 2.0, 3.0, 2.0, 4.0, 3.0, 4.0, 2.0, 2.0, 2.0, 1.0, 4.0 ];
	N = data.length;

	acc = incrmmin( 3 );

	actual = new Array( N );
	for ( i = 0; i < N; i++ ) {
		actual[ i ] = acc( data[ i ] );
	}
	expected = [ 2.0, 2.0, 2.0, 2.0, 2.0, 3.0, 2.0, 2.0, 2.0, 1.0, 1.0 ];

	t.deepEqual( actual, expected, 'returns expected incremental results' );
	t.end();
});

tape( 'if not provided an input value, the accumulator function returns the current minimum value', function test( t ) {
	var data;
	var acc;
	var i;

	data = [ 2.0, 3.0, 5.0, 4.0 ];
	acc = incrmmin( 2 );
	for ( i = 0; i < data.length; i++ ) {
		acc( data[ i ] );
	}
	t.equal( acc(), 4.0 );
	t.end();
});

tape( 'if data has yet to be provided, the accumulator function returns `null`', function test( t ) {
	var acc = incrmmin( 3 );
	t.equal( acc(), null, 'returns null' );
	t.end();
});
<commit_msg>Update tests<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var tape = require( 'tape' );
var isPositiveZero = require( '@stdlib/math/base/assert/is-positive-zero' );
var isNegativeZero = require( '@stdlib/math/base/assert/is-negative-zero' );
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var incrmmin = require( './../lib' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof incrmmin, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function throws an error if not provided a positive integer', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		-5.0,
		0.0,
		3.14,
		true,
		false,
		null,
		void 0,
		NaN,
		[],
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			incrmmin( value );
		};
	}
});

tape( 'the function returns an accumulator function', function test( t ) {
	t.equal( typeof incrmmin( 3 ), 'function', 'returns a function' );
	t.end();
});

tape( 'the accumulator function computes a moving minimum incrementally', function test( t ) {
	var expected;
	var actual;
	var data;
	var acc;
	var N;
	var i;

	data = [ 2.0, 3.0, 2.0, 4.0, 3.0, 4.0, 2.0, 2.0, 2.0, 1.0, 4.0 ];
	N = data.length;

	acc = incrmmin( 3 );

	actual = new Array( N );
	for ( i = 0; i < N; i++ ) {
		actual[ i ] = acc( data[ i ] );
	}
	expected = [ 2.0, 2.0, 2.0, 2.0, 2.0, 3.0, 2.0, 2.0, 2.0, 1.0, 1.0 ];

	t.deepEqual( actual, expected, 'returns expected incremental results' );
	t.end();
});

tape( 'if not provided an input value, the accumulator function returns the current minimum value', function test( t ) {
	var data;
	var acc;
	var i;

	data = [ 2.0, 3.0, 5.0, 4.0 ];
	acc = incrmmin( 2 );
	for ( i = 0; i < data.length; i++ ) {
		acc( data[ i ] );
	}
	t.equal( acc(), 4.0 );
	t.end();
});

tape( 'if data has yet to be provided, the accumulator function returns `null`', function test( t ) {
	var acc = incrmmin( 3 );
	t.equal( acc(), null, 'returns null' );
	t.end();
});

tape( 'the accumulator function correctly handles signed zeros', function test( t ) {
	var expected;
	var data;
	var acc;
	var v;
	var i;

	acc = incrmmin( 3 );

	data = [
		0.0,  // 0
		-0.0, // 0, -0
		0.0,  // 0, -0, 0
		0.0,  // -0, 0, 0
		0.0,  // 0, 0, 0
		-0.0, // 0, 0, -0
		0.0,  // 0, -0, 0
		0.0,  // -0, 0, 0
		0.0,  // 0, 0, 0
		-0.0, // 0, 0, -0
		-0.0, // 0, -0, -0
		-0.0, // -0, -0, -0
		0.0   // -0, -0, 0
	];
	expected = [
		0.0,
		-0.0,
		-0.0,
		-0.0,
		0.0,
		-0.0,
		-0.0,
		-0.0,
		0.0,
		-0.0,
		-0.0,
		-0.0,
		-0.0
	];
	for ( i = 0; i < data.length; i++ ) {
		v = acc( data[ i ] );
		if ( isNegativeZero( expected[ i ] ) ) {
			t.equal( isNegativeZero( v ), true, 'returns expected value for window '+i );
		} else {
			t.equal( isPositiveZero( v ), true, 'returns expected value for window '+i );
		}
	}
	t.end();
});

tape( 'if provided `NaN`, the accumulated value is `NaN` for at least `W` invocations', function test( t ) {
	var expected;
	var data;
	var acc;
	var v;
	var i;

	acc = incrmmin( 3 );

	data = [
		NaN,  // NaN
		3.14, // NaN, 3.14
		3.14, // NaN, 3.14, 3.14
		NaN,  // 3.14, 3.14, NaN
		3.14, // 3.14, NaN, 3.14
		3.14, // NaN, 3.14, 3.14
		3.14, // 3.14, 3.14, 3.14
		NaN,  // 3.14, 3.14, NaN
		3.14, // 3.14, NaN, 3.14
		3.14, // NaN, 3.14, 3.14
		3.14, // 3.14, 3.14, 3.14
		NaN,  // 3.14, 3.14, NaN
		3.14, // 3.14, NaN, 3.14
		3.14, // NaN, 3.14, 3.14
		NaN,  // 3.14, 3.14, NaN
		NaN,  // 3.14, NaN, NaN
		NaN,  // NaN, NaN, NaN
		NaN,  // NaN, NaN, NaN
		3.14  // NaN, NaN, 3.14
	];
	expected = [
		NaN,
		NaN,
		NaN,
		NaN,
		NaN,
		NaN,
		3.14,
		NaN,
		NaN,
		NaN,
		3.14,
		NaN,
		NaN,
		NaN,
		NaN,
		NaN,
		NaN,
		NaN,
		NaN
	];
	for ( i = 0; i < data.length; i++ ) {
		v = acc( data[ i ] );
		if ( isnan( expected[ i ] ) ) {
			t.equal( isnan( v ), true, 'returns expected value for window '+i );
		} else {
			t.equal( v, expected[ i ], 'returns expected value for window '+i );
		}
	}
	t.end();
});
<|endoftext|>


<commit_before><commit_msg>add bucket support to the rspec tests<commit_after><|endoftext|>


<commit_before>primeSum.js
<commit_msg>add desc<commit_after>// Prime Sum

// Given an even number ( greater than 2 ), return two prime numbers whose sum will be equal to given number.

// NOTE A solution will always exist. read Goldbach’s conjecture

// Example:

// Input : 4
// Output: 2 + 2 = 4

// If there are more than one solutions possible, return the lexicographically smaller solution.

// If [a, b] is one solution with a <= b,
// and [c,d] is another solution with c <= d, then

// [a, b] < [c, d]

// If a < c OR a==c AND b < d.
<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var isNumber = require( '@stdlib/assert/is-number' ).isPrimitive;
var sqrt = require( '@stdlib/math/base/special/sqrt' );

// MAIN //

/**
* Returns an accumulator function which incrementally computes a sample Pearson product-moment correlation coefficient.
*
* ## Method
*
* -   We begin by defining the co-moment \\(C_n\\)
*
*     ```tex
*     C_n = \sum_{i=1}^{n} ( x_i - \bar{x}_n ) ( y_i - \bar{y}_n )
*     ```
*
*     where \\(\bar{x}_n\\) and \\(\bar{y}_n\\) are the sample means for \\(x\\) and \\(y\\), respectively.
*
* -   Based on Welford's method, we know the update formulas for the sample means are given by
*
*     ```tex
*     \bar{x}_n = \bar{x}_{n-1} + \frac{x_n - \bar{x}_{n-1}}{n}
*     ```
*
*     and
*
*     ```tex
*     \bar{y}_n = \bar{y}_{n-1} + \frac{y_n - \bar{y}_{n-1}}{n}
*     ```
*
* -   Substituting into the equation for \\(C_n\\) and rearranging terms
*
*     ```tex
*     C_n = C_{n-1} + (x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})
*     ```
*
*     where the apparent asymmetry arises from
*
*     ```tex
*     x_n - \bar{x}_n = \frac{n-1}{n} (x_n - \bar{x}_{n-1})
*     ```
*
*     and, hence, the update term can be equivalently expressed
*
*     ```tex
*     \frac{n-1}{n} (x_n - \bar{x}_{n-1}) (y_n - \bar{y}_{n-1})
*     ```
*
* -   The covariance can be defined
*
*     ```tex
*     \begin{align}
*     \operatorname{cov}_n(x,y) &= \frac{C_n}{n} \\
*     &= \frac{C_{n-1} + (x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})}{n} \\
*     &= \frac{(n-1)\operatorname{cov}_{n-1}(x,y) + (x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})}{n}
*     \end{align}
*     ```
*
* -   Applying Bessel's correction, we arrive at an update formula for calculating an unbiased sample covariance
*
*     ```tex
*     \begin{align}
*     \operatorname{cov}_n(x,y) &= \frac{n}{n-1}\cdot\frac{(n-1)\operatorname{cov}_{n-1}(x,y) + (x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})}{n} \\
*     &= \operatorname{cov}_{n-1}(x,y) + \frac{(x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})}{n-1} \\
*     &= \frac{C_{n-1} + (x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})}{n-1}
*     &= \frac{C_{n-1} + (x_n - \bar{x}_{n-1}) (y_n - \bar{y}_n)}{n-1}
*     \end{align}
*     ```
*
* -   To calculate the corrected sample standard deviation, we can use Welford's method, which can be derived as follows. We can express the variance as
*
*     ```tex
*     \begin{align}
*     S_n &= n \sigma_n^2 \\
*         &= \sum_{i=1}^{n} (x_i - \mu_n)^2 \\
*         &= \biggl(\sum_{i=1}^{n} x_i^2 \biggr) - n\mu^2
*     \end{align}
*     ```
*
*     Accordingly,
*
*     ```tex
*     \begin{align}
*     S_n - S_{n-1} &= \sum_{i=1}^{n} x_i^2 - n\mu_n^2 - \sum_{i=1}^{n-1} x_i^2 + (n-1)\mu_{n-1}^2 \\
*                   &= x_n^2 - n\mu_n^2 + (n-1)\mu_{n-1}^2 \\
*                   &= x_n^2 - \mu_{n-1}^2 + n(\mu_{n-1}^2 - \mu_n^2) \\
*                   &= x_n^2 - \mu_{n-1}^2 + n(\mu_{n-1} - \mu_n)(\mu_{n-1} + \mu_n) \\
*                   &= x_n^2 - \mu_{n-1}^2 + (\mu_{n-1} - x_n)(\mu_{n-1} + \mu_n) \\
*                   &= x_n^2 - \mu_{n-1}^2 + \mu_{n-1}^2 - x_n\mu_n - x_n\mu_{n-1} + \mu_n\mu_{n-1} \\
*                   &= x_n^2 - x_n\mu_n - x_n\mu_{n-1} + \mu_n\mu_{n-1} \\
*                   &= (x_n - \mu_{n-1})(x_n - \mu_n) \\
*                   &= S_{n-1} + (x_n - \mu_{n-1})(x_n - \mu_n)
*     \end{align}
*     ```
*
*     where we use the identity
*
*     ```tex
*     x_n - \mu_{n-1} = n (\mu_n - \mu_{n-1})
*     ```
*
* -   To compute the corrected sample standard deviation, we apply Bessel's correction and take the square root.
*
* -   The sample Pearson product-moment correlation coefficient can thus be calculated as
*
*     ```tex
*     r = \frac{\operatorname{cov}_n(x,y)}{\sigma_x \sigma_y}
*     ```
*
*     where \\(\sigma_x\\) and \\(\sigma_y\\) are the corrected sample standard deviations for \\(x\\) and \\(y\\), respectively.
*
* @param {number} [meanx] - mean value
* @param {number} [meany] - mean value
* @throws {TypeError} first argument must be a number primitive
* @throws {TypeError} second argument must be a number primitive
* @returns {Function} accumulator function
*
* @example
* var accumulator = incrpcorr();
*
* @example
* var accumulator = incrpcorr( 2.0, -3.0 );
*/
function incrpcorr( meanx, meany ) {
	var M2x;
	var M2y;
	var dy1;
	var dy2;
	var dy;
	var dx;
	var mx;
	var my;
	var sx;
	var sy;
	var C;
	var N;

	M2x = 0.0;
	M2y = 0.0;
	C = 0.0;
	N = 0;
	if ( arguments.length ) {
		if ( !isNumber( meanx ) ) {
			throw new TypeError( 'invalid input argument. First argument must be a number primitive. Value: `' + meanx + '`.' );
		}
		if ( !isNumber( meany ) ) {
			throw new TypeError( 'invalid input argument. Second argument must be a number primitive. Value: `' + meany + '`.' );
		}
		mx = meanx;
		my = meany;
		return accumulator2;
	}
	mx = 0.0;
	my = 0.0;
	return accumulator1;

	/**
	* If provided input values, the accumulator function returns an updated sample correlation coefficient. If not provided input values, the accumulator function returns the current sample correlation coefficient.
	*
	* @private
	* @param {number} [x] - new value
	* @param {number} [y] - new value
	* @returns {number} sample correlation coefficient
	*
	* @example
	* var v = accumulator1();
	* // returns 0.0
	*
	* v = accumulator1( 2.0, 1.0 );
	* // returns 0.0
	*
	* v = accumulator1( -5.0, 3.14 );
	* // returns ~-1.0
	*
	* v = accumulator1();
	* // returns ~-1.0
	*/
	function accumulator1( x, y ) {
		var n;
		if ( arguments.length === 0 ) {
			if ( N < 2 ) {
				return 0.0;
			}
			return (C/(N-1)) / ( sx*sy );
		}
		N += 1;

		dx = x - mx;
		mx += dx / N;
		M2x += dx * ( x-mx );

		dy1 = y - my;
		my += dy1 / N;
		dy2 = y - my;
		M2y += dy2 * dy1;

		C += dx * dy2;
		if ( N < 2 ) {
			return 0.0;
		}
		n = N - 1;
		sx = sqrt( M2x/n );
		sy = sqrt( M2y/n );
		return ( C/n ) / ( sx*sy );
	}

	/**
	* If provided input values, the accumulator function returns an updated sample correlation coefficient. If not provided input values, the accumulator function returns the current sample correlation coefficient.
	*
	* @private
	* @param {number} [x] - new value
	* @param {number} [y] - new value
	* @returns {number} sample correlation coefficient
	*/
	function accumulator2( x, y ) {
		var n;
		if ( arguments.length === 0 ) {
			if ( N < 2 ) {
				return 0.0;
			}
			return (C/(N-1)) / ( sx*sy );
		}
		N += 1;

		dx = x - mx;
		M2x += dx * dx;

		dy = y - my;
		M2y += dy * dy;

		C += dx * dy;
		if ( N < 2 ) {
			return 0.0;
		}
		n = N - 1;
		sx = sqrt( M2x/n );
		sy = sqrt( M2y/n );
		return ( C/n ) / ( sx*sy );
	}
}

// EXPORTS //

module.exports = incrpcorr;
<commit_msg>Use unbiased estimators when provided known means<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var isNumber = require( '@stdlib/assert/is-number' ).isPrimitive;
var sqrt = require( '@stdlib/math/base/special/sqrt' );

// MAIN //

/**
* Returns an accumulator function which incrementally computes a sample Pearson product-moment correlation coefficient.
*
* ## Method
*
* -   We begin by defining the co-moment \\(C_n\\)
*
*     ```tex
*     C_n = \sum_{i=1}^{n} ( x_i - \bar{x}_n ) ( y_i - \bar{y}_n )
*     ```
*
*     where \\(\bar{x}_n\\) and \\(\bar{y}_n\\) are the sample means for \\(x\\) and \\(y\\), respectively.
*
* -   Based on Welford's method, we know the update formulas for the sample means are given by
*
*     ```tex
*     \bar{x}_n = \bar{x}_{n-1} + \frac{x_n - \bar{x}_{n-1}}{n}
*     ```
*
*     and
*
*     ```tex
*     \bar{y}_n = \bar{y}_{n-1} + \frac{y_n - \bar{y}_{n-1}}{n}
*     ```
*
* -   Substituting into the equation for \\(C_n\\) and rearranging terms
*
*     ```tex
*     C_n = C_{n-1} + (x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})
*     ```
*
*     where the apparent asymmetry arises from
*
*     ```tex
*     x_n - \bar{x}_n = \frac{n-1}{n} (x_n - \bar{x}_{n-1})
*     ```
*
*     and, hence, the update term can be equivalently expressed
*
*     ```tex
*     \frac{n-1}{n} (x_n - \bar{x}_{n-1}) (y_n - \bar{y}_{n-1})
*     ```
*
* -   The covariance can be defined
*
*     ```tex
*     \begin{align}
*     \operatorname{cov}_n(x,y) &= \frac{C_n}{n} \\
*     &= \frac{C_{n-1} + (x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})}{n} \\
*     &= \frac{(n-1)\operatorname{cov}_{n-1}(x,y) + (x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})}{n}
*     \end{align}
*     ```
*
* -   Applying Bessel's correction, we arrive at an update formula for calculating an unbiased sample covariance
*
*     ```tex
*     \begin{align}
*     \operatorname{cov}_n(x,y) &= \frac{n}{n-1}\cdot\frac{(n-1)\operatorname{cov}_{n-1}(x,y) + (x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})}{n} \\
*     &= \operatorname{cov}_{n-1}(x,y) + \frac{(x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})}{n-1} \\
*     &= \frac{C_{n-1} + (x_n - \bar{x}_n) (y_n - \bar{y}_{n-1})}{n-1}
*     &= \frac{C_{n-1} + (x_n - \bar{x}_{n-1}) (y_n - \bar{y}_n)}{n-1}
*     \end{align}
*     ```
*
* -   To calculate the corrected sample standard deviation, we can use Welford's method, which can be derived as follows. We can express the variance as
*
*     ```tex
*     \begin{align}
*     S_n &= n \sigma_n^2 \\
*         &= \sum_{i=1}^{n} (x_i - \mu_n)^2 \\
*         &= \biggl(\sum_{i=1}^{n} x_i^2 \biggr) - n\mu^2
*     \end{align}
*     ```
*
*     Accordingly,
*
*     ```tex
*     \begin{align}
*     S_n - S_{n-1} &= \sum_{i=1}^{n} x_i^2 - n\mu_n^2 - \sum_{i=1}^{n-1} x_i^2 + (n-1)\mu_{n-1}^2 \\
*                   &= x_n^2 - n\mu_n^2 + (n-1)\mu_{n-1}^2 \\
*                   &= x_n^2 - \mu_{n-1}^2 + n(\mu_{n-1}^2 - \mu_n^2) \\
*                   &= x_n^2 - \mu_{n-1}^2 + n(\mu_{n-1} - \mu_n)(\mu_{n-1} + \mu_n) \\
*                   &= x_n^2 - \mu_{n-1}^2 + (\mu_{n-1} - x_n)(\mu_{n-1} + \mu_n) \\
*                   &= x_n^2 - \mu_{n-1}^2 + \mu_{n-1}^2 - x_n\mu_n - x_n\mu_{n-1} + \mu_n\mu_{n-1} \\
*                   &= x_n^2 - x_n\mu_n - x_n\mu_{n-1} + \mu_n\mu_{n-1} \\
*                   &= (x_n - \mu_{n-1})(x_n - \mu_n) \\
*                   &= S_{n-1} + (x_n - \mu_{n-1})(x_n - \mu_n)
*     \end{align}
*     ```
*
*     where we use the identity
*
*     ```tex
*     x_n - \mu_{n-1} = n (\mu_n - \mu_{n-1})
*     ```
*
* -   To compute the corrected sample standard deviation, we apply Bessel's correction and take the square root.
*
* -   The sample Pearson product-moment correlation coefficient can thus be calculated as
*
*     ```tex
*     r = \frac{\operatorname{cov}_n(x,y)}{\sigma_x \sigma_y}
*     ```
*
*     where \\(\sigma_x\\) and \\(\sigma_y\\) are the corrected sample standard deviations for \\(x\\) and \\(y\\), respectively.
*
* @param {number} [meanx] - mean value
* @param {number} [meany] - mean value
* @throws {TypeError} first argument must be a number primitive
* @throws {TypeError} second argument must be a number primitive
* @returns {Function} accumulator function
*
* @example
* var accumulator = incrpcorr();
*
* @example
* var accumulator = incrpcorr( 2.0, -3.0 );
*/
function incrpcorr( meanx, meany ) {
	var M2x;
	var M2y;
	var dy1;
	var dy2;
	var dy;
	var dx;
	var mx;
	var my;
	var sx;
	var sy;
	var C;
	var N;

	M2x = 0.0;
	M2y = 0.0;
	C = 0.0;
	N = 0;
	if ( arguments.length ) {
		if ( !isNumber( meanx ) ) {
			throw new TypeError( 'invalid input argument. First argument must be a number primitive. Value: `' + meanx + '`.' );
		}
		if ( !isNumber( meany ) ) {
			throw new TypeError( 'invalid input argument. Second argument must be a number primitive. Value: `' + meany + '`.' );
		}
		mx = meanx;
		my = meany;
		return accumulator2;
	}
	mx = 0.0;
	my = 0.0;
	return accumulator1;

	/**
	* If provided input values, the accumulator function returns an updated sample correlation coefficient. If not provided input values, the accumulator function returns the current sample correlation coefficient.
	*
	* @private
	* @param {number} [x] - new value
	* @param {number} [y] - new value
	* @returns {number} sample correlation coefficient
	*
	* @example
	* var v = accumulator1();
	* // returns 0.0
	*
	* v = accumulator1( 2.0, 1.0 );
	* // returns 0.0
	*
	* v = accumulator1( -5.0, 3.14 );
	* // returns ~-1.0
	*
	* v = accumulator1();
	* // returns ~-1.0
	*/
	function accumulator1( x, y ) {
		var n;
		if ( arguments.length === 0 ) {
			if ( N < 2 ) {
				return 0.0;
			}
			return (C/(N-1)) / ( sx*sy );
		}
		N += 1;

		dx = x - mx;
		mx += dx / N;
		M2x += dx * ( x-mx );

		dy1 = y - my;
		my += dy1 / N;
		dy2 = y - my;
		M2y += dy2 * dy1;

		C += dx * dy2;
		if ( N < 2 ) {
			return 0.0;
		}
		n = N - 1;
		sx = sqrt( M2x/n );
		sy = sqrt( M2y/n );
		return ( C/n ) / ( sx*sy ); // Note: why all the dividing by `N`? To avoid overflow.
	}

	/**
	* If provided input values, the accumulator function returns an updated sample correlation coefficient. If not provided input values, the accumulator function returns the current sample correlation coefficient.
	*
	* @private
	* @param {number} [x] - new value
	* @param {number} [y] - new value
	* @returns {number} sample correlation coefficient
	*/
	function accumulator2( x, y ) {
		if ( arguments.length === 0 ) {
			if ( N < 2 ) {
				return 0.0;
			}
			return (C/N) / ( sx*sy );
		}
		N += 1;

		dx = x - mx;
		M2x += dx * dx;

		dy = y - my;
		M2y += dy * dy;

		C += dx * dy;
		if ( N < 2 ) {
			return 0.0;
		}
		sx = sqrt( M2x/N );
		sy = sqrt( M2y/N );
		return ( C/N ) / ( sx*sy ); // Note: why all the dividing by `N`? To avoid overflow.
	}
}

// EXPORTS //

module.exports = incrpcorr;
<|endoftext|>


<commit_before>(function() {
  'use strict';

  moj.Modules.MatchVisitors = {

    init: function() {
      this.cacheEls();
      this.bindEvents();
      this.onRender();
    },

    cacheEls: function() {
      this.notContactCheckbox = 'input[id*="not_on_list"]';
      this.bannedCheckbox = 'input[type="checkbox"][id*="banned"]';

      this.$el = $('.js-visitorList');
      this.$notAllMessage = this.$el.find('.js-notAllProcessed');
      this.totalVisitors = this.$el.find('select').length;
    },

    bindEvents: function() {
      this.$el.find('select').on('change', $.proxy(this.changeSelect, this));
      this.$el.find(this.notContactCheckbox).on('change', $.proxy(this.changeNotOnList, this));
      this.$el.find(this.bannedCheckbox).on('change', $.proxy(this.changeBanned, this));
    },

    onRender: function(){
      var self = this;

      $.each(this.$el.find('select'), function(i,obj){
        var $obj = $(obj),
          option = $obj.find('option').slice(1),
          contact = option.data('contact'),
          val = $obj.val(),
          parent = self.findParent(obj);

        if(val == contact.uid && val != ''){
          self.toggleSelectOptions($obj);
          self.toggleCheckbox($obj, val == '');
          option.prop('disabled', null);
        }

        self.processVisitor(parent, !val == '');
        self.checkStatus();
      });

    },

    changeSelect: function(e) {
      var el = e.currentTarget,
        contactData = $(el).find(':selected').data('contact'),
        adding = el.value == '',
        parent = this.findParent(el);

      this.toggleSelectOptions(el);
      this.toggleCheckbox(el, adding);
      this.processVisitor(parent, !adding);
      this.checkStatus();
      if (this.isContactBanned(contactData)) {
        this.setBanned(parent, this.isContactBanned(contactData));
      }
    },

    changeNotOnList: function(e) {
      var el = $(e.currentTarget),
        parent = this.findParent(el),
        isChecked = el.is(':checked');

      this.processVisitor(parent, isChecked);
      this.checkStatus();
    },

    changeBanned: function(e) {
      var el = $(e.currentTarget),
        parent = this.findParent(el),
        isChecked = el.is(':checked');

      this.setVisitorBanned(parent, isChecked);
      this.checkStatus();
    },

    findParent: function(el) {
      return $(el).parents('li');
    },

    processVisitor: function(el, processed) {
      var select = false,
        noContact = false;
      select = $(el).find('select option:selected').val();
      noContact = $(el).find(this.notContactCheckbox).is(':checked');

      if (select == '' && noContact == false) {
        $(el).attr('data-processed', false);
        $(el).data('valid', false);
      } else {
        $(el).attr('data-processed', true);
        $(el).data('valid', !noContact);
      }
    },

    getProcessed: function() {
      return this.$el.find('[data-processed="true"]').length;
    },

    setVisitorBanned: function(el, banned) {
      $(el).data('banned', banned);
    },

    isVisitorBanned: function(el) {
      return $(el).data('banned');
    },

    checkStatus: function() {
      this.checkLeadVisitorStatus();
      this.checkTotalVisitors();
    },

    checkLeadVisitorStatus: function() {
      var visitor = this.getLeadVisitor(),
        visitorValid = !this.isVisitorBanned(visitor) && visitor.data('valid')? true : false;

      if (visitorValid) {
        moj.Modules.Rejection.removeFromSelected(this.$el);
      } else {
        moj.Modules.Rejection.addToSelected(this.$el);
      }
      moj.Modules.Rejection.actuate(this.$el);
      moj.Modules.BookToNomis.render();
    },

    checkTotalVisitors: function() {
      var unprocessed = this.getProcessed() < this.totalVisitors;

      if (unprocessed) {
        this.showEl(this.$notAllMessage);
      } else {
        this.hideEl(this.$notAllMessage);
      }
    },

    getAges: function() {
      var self = this,
        ages = [];
      $.each(this.getSelectedVisitors(), function(i, obj) {
        var data = $(obj).data('visitor'),
          dob = data.dob.split('-'),
          age = self.calcAge(new Date(dob[0], dob[1], dob[2]));
        ages.push(age);
      });
      return ages;
    },

    calcAge: function(dob) {
      var ageDifMs = new Date() - dob.getTime(), //Date.now() - dob.getTime(),
        ageDate = new Date(ageDifMs);
      return Math.abs(ageDate.getUTCFullYear() - 1970);
    },

    getSelectedVisitors: function() {
      var self = this,
        listItemsArray = this.$el.find('select option:selected').map(function() {
          var parent = self.findParent(this);

          if (this.value != '' && !self.isVisitorBanned(parent)) {
            return parent;
          }
        }).get();
      return listItemsArray;
    },

    getVisitorIDs: function() {
      var self = this,
        visitorIDArray = $('select').map(function() {
          if (this.value !== '') {
            return this.value
          }
        }).get();
      return visitorIDArray;
    },

    toggleSelectOptions: function(el) {
      var self = this,
        options = this.$el.find('select').not(el).find('option').not(':first').not(':selected');

      $.each(options, function(i, obj) {
        var contact = $(obj).data('contact');
        if ($.inArray(contact.uid.toString(), self.getVisitorIDs()) !== -1) {
          $(obj).prop('disabled', 'disabled');
        } else {
          $(obj).prop('disabled', null);
        }
      });
    },

    getCheckbox: function(el) {
      var parent = this.findParent(el);
      return parent.find(this.notContactCheckbox);
    },

    toggleCheckbox: function(el, disable) {
      var checkbox = this.getCheckbox(el);
      checkbox.prop('disabled', !disable);
      if (!disable) {
        checkbox.prop('checked', disable);
      }
    },

    isContactBanned: function(contact) {
      return contact.banned == 'true';
    },

    setBanned: function(el, selected) {
      el.find(this.bannedCheckbox).prop('checked', selected).trigger('change');
    },

    showEl: function(el) {
      el.show().removeClass('visuallyhidden');
    },

    hideEl: function(el) {
      el.hide().addClass('visuallyhidden');
    },

    getLeadVisitor: function(){
      return this.$el.find('.visitor-contact-list li').eq(0);
    }

  };

}());
<commit_msg>Fix undefined contact returning console error<commit_after>(function() {
  'use strict';

  moj.Modules.MatchVisitors = {

    init: function() {
      this.cacheEls();
      this.bindEvents();
      this.onRender();
    },

    cacheEls: function() {
      this.notContactCheckbox = 'input[id*="not_on_list"]';
      this.bannedCheckbox = 'input[type="checkbox"][id*="banned"]';

      this.$el = $('.js-visitorList');
      this.$notAllMessage = this.$el.find('.js-notAllProcessed');
      this.totalVisitors = this.$el.find('select').length;
    },

    bindEvents: function() {
      this.$el.find('select').on('change', $.proxy(this.changeSelect, this));
      this.$el.find(this.notContactCheckbox).on('change', $.proxy(this.changeNotOnList, this));
      this.$el.find(this.bannedCheckbox).on('change', $.proxy(this.changeBanned, this));
    },

    onRender: function(){
      var self = this;

      $.each(this.$el.find('select'), function(i,obj){
        var $obj = $(obj),
          option = $obj.find('option').slice(1),
          contact = option.data('contact'),
          val = $obj.val(),
          parent = self.findParent(obj);

        if(val == contact.uid && val != ''){
          self.toggleSelectOptions($obj);
          self.toggleCheckbox($obj, val == '');
          option.prop('disabled', null);
        }

        self.processVisitor(parent, !val == '');
        self.checkStatus();
      });

    },

    changeSelect: function(e) {
      var el = e.currentTarget,
        contactData = $(el).find(':selected').data('contact'),
        adding = el.value == '',
        parent = this.findParent(el);

      this.toggleSelectOptions(el);
      this.toggleCheckbox(el, adding);
      this.processVisitor(parent, !adding);
      this.checkStatus();
      if (this.isContactBanned(contactData)) {
        this.setBanned(parent, this.isContactBanned(contactData));
      }
    },

    changeNotOnList: function(e) {
      var el = $(e.currentTarget),
        parent = this.findParent(el),
        isChecked = el.is(':checked');

      this.processVisitor(parent, isChecked);
      this.checkStatus();
    },

    changeBanned: function(e) {
      var el = $(e.currentTarget),
        parent = this.findParent(el),
        isChecked = el.is(':checked');

      this.setVisitorBanned(parent, isChecked);
      this.checkStatus();
    },

    findParent: function(el) {
      return $(el).parents('li');
    },

    processVisitor: function(el, processed) {
      var select = false,
        noContact = false;
      select = $(el).find('select option:selected').val();
      noContact = $(el).find(this.notContactCheckbox).is(':checked');

      if (select == '' && noContact == false) {
        $(el).attr('data-processed', false);
        $(el).data('valid', false);
      } else {
        $(el).attr('data-processed', true);
        $(el).data('valid', !noContact);
      }
    },

    getProcessed: function() {
      return this.$el.find('[data-processed="true"]').length;
    },

    setVisitorBanned: function(el, banned) {
      $(el).data('banned', banned);
    },

    isVisitorBanned: function(el) {
      return $(el).data('banned');
    },

    checkStatus: function() {
      this.checkLeadVisitorStatus();
      this.checkTotalVisitors();
    },

    checkLeadVisitorStatus: function() {
      var visitor = this.getLeadVisitor(),
        visitorValid = !this.isVisitorBanned(visitor) && visitor.data('valid')? true : false;

      if (visitorValid) {
        moj.Modules.Rejection.removeFromSelected(this.$el);
      } else {
        moj.Modules.Rejection.addToSelected(this.$el);
      }
      moj.Modules.Rejection.actuate(this.$el);
      moj.Modules.BookToNomis.render();
    },

    checkTotalVisitors: function() {
      var unprocessed = this.getProcessed() < this.totalVisitors;

      if (unprocessed) {
        this.showEl(this.$notAllMessage);
      } else {
        this.hideEl(this.$notAllMessage);
      }
    },

    getAges: function() {
      var self = this,
        ages = [];
      $.each(this.getSelectedVisitors(), function(i, obj) {
        var data = $(obj).data('visitor'),
          dob = data.dob.split('-'),
          age = self.calcAge(new Date(dob[0], dob[1], dob[2]));
        ages.push(age);
      });
      return ages;
    },

    calcAge: function(dob) {
      var ageDifMs = new Date() - dob.getTime(), //Date.now() - dob.getTime(),
        ageDate = new Date(ageDifMs);
      return Math.abs(ageDate.getUTCFullYear() - 1970);
    },

    getSelectedVisitors: function() {
      var self = this,
        listItemsArray = this.$el.find('select option:selected').map(function() {
          var parent = self.findParent(this);

          if (this.value != '' && !self.isVisitorBanned(parent)) {
            return parent;
          }
        }).get();
      return listItemsArray;
    },

    getVisitorIDs: function() {
      var self = this,
        visitorIDArray = $('select').map(function() {
          if (this.value !== '') {
            return this.value
          }
        }).get();
      return visitorIDArray;
    },

    toggleSelectOptions: function(el) {
      var self = this,
        options = this.$el.find('select').not(el).find('option').not(':first').not(':selected');

      $.each(options, function(i, obj) {
        var contact = $(obj).data('contact');
        if ($.inArray(contact.uid.toString(), self.getVisitorIDs()) !== -1) {
          $(obj).prop('disabled', 'disabled');
        } else {
          $(obj).prop('disabled', null);
        }
      });
    },

    getCheckbox: function(el) {
      var parent = this.findParent(el);
      return parent.find(this.notContactCheckbox);
    },

    toggleCheckbox: function(el, disable) {
      var checkbox = this.getCheckbox(el);
      checkbox.prop('disabled', !disable);
      if (!disable) {
        checkbox.prop('checked', disable);
      }
    },

    isContactBanned: function(contact) {
      var isBanned = contact? contact.banned == 'true' : false;
      return isBanned;
    },

    setBanned: function(el, selected) {
      el.find(this.bannedCheckbox).prop('checked', selected).trigger('change');
    },

    showEl: function(el) {
      el.show().removeClass('visuallyhidden');
    },

    hideEl: function(el) {
      el.hide().addClass('visuallyhidden');
    },

    getLeadVisitor: function(){
      return this.$el.find('.visitor-contact-list li').eq(0);
    }

  };

}());
<|endoftext|>


<commit_before>var jsp = require("../../uglify-js/uglify-js").parser,
	pro = require("../../uglify-js/uglify-js").uglify,
	fs = require('fs'),
	path = require('path'),
	U = require('../../utils.js'),
	CU = require('./compilerUtils'),
	_ = require("../../lib/alloy/underscore")._
;

function appendFileExt(p)
{
	var ext = p.substring(p.length-3);
	if (ext!='.js')
	{
		return p + '.js';
	}
	return p;
}

function makeFullPath(basedir,p)
{
	if (p.charAt(0)=='.' || p.charAt(0)=='/')
	{
		return appendFileExt(path.join(basedir,p));
	}
	var f = path.join(basedir,appendFileExt(p));
	if (path.existsSync(f))
	{
		// check to see if it exists and if so, return
		return f;
	}
	// if we can't find the file, assume it's a built-in node_module and we should treat as-is
	return p;
}

function findAllRequires(fn,filterFn)
{
	var basedir = path.dirname(path.resolve(fn));
	var code = String(fs.readFileSync(fn,'utf-8'));
	var ast;
	try {
		ast = jsp.parse(code);
	} catch(e) {
		U.die([
			code,
			e.stack,
			'Failed to parse code in ' + fn
		]);
	}
	var w = pro.ast_walker();
	var requires = [];

	function processCall()
	{
		var entries = this[1];
		if (entries[0] == 'dot')
		{
			entries = entries[1];
		}
		if (entries[0]=='name' && entries[1]=='require')
		{
			var requirePath = makeFullPath(basedir,this[2][0][1]);
			if (filterFn && _.isFunction(filterFn))
			{
				// ask the filter if we should include this file in processing
				var fn = filterFn(requirePath);
				if (fn)
				{
					requirePath = fn;
				}
				else
				{
					return;
				}
			}
			requires.push(requirePath);
		}
	}

	w.with_walkers({"call" : processCall}, function()
	{
		return w.walk(ast);
	});

	return requires;
}

function trimExtension(f)
{
	var ext = f.substring(f.length-3);
	if (ext == '.js')
	{
		return f.substring(0,f.length-3);
	}
	return f;
}

function makeRequiresRelative(fn,resourcesDir,config)
{
	var basedir = path.dirname(path.resolve(fn));
	var code = String(fs.readFileSync(fn,'utf-8'));
	var ast = jsp.parse(code);
	var w = pro.ast_walker();
	var requires = [];

	function processCall()
	{
		var entries = this[1];
		if (entries[0] == 'dot')
		{
			entries = entries[1];
		}
		if (entries[0]=='name' && entries[1]=='require')
		{
			var name = this[2][0][1];
			var requirePath = makeFullPath(basedir,name);
			var relative = requirePath.substring(resourcesDir.length + 1);
			if (relative)
			{
				// re-write to be relative
				this[2][0][1] = trimExtension(relative);
			}
		}
		return this;
	}

	var ast = w.with_walkers({"call" : processCall}, function()
	{
		return w.walk(ast);
	});

	return CU.formatAST(ast,config,fn);
}

exports.findAllRequires = findAllRequires;
exports.makeRequiresRelative = makeRequiresRelative;

<commit_msg>ast parsing error output improvement<commit_after>var jsp = require("../../uglify-js/uglify-js").parser,
	pro = require("../../uglify-js/uglify-js").uglify,
	fs = require('fs'),
	path = require('path'),
	U = require('../../utils.js'),
	CU = require('./compilerUtils'),
	_ = require("../../lib/alloy/underscore")._
;

function appendFileExt(p)
{
	var ext = p.substring(p.length-3);
	if (ext!='.js')
	{
		return p + '.js';
	}
	return p;
}

function makeFullPath(basedir,p)
{
	if (p.charAt(0)=='.' || p.charAt(0)=='/')
	{
		return appendFileExt(path.join(basedir,p));
	}
	var f = path.join(basedir,appendFileExt(p));
	if (path.existsSync(f))
	{
		// check to see if it exists and if so, return
		return f;
	}
	// if we can't find the file, assume it's a built-in node_module and we should treat as-is
	return p;
}

function findAllRequires(fn,filterFn)
{
	var basedir = path.dirname(path.resolve(fn));
	var code = String(fs.readFileSync(fn,'utf-8'));
	var ast;

	try {
		ast = jsp.parse(code);
	} catch (e) {
		var errs = ['Error processing require() calls for file "' + fn];
		if (e.message && typeof e.line !== 'undefined') {
			errs.push(e.message);
			errs.push('line ' + e.line + ', column ' + e.col + ', position ' + e.pos);
		} else {
			errs.unshift(e.stack);
		}
		U.die(errs);
	}

	// try {
	// 	ast = jsp.parse(code);
	// } catch(e) {
	// 	U.die([
	// 		code,
	// 		e.stack,
	// 		'Failed to parse code in ' + fn
	// 	]);
	// }
	var w = pro.ast_walker();
	var requires = [];

	function processCall()
	{
		var entries = this[1];
		if (entries[0] == 'dot')
		{
			entries = entries[1];
		}
		if (entries[0]=='name' && entries[1]=='require')
		{
			var requirePath = makeFullPath(basedir,this[2][0][1]);
			if (filterFn && _.isFunction(filterFn))
			{
				// ask the filter if we should include this file in processing
				var fn = filterFn(requirePath);
				if (fn)
				{
					requirePath = fn;
				}
				else
				{
					return;
				}
			}
			requires.push(requirePath);
		}
	}

	w.with_walkers({"call" : processCall}, function()
	{
		return w.walk(ast);
	});

	return requires;
}

function trimExtension(f)
{
	var ext = f.substring(f.length-3);
	if (ext == '.js')
	{
		return f.substring(0,f.length-3);
	}
	return f;
}

function makeRequiresRelative(fn,resourcesDir,config)
{
	var basedir = path.dirname(path.resolve(fn));
	var code = String(fs.readFileSync(fn,'utf-8'));
	var ast = jsp.parse(code);
	var w = pro.ast_walker();
	var requires = [];

	function processCall()
	{
		var entries = this[1];
		if (entries[0] == 'dot')
		{
			entries = entries[1];
		}
		if (entries[0]=='name' && entries[1]=='require')
		{
			var name = this[2][0][1];
			var requirePath = makeFullPath(basedir,name);
			var relative = requirePath.substring(resourcesDir.length + 1);
			if (relative)
			{
				// re-write to be relative
				this[2][0][1] = trimExtension(relative);
			}
		}
		return this;
	}

	var ast = w.with_walkers({"call" : processCall}, function()
	{
		return w.walk(ast);
	});

	return CU.formatAST(ast,config,fn);
}

exports.findAllRequires = findAllRequires;
exports.makeRequiresRelative = makeRequiresRelative;

<|endoftext|>


<commit_before>// Copyright 2016, University of Colorado Boulder

/**
 * Copies all supporting PhET-iO files, including wrappers, indices, lib files, etc.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Matt Pennington (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';

// modules
const ChipperStringUtils = require( '../../common/ChipperStringUtils' );
const copyDirectory = require( '../copyDirectory' );
const execute = require( '../execute' );
const fs = require( 'fs' );
const grunt = require( 'grunt' );
const minify = require( '../minify' );

// constants
const DEDICATED_REPO_WRAPPER_PREFIX = 'phet-io-wrapper-';
const WRAPPER_COMMON_FOLDER = 'phet-io-wrappers/common';
const PRODUCTION_SITE = 'phet-io.colorado.edu';
const WRAPPERS_FOLDER = 'wrappers/'; // The wrapper index assumes this constant, please see phet-io-wrappers/index/index.js before changing

// phet-io internal files to be consolidated into 1 file and publicly served as a minified phet-io library.
// Make sure to add new files to the jsdoc generation list below also
const LIB_FILES = [
  '../query-string-machine/js/QueryStringMachine.js', // must be first, other types use this
  '../' + WRAPPER_COMMON_FOLDER + '/js/assert.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/WrapperTypes.js',
  '../tandem/js/PhetioIDUtils.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/Client.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/readFile.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/loadTemplate.js'
];

const LIB_OUTPUT_FILE = 'phet-io.js';
const LIB_COPYRIGHT_HEADER = '// Copyright 2002-2018, University of Colorado Boulder\n' +
                             '// This PhET-iO file requires a license\n' +
                             '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                             '// For licensing, please contact phethelp@colorado.edu';

// All libraries and third party files that are used by phet-io wrappers, and need to be copied over for a build
const CONTRIB_FILES = [
  '../sherpa/lib/lodash-4.17.4.min.js',
  '../sherpa/lib/ua-parser-0.7.12.min.js',
  '../sherpa/lib/bootstrap-2.2.2.js',
  '../sherpa/lib/font-awesome-4.5.0',
  '../sherpa/lib/jquery-2.1.0.min.js',
  '../sherpa/lib/jquery-ui-1.8.24.min.js',
  '../sherpa/lib/d3-4.2.2.js',
  '../sherpa/lib/highlight.js-9.1.0/styles/tomorrow-night-bright.css',
  '../sherpa/lib/highlight.js-9.1.0/highlight.js',
  '../sherpa/lib/jsondiffpatch-v0.3.11.umd.js',
  '../sherpa/lib/jsondiffpatch-v0.3.11-annotated.css',
  '../sherpa/lib/jsondiffpatch-v0.3.11-html.css'
];

// list of files to run jsdoc generation with. Assume that anything in the public lib file needs documentation
const JSDOC_FILES = LIB_FILES.concat( [
  ' ../phet-io/js/phet-io-initialize-globals.js',
  ' ../chipper/js/initialize-globals.js'
] );
const JSDOC_README_FILE = '../phet-io/doc/wrapper/phet-io-documentation_README.md';

module.exports = async function( repo, version, simulationDisplayName ) {

  const buildDir = `../${repo}/build/phet-io/`;
  const wrappersLocation = `${buildDir}${WRAPPERS_FOLDER}`;

  // This regex was copied from perennial's `SimVersion.parse()` consult that code before changing things here.
  const matches = version.match( /^(\d+)\.(\d+)\.(\d+)(-(([^\.-]+)\.(\d+)))?(-([^.-]+))?$/ );
  if ( !matches ) {
    throw new Error( 'could not parse version: ' + version );
  }
  const major = parseInt( matches[ 1 ], 10 );
  const minor = parseInt( matches[ 2 ], 10 );
  var latestVersion = `${major}.${minor}`;

  // The filter that we run every phet-io wrapper file through to transform dev content into built content. This mainly
  // involves lots of hard coded copy replace of template strings and marker values.
  const filterWrapper = function( abspath, contents ) {
    const originalContents = contents + '';

    const isWrapperIndex = abspath.indexOf( 'index/index.html' ) >= 0;

    // For info about LIB_OUTPUT_FILE, see handleLib()
    let pathToLib = `lib/${LIB_OUTPUT_FILE}`;

    if ( abspath.indexOf( '.html' ) >= 0 ) {

      // change the paths of sherpa files to point to the contrib/ folder
      CONTRIB_FILES.forEach( function( filePath ) {

        // No need to do this is this file doesn't have this contrib import in it.
        if ( contents.indexOf( filePath ) >= 0 ) {

          const filePathParts = filePath.split( '/' );

          // If the file is in a dedicated wrapper repo, then it is one level higher in the dir tree, and needs 1 less set of dots.
          // see https://github.com/phetsims/phet-io-wrappers/issues/17 for more info. This is hopefully a temporary workaround
          const needsExtraDots = abspath.indexOf( DEDICATED_REPO_WRAPPER_PREFIX ) >= 0;
          const fileName = filePathParts[ filePathParts.length - 1 ];
          const contribFileName = 'contrib/' + fileName;
          let pathToContrib = needsExtraDots ? '../../' + contribFileName : '../' + contribFileName;

          // The wrapper index is a different case because it is placed at the top level of the build dir.
          if ( isWrapperIndex ) {

            pathToContrib = contribFileName;
            filePath = '../' + filePath; // filePath has one less set of relative than are actually in the index.html file.
          }
          contents = ChipperStringUtils.replaceAll( contents, filePath, pathToContrib );
        }
      } );

      /*
       * Remove individual common phet-io code imports because they are all in phetio.js
       */

      // TODO: use LIB_FILES and/or factor this outs
      // This returns the whole line that contains this substring, so it can be removed
      const firstQueryStringLine = ChipperStringUtils.firstLineThatContains( contents, 'QueryStringMachine.js">' );

      // Don't remove the import if it is coming from the phet-io website, only if it is a relative path in requirejs mode.
      if ( firstQueryStringLine && firstQueryStringLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstQueryStringLine, '' ); // included in phetio.js
      }
      const firstAssertLine = ChipperStringUtils.firstLineThatContains( contents, 'assert.js">' );
      if ( firstAssertLine && firstAssertLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstAssertLine, '' ); // included in phetio.js
      }
      const firstIFrameClientLine = ChipperStringUtils.firstLineThatContains( contents, 'Client.js">' );
      if ( firstIFrameClientLine && firstIFrameClientLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstIFrameClientLine, '' ); // included in phetio.js
      }

      // Bundle in readFile and loadTemplate so they can be used uniformly by index (in root location) and wrappers (nested locations)
      const firstReadFileLine = ChipperStringUtils.firstLineThatContains( contents, 'readFile.js">' );
      if ( firstReadFileLine && firstReadFileLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstReadFileLine, '' ); // included in phetio.js
      }
      const firstLoadTemplateLine = ChipperStringUtils.firstLineThatContains( contents, 'loadTemplate.js">' );
      if ( firstLoadTemplateLine && firstLoadTemplateLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstLoadTemplateLine, '' ); // included in phetio.js
      }
      const firstWrapperTypeLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperTypes.js">' );
      if ( firstWrapperTypeLine && firstWrapperTypeLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperTypeLine, '' ); // included in phetio.js
      }
      const firstPhetioIDUtilsLine = ChipperStringUtils.firstLineThatContains( contents, 'PhetioIDUtils.js' );
      if ( firstPhetioIDUtilsLine && firstPhetioIDUtilsLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstPhetioIDUtilsLine, '' ); // included in phetio.js
      }

      // Support wrappers that use code from phet-io-wrappers
      contents = ChipperStringUtils.replaceAll( contents, '/phet-io-wrappers/', '/' );

      // wrapper index is moved to the top level of build dir, and needs no relative dots.
      pathToLib = isWrapperIndex ? pathToLib : `../../${pathToLib}`;
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{phet-io.js}}-->',
        `<script src="${pathToLib}"></script>`
      );

      // This must be after the above phet-io.js import and Client.js stripping. This case is to support wrappers
      // that use the data-client-name attribute to dictate their own Type name.
      contents = ChipperStringUtils.replaceAll( contents,
        '../common/js/Client.js',
        `${pathToLib}`
      );

      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{GOOGLE_ANALYTICS.js}}-->',
        '<script src="/assets/js/phet-io-ga.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{FAVICON.ico}}-->',
        '<link rel="shortcut icon" href="/assets/favicon.ico"/>'
      );
    }
    if ( abspath.indexOf( '.js' ) >= 0 || abspath.indexOf( '.html' ) >= 0 ) {
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_NAME}}', repo );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_DISPLAY_NAME}}', simulationDisplayName );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_VERSION}}', version );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_LATEST_VERSION}}', latestVersion );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_IS_BUILT}}', 'true' );
      contents = ChipperStringUtils.replaceAll( contents, '{{LIB_OUTPUT_FILE}}', LIB_OUTPUT_FILE );
      contents = ChipperStringUtils.replaceAll( contents, '{{PHET_IO_LIB_RELATIVE_PATH}}', pathToLib );
      contents = ChipperStringUtils.replaceAll( contents, '{{Built API Docs not available in RequireJS mode}}', 'API Docs' );

      // phet-io-wrappers/common will be in the top level of wrappers/ in the build directory
      contents = ChipperStringUtils.replaceAll( contents, WRAPPER_COMMON_FOLDER + '/', 'common/' );
    }
    if ( contents !== originalContents ) {
      return contents;
    }
    else {
      return null; // signify no change (helps for images)
    }
  };

// a list of the phet-io wrappers that are built with the phet-io sim
  const wrappers = fs.readFileSync( '../chipper/data/wrappers', 'utf-8' ).trim().split( '\n' ).map( wrappers => wrappers.trim() );

  // Files and directories from wrapper folders that we don't want to copy
  const wrappersBlacklist = [ '.git', 'README.md', '.gitignore', 'node_modules', 'package.json', 'build' ];

  const libFileNames = LIB_FILES.map( function( filePath ) {
    const parts = filePath.split( '/' );
    return parts[ parts.length - 1 ];
  } );

  // Don't copy over the files that are in the lib file, this way we can catch wrapper bugs that are not pointing to the lib.
  const fullBlacklist = wrappersBlacklist.concat( libFileNames );

  // wrapping function for copying the wrappers to the build dir
  const copyWrapper = function( src, dest ) {
    copyDirectory( src, dest, filterWrapper, {
      blacklist: fullBlacklist
    } );
  };

  // Make sure to copy the phet-io-wrappers common wrapper code too.
  wrappers.push( WRAPPER_COMMON_FOLDER );
  wrappers.forEach( function( wrapper ) {

    const wrapperParts = wrapper.split( '/' );

    // either take the last path part, or take the first (repo name) and remove the wrapper prefix
    const wrapperName = wrapperParts.length > 1 ? wrapperParts[ wrapperParts.length - 1 ] : wrapperParts[ 0 ].replace( DEDICATED_REPO_WRAPPER_PREFIX, '' );

    // Copy the wrapper into the build dir /wrappers/, exclude the blacklist
    copyWrapper( `../${wrapper}`, `${wrappersLocation}${wrapperName}` );
  } );

  // Copy the wrapper index into the top level of the build dir, exclude the blacklist
  copyWrapper( '../phet-io-wrappers/index', `${buildDir}` );

  // Create the lib file that is minified and publicly available under the /lib folder of the build
  handleLib( buildDir, filterWrapper );

  // Create the contrib folder and add to it third party libraries used by wrappers.
  handleContrib( buildDir );

  // Create the rendered jsdoc in the `doc` folder
  await handleJSDOC( buildDir );
};

/**
 * Given the list of lib files, apply a filter function to them. Then minify them and consolidate into a single string.
 * Finally write them to the build dir with a license prepended. See https://github.com/phetsims/phet-io/issues/353

 * @param {string} buildDir
 * @param {Function} filter - the filter function used when copying over wrapper files to fix relative paths and such.
 *                            Has arguments like "function(abspath, contents)"
 */
const handleLib = function( buildDir, filter ) {
  grunt.log.debug( 'Creating phet-io lib file from: ', LIB_FILES );

  grunt.file.mkdir( `${buildDir}lib` );

  let consolidated = '';
  LIB_FILES.forEach( function( libFile ) {
    const contents = grunt.file.read( libFile );

    const filteredContents = filter && filter( libFile, contents );

    // The filter should return null if nothing changes
    consolidated += filteredContents ? filteredContents : contents;
  } );

  const minified = minify( consolidated, {
    stripAssertions: false
  } );

  grunt.file.write( `${buildDir}lib/${LIB_OUTPUT_FILE}`, LIB_COPYRIGHT_HEADER + '\n\n' + minified );
};

/**
 * Copy all of the third party libraries from sherpa to the build directory under the 'contrib' folder.
 * @param {string} buildDir
 */
const handleContrib = function( buildDir ) {
  grunt.log.debug( 'Creating phet-io contrib folder' );

  CONTRIB_FILES.forEach( function( filePath ) {
    const filePathParts = filePath.split( '/' );

    const filename = filePathParts[ filePathParts.length - 1 ];

    grunt.file.copy( filePath, `${buildDir}contrib/${filename}` );

  } );
};

/**
 * Generate jsdoc and put it in "build/phet-io/doc"
 * @param {string} buildDir
 * @returns {Promise<void>}
 */
const handleJSDOC = async function( buildDir ) {
  grunt.log.debug( 'generating jsdoc for phet-io from: ', JSDOC_FILES );

  // First we tried to run the jsdoc binary as the cmd, but that wasn't working, and was quite finicky. Then @samreid
  // found https://stackoverflow.com/questions/33664843/how-to-use-jsdoc-with-gulp which recommends the following method
  // (node executable with jsdoc js file)
  await execute( 'node', [ '../chipper/node_modules/jsdoc/jsdoc.js' ].concat(
    JSDOC_FILES.concat( [ '-c', '../phet-io/doc/wrapper/jsdoc-config.json',
      '-d', `${buildDir}doc/`, '--readme', JSDOC_README_FILE ] ) ),
    { cwd: process.cwd(), shell: true } );
};<commit_msg>Fixed Client.SIMULATION_DISPLAY_NAME with escapes, see https://github.com/phetsims/phet-io-wrappers/issues/190<commit_after>// Copyright 2016, University of Colorado Boulder

/**
 * Copies all supporting PhET-iO files, including wrappers, indices, lib files, etc.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Matt Pennington (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';

// modules
const ChipperStringUtils = require( '../../common/ChipperStringUtils' );
const copyDirectory = require( '../copyDirectory' );
const execute = require( '../execute' );
const fs = require( 'fs' );
const grunt = require( 'grunt' );
const minify = require( '../minify' );

// constants
const DEDICATED_REPO_WRAPPER_PREFIX = 'phet-io-wrapper-';
const WRAPPER_COMMON_FOLDER = 'phet-io-wrappers/common';
const PRODUCTION_SITE = 'phet-io.colorado.edu';
const WRAPPERS_FOLDER = 'wrappers/'; // The wrapper index assumes this constant, please see phet-io-wrappers/index/index.js before changing

// phet-io internal files to be consolidated into 1 file and publicly served as a minified phet-io library.
// Make sure to add new files to the jsdoc generation list below also
const LIB_FILES = [
  '../query-string-machine/js/QueryStringMachine.js', // must be first, other types use this
  '../' + WRAPPER_COMMON_FOLDER + '/js/assert.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/WrapperTypes.js',
  '../tandem/js/PhetioIDUtils.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/Client.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/readFile.js',
  '../' + WRAPPER_COMMON_FOLDER + '/js/loadTemplate.js'
];

const LIB_OUTPUT_FILE = 'phet-io.js';
const LIB_COPYRIGHT_HEADER = '// Copyright 2002-2018, University of Colorado Boulder\n' +
                             '// This PhET-iO file requires a license\n' +
                             '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                             '// For licensing, please contact phethelp@colorado.edu';

// All libraries and third party files that are used by phet-io wrappers, and need to be copied over for a build
const CONTRIB_FILES = [
  '../sherpa/lib/lodash-4.17.4.min.js',
  '../sherpa/lib/ua-parser-0.7.12.min.js',
  '../sherpa/lib/bootstrap-2.2.2.js',
  '../sherpa/lib/font-awesome-4.5.0',
  '../sherpa/lib/jquery-2.1.0.min.js',
  '../sherpa/lib/jquery-ui-1.8.24.min.js',
  '../sherpa/lib/d3-4.2.2.js',
  '../sherpa/lib/highlight.js-9.1.0/styles/tomorrow-night-bright.css',
  '../sherpa/lib/highlight.js-9.1.0/highlight.js',
  '../sherpa/lib/jsondiffpatch-v0.3.11.umd.js',
  '../sherpa/lib/jsondiffpatch-v0.3.11-annotated.css',
  '../sherpa/lib/jsondiffpatch-v0.3.11-html.css'
];

// list of files to run jsdoc generation with. Assume that anything in the public lib file needs documentation
const JSDOC_FILES = LIB_FILES.concat( [
  ' ../phet-io/js/phet-io-initialize-globals.js',
  ' ../chipper/js/initialize-globals.js'
] );
const JSDOC_README_FILE = '../phet-io/doc/wrapper/phet-io-documentation_README.md';

module.exports = async function( repo, version, simulationDisplayName ) {

  const buildDir = `../${repo}/build/phet-io/`;
  const wrappersLocation = `${buildDir}${WRAPPERS_FOLDER}`;

  // This regex was copied from perennial's `SimVersion.parse()` consult that code before changing things here.
  const matches = version.match( /^(\d+)\.(\d+)\.(\d+)(-(([^\.-]+)\.(\d+)))?(-([^.-]+))?$/ );
  if ( !matches ) {
    throw new Error( 'could not parse version: ' + version );
  }
  const major = parseInt( matches[ 1 ], 10 );
  const minor = parseInt( matches[ 2 ], 10 );
  var latestVersion = `${major}.${minor}`;

  // The filter that we run every phet-io wrapper file through to transform dev content into built content. This mainly
  // involves lots of hard coded copy replace of template strings and marker values.
  const filterWrapper = function( abspath, contents ) {
    const originalContents = contents + '';

    const isWrapperIndex = abspath.indexOf( 'index/index.html' ) >= 0;

    // For info about LIB_OUTPUT_FILE, see handleLib()
    let pathToLib = `lib/${LIB_OUTPUT_FILE}`;

    if ( abspath.indexOf( '.html' ) >= 0 ) {

      // change the paths of sherpa files to point to the contrib/ folder
      CONTRIB_FILES.forEach( function( filePath ) {

        // No need to do this is this file doesn't have this contrib import in it.
        if ( contents.indexOf( filePath ) >= 0 ) {

          const filePathParts = filePath.split( '/' );

          // If the file is in a dedicated wrapper repo, then it is one level higher in the dir tree, and needs 1 less set of dots.
          // see https://github.com/phetsims/phet-io-wrappers/issues/17 for more info. This is hopefully a temporary workaround
          const needsExtraDots = abspath.indexOf( DEDICATED_REPO_WRAPPER_PREFIX ) >= 0;
          const fileName = filePathParts[ filePathParts.length - 1 ];
          const contribFileName = 'contrib/' + fileName;
          let pathToContrib = needsExtraDots ? '../../' + contribFileName : '../' + contribFileName;

          // The wrapper index is a different case because it is placed at the top level of the build dir.
          if ( isWrapperIndex ) {

            pathToContrib = contribFileName;
            filePath = '../' + filePath; // filePath has one less set of relative than are actually in the index.html file.
          }
          contents = ChipperStringUtils.replaceAll( contents, filePath, pathToContrib );
        }
      } );

      /*
       * Remove individual common phet-io code imports because they are all in phetio.js
       */

      // TODO: use LIB_FILES and/or factor this outs
      // This returns the whole line that contains this substring, so it can be removed
      const firstQueryStringLine = ChipperStringUtils.firstLineThatContains( contents, 'QueryStringMachine.js">' );

      // Don't remove the import if it is coming from the phet-io website, only if it is a relative path in requirejs mode.
      if ( firstQueryStringLine && firstQueryStringLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstQueryStringLine, '' ); // included in phetio.js
      }
      const firstAssertLine = ChipperStringUtils.firstLineThatContains( contents, 'assert.js">' );
      if ( firstAssertLine && firstAssertLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstAssertLine, '' ); // included in phetio.js
      }
      const firstIFrameClientLine = ChipperStringUtils.firstLineThatContains( contents, 'Client.js">' );
      if ( firstIFrameClientLine && firstIFrameClientLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstIFrameClientLine, '' ); // included in phetio.js
      }

      // Bundle in readFile and loadTemplate so they can be used uniformly by index (in root location) and wrappers (nested locations)
      const firstReadFileLine = ChipperStringUtils.firstLineThatContains( contents, 'readFile.js">' );
      if ( firstReadFileLine && firstReadFileLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstReadFileLine, '' ); // included in phetio.js
      }
      const firstLoadTemplateLine = ChipperStringUtils.firstLineThatContains( contents, 'loadTemplate.js">' );
      if ( firstLoadTemplateLine && firstLoadTemplateLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstLoadTemplateLine, '' ); // included in phetio.js
      }
      const firstWrapperTypeLine = ChipperStringUtils.firstLineThatContains( contents, 'WrapperTypes.js">' );
      if ( firstWrapperTypeLine && firstWrapperTypeLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstWrapperTypeLine, '' ); // included in phetio.js
      }
      const firstPhetioIDUtilsLine = ChipperStringUtils.firstLineThatContains( contents, 'PhetioIDUtils.js' );
      if ( firstPhetioIDUtilsLine && firstPhetioIDUtilsLine.indexOf( PRODUCTION_SITE ) === -1 ) {
        contents = ChipperStringUtils.replaceAll( contents, firstPhetioIDUtilsLine, '' ); // included in phetio.js
      }

      // Support wrappers that use code from phet-io-wrappers
      contents = ChipperStringUtils.replaceAll( contents, '/phet-io-wrappers/', '/' );

      // wrapper index is moved to the top level of build dir, and needs no relative dots.
      pathToLib = isWrapperIndex ? pathToLib : `../../${pathToLib}`;
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{phet-io.js}}-->',
        `<script src="${pathToLib}"></script>`
      );

      // This must be after the above phet-io.js import and Client.js stripping. This case is to support wrappers
      // that use the data-client-name attribute to dictate their own Type name.
      contents = ChipperStringUtils.replaceAll( contents,
        '../common/js/Client.js',
        `${pathToLib}`
      );

      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{GOOGLE_ANALYTICS.js}}-->',
        '<script src="/assets/js/phet-io-ga.js"></script>'
      );
      contents = ChipperStringUtils.replaceAll( contents,
        '<!--{{FAVICON.ico}}-->',
        '<link rel="shortcut icon" href="/assets/favicon.ico"/>'
      );
    }
    if ( abspath.indexOf( '.js' ) >= 0 || abspath.indexOf( '.html' ) >= 0 ) {
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_NAME}}', repo );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_DISPLAY_NAME}}', simulationDisplayName );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_DISPLAY_NAME_ESCAPED}}', simulationDisplayName.replace( /'/g, '\\\'' ) );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_VERSION}}', version );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_LATEST_VERSION}}', latestVersion );
      contents = ChipperStringUtils.replaceAll( contents, '{{SIMULATION_IS_BUILT}}', 'true' );
      contents = ChipperStringUtils.replaceAll( contents, '{{LIB_OUTPUT_FILE}}', LIB_OUTPUT_FILE );
      contents = ChipperStringUtils.replaceAll( contents, '{{PHET_IO_LIB_RELATIVE_PATH}}', pathToLib );
      contents = ChipperStringUtils.replaceAll( contents, '{{Built API Docs not available in RequireJS mode}}', 'API Docs' );

      // phet-io-wrappers/common will be in the top level of wrappers/ in the build directory
      contents = ChipperStringUtils.replaceAll( contents, WRAPPER_COMMON_FOLDER + '/', 'common/' );
    }
    if ( contents !== originalContents ) {
      return contents;
    }
    else {
      return null; // signify no change (helps for images)
    }
  };

// a list of the phet-io wrappers that are built with the phet-io sim
  const wrappers = fs.readFileSync( '../chipper/data/wrappers', 'utf-8' ).trim().split( '\n' ).map( wrappers => wrappers.trim() );

  // Files and directories from wrapper folders that we don't want to copy
  const wrappersBlacklist = [ '.git', 'README.md', '.gitignore', 'node_modules', 'package.json', 'build' ];

  const libFileNames = LIB_FILES.map( function( filePath ) {
    const parts = filePath.split( '/' );
    return parts[ parts.length - 1 ];
  } );

  // Don't copy over the files that are in the lib file, this way we can catch wrapper bugs that are not pointing to the lib.
  const fullBlacklist = wrappersBlacklist.concat( libFileNames );

  // wrapping function for copying the wrappers to the build dir
  const copyWrapper = function( src, dest ) {
    copyDirectory( src, dest, filterWrapper, {
      blacklist: fullBlacklist
    } );
  };

  // Make sure to copy the phet-io-wrappers common wrapper code too.
  wrappers.push( WRAPPER_COMMON_FOLDER );
  wrappers.forEach( function( wrapper ) {

    const wrapperParts = wrapper.split( '/' );

    // either take the last path part, or take the first (repo name) and remove the wrapper prefix
    const wrapperName = wrapperParts.length > 1 ? wrapperParts[ wrapperParts.length - 1 ] : wrapperParts[ 0 ].replace( DEDICATED_REPO_WRAPPER_PREFIX, '' );

    // Copy the wrapper into the build dir /wrappers/, exclude the blacklist
    copyWrapper( `../${wrapper}`, `${wrappersLocation}${wrapperName}` );
  } );

  // Copy the wrapper index into the top level of the build dir, exclude the blacklist
  copyWrapper( '../phet-io-wrappers/index', `${buildDir}` );

  // Create the lib file that is minified and publicly available under the /lib folder of the build
  handleLib( buildDir, filterWrapper );

  // Create the contrib folder and add to it third party libraries used by wrappers.
  handleContrib( buildDir );

  // Create the rendered jsdoc in the `doc` folder
  await handleJSDOC( buildDir );
};

/**
 * Given the list of lib files, apply a filter function to them. Then minify them and consolidate into a single string.
 * Finally write them to the build dir with a license prepended. See https://github.com/phetsims/phet-io/issues/353

 * @param {string} buildDir
 * @param {Function} filter - the filter function used when copying over wrapper files to fix relative paths and such.
 *                            Has arguments like "function(abspath, contents)"
 */
const handleLib = function( buildDir, filter ) {
  grunt.log.debug( 'Creating phet-io lib file from: ', LIB_FILES );

  grunt.file.mkdir( `${buildDir}lib` );

  let consolidated = '';
  LIB_FILES.forEach( function( libFile ) {
    const contents = grunt.file.read( libFile );

    const filteredContents = filter && filter( libFile, contents );

    // The filter should return null if nothing changes
    consolidated += filteredContents ? filteredContents : contents;
  } );

  const minified = minify( consolidated, {
    stripAssertions: false
  } );

  grunt.file.write( `${buildDir}lib/${LIB_OUTPUT_FILE}`, LIB_COPYRIGHT_HEADER + '\n\n' + minified );
};

/**
 * Copy all of the third party libraries from sherpa to the build directory under the 'contrib' folder.
 * @param {string} buildDir
 */
const handleContrib = function( buildDir ) {
  grunt.log.debug( 'Creating phet-io contrib folder' );

  CONTRIB_FILES.forEach( function( filePath ) {
    const filePathParts = filePath.split( '/' );

    const filename = filePathParts[ filePathParts.length - 1 ];

    grunt.file.copy( filePath, `${buildDir}contrib/${filename}` );

  } );
};

/**
 * Generate jsdoc and put it in "build/phet-io/doc"
 * @param {string} buildDir
 * @returns {Promise<void>}
 */
const handleJSDOC = async function( buildDir ) {
  grunt.log.debug( 'generating jsdoc for phet-io from: ', JSDOC_FILES );

  // First we tried to run the jsdoc binary as the cmd, but that wasn't working, and was quite finicky. Then @samreid
  // found https://stackoverflow.com/questions/33664843/how-to-use-jsdoc-with-gulp which recommends the following method
  // (node executable with jsdoc js file)
  await execute( 'node', [ '../chipper/node_modules/jsdoc/jsdoc.js' ].concat(
    JSDOC_FILES.concat( [ '-c', '../phet-io/doc/wrapper/jsdoc-config.json',
      '-d', `${buildDir}doc/`, '--readme', JSDOC_README_FILE ] ) ),
    { cwd: process.cwd(), shell: true } );
};<|endoftext|>