<commit_before>var fs = require('fs');
var child_process = require('child_process')
var max_sleep = 300
//Added a comment that achieves no real goal.

var commit_messages = [
  "Fixing an important issue with the universe.",
  "Someone poisoned the waterhole!",
  "You feeling lucky punk?",
  "Five bullets or six?",
  "Fixed memory parsing error #1337",
  "Cleaned out the intertubes",
  "AAAAAAAAAAARGH!",
  "Fixed a typo",
  "Updated the readme",
  "Fixing a typo added in the last readme update",
  "GET A HAIRCUT!",
  "Updated the readme again",
  "Found an issue with the letter 'a', so I fixed it.",
  "Incompatible dimensions found, Cthuhlu Error 666",
  "Who you calling a fool?",
  "How many more of these can I make?",
  "Fixing an error introduced in the last commit",
  "Re-updating the readme.",
  "Work in Progress",
  "WIP",
  "Quick fix",
  "Grammar fix",
  "Fixing punctuation",
  "Correcting style",
  "Logic error"
]

if ( process.argv[ 2 ] && process.argv[ 3 ] ) {
  var inFile = process.argv[ 2 ]
  var outFile = process.argv[ 3 ]
  if (process.argv [ 4 ]) max_sleep = process.argv [ 4 ]
  console.info("Writing from %s to %s, with up to %s seconds between commits", inFile, outFile, max_sleep)
  var outFD = fs.openSync(outFile, 'w')
  fs.readFile(inFile, f<commit_msg>Logic error<commit_after>var fs = require('fs');
var child_process = require('child_process')
var max_sleep = 300
//Added a comment that achieves no real goal.

var commit_messages = [
  "Fixing an important issue with the universe.",
  "Someone poisoned the waterhole!",
  "You feeling lucky punk?",
  "Five bullets or six?",
  "Fixed memory parsing error #1337",
  "Cleaned out the intertubes",
  "AAAAAAAAAAARGH!",
  "Fixed a typo",
  "Updated the readme",
  "Fixing a typo added in the last readme update",
  "GET A HAIRCUT!",
  "Updated the readme again",
  "Found an issue with the letter 'a', so I fixed it.",
  "Incompatible dimensions found, Cthuhlu Error 666",
  "Who you calling a fool?",
  "How many more of these can I make?",
  "Fixing an error introduced in the last commit",
  "Re-updating the readme.",
  "Work in Progress",
  "WIP",
  "Quick fix",
  "Grammar fix",
  "Fixing punctuation",
  "Correcting style",
  "Logic error"
]

if ( process.argv[ 2 ] && process.argv[ 3 ] ) {
  var inFile = process.argv[ 2 ]
  var outFile = process.argv[ 3 ]
  if (process.argv [ 4 ]) max_sleep = process.argv [ 4 ]
  console.info("Writing from %s to %s, with up to %s seconds between commits", inFile, outFile, max_sleep)
  var outFD = fs.openSync(outFile, 'w')
  fs.readFile(inFile, fu<|endoftext|>


<commit_before>var child_process = require('child_process')
var fs = require('fs');
var max_sleep = 300
var step = 10
//Added a comment that achieves no real goal.
//Non-blocking loop
//Adjustable step
//Status line
//Add a rough time indicator
//First cut at libfaketime support

var commit_messages = [
  "Fixing an important issue with the universe.",
  "Someone poisoned the waterhole!",
  "You feeling lucky punk?",
  "Five bullets or six?",
  "Fixed memory parsing error #1337",
  "Cleaned out the intertubes",
  "AAAAAAAAAAARGH!",
  "Fixed a typo",
  "Updated the readme",
  "Fixing a typo added in the last readme update",
  "GET A HAIRCUT!",
  "Updated the readme again",
  "Found an issue with the letter 'a', so I fixed it.",
  "Incompatible dimensions found, Cthuhlu Error 666",
  "Who you calling a fool?",
  "How many more of these can I make?",
  "Fixing an error introduced in the last commit",
  "Re-updating the readme.",
  "Work in Progress",
  "WIP",
  "Quick fix",
  "Grammar fix",
  "Fixing punctuation",
  "Correcting style",
  "Logic error",
  "Almost there",
  "Nearly got it",
  "Not quite working",
  "Ooops!",
  "Did I do that?",
  "Commit all the required changes",
  "Really commit all the required changes",
  "Should work now",
  "Back out the last commit",
  "Reverting changes",
  "One more time...",
  "Re-commiting last",
  "One more User Story done",
  "Can work my way through the Kanban",
  "Sprint complete",
  "Well that seemed pretty easy",
  "What do you mean you can't pack up the commit?",
  "Merge in master",
  "Fast-forwarding changes"
]

if ( process.argv[ 2 ] && process.argv[ 3 ] ) {
  var inFile = process.argv[ 2 ]
  var outFile = process.argv[ 3 ]
  if (inFile == outFile) {
    console.error("Aborted: infile and outfile must be different")
    return(-1);
  }
  if (process.argv [ 4 ]) max_sleep = process.argv [ 4 ]
  if (process.argv [ 5 ]) step = parseInt(process.argv [ 5 ])
  console.info("Writing from %s to %s, with up to %s seconds between commits of %s bytes", inFile, outFile, max_sleep, step)
  var outFD = fs.openSync(outFile, 'w')
  fs.readFile(inFile, function(err,data) {
    var length = data.length
    var commit_count = Math.ceil(length / step)
    var commit_time = commit_count * max_sleep / 2
    var commit_time_hour = Math.floor(commit_time / 3600)
    var commit_time_rem = commit_time - (commit_time_hour * 3600)
    var commit_time_min = Math.floor(commit_time_rem / 60)
    var commit_time_sec = commit_time_rem - (commit_time_min * 60)
    console.info("Input file is %s Bytes, this will generate %s commits and take approximately %s hours %s minutes %s seconds", length, commit_count, commit_time_hour, commit_time_min, commit_time_sec)
    var t_offset = 0
    if (process.env.FAKETIME) {
      // 20% buffer, to reduce the likelihood of going in to the future
      t_offset = commit_time * -1.2;
      faketime(t_offset);
    }
    try {
      child_process.execFileSync('git', ['add', outFile])
    } catch (e) {
      console.error("Couldn't add %s to git: %s", outFile, e)
    }
    var counter = 0
    process.stdout.write("Queueing up the first commit...\r");
    var timeout;
    if (process.env.FAKETIME) {
      faketime(t_offset);
      timeout = 20;
    } else {
      timeout = randomIntFromInterval(0, max_sleep) * 1000
    }
    setTimeout(commit, timeout, t_offset, outFD, outFile, data, counter, length);
  })
} else {
  console.info("Usage:")
  console.info("node commit.js infile outfile [max_time_in_seconds] [step]")
}

function sleep(seconds) {
  var endTime = new Date().getTime() + (seconds * 1000);
  while (new Date().getTime() <= endTime) {;}
}

function randomIntFromInterval(min,max)
{
   return Math.floor(Math.random()*(max-min+1)+min);
}

function commit(t_offset, outFD, outFile, data, counter, length)
{
  if (counter < length)
  {
    var message = commit_messages[randomIntFromInterval(0, commit_messages.length - 1)]
    var args = ['commit', outFile, '-m', message]
    if (counter + step >= length) step = length - counter
    process.stdout.write("\033[2K Writing " + counter + "/" + length + " bytes: " + message + "\r");
    fs.writeSync(outFD, data.slice(counter, counter+step), 0, step)
    child_process.execFileSync('/Applications/Xcode.app/Contents/Developer/usr/bin/git', args)
    var timeout = randomIntFromInterval(0, max_sleep)
    counter = counter + step
    if (process.env.FAKETIME) {
      t_offset = t_offset + timeout;
      faketime(t_offset);
      timeout = 20;
    } else {
      timeout = timeout * 1000
    }
    setTimeout(commit, timeout, t_offset, outFD, outFile, data, counter, length);
  } else <commit_msg>Five bullets or six?<commit_after>var child_process = require('child_process')
var fs = require('fs');
var max_sleep = 300
var step = 10
//Added a comment that achieves no real goal.
//Non-blocking loop
//Adjustable step
//Status line
//Add a rough time indicator
//First cut at libfaketime support

var commit_messages = [
  "Fixing an important issue with the universe.",
  "Someone poisoned the waterhole!",
  "You feeling lucky punk?",
  "Five bullets or six?",
  "Fixed memory parsing error #1337",
  "Cleaned out the intertubes",
  "AAAAAAAAAAARGH!",
  "Fixed a typo",
  "Updated the readme",
  "Fixing a typo added in the last readme update",
  "GET A HAIRCUT!",
  "Updated the readme again",
  "Found an issue with the letter 'a', so I fixed it.",
  "Incompatible dimensions found, Cthuhlu Error 666",
  "Who you calling a fool?",
  "How many more of these can I make?",
  "Fixing an error introduced in the last commit",
  "Re-updating the readme.",
  "Work in Progress",
  "WIP",
  "Quick fix",
  "Grammar fix",
  "Fixing punctuation",
  "Correcting style",
  "Logic error",
  "Almost there",
  "Nearly got it",
  "Not quite working",
  "Ooops!",
  "Did I do that?",
  "Commit all the required changes",
  "Really commit all the required changes",
  "Should work now",
  "Back out the last commit",
  "Reverting changes",
  "One more time...",
  "Re-commiting last",
  "One more User Story done",
  "Can work my way through the Kanban",
  "Sprint complete",
  "Well that seemed pretty easy",
  "What do you mean you can't pack up the commit?",
  "Merge in master",
  "Fast-forwarding changes"
]

if ( process.argv[ 2 ] && process.argv[ 3 ] ) {
  var inFile = process.argv[ 2 ]
  var outFile = process.argv[ 3 ]
  if (inFile == outFile) {
    console.error("Aborted: infile and outfile must be different")
    return(-1);
  }
  if (process.argv [ 4 ]) max_sleep = process.argv [ 4 ]
  if (process.argv [ 5 ]) step = parseInt(process.argv [ 5 ])
  console.info("Writing from %s to %s, with up to %s seconds between commits of %s bytes", inFile, outFile, max_sleep, step)
  var outFD = fs.openSync(outFile, 'w')
  fs.readFile(inFile, function(err,data) {
    var length = data.length
    var commit_count = Math.ceil(length / step)
    var commit_time = commit_count * max_sleep / 2
    var commit_time_hour = Math.floor(commit_time / 3600)
    var commit_time_rem = commit_time - (commit_time_hour * 3600)
    var commit_time_min = Math.floor(commit_time_rem / 60)
    var commit_time_sec = commit_time_rem - (commit_time_min * 60)
    console.info("Input file is %s Bytes, this will generate %s commits and take approximately %s hours %s minutes %s seconds", length, commit_count, commit_time_hour, commit_time_min, commit_time_sec)
    var t_offset = 0
    if (process.env.FAKETIME) {
      // 20% buffer, to reduce the likelihood of going in to the future
      t_offset = commit_time * -1.2;
      faketime(t_offset);
    }
    try {
      child_process.execFileSync('git', ['add', outFile])
    } catch (e) {
      console.error("Couldn't add %s to git: %s", outFile, e)
    }
    var counter = 0
    process.stdout.write("Queueing up the first commit...\r");
    var timeout;
    if (process.env.FAKETIME) {
      faketime(t_offset);
      timeout = 20;
    } else {
      timeout = randomIntFromInterval(0, max_sleep) * 1000
    }
    setTimeout(commit, timeout, t_offset, outFD, outFile, data, counter, length);
  })
} else {
  console.info("Usage:")
  console.info("node commit.js infile outfile [max_time_in_seconds] [step]")
}

function sleep(seconds) {
  var endTime = new Date().getTime() + (seconds * 1000);
  while (new Date().getTime() <= endTime) {;}
}

function randomIntFromInterval(min,max)
{
   return Math.floor(Math.random()*(max-min+1)+min);
}

function commit(t_offset, outFD, outFile, data, counter, length)
{
  if (counter < length)
  {
    var message = commit_messages[randomIntFromInterval(0, commit_messages.length - 1)]
    var args = ['commit', outFile, '-m', message]
    if (counter + step >= length) step = length - counter
    process.stdout.write("\033[2K Writing " + counter + "/" + length + " bytes: " + message + "\r");
    fs.writeSync(outFD, data.slice(counter, counter+step), 0, step)
    child_process.execFileSync('/Applications/Xcode.app/Contents/Developer/usr/bin/git', args)
    var timeout = randomIntFromInterval(0, max_sleep)
    counter = counter + step
    if (process.env.FAKETIME) {
      t_offset = t_offset + timeout;
      faketime(t_offset);
      timeout = 20;
    } else {
      timeout = timeout * 1000
    }
    setTimeout(commit, timeout, t_offset, outFD, outFile, data, counter, length);
  } else {<|endoftext|>


<commit_before>// the common object model of triton on server and client -sra.

module.exports = {
	page: require("./core/PageObject"),
	baseStore: require('./core/stores/BaseStore'),
	link: require('./core/components/Link'),
	actions: require('./core/actions'),
	objectGraph: require('./core/util/ObjectGraph'),
	bundleNameUtil: require("./core/util/bundleNameUtil"),
	config: require("./core/config")
}<commit_msg>Responding to code review comment and keeping the Triton capitalization consistent with file names<commit_after>// the common object model of triton on server and client -sra.

module.exports = {
	PageObject: require("./core/PageObject"),
	BaseStore: require('./core/stores/BaseStore'),
	Link: require('./core/components/Link'),
	actions: require('./core/actions'),
	ObjectGraph: require('./core/util/ObjectGraph'),
	bundleNameUtil: require("./core/util/bundleNameUtil"),
	config: require("./core/config")
}<|endoftext|>


<commit_before>getCookieValue=function(f) {
        if (!f) {
            return null
        }
        var a = f + "=";
        var d = document.cookie.split(/;\s/g);
        for (var b = 0; b < d.length; b++) {
            var e = d[b];
            while (e.charAt(0) == " ") {
                e = e.substring(1, e.length)
            }
            if (e.indexOf(a) === 0) {
                return e.substring(a.length, e.length)
            }
        }
        return null
    }
protocol=("https:" === document.location.protocol ? "https://" : "http://")
head=document.getElementsByTagName("HEAD").item(0);
element=function(item){return document.createElement(item);
deleteCookie=function(a) {
  if (!a) {
   return
  }
  document.cookie = a + "=;expires=Thu, 01-Jan-1970 00:00:01 GMT;path=/; example.com;"
 }
pathname = window.location.pathname; // Returns path only
url      = window.location.href;   // Return full url
getHasValue= window.location.hash.substr(1);
<commit_msg>adding active tab snippest and removing white space<commit_after>getCookieValue=function(f) { if (!f) { return null } var a = f + "=";  var d = document.cookie.split(/;\s/g); for (var b = 0; b < d.length; b++) {
            var e = d[b];  while (e.charAt(0) == " ") { e = e.substring(1, e.length)  }if (e.indexOf(a) === 0) { return e.substring(a.length, e.length)
            } } return null
    }
protocol=("https:" === document.location.protocol ? "https://" : "http://")
head=document.getElementsByTagName("HEAD").item(0);
element=function(item){return document.createElement(item);
deleteCookie=function(a) {  if (!a) { return } document.cookie = a + "=;expires=Thu, 01-Jan-1970 00:00:01 GMT;path=/; example.com;" }
pathname = window.location.pathname; // Returns path only
url      = window.location.href;   // Return full url
getHasValue= window.location.hash.substr(1);
getWithHasValue= window.location.hash.substr(0);
activeTab=function (tab){ $('.nav-tabs a[href="#' + tab + '"]').tab('show');} };
<|endoftext|>


<commit_before>/**
* Unobtrusive normalization support library for jQuery Validate and jQuery Unobtrusive Validation
* @copyright cwmillerjr
* @version
* @module jquery-validation-unobtrusive-normalize
*/

(function (factory) {
    if (typeof define === 'function' && define.amd) {
        define("jquery-validation-unobtrusive-normalize", ["jquery-validation-unobtrusive"], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory(require("jquery-validation-unobtrusive"));
    } else {
        jQuery.validator.unobtrusive.normalizers = factory(jQuery.validator.unobtrusive);
    }
}(function ($jQuno) {

        /**
         * Callback used to normalize a value before validation.
         * @callback normalizer
         * @param {string} value - Value to be normalized.
         * @returns {string} Normalized value.
         */

        /**
         * Unobtrusive normalization add-on for jQuery Validation Unobtrusive
         * @classdesc This dictionary catalogs the normalizer functions available to be used via the data-val-normalizer="name" attributes.
         * @constructor
         */
        function normalizerDictionary () {

            var self = this;

            /**
             * @function
             * @name addNormalizer
             * Adds a named normalizer function to the dictionary.
             * @example
             * $.validator.unobtrusive.normalize.addNormalizer('remove-amps',function(value){return (value || '').replace('&','');});
             * $.validator.unobtrusive.normalize.addNormalizer('remove-amps','first next last');
             * @param {string} name - The name of the normalizer.
             * @param {(string|normalizer)} method - The method to normalize a value or a space delimited list of normalizer names to execute.
             */
            this.addNormalizer = function (name, method) {
                method.name = name;
                self[name] = method;
            }
        }

        $jQuno.normalizers = new normalizerDictionary ();

        /**
         * Inner normalization helper.
         * @param {string} value - Value being validated.
         * @param {string} normalizer - Space delimited list of named normalizers to apply.
         * @this DOMElement being validated.
         */
        function normalize (value, normalizer) { //this is the DOMElement whose value is being normalized
        //attributes that are space delimited are a list of rules, so split them and find each individually.
        if (/\s/.test(normalizer)) {
            normalizer.split(/\s+/).forEach(function (childNormalizer) {
                value = normalize.call(this, value, childNormalizer);
            });
        } else {
            //a rule in the dictionary can be a list of space delimited rules as well.
            normalizerFn = $jQuno.normalizers[normalizer];
            if (typeof normalizerFn === 'string') {
                value = normalize.call(this, value, normalizerFn);
            }
            else {
                value = normalizerFn.call(this, value);
            }
        }
        return value;
    }

    //Adds the unobtrusive "normalizer" adapter to Unobtrusive Validation
    $jQuno.adapters.add("normalizer", [], function (options) {
        options.rules["normalizer"] = function (value) { //this is the DOMElement whose value is being normalized
            return normalize.call(this, value, options.message)
        }
    });

    $jQuno.normalizers.addNormalizer("remove-periods", function (val) {
        var result = val;
        if (val) {
            result = val.replace(/./g, '');
        }
        return result;
    });
    $jQuno.normalizers.addNormalizer("remove-commas", function (val) {
        var result = val;
        if (val) {
            result = val.replace(/,/g, '');
        }
        return result;
    });
    $jQuno.normalizers.addNormalizer("number-only", function (val) {
        var result = val;
        if (val) {
            result = val.replace(/[^0-9\.]/g);
        }
        return result;
    });
    $jQuno.normalizers.addNormalizer("remove-currency", function (value) {
        if (value) {
            return value.replace(/[$]/g, "");
        }
        return value;
    });
    $jQuno.normalizers.addNormalizer("currency-to-number", "remove-currency remove-commas");
    $jQuno.normalizers.addNormalizer("remove-fractional", function (value) {
        if (value) {
            return value.replace(/\.\d*$/g, "");
        }
        return value;
    });
    $jQuno.normalizers.addNormalizer("remove-hyphens", function (value) {
        if (value) {
            return value.replace(/[-]/g, "");
        }
        return value;
    });
    $jQuno.normalizers.addNormalizer("normalize-number", function (value) {
        if (value) {
            value = value.trim().replace(",", "");
            return value.replace(/^(-?)\$?(-?)([0-9]*\.[0-9]+|[0-9]+)(e-?[0-9]+)?(-?)$/gi, "$1$2$5$3$4");
        }
        return value;
    });

    return $jQuno.normalizers;
}));<commit_msg>fix escaping in remove-periods in regex; refactor<commit_after>/**
* Unobtrusive normalization support library for jQuery Validate and jQuery Unobtrusive Validation
* @copyright cwmillerjr
* @version
* @module jquery-validation-unobtrusive-normalize
*/

(function (factory) {
    if (typeof define === 'function' && define.amd) {
        define("jquery-validation-unobtrusive-normalize", ["jquery-validation-unobtrusive"], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory(require("jquery-validation-unobtrusive"));
    } else {
        jQuery.validator.unobtrusive.normalizers = factory(jQuery.validator.unobtrusive);
    }
}(function ($jQuno) {

        /**
         * Callback used to normalize a value before validation.
         * @callback normalizer
         * @param {string} value - Value to be normalized.
         * @returns {string} Normalized value.
         */

        /**
         * Unobtrusive normalization add-on for jQuery Validation Unobtrusive
         * @classdesc This dictionary catalogs the normalizer functions available to be used via the data-val-normalizer="name" attributes.
         * @constructor
         */
        function normalizerDictionary () {

            var self = this;

            /**
             * @function
             * @name addNormalizer
             * Adds a named normalizer function to the dictionary.
             * @example
             * $.validator.unobtrusive.normalize.addNormalizer('remove-amps',function(value){return (value || '').replace('&','');});
             * $.validator.unobtrusive.normalize.addNormalizer('remove-amps','first next last');
             * @param {string} name - The name of the normalizer.
             * @param {(string|normalizer)} method - The method to normalize a value or a space delimited list of normalizer names to execute.
             */
            this.addNormalizer = function (name, method) {
                method.name = name;
                self[name] = method;
            }
        }

        $jQuno.normalizers = new normalizerDictionary ();

        /**
         * Inner normalization helper.
         * @param {string} value - Value being validated.
         * @param {string} normalizer - Space delimited list of named normalizers to apply.
         * @this DOMElement being validated.
         */
        function normalize (value, normalizer) { //this is the DOMElement whose value is being normalized
        //attributes that are space delimited are a list of rules, so split them and find each individually.
        if (/\s/.test(normalizer)) {
            normalizer.split(/\s+/).forEach(function (childNormalizer) {
                value = normalize.call(this, value, childNormalizer);
            });
        } else {
            //a rule in the dictionary can be a list of space delimited rules as well.
            normalizerFn = $jQuno.normalizers[normalizer];
            if (typeof normalizerFn === 'string') {
                value = normalize.call(this, value, normalizerFn);
            }
            else {
                value = normalizerFn.call(this, value);
            }
        }
        return value;
    }

    //Adds the unobtrusive "normalizer" adapter to Unobtrusive Validation
    $jQuno.adapters.add("normalizer", [], function (options) {
        options.rules["normalizer"] = function (value) { //this is the DOMElement whose value is being normalized
            return normalize.call(this, value, options.message)
        }
    });

    $jQuno.normalizers.addNormalizer("remove-periods", function (val) {
        if (value) {
            return value.replace(/\./g, '');
        }
        return value;
    });
    $jQuno.normalizers.addNormalizer("remove-commas", function (value) {
        if (value) {
            return value.replace(/,/g, '');
        }
        return value;
    });
    $jQuno.normalizers.addNormalizer("number-only", function (value) {
        if (value) {
            return value.replace(/[^0-9\.]/g);
        }
        return value;
    });
    $jQuno.normalizers.addNormalizer("remove-currency", function (value) {
        if (value) {
            return value.replace(/[$]/g, "");
        }
        return value;
    });
    $jQuno.normalizers.addNormalizer("currency-to-number", "remove-currency remove-commas");
    $jQuno.normalizers.addNormalizer("remove-fractional", function (value) {
        if (value) {
            return value.replace(/\.\d*$/g, "");
        }
        return value;
    });
    $jQuno.normalizers.addNormalizer("remove-hyphens", function (value) {
        if (value) {
            return value.replace(/[-]/g, "");
        }
        return value;
    });
    $jQuno.normalizers.addNormalizer("normalize-number", function (value) {
        if (value) {
            value = value.trim().replace(",", "");
            return value.replace(/^(-?)\$?(-?)([0-9]*\.[0-9]+|[0-9]+)(e-?[0-9]+)?(-?)$/gi, "$1$2$5$3$4");
        }
        return value;
    });

    return $jQuno.normalizers;
}));
<|endoftext|>


<commit_before>/**
 * Helper function that will load data from the Princeton Election Consortium's endpoints
 * and update the extension's HTML pages with the data
 *
 * @returns Void
 */
function updatePECBackground() {
	updatePEC('background');
}

/**
 * Helper function that will load data from the Princeton Election Consortium's endpoints
 * and update the extension's HTML pages with the data
 *
 *
 * @returns Void
 */
function updatePECPopup() {
	updatePEC('popup');
}

/**
 * Helper function that will load data from the Princeton Election Consortium's endpoints
 * and update the extension's HTML pages with the data
 *
 * @param   String target one of 'popup' or 'background'
 *
 * @returns Void
 */
function updatePEC(target) {
	// the 'current_ev.html' is a microformat page consisting of a UL with a number of child LI elements.
	// the content of these change with the election season and we may update our formatting below to adjust
	$("#raw").load("http://election.princeton.edu/wp-content/uploads/autotext/current_ev.html", function() {
		var newestPost = '';
		var newestPostByline = '';

		var lidata = new Array();

		for (i=0; i < 6; i++) {
			val = $("#raw li").eq(i).text();
			if (val !='' && val != 'RSS') {
				lidata[i] = val;
			}
		}

		if (target == 'popup') {

			for (i=0; i< 6; i++) {
				if (typeof(lidata[i] != 'undefined')) {
					$("#li" + i).html(lidata[i]);
				}
			}

			// load the RSS feed so we can link to the newest post
			$.get("http://election.princeton.edu/feed", function(data) {
				var $xml = $(data);
				$xml.find("item:first").each(function() {
					var $this = $(this),
						item = {
							title: $this.find("title").text(),
							link: $this.find("link").text(),
							description: $this.find("description").text(),
							pubDate: $this.find("pubDate").text(),
							author: $this.find("creator").text()

					}

					newestPost = '' + item.title + '';
					newestPostByline = 'by ' + item.author + ' at ' + item.pubDate;
				  $("#newest-post-link").html(newestPost);
				  $("#newest-post-byline").html(newestPostByline);
				});
			});

			// graph image
			var graph_image_url = 'http://election.princeton.edu/wp-content/uploads/autographics/EV_histogram_today-200px.png';
			$("#graph-image").attr("src", graph_image_url);

			// map image
			var map_image_url = "http://www.270towin.com/map-images/princeton-election-consortium";
			$("#map-image").attr("src", map_image_url);

			// hide spinner
			$("#spinner").hide();

			// reveal the loaded content container
			$("#loaded-content").show();
		}

		//chrome.browserAction.setBadgeText({ text: mm.replace("Meta-Margin: ", "").replace("D +","D").replace("R +", "R").replace("%","") });

		var title = lidata[0] + ' ' + lidata[1];
		var badge = '';
		chrome.browserAction.setBadgeText({ text: badge });
		chrome.browserAction.setTitle( { title: title } );

	});

}<commit_msg>Update URL to main output data<commit_after>/**
 * Helper function that will load data from the Princeton Election Consortium's endpoints
 * and update the extension's HTML pages with the data
 *
 * @returns Void
 */
function updatePECBackground() {
	updatePEC('background');
}

/**
 * Helper function that will load data from the Princeton Election Consortium's endpoints
 * and update the extension's HTML pages with the data
 *
 *
 * @returns Void
 */
function updatePECPopup() {
	updatePEC('popup');
}

/**
 * Helper function that will load data from the Princeton Election Consortium's endpoints
 * and update the extension's HTML pages with the data
 *
 * @param   String target one of 'popup' or 'background'
 *
 * @returns Void
 */
function updatePEC(target) {
	// the 'current_ev.html' is a microformat page consisting of a UL with a number of child LI elements.
	// the content of these change with the election season and we may update our formatting below to adjust
	$("#raw").load("http://election.princeton.edu/code/output/current_ev.html", function() {
		var newestPost = '';
		var newestPostByline = '';

		var lidata = new Array();

		for (i=0; i < 6; i++) {
			val = $("#raw li").eq(i).text();
			if (val !='' && val != 'RSS') {
				lidata[i] = val;
			}
		}

		if (target == 'popup') {

			for (i=0; i< 6; i++) {
				if (typeof(lidata[i] != 'undefined')) {
					$("#li" + i).html(lidata[i]);
				}
			}

			// load the RSS feed so we can link to the newest post
			$.get("http://election.princeton.edu/feed", function(data) {
				var $xml = $(data);
				$xml.find("item:first").each(function() {
					var $this = $(this),
						item = {
							title: $this.find("title").text(),
							link: $this.find("link").text(),
							description: $this.find("description").text(),
							pubDate: $this.find("pubDate").text(),
							author: $this.find("creator").text()

					}

					newestPost = '' + item.title + '';
					newestPostByline = 'by ' + item.author + ' at ' + item.pubDate;
				  $("#newest-post-link").html(newestPost);
				  $("#newest-post-byline").html(newestPostByline);
				});
			});

			// graph image
			var graph_image_url = 'http://election.princeton.edu/wp-content/uploads/autographics/EV_histogram_today-200px.png';
			$("#graph-image").attr("src", graph_image_url);

			// map image
			var map_image_url = "http://www.270towin.com/map-images/princeton-election-consortium";
			$("#map-image").attr("src", map_image_url);

			// hide spinner
			$("#spinner").hide();

			// reveal the loaded content container
			$("#loaded-content").show();
		}

		//chrome.browserAction.setBadgeText({ text: mm.replace("Meta-Margin: ", "").replace("D +","D").replace("R +", "R").replace("%","") });

		var title = lidata[0] + ' ' + lidata[1];
		var badge = '';
		chrome.browserAction.setBadgeText({ text: badge });
		chrome.browserAction.setTitle( { title: title } );

	});

}<|endoftext|>


<commit_before>// animate between the list view and the detail view, using FLIP animations
// https://aerotwist.com/blog/flip-your-animations/

var utils = require('./utils');
var themeManager = require('./themeManager');
var worker = require('./../shared/worker');
var each = require('lodash/collection/each');

var $ = document.querySelector.bind(document);

// elements
var detailView;
var detailViewContainer;
var detailPanel;
var monstersList;
var detailSprite;
var detailBackButton;
var spriteFacade;
var spinnerHolder;
var spinnerTimeout;

var screenWidth = window.innerWidth;
var screenHeight = window.innerHeight;

var cachedSpanHeight;
var dimensToSpriteRect = {};
var runningAnimationPartOne = false;
var queuedAnimation = false;
var queuedThemeColor;

var animationDelay = utils.canRunHighPerfAnims() ? 0 : 500;

function getScrollTop() {
  // browsers seem to disagree on this
  return document.body.scrollTop || document.documentElement.scrollTop;
}

function computeTransformsPartOne(nationalId) {
  console.time('computeTransformsPartOne()');

  var sourceSprite = monstersList.querySelector(`.sprite-${nationalId}`);
  var sourceTitleSpan = sourceSprite.parentElement.querySelector('span');

  var sourceSpriteRect = sourceSprite.getBoundingClientRect();
  var detailSpriteRect = getDetailSpriteRect();
  var sourceTitleSpanHeight = getSpanHeight(sourceTitleSpan);

  var spriteChangeX = sourceSpriteRect.left - detailSpriteRect.left;
  var spriteChangeY = sourceSpriteRect.top - detailSpriteRect.top;

  var scaleX = sourceSpriteRect.width / screenWidth;
  var scaleY = (sourceSpriteRect.height - sourceTitleSpanHeight) / screenHeight;
  var toX = sourceSpriteRect.left;
  var toY = sourceSpriteRect.top;

  var bgTransform = `translate(${toX}px,${toY}px) scale(${scaleX},${scaleY})`;
  var spriteTransform = `translate(${spriteChangeX}px, ${spriteChangeY}px)`;

  console.timeEnd('computeTransformsPartOne()');

  return {
    bgTransform,
    spriteTransform,
    spriteTop: detailSpriteRect.top + getScrollTop(),
    spriteLeft: detailSpriteRect.left
  };
}

function computeTransformsPartTwo(nationalId, outAnimation) {
  console.time('computeTransformsPartTwo()');

  // reeeaaally fling it away when animating out. looks better
  var slideInY = outAnimation ? screenHeight * 1.1 : screenHeight * 0.6;

  var fgTransform = `translateY(${slideInY}px)`;

  console.timeEnd('computeTransformsPartTwo()');

  return {
    fgTransform
  };
}

function createSpriteFacade() {
  spriteFacade = document.createElement('div');
  spriteFacade.classList.add('monster-sprite');
  spriteFacade.classList.add('monster-sprite-facade');
  spriteFacade.classList.add('hidden');
  spriteFacade.style.top = `0px`;
  spriteFacade.style.left = `0px`;
  document.body.appendChild(spriteFacade);
  return spriteFacade;
}

function styleSpriteFacade(nationalId, top, left, transform) {
  for (var i = 0; i < spriteFacade.classList.length; i++) {
    var className = spriteFacade.classList[i];
    if (/^sprite-/.test(className)) {
      spriteFacade.classList.remove(className);
      break;
    }
  }
  spriteFacade.classList.add(`sprite-${nationalId}`);
  spriteFacade.style.top = `${top}px`;
  spriteFacade.style.left = `${left}px`;
  spriteFacade.style.transform = transform;
  return spriteFacade;
}

function doInAnimationPartOne(nationalId) {
  document.body.style.overflowY = 'hidden'; // disable scrolling
  detailViewContainer.classList.remove('hidden');
  var transforms = computeTransformsPartOne(nationalId, false);
  var {bgTransform, spriteTransform, spriteTop, spriteLeft} = transforms;
  var targetBackground = detailView.querySelector('.detail-view-bg');
  var sourceSprite = monstersList.querySelector(`.sprite-${nationalId}`);
  targetBackground.style.background = sourceSprite.parentElement.style.background;
  var spriteFacade = styleSpriteFacade(nationalId, spriteTop, spriteLeft, spriteTransform);
  spriteFacade.classList.remove('hidden');
  detailBackButton.style.transform = 'translateX(-40px)';
  targetBackground.style.willChange = 'transform';
  targetBackground.style.transform = bgTransform;

  requestAnimationFrame(() => {
    // go go go!
    targetBackground.classList.add('animating');
    spriteFacade.classList.add('animating');
    targetBackground.style.transform = '';
    spriteFacade.style.transform = '';
  });

  function onAnimEnd() {
    console.log('done animating');
    targetBackground.classList.remove('animating');
    spriteFacade.classList.remove('animating');
    targetBackground.removeEventListener('transitionend', onAnimEnd);
    targetBackground.style.willChange = '';
  }

  targetBackground.addEventListener('transitionend', onAnimEnd);
  detailPanel.classList.add('hidden');

  requestAnimationFrame(() => {
    runningAnimationPartOne = false;
    if (queuedAnimation) {
      requestAnimationFrame(() => {
        queuedAnimation();
        queuedAnimation = null;
      });
    } else {
      // if the second animation is delayed more than 5 seconds,
      // show a spinner to reassure the user (only happens with slow
      // connections on first load)
      spinnerTimeout = setTimeout(() => {
        spinnerHolder.classList.add('shown');
      }, 5000);
    }
    if (queuedThemeColor) {
      themeManager.setThemeColor(queuedThemeColor);
      queuedThemeColor = null;
    }
  });
}

function doInAnimationPartTwo(nationalId) {
  spinnerHolder.classList.remove('shown');
  clearTimeout(spinnerTimeout);
  detailPanel.style.overflowY = 'auto'; // re-enable overflow on the panel
  document.body.style.overflowY = 'hidden'; // disable scrolling

  // hide monster moves until they're shown after the panel
  detailPanel.querySelector('.monster-moves').classList.add('hidden');
  detailPanel.classList.remove('hidden');
  detailSprite.style.opacity = 0;
  var {fgTransform} = computeTransformsPartTwo(nationalId, false);

  detailPanel.style.transform = fgTransform;

  requestAnimationFrame(() => {
    // go go go!
    detailPanel.classList.add('animating');
    detailBackButton.classList.add('animating');
    detailPanel.style.transform = '';
    detailBackButton.style.transform = '';
  });

  function onAnimEnd() {
    console.log('done animating part two');
    detailBackButton.classList.remove('animating');
    detailPanel.classList.remove('animating');

    spriteFacade.classList.add('hidden');

    detailSprite.style.opacity = 1;

    // don't run this until the panel has animated in, in order to keep the
    // the animation smooth
    worker.postMessage({
      type: 'movesDetail',
      nationalId: nationalId
    });

    detailPanel.removeEventListener('transitionend', onAnimEnd);
  }

  detailPanel.addEventListener('transitionend', onAnimEnd);
}

function doOutAnimation(nationalId) {
  detailPanel.scrollTop = 0; // scroll panel to top, disable scrolling during animation
  detailPanel.style.overflowY = 'visible';
  document.body.style.overflowY = 'visible'; // re-enable scrolling
  var transforms = computeTransformsPartOne(nationalId, true);
  var {bgTransform, spriteTransform, spriteTop, spriteLeft} = transforms;
  var {fgTransform} = computeTransformsPartTwo(nationalId, true);

  var targetBackground = detailView.querySelector('.detail-view-bg');
  var detailSprite = detailView.querySelector('.detail-sprite');
  var spriteFacade = styleSpriteFacade(nationalId, spriteTop, spriteLeft, '');
  spriteFacade.classList.remove('hidden');
  detailSprite.style.opacity = 0;
  targetBackground.style.willChange = 'transform';
  targetBackground.style.transform = '';
  detailPanel.style.transform = '';

  requestAnimationFrame(() => {
    // go go go!
    detailBackButton.classList.add('animating');
    detailPanel.classList.add('animating');
    targetBackground.classList.add('animating');
    spriteFacade.classList.add('animating');
    spriteFacade.style.transform = spriteTransform;
    targetBackground.style.transform = bgTransform;
    detailPanel.style.transform = fgTransform;
    detailBackButton.style.transform = 'translateX(-60px)';
  });

  function onAnimEnd() {
    console.log('done animating part one');
    detailPanel.classList.remove('animating');
    targetBackground.classList.remove('animating');
    spriteFacade.classList.remove('animating');
    detailBackButton.classList.remove('animating');
    targetBackground.style.transform = '';
    detailPanel.style.transform = '';
    detailViewContainer.classList.add('hidden');
    spriteFacade.classList.add('hidden');
    detailSprite.style.opacity = 1;
    // clean up any expanded move lists
    each(detailView.querySelectorAll('.moves-row-detail'), el => el.classList.add('hidden'));
    each(detailView.querySelectorAll('.dropdown-button-image'), el => el.style.transform = '');
    targetBackground.removeEventListener('transitionend', onAnimEnd);
    targetBackground.style.willChange = '';
  }

  themeManager.resetThemeColor();

  targetBackground.addEventListener('transitionend', onAnimEnd);
}

function init() {
  detailView = $('#detail-view');
  detailViewContainer = $('#detail-view-container');
  monstersList = $('#monsters-list');
  detailPanel = $('.detail-panel');
  spinnerHolder = $('.big-spinner-holder');
  detailSprite = detailView.querySelector('.detail-sprite');
  detailBackButton = detailView.querySelector('.detail-back-button');
  spriteFacade = createSpriteFacade();

}

function onResize() {
  // these are expensive to compute, so only compute when the window is resized
  screenWidth = window.innerWidth;
  screenHeight = window.innerHeight;
}

function getCachedDetailSpriteRect() {
  var dimens = screenWidth + '_' + screenHeight;
  return dimensToSpriteRect[dimens];
}

function getDetailSpriteRect() {
  // the size/location of the target sprite is fixed given the window size,
  // so we can just cache it and avoid the expensive getBoundingClientRect()
  var result = getCachedDetailSpriteRect();
  if (result) {
    return result;
  }
  var dimens = screenWidth + '_' + screenHeight;
  result = dimensToSpriteRect[dimens] = detailSprite.getBoundingClientRect();
  return result;
}

function getSpanHeight(span) {
  // this never changes, so we can cache it instead of recomputing style
  // every time
  if (typeof cachedSpanHeight === 'number') {
    return cachedSpanHeight;
  }
  var spanStyle = getComputedStyle(span);
  cachedSpanHeight = parseInt(spanStyle.height.replace('px', ''), 10);
  return cachedSpanHeight;
}

document.addEventListener('DOMContentLoaded', init);

window.addEventListener('resize', onResize);

function animateInPartOne(nationalId) {
  runningAnimationPartOne = true;
  // artificial delay to let the material animation play
  setTimeout(() => {
    requestAnimationFrame(() => doInAnimationPartOne(nationalId));
  }, 20);
}

function createPartTwoAnimation(nationalId) {
  // it is very likely that old Android phones will not run these two
  // animations very well if they occur at the same time. so stagger them
  if (animationDelay === 0) {
    return () => requestAnimationFrame(() => doInAnimationPartTwo(nationalId));
  }
  return () => setTimeout(() =>
    requestAnimationFrame(() => doInAnimationPartTwo(nationalId)),
  animationDelay);
}

function animateInPartTwo(nationalId) {
  var runPartTwoAnimation = createPartTwoAnimation(nationalId);
  if (runningAnimationPartOne) {
    console.log('waiting for part one animation to finish');
    queuedAnimation = runPartTwoAnimation;
  } else {
    console.log('running part two animation immediately');
    runPartTwoAnimation();
  }
}

function animateOut(nationalId) {
  requestAnimationFrame(() => doOutAnimation(nationalId));
}

function setThemeColor(color) {
  if (runningAnimationPartOne) {
    queuedThemeColor = color;
  } else {
    themeManager.setThemeColor(color);
  }
}

worker.addEventListener('message', e => {
  if (e.data.type === 'themeColor') {
    setThemeColor(e.data.color);
  }
});

module.exports = {
  animateInPartOne,
  animateInPartTwo,
  animateOut
};<commit_msg>fix ios sprite animation (fixes #19)<commit_after>// animate between the list view and the detail view, using FLIP animations
// https://aerotwist.com/blog/flip-your-animations/

var utils = require('./utils');
var themeManager = require('./themeManager');
var worker = require('./../shared/worker');
var each = require('lodash/collection/each');

var $ = document.querySelector.bind(document);

// elements
var detailView;
var detailViewContainer;
var detailPanel;
var monstersList;
var detailSprite;
var detailBackButton;
var spriteFacade;
var spinnerHolder;
var spinnerTimeout;

var screenWidth = window.innerWidth;
var screenHeight = window.innerHeight;

var cachedSpanHeight;
var dimensToSpriteRect = {};
var runningAnimationPartOne = false;
var queuedAnimation = false;
var queuedThemeColor;

var animationDelay = utils.canRunHighPerfAnims() ? 0 : 500;

function getScrollTop() {
  // browsers seem to disagree on this
  return document.body.scrollTop || document.documentElement.scrollTop;
}

function computeTransformsPartOne(nationalId) {
  console.time('computeTransformsPartOne()');

  var sourceSprite = monstersList.querySelector(`.sprite-${nationalId}`);
  var sourceTitleSpan = sourceSprite.parentElement.querySelector('span');

  var sourceSpriteRect = sourceSprite.getBoundingClientRect();
  var detailSpriteRect = getDetailSpriteRect();
  var sourceTitleSpanHeight = getSpanHeight(sourceTitleSpan);

  var spriteChangeX = sourceSpriteRect.left - detailSpriteRect.left;
  var spriteChangeY = sourceSpriteRect.top - detailSpriteRect.top;

  var scaleX = sourceSpriteRect.width / screenWidth;
  var scaleY = (sourceSpriteRect.height - sourceTitleSpanHeight) / screenHeight;
  var toX = sourceSpriteRect.left;
  var toY = sourceSpriteRect.top;

  var bgTransform = `translate(${toX}px,${toY}px) scale(${scaleX},${scaleY})`;
  var spriteTransform = `translate(${spriteChangeX}px, ${spriteChangeY}px)`;

  console.timeEnd('computeTransformsPartOne()');

  return {
    bgTransform,
    spriteTransform,
    spriteTop: detailSpriteRect.top + getScrollTop(),
    spriteLeft: detailSpriteRect.left
  };
}

function computeTransformsPartTwo(nationalId, outAnimation) {
  console.time('computeTransformsPartTwo()');

  // reeeaaally fling it away when animating out. looks better
  var slideInY = outAnimation ? screenHeight * 1.1 : screenHeight * 0.6;

  var fgTransform = `translateY(${slideInY}px)`;

  console.timeEnd('computeTransformsPartTwo()');

  return {
    fgTransform
  };
}

function createSpriteFacade() {
  spriteFacade = document.createElement('div');
  spriteFacade.classList.add('monster-sprite');
  spriteFacade.classList.add('monster-sprite-facade');
  spriteFacade.classList.add('hidden');
  spriteFacade.style.top = `0px`;
  spriteFacade.style.left = `0px`;
  document.body.appendChild(spriteFacade);
  return spriteFacade;
}

function styleSpriteFacade(nationalId, top, left, transform) {
  for (var i = 0; i < spriteFacade.classList.length; i++) {
    var className = spriteFacade.classList[i];
    if (/^sprite-/.test(className)) {
      spriteFacade.classList.remove(className);
      break;
    }
  }
  spriteFacade.classList.add(`sprite-${nationalId}`);
  spriteFacade.style.top = `${top}px`;
  spriteFacade.style.left = `${left}px`;
  spriteFacade.style.transform = transform;
  return spriteFacade;
}

function doInAnimationPartOne(nationalId) {
  document.body.style.overflowY = 'hidden'; // disable scrolling
  detailViewContainer.classList.remove('hidden');
  var transforms = computeTransformsPartOne(nationalId, false);
  var {bgTransform, spriteTransform, spriteTop, spriteLeft} = transforms;
  var targetBackground = detailView.querySelector('.detail-view-bg');
  var sourceSprite = monstersList.querySelector(`.sprite-${nationalId}`);
  targetBackground.style.background = sourceSprite.parentElement.style.background;
  var spriteFacade = styleSpriteFacade(nationalId, spriteTop, spriteLeft, spriteTransform);
  spriteFacade.classList.remove('hidden');
  detailBackButton.style.transform = 'translateX(-40px)';
  targetBackground.style.willChange = 'transform';
  targetBackground.style.transform = bgTransform;

  function onAnimEnd() {
    console.log('done animating');
    targetBackground.classList.remove('animating');
    spriteFacade.classList.remove('animating');
    targetBackground.removeEventListener('transitionend', onAnimEnd);
    targetBackground.style.willChange = '';
  }

  targetBackground.addEventListener('transitionend', onAnimEnd);
  detailPanel.classList.add('hidden');

  requestAnimationFrame(() => {
    // go go go!
    targetBackground.classList.add('animating');
    spriteFacade.classList.add('animating');
    targetBackground.style.transform = '';
    spriteFacade.style.transform = '';

    requestAnimationFrame(() => {
      runningAnimationPartOne = false;
      if (queuedAnimation) {
        requestAnimationFrame(() => {
          queuedAnimation();
          queuedAnimation = null;
        });
      } else {
        // if the second animation is delayed more than 5 seconds,
        // show a spinner to reassure the user (only happens with slow
        // connections on first load)
        spinnerTimeout = setTimeout(() => {
          spinnerHolder.classList.add('shown');
        }, 5000);
      }
      if (queuedThemeColor) {
        themeManager.setThemeColor(queuedThemeColor);
        queuedThemeColor = null;
      }
    });
  });
}

function doInAnimationPartTwo(nationalId) {
  spinnerHolder.classList.remove('shown');
  clearTimeout(spinnerTimeout);
  detailPanel.style.overflowY = 'auto'; // re-enable overflow on the panel
  document.body.style.overflowY = 'hidden'; // disable scrolling

  // hide monster moves until they're shown after the panel
  detailPanel.querySelector('.monster-moves').classList.add('hidden');
  detailPanel.classList.remove('hidden');
  detailSprite.style.opacity = 0;
  var {fgTransform} = computeTransformsPartTwo(nationalId, false);

  detailPanel.style.transform = fgTransform;

  requestAnimationFrame(() => {
    // go go go!
    detailPanel.classList.add('animating');
    detailBackButton.classList.add('animating');
    detailPanel.style.transform = '';
    detailBackButton.style.transform = '';
  });

  function onAnimEnd() {
    console.log('done animating part two');
    detailBackButton.classList.remove('animating');
    detailPanel.classList.remove('animating');

    spriteFacade.classList.add('hidden');

    detailSprite.style.opacity = 1;

    // don't run this until the panel has animated in, in order to keep the
    // the animation smooth
    worker.postMessage({
      type: 'movesDetail',
      nationalId: nationalId
    });

    detailPanel.removeEventListener('transitionend', onAnimEnd);
  }

  detailPanel.addEventListener('transitionend', onAnimEnd);
}

function doOutAnimation(nationalId) {
  detailPanel.scrollTop = 0; // scroll panel to top, disable scrolling during animation
  detailPanel.style.overflowY = 'visible';
  document.body.style.overflowY = 'visible'; // re-enable scrolling
  var transforms = computeTransformsPartOne(nationalId, true);
  var {bgTransform, spriteTransform, spriteTop, spriteLeft} = transforms;
  var {fgTransform} = computeTransformsPartTwo(nationalId, true);

  var targetBackground = detailView.querySelector('.detail-view-bg');
  var detailSprite = detailView.querySelector('.detail-sprite');
  var spriteFacade = styleSpriteFacade(nationalId, spriteTop, spriteLeft, '');
  spriteFacade.classList.remove('hidden');
  detailSprite.style.opacity = 0;
  targetBackground.style.willChange = 'transform';
  targetBackground.style.transform = '';
  detailPanel.style.transform = '';

  requestAnimationFrame(() => {
    // go go go!
    detailBackButton.classList.add('animating');
    detailPanel.classList.add('animating');
    targetBackground.classList.add('animating');
    spriteFacade.classList.add('animating');
    spriteFacade.style.transform = spriteTransform;
    targetBackground.style.transform = bgTransform;
    detailPanel.style.transform = fgTransform;
    detailBackButton.style.transform = 'translateX(-60px)';
  });

  function onAnimEnd() {
    console.log('done animating part one');
    detailPanel.classList.remove('animating');
    targetBackground.classList.remove('animating');
    spriteFacade.classList.remove('animating');
    detailBackButton.classList.remove('animating');
    targetBackground.style.transform = '';
    detailPanel.style.transform = '';
    detailViewContainer.classList.add('hidden');
    spriteFacade.classList.add('hidden');
    detailSprite.style.opacity = 1;
    // clean up any expanded move lists
    each(detailView.querySelectorAll('.moves-row-detail'), el => el.classList.add('hidden'));
    each(detailView.querySelectorAll('.dropdown-button-image'), el => el.style.transform = '');
    targetBackground.removeEventListener('transitionend', onAnimEnd);
    targetBackground.style.willChange = '';
  }

  themeManager.resetThemeColor();

  targetBackground.addEventListener('transitionend', onAnimEnd);
}

function init() {
  detailView = $('#detail-view');
  detailViewContainer = $('#detail-view-container');
  monstersList = $('#monsters-list');
  detailPanel = $('.detail-panel');
  spinnerHolder = $('.big-spinner-holder');
  detailSprite = detailView.querySelector('.detail-sprite');
  detailBackButton = detailView.querySelector('.detail-back-button');
  spriteFacade = createSpriteFacade();

}

function onResize() {
  // these are expensive to compute, so only compute when the window is resized
  screenWidth = window.innerWidth;
  screenHeight = window.innerHeight;
}

function getCachedDetailSpriteRect() {
  var dimens = screenWidth + '_' + screenHeight;
  return dimensToSpriteRect[dimens];
}

function getDetailSpriteRect() {
  // the size/location of the target sprite is fixed given the window size,
  // so we can just cache it and avoid the expensive getBoundingClientRect()
  var result = getCachedDetailSpriteRect();
  if (result) {
    return result;
  }
  var dimens = screenWidth + '_' + screenHeight;
  result = dimensToSpriteRect[dimens] = detailSprite.getBoundingClientRect();
  return result;
}

function getSpanHeight(span) {
  // this never changes, so we can cache it instead of recomputing style
  // every time
  if (typeof cachedSpanHeight === 'number') {
    return cachedSpanHeight;
  }
  var spanStyle = getComputedStyle(span);
  cachedSpanHeight = parseInt(spanStyle.height.replace('px', ''), 10);
  return cachedSpanHeight;
}

document.addEventListener('DOMContentLoaded', init);

window.addEventListener('resize', onResize);

function animateInPartOne(nationalId) {
  runningAnimationPartOne = true;
  // artificial delay to let the material animation play
  setTimeout(() => {
    requestAnimationFrame(() => doInAnimationPartOne(nationalId));
  }, 20);
}

function createPartTwoAnimation(nationalId) {
  // it is very likely that old Android phones will not run these two
  // animations very well if they occur at the same time. so stagger them
  if (animationDelay === 0) {
    return () => requestAnimationFrame(() => doInAnimationPartTwo(nationalId));
  }
  return () => setTimeout(() =>
      requestAnimationFrame(() => doInAnimationPartTwo(nationalId)),
    animationDelay);
}

function animateInPartTwo(nationalId) {
  var runPartTwoAnimation = createPartTwoAnimation(nationalId);
  if (runningAnimationPartOne) {
    console.log('waiting for part one animation to finish');
    queuedAnimation = runPartTwoAnimation;
  } else {
    console.log('running part two animation immediately');
    runPartTwoAnimation();
  }
}

function animateOut(nationalId) {
  requestAnimationFrame(() => doOutAnimation(nationalId));
}

function setThemeColor(color) {
  if (runningAnimationPartOne) {
    queuedThemeColor = color;
  } else {
    themeManager.setThemeColor(color);
  }
}

worker.addEventListener('message', e => {
  if (e.data.type === 'themeColor') {
    setThemeColor(e.data.color);
  }
});

module.exports = {
  animateInPartOne,
  animateInPartTwo,
  animateOut
};<|endoftext|>


<commit_before>// animate between the list view and the detail view, using FLIP animations
// https://aerotwist.com/blog/flip-your-animations/

var utils = require('./utils');
var themeManager = require('./themeManager');
var worker = require('./../shared/worker');
var each = require('lodash/collection/each');

var $ = document.querySelector.bind(document);

// elements
var detailView;
var detailViewContainer;
var detailPanel;
var monstersList;
var detailSprite;
var detailBackButton;
var spriteFacade;
var spinnerHolder;
var spinnerTimeout;

var screenWidth = window.innerWidth;
var screenHeight = window.innerHeight;

var cachedSpanHeight;
var dimensToSpriteRect = {};
var runningAnimationPartOne = false;
var queuedAnimation = false;
var queuedThemeColor;

var animationDelay = utils.canRunHighPerfAnims() ? 0 : 500;

function getScrollTop() {
  // browsers seem to disagree on this
  return document.body.scrollTop || document.documentElement.scrollTop;
}

function computeTransformsPartOne(nationalId) {
  console.time('computeTransformsPartOne()');

  var sourceSprite = monstersList.querySelector(`.sprite-${nationalId}`);
  var sourceTitleSpan = sourceSprite.parentElement.querySelector('span');

  var sourceSpriteRect = sourceSprite.getBoundingClientRect();
  var detailSpriteRect = getDetailSpriteRect();
  var sourceTitleSpanHeight = getSpanHeight(sourceTitleSpan);

  var spriteChangeX = sourceSpriteRect.left - detailSpriteRect.left;
  var spriteChangeY = sourceSpriteRect.top - detailSpriteRect.top;

  var scaleX = sourceSpriteRect.width / screenWidth;
  var scaleY = (sourceSpriteRect.height - sourceTitleSpanHeight) / screenHeight;
  var toX = sourceSpriteRect.left;
  var toY = sourceSpriteRect.top;

  var bgTransform = `translate(${toX}px,${toY}px) scale(${scaleX},${scaleY})`;
  var spriteTransform = `translate(${spriteChangeX}px, ${spriteChangeY}px)`;

  console.timeEnd('computeTransformsPartOne()');

  return {
    bgTransform,
    spriteTransform,
    spriteTop: detailSpriteRect.top + getScrollTop(),
    spriteLeft: detailSpriteRect.left
  };
}

function computeTransformsPartTwo(nationalId, outAnimation) {
  console.time('computeTransformsPartTwo()');

  // reeeaaally fling it away when animating out. looks better
  var slideInY = outAnimation ? screenHeight * 1.1 : screenHeight * 0.6;

  var fgTransform = `translateY(${slideInY}px)`;

  console.timeEnd('computeTransformsPartTwo()');

  return {
    fgTransform
  };
}

function createSpriteFacade() {
  spriteFacade = document.createElement('div');
  spriteFacade.classList.add('monster-sprite');
  spriteFacade.classList.add('monster-sprite-facade');
  spriteFacade.classList.add('hidden');
  spriteFacade.style.top = `0px`;
  spriteFacade.style.left = `0px`;
  document.body.appendChild(spriteFacade);
  return spriteFacade;
}

function styleSpriteFacade(nationalId, top, left, transform) {
  for (var i = 0; i < spriteFacade.classList.length; i++) {
    var className = spriteFacade.classList[i];
    if (/^sprite-/.test(className)) {
      spriteFacade.classList.remove(className);
      break;
    }
  }
  spriteFacade.classList.add(`sprite-${nationalId}`);
  spriteFacade.style.top = `${top}px`;
  spriteFacade.style.left = `${left}px`;
  spriteFacade.style.transform = transform;
  return spriteFacade;
}

function doInAnimationPartOne(nationalId) {
  document.body.style.overflowY = 'hidden'; // disable scrolling
  detailViewContainer.classList.remove('hidden');
  var transforms = computeTransformsPartOne(nationalId, false);
  var {bgTransform, spriteTransform, spriteTop, spriteLeft} = transforms;
  var targetBackground = detailView.querySelector('.detail-view-bg');
  var sourceSprite = monstersList.querySelector(`.sprite-${nationalId}`);
  targetBackground.style.background = sourceSprite.parentElement.style.background;
  var spriteFacade = styleSpriteFacade(nationalId, spriteTop, spriteLeft, spriteTransform);
  spriteFacade.classList.remove('hidden');
  detailBackButton.style.transform = 'translateX(-40px)';
  targetBackground.style.willChange = 'transform';
  targetBackground.style.transform = bgTransform;

  function onAnimEnd() {
    console.log('done animating');
    targetBackground.classList.remove('animating');
    spriteFacade.classList.remove('animating');
    targetBackground.removeEventListener('transitionend', onAnimEnd);
    targetBackground.style.willChange = '';
  }

  targetBackground.addEventListener('transitionend', onAnimEnd);
  detailPanel.classList.add('hidden');

  requestAnimationFrame(() => {
    // go go go!
    targetBackground.classList.add('animating');
    spriteFacade.classList.add('animating');
    targetBackground.style.transform = '';
    spriteFacade.style.transform = '';

    requestAnimationFrame(() => {
      runningAnimationPartOne = false;
      if (queuedAnimation) {
        requestAnimationFrame(() => {
          queuedAnimation();
          queuedAnimation = null;
        });
      } else {
        // if the second animation is delayed more than 5 seconds,
        // show a spinner to reassure the user (only happens with slow
        // connections on first load)
        spinnerTimeout = setTimeout(() => {
          spinnerHolder.classList.add('shown');
        }, 5000);
      }
      if (queuedThemeColor) {
        themeManager.setThemeColor(queuedThemeColor);
        queuedThemeColor = null;
      }
    });
  });
}

function doInAnimationPartTwo(nationalId) {
  spinnerHolder.classList.remove('shown');
  clearTimeout(spinnerTimeout);
  detailPanel.style.overflowY = 'auto'; // re-enable overflow on the panel
  document.body.style.overflowY = 'hidden'; // disable scrolling
  document.documentElement.style.overflowY = 'hidden'; //disable scrolling

  // hide monster moves until they're shown after the panel
  detailPanel.querySelector('.monster-moves').classList.add('hidden');
  detailPanel.classList.remove('hidden');
  detailSprite.style.opacity = 0;
  var {fgTransform} = computeTransformsPartTwo(nationalId, false);

  detailPanel.style.transform = fgTransform;

  requestAnimationFrame(() => {
    // go go go!
    detailPanel.classList.add('animating');
    detailBackButton.classList.add('animating');
    detailPanel.style.transform = '';
    detailBackButton.style.transform = '';
  });

  function onAnimEnd() {
    console.log('done animating part two');
    detailBackButton.classList.remove('animating');
    detailPanel.classList.remove('animating');

    spriteFacade.classList.add('hidden');

    detailSprite.style.opacity = 1;

    // don't run this until the panel has animated in, in order to keep the
    // the animation smooth
    worker.postMessage({
      type: 'movesDetail',
      nationalId: nationalId
    });

    detailPanel.removeEventListener('transitionend', onAnimEnd);
  }

  detailPanel.addEventListener('transitionend', onAnimEnd);
}

function doOutAnimation(nationalId) {
  detailPanel.scrollTop = 0; // scroll panel to top, disable scrolling during animation
  detailPanel.style.overflowY = 'visible';
  document.body.style.overflowY = 'visible'; // re-enable scrolling
  document.documentElement.style.overflowY = 'visible'; //re-enable scrolling
  var transforms = computeTransformsPartOne(nationalId, true);
  var {bgTransform, spriteTransform, spriteTop, spriteLeft} = transforms;
  var {fgTransform} = computeTransformsPartTwo(nationalId, true);

  var targetBackground = detailView.querySelector('.detail-view-bg');
  var detailSprite = detailView.querySelector('.detail-sprite');
  var spriteFacade = styleSpriteFacade(nationalId, spriteTop, spriteLeft, '');
  spriteFacade.classList.remove('hidden');
  detailSprite.style.opacity = 0;
  targetBackground.style.willChange = 'transform';
  targetBackground.style.transform = '';
  detailPanel.style.transform = '';

  requestAnimationFrame(() => {
    // go go go!
    detailBackButton.classList.add('animating');
    detailPanel.classList.add('animating');
    targetBackground.classList.add('animating');
    spriteFacade.classList.add('animating');
    spriteFacade.style.transform = spriteTransform;
    targetBackground.style.transform = bgTransform;
    detailPanel.style.transform = fgTransform;
    detailBackButton.style.transform = 'translateX(-60px)';
  });

  function onAnimEnd() {
    console.log('done animating part one');
    detailPanel.classList.remove('animating');
    targetBackground.classList.remove('animating');
    spriteFacade.classList.remove('animating');
    detailBackButton.classList.remove('animating');
    targetBackground.style.transform = '';
    detailPanel.style.transform = '';
    detailViewContainer.classList.add('hidden');
    spriteFacade.classList.add('hidden');
    detailSprite.style.opacity = 1;
    // clean up any expanded move lists
    each(detailView.querySelectorAll('.moves-row-detail'), el => el.classList.add('hidden'));
    each(detailView.querySelectorAll('.dropdown-button-image'), el => el.style.transform = '');
    targetBackground.removeEventListener('transitionend', onAnimEnd);
    targetBackground.style.willChange = '';
  }

  themeManager.resetThemeColor();

  targetBackground.addEventListener('transitionend', onAnimEnd);
}

function init() {
  detailView = $('#detail-view');
  detailViewContainer = $('#detail-view-container');
  monstersList = $('#monsters-list');
  detailPanel = $('.detail-panel');
  spinnerHolder = $('.big-spinner-holder');
  detailSprite = detailView.querySelector('.detail-sprite');
  detailBackButton = detailView.querySelector('.detail-back-button');
  spriteFacade = createSpriteFacade();

}

function onResize() {
  // these are expensive to compute, so only compute when the window is resized
  screenWidth = window.innerWidth;
  screenHeight = window.innerHeight;
}

function getCachedDetailSpriteRect() {
  var dimens = screenWidth + '_' + screenHeight;
  return dimensToSpriteRect[dimens];
}

function getDetailSpriteRect() {
  // the size/location of the target sprite is fixed given the window size,
  // so we can just cache it and avoid the expensive getBoundingClientRect()
  var result = getCachedDetailSpriteRect();
  if (result) {
    return result;
  }
  var dimens = screenWidth + '_' + screenHeight;
  result = dimensToSpriteRect[dimens] = detailSprite.getBoundingClientRect();
  return result;
}

function getSpanHeight(span) {
  // this never changes, so we can cache it instead of recomputing style
  // every time
  if (typeof cachedSpanHeight === 'number') {
    return cachedSpanHeight;
  }
  var spanStyle = getComputedStyle(span);
  cachedSpanHeight = parseInt(spanStyle.height.replace('px', ''), 10);
  return cachedSpanHeight;
}

document.addEventListener('DOMContentLoaded', init);

window.addEventListener('resize', onResize);

function animateInPartOne(nationalId) {
  runningAnimationPartOne = true;
  // artificial delay to let the material animation play
  setTimeout(() => {
    requestAnimationFrame(() => doInAnimationPartOne(nationalId));
  }, 20);
}

function createPartTwoAnimation(nationalId) {
  // it is very likely that old Android phones will not run these two
  // animations very well if they occur at the same time. so stagger them
  if (animationDelay === 0) {
    return () => requestAnimationFrame(() => doInAnimationPartTwo(nationalId));
  }
  return () => setTimeout(() =>
      requestAnimationFrame(() => doInAnimationPartTwo(nationalId)),
    animationDelay);
}

function animateInPartTwo(nationalId) {
  var runPartTwoAnimation = createPartTwoAnimation(nationalId);
  if (runningAnimationPartOne) {
    console.log('waiting for part one animation to finish');
    queuedAnimation = runPartTwoAnimation;
  } else {
    console.log('running part two animation immediately');
    runPartTwoAnimation();
  }
}

function animateOut(nationalId) {
  requestAnimationFrame(() => doOutAnimation(nationalId));
}

function setThemeColor(color) {
  if (runningAnimationPartOne) {
    queuedThemeColor = color;
  } else {
    themeManager.setThemeColor(color);
  }
}

worker.addEventListener('message', e => {
  if (e.data.type === 'themeColor') {
    setThemeColor(e.data.color);
  }
});

module.exports = {
  animateInPartOne,
  animateInPartTwo,
  animateOut
};
<commit_msg>Update detailViewOrchestrator.js<commit_after>// animate between the list view and the detail view, using FLIP animations
// https://aerotwist.com/blog/flip-your-animations/

var utils = require('./utils');
var themeManager = require('./themeManager');
var worker = require('./../shared/worker');
var each = require('lodash/collection/each');

var $ = document.querySelector.bind(document);

// elements
var detailView;
var detailViewContainer;
var detailPanel;
var monstersList;
var detailSprite;
var detailBackButton;
var spriteFacade;
var spinnerHolder;
var spinnerTimeout;

var screenWidth = window.innerWidth;
var screenHeight = window.innerHeight;

var cachedSpanHeight;
var dimensToSpriteRect = {};
var runningAnimationPartOne = false;
var queuedAnimation = false;
var queuedThemeColor;

var animationDelay = utils.canRunHighPerfAnims() ? 0 : 500;

function getScrollTop() {
  // browsers seem to disagree on this
  return document.body.scrollTop || document.documentElement.scrollTop;
}

function computeTransformsPartOne(nationalId) {
  console.time('computeTransformsPartOne()');

  var sourceSprite = monstersList.querySelector(`.sprite-${nationalId}`);
  var sourceTitleSpan = sourceSprite.parentElement.querySelector('span');

  var sourceSpriteRect = sourceSprite.getBoundingClientRect();
  var detailSpriteRect = getDetailSpriteRect();
  var sourceTitleSpanHeight = getSpanHeight(sourceTitleSpan);

  var spriteChangeX = sourceSpriteRect.left - detailSpriteRect.left;
  var spriteChangeY = sourceSpriteRect.top - detailSpriteRect.top;

  var scaleX = sourceSpriteRect.width / screenWidth;
  var scaleY = (sourceSpriteRect.height - sourceTitleSpanHeight) / screenHeight;
  var toX = sourceSpriteRect.left;
  var toY = sourceSpriteRect.top;

  var bgTransform = `translate(${toX}px,${toY}px) scale(${scaleX},${scaleY})`;
  var spriteTransform = `translate(${spriteChangeX}px, ${spriteChangeY}px)`;

  console.timeEnd('computeTransformsPartOne()');

  return {
    bgTransform,
    spriteTransform,
    spriteTop: detailSpriteRect.top + getScrollTop(),
    spriteLeft: detailSpriteRect.left
  };
}

function computeTransformsPartTwo(nationalId, outAnimation) {
  console.time('computeTransformsPartTwo()');

  // reeeaaally fling it away when animating out. looks better
  var slideInY = outAnimation ? screenHeight * 1.1 : screenHeight * 0.6;

  var fgTransform = `translateY(${slideInY}px)`;

  console.timeEnd('computeTransformsPartTwo()');

  return {
    fgTransform
  };
}

function createSpriteFacade() {
  spriteFacade = document.createElement('div');
  spriteFacade.classList.add('monster-sprite');
  spriteFacade.classList.add('monster-sprite-facade');
  spriteFacade.classList.add('hidden');
  spriteFacade.style.top = `0px`;
  spriteFacade.style.left = `0px`;
  document.body.appendChild(spriteFacade);
  return spriteFacade;
}

function styleSpriteFacade(nationalId, top, left, transform) {
  for (var i = 0; i < spriteFacade.classList.length; i++) {
    var className = spriteFacade.classList[i];
    if (/^sprite-/.test(className)) {
      spriteFacade.classList.remove(className);
      break;
    }
  }
  spriteFacade.classList.add(`sprite-${nationalId}`);
  spriteFacade.style.top = `${top}px`;
  spriteFacade.style.left = `${left}px`;
  spriteFacade.style.transform = transform;
  return spriteFacade;
}

function doInAnimationPartOne(nationalId) {
  document.body.style.overflowY = 'hidden'; // disable scrolling
  detailViewContainer.classList.remove('hidden');
  var transforms = computeTransformsPartOne(nationalId, false);
  var {bgTransform, spriteTransform, spriteTop, spriteLeft} = transforms;
  var targetBackground = detailView.querySelector('.detail-view-bg');
  var sourceSprite = monstersList.querySelector(`.sprite-${nationalId}`);
  targetBackground.style.background = sourceSprite.parentElement.style.background;
  var spriteFacade = styleSpriteFacade(nationalId, spriteTop, spriteLeft, spriteTransform);
  spriteFacade.classList.remove('hidden');
  detailBackButton.style.transform = 'translateX(-40px)';
  targetBackground.style.willChange = 'transform';
  targetBackground.style.transform = bgTransform;

  function onAnimEnd() {
    console.log('done animating');
    targetBackground.classList.remove('animating');
    spriteFacade.classList.remove('animating');
    targetBackground.removeEventListener('transitionend', onAnimEnd);
    targetBackground.style.willChange = '';
  }

  targetBackground.addEventListener('transitionend', onAnimEnd);
  detailPanel.classList.add('hidden');

  requestAnimationFrame(() => {
    // go go go!
    targetBackground.classList.add('animating');
    spriteFacade.classList.add('animating');
    targetBackground.style.transform = '';
    spriteFacade.style.transform = '';

    requestAnimationFrame(() => {
      runningAnimationPartOne = false;
      if (queuedAnimation) {
        requestAnimationFrame(() => {
          queuedAnimation();
          queuedAnimation = null;
        });
      } else {
        // if the second animation is delayed more than 5 seconds,
        // show a spinner to reassure the user (only happens with slow
        // connections on first load)
        spinnerTimeout = setTimeout(() => {
          spinnerHolder.classList.add('shown');
        }, 5000);
      }
      if (queuedThemeColor) {
        themeManager.setThemeColor(queuedThemeColor);
        queuedThemeColor = null;
      }
    });
  });
}

function doInAnimationPartTwo(nationalId) {
  spinnerHolder.classList.remove('shown');
  clearTimeout(spinnerTimeout);
  detailPanel.style.overflowY = 'auto'; // re-enable overflow on the panel
  document.body.style.overflowY = 'hidden'; // disable scrolling
  document.documentElement.style.overflowY = 'hidden'; //disable scrolling

  // hide monster moves until they're shown after the panel
  detailPanel.querySelector('.monster-moves').classList.add('hidden');
  detailPanel.classList.remove('hidden');
  detailSprite.style.opacity = 0;
  var {fgTransform} = computeTransformsPartTwo(nationalId, false);

  detailPanel.style.transform = fgTransform;

  requestAnimationFrame(() => {
    // go go go!
    detailPanel.classList.add('animating');
    detailBackButton.classList.add('animating');
    detailPanel.style.transform = '';
    detailBackButton.style.transform = '';
  });

  function onAnimEnd() {
    console.log('done animating part two');
    detailBackButton.classList.remove('animating');
    detailPanel.classList.remove('animating');

    spriteFacade.classList.add('hidden');

    detailSprite.style.opacity = 1;

    // don't run this until the panel has animated in, in order to keep the
    // the animation smooth
    worker.postMessage({
      type: 'movesDetail',
      nationalId: nationalId
    });

    detailPanel.removeEventListener('transitionend', onAnimEnd);
  }

  detailPanel.addEventListener('transitionend', onAnimEnd);
}

function doOutAnimation(nationalId) {
  detailPanel.scrollTop = 0; // scroll panel to top, disable scrolling during animation
  detailPanel.style.overflowY = 'visible';
  document.body.style.overflowY = 'visible'; // re-enable scrolling
  document.documentElement.style.overflowY = ''; //re-enable scrolling
  var transforms = computeTransformsPartOne(nationalId, true);
  var {bgTransform, spriteTransform, spriteTop, spriteLeft} = transforms;
  var {fgTransform} = computeTransformsPartTwo(nationalId, true);

  var targetBackground = detailView.querySelector('.detail-view-bg');
  var detailSprite = detailView.querySelector('.detail-sprite');
  var spriteFacade = styleSpriteFacade(nationalId, spriteTop, spriteLeft, '');
  spriteFacade.classList.remove('hidden');
  detailSprite.style.opacity = 0;
  targetBackground.style.willChange = 'transform';
  targetBackground.style.transform = '';
  detailPanel.style.transform = '';

  requestAnimationFrame(() => {
    // go go go!
    detailBackButton.classList.add('animating');
    detailPanel.classList.add('animating');
    targetBackground.classList.add('animating');
    spriteFacade.classList.add('animating');
    spriteFacade.style.transform = spriteTransform;
    targetBackground.style.transform = bgTransform;
    detailPanel.style.transform = fgTransform;
    detailBackButton.style.transform = 'translateX(-60px)';
  });

  function onAnimEnd() {
    console.log('done animating part one');
    detailPanel.classList.remove('animating');
    targetBackground.classList.remove('animating');
    spriteFacade.classList.remove('animating');
    detailBackButton.classList.remove('animating');
    targetBackground.style.transform = '';
    detailPanel.style.transform = '';
    detailViewContainer.classList.add('hidden');
    spriteFacade.classList.add('hidden');
    detailSprite.style.opacity = 1;
    // clean up any expanded move lists
    each(detailView.querySelectorAll('.moves-row-detail'), el => el.classList.add('hidden'));
    each(detailView.querySelectorAll('.dropdown-button-image'), el => el.style.transform = '');
    targetBackground.removeEventListener('transitionend', onAnimEnd);
    targetBackground.style.willChange = '';
  }

  themeManager.resetThemeColor();

  targetBackground.addEventListener('transitionend', onAnimEnd);
}

function init() {
  detailView = $('#detail-view');
  detailViewContainer = $('#detail-view-container');
  monstersList = $('#monsters-list');
  detailPanel = $('.detail-panel');
  spinnerHolder = $('.big-spinner-holder');
  detailSprite = detailView.querySelector('.detail-sprite');
  detailBackButton = detailView.querySelector('.detail-back-button');
  spriteFacade = createSpriteFacade();

}

function onResize() {
  // these are expensive to compute, so only compute when the window is resized
  screenWidth = window.innerWidth;
  screenHeight = window.innerHeight;
}

function getCachedDetailSpriteRect() {
  var dimens = screenWidth + '_' + screenHeight;
  return dimensToSpriteRect[dimens];
}

function getDetailSpriteRect() {
  // the size/location of the target sprite is fixed given the window size,
  // so we can just cache it and avoid the expensive getBoundingClientRect()
  var result = getCachedDetailSpriteRect();
  if (result) {
    return result;
  }
  var dimens = screenWidth + '_' + screenHeight;
  result = dimensToSpriteRect[dimens] = detailSprite.getBoundingClientRect();
  return result;
}

function getSpanHeight(span) {
  // this never changes, so we can cache it instead of recomputing style
  // every time
  if (typeof cachedSpanHeight === 'number') {
    return cachedSpanHeight;
  }
  var spanStyle = getComputedStyle(span);
  cachedSpanHeight = parseInt(spanStyle.height.replace('px', ''), 10);
  return cachedSpanHeight;
}

document.addEventListener('DOMContentLoaded', init);

window.addEventListener('resize', onResize);

function animateInPartOne(nationalId) {
  runningAnimationPartOne = true;
  // artificial delay to let the material animation play
  setTimeout(() => {
    requestAnimationFrame(() => doInAnimationPartOne(nationalId));
  }, 20);
}

function createPartTwoAnimation(nationalId) {
  // it is very likely that old Android phones will not run these two
  // animations very well if they occur at the same time. so stagger them
  if (animationDelay === 0) {
    return () => requestAnimationFrame(() => doInAnimationPartTwo(nationalId));
  }
  return () => setTimeout(() =>
      requestAnimationFrame(() => doInAnimationPartTwo(nationalId)),
    animationDelay);
}

function animateInPartTwo(nationalId) {
  var runPartTwoAnimation = createPartTwoAnimation(nationalId);
  if (runningAnimationPartOne) {
    console.log('waiting for part one animation to finish');
    queuedAnimation = runPartTwoAnimation;
  } else {
    console.log('running part two animation immediately');
    runPartTwoAnimation();
  }
}

function animateOut(nationalId) {
  requestAnimationFrame(() => doOutAnimation(nationalId));
}

function setThemeColor(color) {
  if (runningAnimationPartOne) {
    queuedThemeColor = color;
  } else {
    themeManager.setThemeColor(color);
  }
}

worker.addEventListener('message', e => {
  if (e.data.type === 'themeColor') {
    setThemeColor(e.data.color);
  }
});

module.exports = {
  animateInPartOne,
  animateInPartTwo,
  animateOut
};
<|endoftext|>


<commit_before>import layerActions from 'actions/LayerActions';
import layerKeys from 'constants/LayerConstants';
import utils from 'utils/AppUtils';
import {loadJS, loadCSS} from 'utils/loaders';
import {assetUrls} from 'js/config';
import React, { Component, PropTypes } from 'react';

const lossOptions = [];

export default class LossControls extends Component {
  static contextTypes = {
    language: PropTypes.string.isRequired,
    map: PropTypes.object.isRequired,
    settings: PropTypes.object.isRequired
  };

  constructor(props) {
    super(props);

    this.state = {
      playing: false
    };
  }

  componentDidMount () {

    // const url = 'http://gis-treecover.wri.org/arcgis/rest/services/ForestCover_lossyear/ImageServer';
    // layerUtils.getLayerMetadata(url).then((results) => {
      const min = 1;
      const max = 15;
      for ( let i = min; i <= max; i++ ) {
        lossOptions.push({ label: 2000 + i + '', value: i });
      }
      //- Update the defaults to be the last year
      layerActions.updateLossTimeline.defer({
        fromSelectedIndex: 0,
        toSelectedIndex: 14
      });
      //- Set the options in the store so others can use it
      layerActions.setLossOptions.defer(lossOptions);
    // });

    let base = window._app.base ? window._app.base + '/' : '';
    if (base && base[base.length - 1] === '/' && base[base.length - 2] === '/') {
      base = base.substring(0, base.length - 1);
    }

    loadJS(base + assetUrls.rangeSlider).then(() => {
      // initialize the slider
      if ($('#loss-slider').ionRangeSlider) {
        $('#loss-slider').ionRangeSlider({
          type: 'double',
          values: lossOptions.map(option => option.label),
          grid: true,
          grid_snap: true,
          hide_min_max: true,
          prettify_enabled: false,
          onFinish: this.sliderChanged
        });
        this.lossSlider = $('#loss-slider').data('ionRangeSlider');
      }
    });
    loadCSS(base + assetUrls.ionCSS);
    loadCSS(base + assetUrls.ionSkinCSS);
  }

  componentDidUpdate (prevProps, prevState, prevContext) {
    //- If the options are ready and something has changed
    const {lossFromSelectIndex, lossToSelectIndex, canopyDensity} = this.props;
    const fromYear = lossOptions[lossFromSelectIndex].label;
    const toYear = lossOptions[lossToSelectIndex].label;
    const {map} = this.context;

    if (map.loaded) {

        if (this.props.lossOptions.length && (prevProps.canopyDensity !== canopyDensity)
        ) {
          this.updateDensity(map.getLayer(layerKeys.TREE_COVER_LOSS), canopyDensity);
        }

        if (prevContext.map !== map) {
          const signal = map.on('update-end', () => {
            signal.remove();
            this.updateDates(map.getLayer(layerKeys.TREE_COVER_LOSS), fromYear, toYear);
          });
        }
    }

  }

  updateDates (layer, fromYear, toYear) {
    if (layer && layer.setDateRange) {
      layer.setDateRange(fromYear - 2000, toYear - 2000);
    }
  }

  updateDensity (layer, density) {
    const {lossFromSelectIndex, lossToSelectIndex} = this.props;
    const { settings } = this.context;
    const layerGroups = settings.layerPanel;
    const layerConf = utils.getObject(layerGroups.GROUP_LCD.layers, 'id', this.props.layerId);
    let baseUrl = layerConf.url;
    baseUrl = baseUrl.split('tc')[0] + 'tc';
    baseUrl += density;
    baseUrl += '/{z}/{x}/{y}.png';

    layer.setUrl(baseUrl);
    layer.setDateRange(lossFromSelectIndex, lossToSelectIndex);
  }

  startVisualization = () => {
    const lossSlider = this.lossSlider;
    const layer = this.context.map.getLayer(layerKeys.TREE_COVER_LOSS);
    const start = lossOptions[this.lossSlider.result.from].label - 2000;
    const stop = lossOptions[this.lossSlider.result.to].label - 2000;
    const p_step = lossSlider.coords.p_step;
    const p_handle = lossSlider.coords.p_handle;
    const p = p_step - (p_step / p_handle); // Width of one step of the slider (percent)
    const tooltip = this.refs.sliderTooltip;
    const tooltipValue = lossSlider.result.from_value + 1;
    let range = start + 1;
    let barWidth = p;
    let tooltipHtml = tooltipValue;

    lossSlider.update({
      to_fixed: true,
      from_fixed: true,
      hide_from_to: true
    });
    // Set an interval to increase the date range every second, then start over when at max range
    const timer = setInterval(visualizeLoss, 1000);

    function visualizeLoss() {
      const sliderBar = document.querySelector('.irs-bar');

      if (range === stop + 1) {
        range = start + 1;
        barWidth = p;
        tooltipHtml = tooltipValue;
      }

      layer.setDateRange(start, range);
      sliderBar.style.width = `${barWidth}%`;
      const rect = sliderBar.getBoundingClientRect();
      tooltip.style.left = `${rect.left + rect.width - 69}px`; //TODO Figure out a better way to calculate all of the correct values for bar and tooltip
      tooltip.innerHTML = tooltipHtml;
      tooltip.style.display = 'block';
      range++;
      barWidth += p; // increase barWidth by one step length each iteration
      tooltipHtml++;
    }

    this.setState({playing: true, timer});
  }

  stopVisualization = () => {
    const fromYear = lossOptions[this.lossSlider.result.from].label;
    const toYear = lossOptions[this.lossSlider.result.to].label;
    this.refs.sliderTooltip.style.display = 'none';

    const layer = this.context.map.getLayer(layerKeys.TREE_COVER_LOSS);

    clearInterval(this.state.timer);
    layer.setDateRange(fromYear - 2000, toYear - 2000);
    this.lossSlider.update({
      to_fixed: false,
      from_fixed: false,
      hide_from_to: false
    });
    layerActions.updateLossTimeline({
      fromSelectedIndex: this.lossSlider.result.from,
      toSelectedIndex: this.lossSlider.result.to
    });
    this.setState({playing: false, timer: null});
  }

  sliderChanged = (data) => {
    const { map } = this.context;
    this.updateDates(map.getLayer(layerKeys.TREE_COVER_LOSS), data.from_value, data.to_value);
    layerActions.updateLossTimeline({
      fromSelectedIndex: this.lossSlider.result.from,
      toSelectedIndex: this.lossSlider.result.to
    });
  }

  render () {
    if (lossOptions.length === 0) {
      return
loading...
;
    }

    return (

        <div id="lossPlayButton" className={`${this.state.playing ? ' hidden' : ''}`} onClick={this.startVisualization}>►

        <div id="lossPauseButton" className={`${this.state.playing ? '' : ' hidden'}`} onClick={this.stopVisualization}>❚❚

    );
  }
}
<commit_msg>fix timeslider when only one year selected<commit_after>import layerActions from 'actions/LayerActions';
import layerKeys from 'constants/LayerConstants';
import utils from 'utils/AppUtils';
import {loadJS, loadCSS} from 'utils/loaders';
import {assetUrls} from 'js/config';
import React, { Component, PropTypes } from 'react';

const lossOptions = [];

export default class LossControls extends Component {
  static contextTypes = {
    language: PropTypes.string.isRequired,
    map: PropTypes.object.isRequired,
    settings: PropTypes.object.isRequired
  };

  constructor(props) {
    super(props);

    this.state = {
      playing: false
    };
  }

  componentDidMount () {

    // const url = 'http://gis-treecover.wri.org/arcgis/rest/services/ForestCover_lossyear/ImageServer';
    // layerUtils.getLayerMetadata(url).then((results) => {
      const min = 1;
      const max = 15;
      for ( let i = min; i <= max; i++ ) {
        lossOptions.push({ label: 2000 + i + '', value: i });
      }
      //- Update the defaults to be the last year
      layerActions.updateLossTimeline.defer({
        fromSelectedIndex: 0,
        toSelectedIndex: 14
      });
      //- Set the options in the store so others can use it
      layerActions.setLossOptions.defer(lossOptions);
    // });

    let base = window._app.base ? window._app.base + '/' : '';
    if (base && base[base.length - 1] === '/' && base[base.length - 2] === '/') {
      base = base.substring(0, base.length - 1);
    }

    loadJS(base + assetUrls.rangeSlider).then(() => {
      // initialize the slider
      if ($('#loss-slider').ionRangeSlider) {
        $('#loss-slider').ionRangeSlider({
          type: 'double',
          values: lossOptions.map(option => option.label),
          grid: true,
          grid_snap: true,
          hide_min_max: true,
          prettify_enabled: false,
          onFinish: this.sliderChanged
        });
        this.lossSlider = $('#loss-slider').data('ionRangeSlider');
      }
    });
    loadCSS(base + assetUrls.ionCSS);
    loadCSS(base + assetUrls.ionSkinCSS);
  }

  componentDidUpdate (prevProps, prevState, prevContext) {
    //- If the options are ready and something has changed
    const {lossFromSelectIndex, lossToSelectIndex, canopyDensity} = this.props;
    const fromYear = lossOptions[lossFromSelectIndex].label;
    const toYear = lossOptions[lossToSelectIndex].label;
    const {map} = this.context;

    if (map.loaded) {

        if (this.props.lossOptions.length && (prevProps.canopyDensity !== canopyDensity)
        ) {
          this.updateDensity(map.getLayer(layerKeys.TREE_COVER_LOSS), canopyDensity);
        }

        if (prevContext.map !== map) {
          const signal = map.on('update-end', () => {
            signal.remove();
            this.updateDates(map.getLayer(layerKeys.TREE_COVER_LOSS), fromYear, toYear);
          });
        }
    }

  }

  updateDates (layer, fromYear, toYear) {
    if (layer && layer.setDateRange) {
      layer.setDateRange(fromYear - 2000, toYear - 2000);
    }
  }

  updateDensity (layer, density) {
    const {lossFromSelectIndex, lossToSelectIndex} = this.props;
    const { settings } = this.context;
    const layerGroups = settings.layerPanel;
    const layerConf = utils.getObject(layerGroups.GROUP_LCD.layers, 'id', this.props.layerId);
    let baseUrl = layerConf.url;
    baseUrl = baseUrl.split('tc')[0] + 'tc';
    baseUrl += density;
    baseUrl += '/{z}/{x}/{y}.png';

    layer.setUrl(baseUrl);
    layer.setDateRange(lossFromSelectIndex, lossToSelectIndex);
  }

  startVisualization = () => {
    const _this = this;
    const lossSlider = this.lossSlider;
    const layer = this.context.map.getLayer(layerKeys.TREE_COVER_LOSS);
    const start = lossOptions[this.lossSlider.result.from].label - 2000;
    const stop = lossOptions[this.lossSlider.result.to].label - 2000;
    const p_step = lossSlider.coords.p_step;
    const p_handle = lossSlider.coords.p_handle;
    const p = p_step - (p_step / p_handle); // Width of one step of the slider (percent)
    const tooltip = this.refs.sliderTooltip;
    const tooltipValue = lossSlider.result.from_value;
    let range = start;
    let barWidth = 0;
    let tooltipHtml = tooltipValue;

    lossSlider.update({
      to_fixed: true,
      from_fixed: true,
      hide_from_to: true
    });
    // Set an interval to increase the date range every second, then start over when at max range
    const timer = setInterval(visualizeLoss, 1000);

    function visualizeLoss() {

      if (start === stop) { // if we are on viewing only a single year of data cancel the animation and don't run the rest of the function
        _this.stopVisualization();
        return;
      }
      const sliderBar = document.querySelector('.irs-bar');

      if (range === stop + 1) {
        range = start;
        barWidth = 0;
        tooltipHtml = tooltipValue;
      }

      layer.setDateRange(start, range);
      sliderBar.style.width = `${barWidth}%`;
      const rect = sliderBar.getBoundingClientRect();
      tooltip.style.left = `${rect.left + rect.width - 69}px`; //TODO Figure out a better way to calculate all of the correct values for bar and tooltip
      tooltip.innerHTML = tooltipHtml;
      tooltip.style.display = 'block';
      range++;
      barWidth += p; // increase barWidth by one step length each iteration
      tooltipHtml++;
    }

    this.setState({playing: true, timer});
  }

  stopVisualization = () => {
    const fromYear = lossOptions[this.lossSlider.result.from].label;
    const toYear = lossOptions[this.lossSlider.result.to].label;
    this.refs.sliderTooltip.style.display = 'none';

    const layer = this.context.map.getLayer(layerKeys.TREE_COVER_LOSS);

    clearInterval(this.state.timer);
    layer.setDateRange(fromYear - 2000, toYear - 2000);
    this.lossSlider.update({
      to_fixed: false,
      from_fixed: false,
      hide_from_to: false
    });
    layerActions.updateLossTimeline({
      fromSelectedIndex: this.lossSlider.result.from,
      toSelectedIndex: this.lossSlider.result.to
    });
    this.setState({playing: false, timer: null});
  }

  sliderChanged = (data) => {
    const { map } = this.context;
    this.updateDates(map.getLayer(layerKeys.TREE_COVER_LOSS), data.from_value, data.to_value);
    layerActions.updateLossTimeline({
      fromSelectedIndex: this.lossSlider.result.from,
      toSelectedIndex: this.lossSlider.result.to
    });
  }

  render () {
    if (lossOptions.length === 0) {
      return
loading...
;
    }

    return (

        <div id="lossPlayButton" className={`${this.state.playing ? ' hidden' : ''}`} onClick={this.startVisualization}>►

        <div id="lossPauseButton" className={`${this.state.playing ? '' : ' hidden'}`} onClick={this.stopVisualization}>❚❚

    );
  }
}
<|endoftext|>