<commit_before>let objBrowser = chrome ? chrome : browser;

class EtherAddressLookup {

    constructor(objWeb3)
    {
        console.log("Init EAL");
        this.objWeb3 = objWeb3;
        this.setDefaultExtensionSettings();
        this.init();
    }

    setDefaultExtensionSettings()
    {
        this.blHighlight = false;
        this.strBlockchainExplorer = "https://etherscan.io/address";
        this.strRpcProvider = "https://localhost:8545";

        this.intSettingsCount = 0;
        this.intSettingsTotalCount = 2;
    }

    /**
     * @name init
     * @desc Gets extension settings and applies DOM manipulation
     */
    init()
    {
        let objBrowser = chrome ? chrome : browser;
        //Get the highlight option for the user
        objBrowser.runtime.sendMessage({func: "highlight_option"}, function(objResponse) {
            if(objResponse && objResponse.hasOwnProperty("resp")) {
                this.blHighlight = (objResponse.resp == 1);
            }
            ++this.intSettingsCount;
        }.bind(this));

        //Get the blockchain explorer for the user
        objBrowser.runtime.sendMessage({func: "blockchain_explorer"}, function(objResponse) {
            this.strBlockchainExplorer = objResponse.resp;
            ++this.intSettingsCount;
        }.bind(this));

        //Update the DOM once all settings have been received...
        setTimeout(function() {
            // Needs to happen after user settings have been collected
            // and in the context of init();
            this.setSearchAndReplaceSettings();
            this.setWarningSettings();
            this.manipulateDOM();
        }.bind(this), 10);
    }

    /**
     * @name Set Search And Replace Settings
     * @desc
     */
    setSearchAndReplaceSettings()
    {
        // Check user is on their favourite block explorer, on fail target blank.
        this.target = (this.isBlockchainExplorerSite() ? '_self' : '_blank');

        // Register RegEx Patterns
        this.regExPatterns = [
            // Ethereum Address Regex
            /(^|\s|:|-)((?:0x)[0-9a-fA-F]{40})(\s|$)/gi,

            // ENS Address Regex
            /(^|\s|:|-)([a-z0-9][a-z0-9-.]+[a-z0-9](?:\.eth))(\s|$)/gi,

            // ENS With ZWCs
            /(^|\s|:|-)([a-z0-9][a-z0-9-.]*(\u200B|\u200C|\u200D|\uFEFF|\u2028|\u2029||)[a-z0-9]*(?:\.eth))(\s|$)/gi
        ];

        // Register RegEx Matching Patterns
        this.matchPatterns = [
            // Ethereum Match Pattern
            /((?:0x)[0-9a-fA-F]{40})/gi,

            // ENS Match Pattern
            this.regExPatterns[1],

            // ENS With ZWCs
            this.regExPatterns[2]
        ];

        // Register Replace Patterns
        this.replacePatterns = [
            // Ethereum Address Replace
            '$1' +
            '

 $2' +
            '$3',

            // ENS Address Replace
            '$1$2$3',

            // ENS With ZWCs Replace
            '$1<slot title="WARNING! This ENS address has ZWCs. Someone may be trying to scam you." ' +
            'class="ext-etheraddresslookup-warning">$2</slot>$3'
        ];
    }

    /**
     * @name Set Warning Settings
     * @desc
     */
    setWarningSettings()
    {
        // The block explorers that can handle ENS addresses
        this.ENSCompatiableExplorers = [
            "https://etherscan.io/address",
            "https://etherchain.org/account"
        ];

        // Does the user's favorite explorer support an ENS address
        for(var i=0; i<this.ENSCompatiableExplorers.length; i++){
            if(this.strBlockchainExplorer == this.ENSCompatiableExplorers[i]){
                this.ENSCompatiable = true;
                break;
            }
            this.ENSCompatiable = false;
        }

        // On failure give the user a warning.
        if(!this.ENSCompatiable){
            this.replacePatterns[1] = '$1$2$3';
        }
    }

    manipulateDOM()
    {
        if(true || this.intSettingsCount === this.intSettingsTotalCount) {
            if(this.blBlacklistDomains) {
                this.blacklistedDomainCheck();
            }
            this.convertAddressToLink();

            this.setAddressOnHoverBehaviour();
        }
    }

    /**
     * @name Convert Address To Link
     * @desc Finds Ethereum addresses and converts to a link to a block explorer
     */
    convertAddressToLink()
    {
        var arrWhitelistedTags = ["code", "span", "p", "td", "li", "em", "i", "b", "strong", "small"];

        //Get the whitelisted nodes
        for(var i=0; i<arrWhitelistedTags.length; i++) {
            var objNodes = document.getElementsByTagName(arrWhitelistedTags[i]);
            //Loop through the whitelisted content
            for(var x=0; x<objNodes.length; x++) {

                if(this.hasIgnoreAttributes(objNodes[x])){
                    continue;
                }

                this.convertAddresses(objNodes[x]);
            }
        }

        this.tidyUpSlots();
        this.addBlockies();

        if(this.blHighlight) {
            this.addHighlightStyle();
        }
    }

    /**
     * @name Convert Addresses
     * @desc Takes a Node and checks if any of its children are textNodes. On success replace textNode with slot node
     * @desc slot node contains regex replaced content; see generateReplacementContent()
     * @param {Node} objNode
     */
    convertAddresses(objNode)
    {
        // Some nodes have non-textNode children
        // we need to ensure regex is applied only to text otherwise we will mess the html up
        for(var i=0; i < objNode.childNodes.length; i++){
            // Only check textNodes to prevent applying RegEx against element attributes
            if(objNode.childNodes[i].nodeType == 3){ // nodeType 3 = a text node

                var child = objNode.childNodes[i];
                var childContent = child.textContent;

                // Only start replacing stuff if the we get a RegEx match.
                if(this.isPatternMatched(childContent)) {
                    // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot
                    var replacement = document.createElement('slot');
                    replacement.setAttribute('class', 'ext-etheraddresslookup-temporary');
                    replacement.innerHTML = this.generateReplacementContent(childContent);
                    objNode.replaceChild(replacement, child);
                }
            }
        }
    }

    /**
     * @name Generate Replacement Content
     * @desc Takes string and replaces any regex pattern matches with the associated replace patterns
     * @param {string} content
     * @returns {string}
     */
    generateReplacementContent(content)
    {
        for(var i=0; i < this.regExPatterns.length; i++){
            content = content.replace(this.regExPatterns[i], this.replacePatterns[i]);
        }
        return content;
    }

    /**
     * @name Has Ignore Attributes
     * @desc Checks if a node contains any attribute that we want to avoid manipulating
     * @param {Element} node
     * @returns {boolean}
     */
    hasIgnoreAttributes(node)
    {
        var ignoreAttributes = {
            "class": ["ng-binding"]
        };

        // Loop through all attributes we want to test for ignoring
        for(var attributeName in ignoreAttributes){
            // Filter out the object's default properties
            if (ignoreAttributes.hasOwnProperty(attributeName)) {

                // Check this node has the attribute we are currently checking for
                if(node.hasAttribute(attributeName)){

                    // This node's value for the attribute we are checking
                    var nodeAttributeValue = node.getAttribute(attributeName);
                    // The values we want to ignore for this attribute
                    var badAttributeValueList = ignoreAttributes[attributeName];

                    // Loop through the attribute values we want to ignore
                    for(var i=0; i < badAttributeValueList.length; i++){
                        // If we find an indexOf, this value is present in the attribute
                        if(nodeAttributeValue.indexOf(badAttributeValueList[i]) !== -1){
                            return true;
                        }
                    }

                }

            }
        }

        return false;
    }

    /**
     * @name Is Pattern Matched
     * @desc Checks content matches any of the object's matchPatterns
     * @param {string} content
     * @returns {boolean}
     */
    isPatternMatched(content)
    {
        for(var i=0; i < this.matchPatterns.length; i++){
            if(this.matchPatterns[i].exec(content) !== null){
                return true;
            }
        }
        return false;
    }

    /**
     * @name Is Blockchain Explorer Site
     * @desc Check if the current website is the user's selected block explorer
     * @returns {boolean}
     */
    isBlockchainExplorerSite()
    {
        var objBlockchainExplorer = document.createElement("a");
        objBlockchainExplorer.href = this.strBlockchainExplorer;
        return (objBlockchainExplorer.hostname === window.location.hostname);
    }

    /**
     * @name Tidy Slots
     * @desc Searches document for slots adds the slot's child nodes to its parent then removes the slot
     */
    tidyUpSlots()
    {
        var slots = document.querySelectorAll("slot.ext-etheraddresslookup-temporary");
        for(var i=0; i < slots.length; i++){
            while(slots[i].childNodes.length > 0){
                slots[i].parentNode.insertBefore(slots[i].firstChild, slots[i]);
            }
            slots[i].parentNode.removeChild(slots[i]);
        }
    }

    addBlockies()
    {
        var blockieDivs = document.querySelectorAll("div.ext-etheraddresslookup-blockie");
        for(var i = 0; i < blockieDivs.length; i++){

            blockieDivs[i].style.backgroundImage = 'url(' + blockies.create({
                // toLowerCase is used because standard blockies are based on none-checksum Ethereum addresses
                seed:blockieDivs[i].getAttribute('data-ether-address').toLowerCase(),
                size: 8,
                scale: 16
            }).toDataURL() +')';
        }
    }

    //Removes the highlight style from Ethereum addresses
    removeHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-no_highlight");
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
        }
        return false;
    }

    //Adds the highlight style
    addHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
            objEtherAddresses[i].classList.remove("ext-etheraddresslookup-link-no_highlight");
        }
        return false;
    }

    //Sets the on hover behaviour for the address
    // - get the address stats with rpc
    setAddressOnHoverBehaviour()
    {
        var objNodes = document.getElementsByClassName("ext-etheraddresslookup-0xaddress");
        for (var i = 0; i < objNodes.length; i++) {
            objNodes[i].addEventListener('mouseover', this.event_0xAddressHover, false);
        }
    }

    //The event handler for 0x address mouseover.
    //It will do the logic to call the web3 rpc to get the address balance.
    event_0xAddressHover()
    {
        if(this.children.length > 1 && this.children[1].className == "ext-etheraddresslookup-address_stats_hover") {
            return false;
        }

        var objHoverNode = document.createElement("div");
        objHoverNode.className = "ext-etheraddresslookup-address_stats_hover";
        var objHoverNodeContent = document.createElement("div");
        objHoverNodeContent.className = "ext-etheraddresslookup-address_stats_hover_content";
        objHoverNodeContent.innerHTML = "
Fetching Data...
";
        objHoverNodeContent.innerHTML += "";

        objHoverNode.appendChild(objHoverNodeContent);
        this.appendChild(objHoverNode);

        //Get the RPC provider for the user
        objBrowser.runtime.sendMessage({func: "rpc_provider"}, function(objResponse) {
            var web3 = new Web3(new Web3.providers.HttpProvider(objResponse.resp));
            var str0xAddress = this.getAttribute("data-address");
            var strAccountBalance = parseFloat(web3.fromWei(web3.eth.getBalance(str0xAddress).toString(10), "ether")).toLocaleString('en-US', {maximumSignificantDigits: 9});
            var intTransactionCount = parseInt(web3.eth.getTransactionCount(str0xAddress)).toLocaleString();
            var blIsContractAddress = web3.eth.getCode(str0xAddress) == "0x" ? false: true;

            var objHoverNodeContent = this.children[1].children[0];
            objHoverNodeContent.innerHTML = "
ETH: "+ strAccountBalance +"
";
            objHoverNodeContent.innerHTML += "
Transactions out: "+ intTransactionCount +"
";
            if(blIsContractAddress) {
                objHoverNodeContent.innerHTML += "
This is a contract address
";
            }
            objHoverNodeContent.innerHTML += "";

            return false;
        }.bind(this));
    }
}

window.addEventListener("load", function() {
    let objEtherAddressLookup = new EtherAddressLookup(Web3);
});

//Send message from the extension to here.
objBrowser.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        let objEtherAddressLookup = new EtherAddressLookup();
        if(typeof request.func !== "undefined") {
            if(typeof objEtherAddressLookup[request.func] == "function") {
                objEtherAddressLookup[request.func]();
                sendResponse({status: "ok"});
                return true;
            }
        }
        sendResponse({status: "fail"});
    }
);
<commit_msg>Issue #247 - Made RPC calls async to prevent lag when waiting for RPC responses<commit_after>let objBrowser = chrome ? chrome : browser;

class EtherAddressLookup {

    constructor(objWeb3)
    {
        console.log("Init EAL");
        this.objWeb3 = objWeb3;
        this.setDefaultExtensionSettings();
        this.init();
    }

    setDefaultExtensionSettings()
    {
        this.blHighlight = false;
        this.strBlockchainExplorer = "https://etherscan.io/address";
        this.strRpcProvider = "https://localhost:8545";

        this.intSettingsCount = 0;
        this.intSettingsTotalCount = 2;
    }

    /**
     * @name init
     * @desc Gets extension settings and applies DOM manipulation
     */
    init()
    {
        let objBrowser = chrome ? chrome : browser;
        //Get the highlight option for the user
        objBrowser.runtime.sendMessage({func: "highlight_option"}, function(objResponse) {
            if(objResponse && objResponse.hasOwnProperty("resp")) {
                this.blHighlight = (objResponse.resp == 1);
            }
            ++this.intSettingsCount;
        }.bind(this));

        //Get the blockchain explorer for the user
        objBrowser.runtime.sendMessage({func: "blockchain_explorer"}, function(objResponse) {
            this.strBlockchainExplorer = objResponse.resp;
            ++this.intSettingsCount;
        }.bind(this));

        //Update the DOM once all settings have been received...
        setTimeout(function() {
            // Needs to happen after user settings have been collected
            // and in the context of init();
            this.setSearchAndReplaceSettings();
            this.setWarningSettings();
            this.manipulateDOM();
        }.bind(this), 10);
    }

    /**
     * @name Set Search And Replace Settings
     * @desc
     */
    setSearchAndReplaceSettings()
    {
        // Check user is on their favourite block explorer, on fail target blank.
        this.target = (this.isBlockchainExplorerSite() ? '_self' : '_blank');

        // Register RegEx Patterns
        this.regExPatterns = [
            // Ethereum Address Regex
            /(^|\s|:|-)((?:0x)[0-9a-fA-F]{40})(\s|$)/gi,

            // ENS Address Regex
            /(^|\s|:|-)([a-z0-9][a-z0-9-.]+[a-z0-9](?:\.eth))(\s|$)/gi,

            // ENS With ZWCs
            /(^|\s|:|-)([a-z0-9][a-z0-9-.]*(\u200B|\u200C|\u200D|\uFEFF|\u2028|\u2029||)[a-z0-9]*(?:\.eth))(\s|$)/gi
        ];

        // Register RegEx Matching Patterns
        this.matchPatterns = [
            // Ethereum Match Pattern
            /((?:0x)[0-9a-fA-F]{40})/gi,

            // ENS Match Pattern
            this.regExPatterns[1],

            // ENS With ZWCs
            this.regExPatterns[2]
        ];

        // Register Replace Patterns
        this.replacePatterns = [
            // Ethereum Address Replace
            '$1' +
            '

 $2' +
            '$3',

            // ENS Address Replace
            '$1$2$3',

            // ENS With ZWCs Replace
            '$1<slot title="WARNING! This ENS address has ZWCs. Someone may be trying to scam you." ' +
            'class="ext-etheraddresslookup-warning">$2</slot>$3'
        ];
    }

    /**
     * @name Set Warning Settings
     * @desc
     */
    setWarningSettings()
    {
        // The block explorers that can handle ENS addresses
        this.ENSCompatiableExplorers = [
            "https://etherscan.io/address",
            "https://etherchain.org/account"
        ];

        // Does the user's favorite explorer support an ENS address
        for(var i=0; i<this.ENSCompatiableExplorers.length; i++){
            if(this.strBlockchainExplorer == this.ENSCompatiableExplorers[i]){
                this.ENSCompatiable = true;
                break;
            }
            this.ENSCompatiable = false;
        }

        // On failure give the user a warning.
        if(!this.ENSCompatiable){
            this.replacePatterns[1] = '$1$2$3';
        }
    }

    manipulateDOM()
    {
        if(true || this.intSettingsCount === this.intSettingsTotalCount) {
            if(this.blBlacklistDomains) {
                this.blacklistedDomainCheck();
            }
            this.convertAddressToLink();

            this.setAddressOnHoverBehaviour();
        }
    }

    /**
     * @name Convert Address To Link
     * @desc Finds Ethereum addresses and converts to a link to a block explorer
     */
    convertAddressToLink()
    {
        var arrWhitelistedTags = ["code", "span", "p", "td", "li", "em", "i", "b", "strong", "small"];

        //Get the whitelisted nodes
        for(var i=0; i<arrWhitelistedTags.length; i++) {
            var objNodes = document.getElementsByTagName(arrWhitelistedTags[i]);
            //Loop through the whitelisted content
            for(var x=0; x<objNodes.length; x++) {

                if(this.hasIgnoreAttributes(objNodes[x])){
                    continue;
                }

                this.convertAddresses(objNodes[x]);
            }
        }

        this.tidyUpSlots();
        this.addBlockies();

        if(this.blHighlight) {
            this.addHighlightStyle();
        }
    }

    /**
     * @name Convert Addresses
     * @desc Takes a Node and checks if any of its children are textNodes. On success replace textNode with slot node
     * @desc slot node contains regex replaced content; see generateReplacementContent()
     * @param {Node} objNode
     */
    convertAddresses(objNode)
    {
        // Some nodes have non-textNode children
        // we need to ensure regex is applied only to text otherwise we will mess the html up
        for(var i=0; i < objNode.childNodes.length; i++){
            // Only check textNodes to prevent applying RegEx against element attributes
            if(objNode.childNodes[i].nodeType == 3){ // nodeType 3 = a text node

                var child = objNode.childNodes[i];
                var childContent = child.textContent;

                // Only start replacing stuff if the we get a RegEx match.
                if(this.isPatternMatched(childContent)) {
                    // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot
                    var replacement = document.createElement('slot');
                    replacement.setAttribute('class', 'ext-etheraddresslookup-temporary');
                    replacement.innerHTML = this.generateReplacementContent(childContent);
                    objNode.replaceChild(replacement, child);
                }
            }
        }
    }

    /**
     * @name Generate Replacement Content
     * @desc Takes string and replaces any regex pattern matches with the associated replace patterns
     * @param {string} content
     * @returns {string}
     */
    generateReplacementContent(content)
    {
        for(var i=0; i < this.regExPatterns.length; i++){
            content = content.replace(this.regExPatterns[i], this.replacePatterns[i]);
        }
        return content;
    }

    /**
     * @name Has Ignore Attributes
     * @desc Checks if a node contains any attribute that we want to avoid manipulating
     * @param {Element} node
     * @returns {boolean}
     */
    hasIgnoreAttributes(node)
    {
        var ignoreAttributes = {
            "class": ["ng-binding"]
        };

        // Loop through all attributes we want to test for ignoring
        for(var attributeName in ignoreAttributes){
            // Filter out the object's default properties
            if (ignoreAttributes.hasOwnProperty(attributeName)) {

                // Check this node has the attribute we are currently checking for
                if(node.hasAttribute(attributeName)){

                    // This node's value for the attribute we are checking
                    var nodeAttributeValue = node.getAttribute(attributeName);
                    // The values we want to ignore for this attribute
                    var badAttributeValueList = ignoreAttributes[attributeName];

                    // Loop through the attribute values we want to ignore
                    for(var i=0; i < badAttributeValueList.length; i++){
                        // If we find an indexOf, this value is present in the attribute
                        if(nodeAttributeValue.indexOf(badAttributeValueList[i]) !== -1){
                            return true;
                        }
                    }

                }

            }
        }

        return false;
    }

    /**
     * @name Is Pattern Matched
     * @desc Checks content matches any of the object's matchPatterns
     * @param {string} content
     * @returns {boolean}
     */
    isPatternMatched(content)
    {
        for(var i=0; i < this.matchPatterns.length; i++){
            if(this.matchPatterns[i].exec(content) !== null){
                return true;
            }
        }
        return false;
    }

    /**
     * @name Is Blockchain Explorer Site
     * @desc Check if the current website is the user's selected block explorer
     * @returns {boolean}
     */
    isBlockchainExplorerSite()
    {
        var objBlockchainExplorer = document.createElement("a");
        objBlockchainExplorer.href = this.strBlockchainExplorer;
        return (objBlockchainExplorer.hostname === window.location.hostname);
    }

    /**
     * @name Tidy Slots
     * @desc Searches document for slots adds the slot's child nodes to its parent then removes the slot
     */
    tidyUpSlots()
    {
        var slots = document.querySelectorAll("slot.ext-etheraddresslookup-temporary");
        for(var i=0; i < slots.length; i++){
            while(slots[i].childNodes.length > 0){
                slots[i].parentNode.insertBefore(slots[i].firstChild, slots[i]);
            }
            slots[i].parentNode.removeChild(slots[i]);
        }
    }

    addBlockies()
    {
        var blockieDivs = document.querySelectorAll("div.ext-etheraddresslookup-blockie");
        for(var i = 0; i < blockieDivs.length; i++){

            blockieDivs[i].style.backgroundImage = 'url(' + blockies.create({
                // toLowerCase is used because standard blockies are based on none-checksum Ethereum addresses
                seed:blockieDivs[i].getAttribute('data-ether-address').toLowerCase(),
                size: 8,
                scale: 16
            }).toDataURL() +')';
        }
    }

    //Removes the highlight style from Ethereum addresses
    removeHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-no_highlight");
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
        }
        return false;
    }

    //Adds the highlight style
    addHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
            objEtherAddresses[i].classList.remove("ext-etheraddresslookup-link-no_highlight");
        }
        return false;
    }

    //Sets the on hover behaviour for the address
    // - get the address stats with rpc
    setAddressOnHoverBehaviour()
    {
        var objNodes = document.getElementsByClassName("ext-etheraddresslookup-0xaddress");
        for (var i = 0; i < objNodes.length; i++) {
            objNodes[i].addEventListener('mouseover', this.event_0xAddressHover, false);
        }
    }

    //The event handler for 0x address mouseover.
    //It will do the logic to call the web3 rpc to get the address balance.
    event_0xAddressHover()
    {
        if(this.children.length > 1 && this.children[1].className == "ext-etheraddresslookup-address_stats_hover") {
            return false;
        }

        let intUniqueId = (new Date()).getTime();

        var objHoverNode = document.createElement("div");
        objHoverNode.className = "ext-etheraddresslookup-address_stats_hover";
        var objHoverNodeContent = document.createElement("div");
        objHoverNodeContent.className = "ext-etheraddresslookup-address_stats_hover_content";
        objHoverNodeContent.innerHTML = "
Fetching Data...
";
        objHoverNodeContent.innerHTML += "

";
        objHoverNodeContent.innerHTML += "
";
        objHoverNodeContent.innerHTML += "";

        objHoverNode.appendChild(objHoverNodeContent);
        this.appendChild(objHoverNode);

        //Get the RPC provider for the user
        objBrowser.runtime.sendMessage({func: "rpc_provider"}, function(objResponse) {
            var web3 = new Web3(new Web3.providers.HttpProvider(objResponse.resp));
            var str0xAddress = this.getAttribute("data-address");
            let objHoverNodeContent = this.children[1].children[0];

            //Get transaction count
            web3.eth.getTransactionCount(str0xAddress, function(error, result) {
                if(objHoverNodeContent.children[0].id == "ext-etheraddresslookup-fetching_data_"+intUniqueId) {
                    objHoverNodeContent.children[0].style.display = 'none';
                }

                var intTransactionCount = "";
                if(error) {
                    intTransactionCount = -1;
                } else {
                    intTransactionCount = parseInt(result).toLocaleString();
                }

                var objTransactionCount = objHoverNodeContent.querySelector("#ext-etheraddresslookup-transactions_out_"+intUniqueId);
                objTransactionCount.innerHTML = "Transactions out: "+ intTransactionCount;
            });

            //Get the account balance
            web3.eth.getBalance(str0xAddress, function(error, result) {
                if(objHoverNodeContent.children[0].id == "ext-etheraddresslookup-fetching_data_"+intUniqueId) {
                    objHoverNodeContent.children[0].style.display = 'none';
                }

                var flEthBalance = "";
                if(error) {
                    flEthBalance = -1;
                } else {
                    flEthBalance = web3.fromWei(result.toString(10), "ether").toLocaleString("en-US", {maximumSignificantDigits: 9});
                }

                var objAddressBalance = objHoverNodeContent.querySelector("#ext-etheraddresslookup-address_balance_"+intUniqueId);
                objAddressBalance.innerHTML += "ETH: "+ flEthBalance;
            });

            //See if the address is a contract
            web3.eth.getCode(str0xAddress, function(error, result) {
                if(objHoverNodeContent.children[0].id == "ext-etheraddresslookup-fetching_data_"+intUniqueId) {
                    objHoverNodeContent.children[0].style.display = 'none';
                }

                var objContractAddress = objHoverNodeContent.querySelector("#ext-etheraddresslookup-contract_address_"+intUniqueId);

                if(error) {
                    objContractAddress.innerHTML += "Unable to determine if contract";
                } else {
                    var blIsContractAddress = result == "0x" ? false : true;
                    if (blIsContractAddress) {
                        objContractAddress.innerHTML += "This is a contract address";
                    }
                }
            });

            if(objResponse.resp.includes("quiknode.io")) {
                objHoverNodeContent.innerHTML += "";
            }

            return false;
        }.bind(this));
    }
}

window.addEventListener("load", function() {
    let objEtherAddressLookup = new EtherAddressLookup(Web3);
});

//Send message from the extension to here.
objBrowser.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        let objEtherAddressLookup = new EtherAddressLookup();
        if(typeof request.func !== "undefined") {
            if(typeof objEtherAddressLookup[request.func] == "function") {
                objEtherAddressLookup[request.func]();
                sendResponse({status: "ok"});
                return true;
            }
        }
        sendResponse({status: "fail"});
    }
);
<|endoftext|>


<commit_before>'use strict';

// MODULES //

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

// TESTS //

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

tape( 'function returns `true` if provided an array-like object', function test( t ) {
	var values = [
			[],
			{'length': 10},
			'beep',
			arguments
		];

		for ( var i = 0; i < values.length; i++ ) {
			t.ok( isArrayLike( values[i] ), values[i] );
		}
	t.end();
});

tape( 'function returns `false` if not provided an array-like object', function test( t ) {
	var values = [
		5,
		null,
		undefined,
		NaN,
		true,
		false,
		{},
		function boop( a, b, c ) {}
	];

	for ( var i = 0; i < values.length; i++ ) {
		t.ok( !isArrayLike( values[i] ) );
	}
	t.end();
});
<commit_msg>Update is-array-like tests<commit_after>'use strict';

// MODULES //

var tape = require( 'tape' );
var UINT32_MAX = require( '@stdlib/math/constants/uint32-max' );
var isArrayLike = require( './../lib' );

// TESTS //

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

tape( 'function returns `true` if provided an array-like object', function test( t ) {
	var values;
	var i;

	values = [
		[],
		{'length': 10},
		'beep',
		arguments
	];

	for ( i = 0; i < values.length; i++ ) {
		t.equal( isArrayLike( values[i] ), true, 'returns true when provided '+values[i] );
	}
	t.end();
});

tape( 'function returns `false` if provided an array-like object whose length exceeds the maximum array length', function test( t ) {
	var o = {'length': UINT32_MAX+1};
	t.equal( isArrayLike( o ), false, 'returns false' );
	t.end();
});

tape( 'function returns `false` if not provided an array-like object', function test( t ) {
	var values;
	var i;

	values = [
		5,
		null,
		undefined,
		NaN,
		true,
		false,
		{},
		function boop( a, b, c ) {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.equal( isArrayLike( values[i] ), false, 'returns false when provided '+values[i] );
	}
	t.end();
});
<|endoftext|>


<commit_before>// Copyright 2002-2014, University of Colorado Boulder

/**
 * Eye dropper, with a button for dispensing whatever is in the dropper.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Property = require( 'AXON/Property' );
  var RoundMomentaryButton = require( 'SUN/buttons/RoundMomentaryButton' );
  var Shape = require( 'KITE/Shape' );

  // images
  var foregroundImage = require( 'image!SCENERY_PHET/eye_dropper_foreground.png' );
  var backgroundImage = require( 'image!SCENERY_PHET/eye_dropper_background.png' );

  // constants
  var DEBUG_ORIGIN = false; // if true, put a red dot at the dropper's origin (bottom center)

  // constants specific to the image files
  var TIP_WIDTH = 15;
  var TIP_HEIGHT = 4;
  var GLASS_WIDTH = 46;
  var GLASS_MAX_Y = -18; // relative to bottom center
  var GLASS_MIN_Y = -124; // relative to bottom center
  var BUTTON_CENTER_Y_OFFSET = 32; // y-offset of button's center in dropper image file

  /**
   * @param {Object} [options]
   * @constructor
   */
  function EyeDropperNode( options ) {

    options = _.extend( {
      cursor: 'pointer',
      onProperty: new Property( false ), // is the dropper on? (ie, dispensing)
      enabledProperty: new Property( true ), // is the button enabled?
      emptyProperty: new Property( false ), // does the dropper appear to be empty?
      buttonTouchAreaDilation: 30, // dilation of the button's radius for touchArea
      fluidColor: 'yellow', // {Color|String} color of the fluid in the glass
      buttonComponentID: null // componentID for the dropper button, see together
    }, options );

    var thisNode = this;

    // @public
    this.onProperty = options.onProperty;
    this.enabledProperty = options.enabledProperty;
    this.emptyProperty = options.emptyProperty;

    // @private fluid fills the glass portion of the dropper, shape is specific to the dropper image file
    this.fluidNode = new Path( new Shape()
      .moveTo( -TIP_WIDTH / 2, 0 )
      .lineTo( -TIP_WIDTH / 2, -TIP_HEIGHT )
      .lineTo( -GLASS_WIDTH / 2, GLASS_MAX_Y )
      .lineTo( -GLASS_WIDTH / 2, GLASS_MIN_Y )
      .lineTo( GLASS_WIDTH / 2, GLASS_MIN_Y )
      .lineTo( GLASS_WIDTH / 2, GLASS_MAX_Y )
      .lineTo( TIP_WIDTH / 2, -TIP_HEIGHT )
      .lineTo( TIP_WIDTH / 2, 0 )
      .close(), {
      fill: options.fluidColor
    } );

    // images, origin moved to bottom center
    var foreground = new Image( foregroundImage );
    var background = new Image( backgroundImage );
    foreground.x = -foreground.width / 2;
    foreground.y = -foreground.height;
    background.x = -background.width / 2;
    background.y = -background.height;

    // button, centered in the dropper's bulb
    var button = new RoundMomentaryButton( this.onProperty, {
      baseColor: 'red',
      radius: 18,
      componentID: options.buttonComponentID
    } );
    this.enabledProperty.link( function( enabled ) { button.enabled = enabled; } );
    button.touchArea = Shape.circle( button.width / 2, button.height / 2, ( button.width / 2 ) + options.buttonTouchAreaDilation );
    button.centerX = foreground.centerX;
    button.centerY = foreground.top + BUTTON_CENTER_Y_OFFSET;

    // make the background visible only when the dropper is empty
    this.emptyProperty.link( function( empty ) {
      thisNode.fluidNode.visible = !empty;
      background.visible = empty;
    } );

    options.children = [ this.fluidNode, background, foreground, button ];

    // add a red dot at the origin
    if ( DEBUG_ORIGIN ) {
      options.children.push( new Circle( { radius: 3, fill: 'red' } ) );
    }

    Node.call( this, options );
  }

  return inherit( Node, EyeDropperNode, {

    // You'll need these if you want to create fluid coming out of the tip, or put a label on the glass.
    TIP_WIDTH: TIP_WIDTH,
    TIP_HEIGHT: TIP_HEIGHT,
    GLASS_WIDTH: GLASS_WIDTH,
    GLASS_MIN_Y: GLASS_MIN_Y,
    GLASS_MAX_Y: GLASS_MAX_Y,

    set on( value ) { this.onProperty = value; },

    get on() { return this.onProperty.value; },

    set enabled( value ) { this.enabledProperty = value; },

    get enabled() { return this.enabledProperty.value; },

    set empty( value ) { this.emptyProperty = value; },

    get empty() { return this.emptyProperty.value; },

    set fluidColor( value ) { this.fluidNode.fill = value; },

    get fluidColor() { return this.fluidNode.fill; }
  } );
} );
<commit_msg>Moved componentID and together.addComponent call from BLLDropperNode to EyeDropperNode, see https://github.com/phetsims/arch/issues/14 and https://github.com/phetsims/concentration/issues/34<commit_after>// Copyright 2002-2014, University of Colorado Boulder

/**
 * Eye dropper, with a button for dispensing whatever is in the dropper.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Property = require( 'AXON/Property' );
  var RoundMomentaryButton = require( 'SUN/buttons/RoundMomentaryButton' );
  var Shape = require( 'KITE/Shape' );

  // images
  var foregroundImage = require( 'image!SCENERY_PHET/eye_dropper_foreground.png' );
  var backgroundImage = require( 'image!SCENERY_PHET/eye_dropper_background.png' );

  // constants
  var DEBUG_ORIGIN = false; // if true, put a red dot at the dropper's origin (bottom center)

  // constants specific to the image files
  var TIP_WIDTH = 15;
  var TIP_HEIGHT = 4;
  var GLASS_WIDTH = 46;
  var GLASS_MAX_Y = -18; // relative to bottom center
  var GLASS_MIN_Y = -124; // relative to bottom center
  var BUTTON_CENTER_Y_OFFSET = 32; // y-offset of button's center in dropper image file

  /**
   * @param {Object} [options]
   * @constructor
   */
  function EyeDropperNode( options ) {

    options = _.extend( {
      cursor: 'pointer',
      onProperty: new Property( false ), // is the dropper on? (ie, dispensing)
      enabledProperty: new Property( true ), // is the button enabled?
      emptyProperty: new Property( false ), // does the dropper appear to be empty?
      buttonTouchAreaDilation: 30, // dilation of the button's radius for touchArea
      fluidColor: 'yellow', // {Color|String} color of the fluid in the glass
      componentID: null, //componentID for the dropper, see together
      buttonComponentID: null // componentID for the dropper button, see together
    }, options );

    this.componentID = options.componentID;

    var thisNode = this;

    // @public
    this.onProperty = options.onProperty;
    this.enabledProperty = options.enabledProperty;
    this.emptyProperty = options.emptyProperty;

    // @private fluid fills the glass portion of the dropper, shape is specific to the dropper image file
    this.fluidNode = new Path( new Shape()
      .moveTo( -TIP_WIDTH / 2, 0 )
      .lineTo( -TIP_WIDTH / 2, -TIP_HEIGHT )
      .lineTo( -GLASS_WIDTH / 2, GLASS_MAX_Y )
      .lineTo( -GLASS_WIDTH / 2, GLASS_MIN_Y )
      .lineTo( GLASS_WIDTH / 2, GLASS_MIN_Y )
      .lineTo( GLASS_WIDTH / 2, GLASS_MAX_Y )
      .lineTo( TIP_WIDTH / 2, -TIP_HEIGHT )
      .lineTo( TIP_WIDTH / 2, 0 )
      .close(), {
      fill: options.fluidColor
    } );

    // images, origin moved to bottom center
    var foreground = new Image( foregroundImage );
    var background = new Image( backgroundImage );
    foreground.x = -foreground.width / 2;
    foreground.y = -foreground.height;
    background.x = -background.width / 2;
    background.y = -background.height;

    // button, centered in the dropper's bulb
    var button = new RoundMomentaryButton( this.onProperty, {
      baseColor: 'red',
      radius: 18,
      componentID: options.buttonComponentID
    } );
    this.enabledProperty.link( function( enabled ) { button.enabled = enabled; } );
    button.touchArea = Shape.circle( button.width / 2, button.height / 2, ( button.width / 2 ) + options.buttonTouchAreaDilation );
    button.centerX = foreground.centerX;
    button.centerY = foreground.top + BUTTON_CENTER_Y_OFFSET;

    // make the background visible only when the dropper is empty
    this.emptyProperty.link( function( empty ) {
      thisNode.fluidNode.visible = !empty;
      background.visible = empty;
    } );

    options.children = [ this.fluidNode, background, foreground, button ];

    // add a red dot at the origin
    if ( DEBUG_ORIGIN ) {
      options.children.push( new Circle( { radius: 3, fill: 'red' } ) );
    }

    Node.call( this, options );

    together && together.addComponent( this );
  }

  return inherit( Node, EyeDropperNode, {

    // You'll need these if you want to create fluid coming out of the tip, or put a label on the glass.
    TIP_WIDTH: TIP_WIDTH,
    TIP_HEIGHT: TIP_HEIGHT,
    GLASS_WIDTH: GLASS_WIDTH,
    GLASS_MIN_Y: GLASS_MIN_Y,
    GLASS_MAX_Y: GLASS_MAX_Y,

    set on( value ) { this.onProperty = value; },

    get on() { return this.onProperty.value; },

    set enabled( value ) { this.enabledProperty = value; },

    get enabled() { return this.enabledProperty.value; },

    set empty( value ) { this.emptyProperty = value; },

    get empty() { return this.emptyProperty.value; },

    set fluidColor( value ) { this.fluidNode.fill = value; },

    get fluidColor() { return this.fluidNode.fill; }
  } );
} );
<|endoftext|>


<commit_before>/* global BOOM.Tile */

var BOOM = BOOM || {};

BOOM.GameBoardModel = function (hTiles, vTiles, bombPercent) {
    this.hTiles = hTiles;
    this.vTiles = vTiles;
    this.bombPercent = bombPercent;

    this.tileArray = [];
    this.numberOfBombs = 0;
    this.correctlyMarkedBombs = 0;

    this.TileType = {
        BLANK: 0, ONE: 1, TWO: 2, THREE: 3, FOUR: 4, FIVE: 5, SIX: 6,
        SEVEN: 7, EIGHT: 8, BOMB: 9, COVERED: 10, FLAG: 11, SAFE: 12
    };
};

BOOM.GameBoardModel.prototype = {

    constructor: BOOM.GameBoardModel,

    createGameBoard: function () {
        var vTilesTemp = this.vTiles + 2,
            hTilesTemp = this.hTiles + 2,
            tileArrayTemp = [],
            i, j, k, l;

        // create empty 2-dimensional tileArray and tileArrayTemp
        for (i = 0; i < this.vTiles; i++) {
            this.tileArray[i] = new Array(this.hTiles);
        }
        for (i = 0; i < vTilesTemp; i++) {
            tileArrayTemp[i] = new Array(hTilesTemp);
        }

        for (i = 0; i < vTilesTemp; i++) {
            for (j = 0; j < hTilesTemp; j++) {
                if (i === 0 || i === vTilesTemp - 1 || j === 0 || j === hTilesTemp - 1) {
                    tileArrayTemp[i][j] = new BOOM.Tile(this.TileType.BLANK);
                } else {
                    if (Math.random() * 100 < this.bombPercent) {
                        tileArrayTemp[i][j] = new BOOM.Tile(this.TileType.BOMB);
                        this.numberOfBombs++;
                    } else {
                        tileArrayTemp[i][j] = new BOOM.Tile(this.TileType.BLANK);
                    }
                }
            }
        }

        for (i = 1; i < vTilesTemp - 1; i++) {
            for (j = 1; j < hTilesTemp - 1; j++) {
                var numberOfAdjacentBombs = 0;
                if (tileArrayTemp[i][j].tileType !== this.TileType.BOMB) {
                    for (k = -1; k < 2; k++) {
                        for (l = -1; l < 2; l++) {
                            if (tileArrayTemp[i + k][j + l].tileType === this.TileType.BOMB) {
                                numberOfAdjacentBombs++;
                            }
                        }
                    }
                    tileArrayTemp[i][j].tileType = numberOfAdjacentBombs;
                }
            }
        }

        for (i = 0; i < this.vTiles; i++) {
            for (j = 0; j < this.hTiles; j++) {
                this.tileArray[i][j] = tileArrayTemp[i + 1][j + 1];
            }
        }
        console.log(this.tileArray);
    },

    toggleFlag: function (vPos, hPos) {
        var tile = this.tileArray[vPos][hPos];
        if (tile.flag) {
            tile.flag = false;
            if (tile.tileType === this.TileType.BOMB) {
                this.correctlyMarkedBombs--;
            }
        } else {
            tile.flag = true;
            if (tile.tileType === this.TileType.BOMB) {
                this.correctlyMarkedBombs++;
                if (this.correctlyMarkedBombs === this.numberOfBombs) {
                    console.log('game over!!! you win!!!');
                }
            }
        }
        BOOM.gbView.toggleFlag(vPos, hPos);
        console.log(this.correctlyMarkedBombs + '/' + this.numberOfBombs);
    },

    uncoverTile: function (vPos, hPos) {
        var tile = this.tileArray[vPos][hPos];
        tile.covered = false;
        BOOM.gbView.uncoverTile(vPos, hPos);
        if (tile.tileType === this.TileType.BOMB) {
            this.gameOver();
        } else if (tile.tileType === this.TileType.BLANK) {
            tile.tileType = this.TileType.SAFE;
            this.uncoverSafeZone(vPos, hPos);
        }
    },

    gameOver: function () {
        console.log('you loose, bitches kABOOM');
    },

    uncoverSafeZone: function (vPos, hPos) {
        hPos = Number(hPos);
        vPos = Number(vPos);
        for (var i = -1; i < 2; i++) {
            for (var j = -1; j < 2; j++) {
                if (((vPos + i) >= 0) && ((vPos + i) < this.vTiles) &&
                    ((hPos + j) >= 0) && ((hPos + j) < this.hTiles)) {
                    var tile = this.tileArray[vPos + i][hPos + j];
                    // this.uncoverTile(vPos + i, hPos + j);
console.log(vPos + i, hPos + j);
                    tile.covered = false;
                    BOOM.gbView.uncoverTile(vPos + i, hPos + j);
                    if (tile.tileType === this.TileType.BLANK) {
                        tile.tileType = this.TileType.SAFE;
                        this.uncoverSafeZone(vPos + i, hPos + j);
                    }
                }
            }
        }
    }
};

<commit_msg>gameboard changes<commit_after>'use strict';

/* global BOOM.Tile */

var BOOM = BOOM || {};

BOOM.GameBoardModel = function (hTiles, vTiles, bombPercent, animSpeed) {
    this.hTiles = hTiles;
    this.vTiles = vTiles;
    this.bombPercent = bombPercent;
    this.animSpeed = animSpeed;

    this.tileArray = [];
    this.numberOfBombs = 0;
    this.correctlyMarkedBombs = 0;
    this.animTimer;

    this.TileType = {
        BLANK: 0, ONE: 1, TWO: 2, THREE: 3, FOUR: 4, FIVE: 5, SIX: 6,
        SEVEN: 7, EIGHT: 8, BOMB: 9, COVERED: 10, FLAG: 11, SAFE: 12
    };
};

BOOM.GameBoardModel.prototype = {

    constructor: BOOM.GameBoardModel,

    createGameBoard: function () {
        var vTilesTemp = this.vTiles + 2;
        var hTilesTemp = this.hTiles + 2;
        var tileArrayTemp = [];
        var i, j, k, l;

        // create empty 2-dimensional tileArray and tileArrayTemp
        for (i = 0; i < this.vTiles; i++) {
            this.tileArray[i] = new Array(this.hTiles);
        }
        for (i = 0; i < vTilesTemp; i++) {
            tileArrayTemp[i] = new Array(hTilesTemp);
        }

        // populate tileArrayTemp with bombs and blanks
        for (i = 0; i < vTilesTemp; i++) {
            for (j = 0; j < hTilesTemp; j++) {
                if (i === 0 || i === vTilesTemp - 1 || j === 0 || j === hTilesTemp - 1) {
                    tileArrayTemp[i][j] = new BOOM.Tile(this.TileType.BLANK);
                } else {
                    if (Math.random() * 100 < this.bombPercent) {
                        tileArrayTemp[i][j] = new BOOM.Tile(this.TileType.BOMB);
                        this.numberOfBombs++;
                    } else {
                        tileArrayTemp[i][j] = new BOOM.Tile(this.TileType.BLANK);
                    }
                }
            }
        }

        // replace blank tileTypes with ones that indicate the
        // number of surrounding bombs, if needed
        for (i = 1; i < vTilesTemp - 1; i++) {
            for (j = 1; j < hTilesTemp - 1; j++) {
                var numberOfAdjacentBombs = 0;
                if (tileArrayTemp[i][j].tileType !== this.TileType.BOMB) {
                    for (k = -1; k < 2; k++) {
                        for (l = -1; l < 2; l++) {
                            if (tileArrayTemp[i + k][j + l].tileType === this.TileType.BOMB) {
                                numberOfAdjacentBombs++;
                            }
                        }
                    }
                    tileArrayTemp[i][j].tileType = numberOfAdjacentBombs;
                }
            }
        }

        // copy tileArrayTemp to tileArray minus the outside columns and rows
        for (i = 0; i < this.vTiles; i++) {
            for (j = 0; j < this.hTiles; j++) {
                this.tileArray[i][j] = tileArrayTemp[i + 1][j + 1];
            }
        }
    },

    toggleFlag: function (vPos, hPos) {
        var tile = this.tileArray[vPos][hPos];
        if (tile.flag) {
            tile.flag = false;
            if (tile.tileType === this.TileType.BOMB) {
                this.correctlyMarkedBombs--;
            }
        } else {
            tile.flag = true;
            if (tile.tileType === this.TileType.BOMB) {
                this.correctlyMarkedBombs++;
                if (this.correctlyMarkedBombs === this.numberOfBombs) {
                    this.gameOver(true);
                }
            }
        }
        BOOM.gbView.toggleFlag(vPos, hPos);
    },

    uncoverTile: function (vPos, hPos) {
        var tile = this.tileArray[vPos][hPos];
        tile.covered = false;
        BOOM.gbView.uncoverTile(vPos, hPos);
        if (tile.tileType === this.TileType.BOMB) {
            this.gameOver(false);
        } else if (tile.tileType === this.TileType.BLANK) {
            tile.tileType = this.TileType.SAFE;
            this.animTimer = 0;
            this.uncoverSafeZone(vPos, hPos);
        }
    },

    gameOver: function (won) {
        if (won) {
            console.log('game over!!! you win!!!')
        } else {
            BOOM.gbView.gameOverLose();
        }
    },

    // recursive function that reveals adjacent blank tiles
    uncoverSafeZone: function (vPos, hPos) {
        hPos = Number(hPos);
        vPos = Number(vPos);
        for (var i = -1; i < 2; i++) {
            for (var j = -1; j < 2; j++) {
                if (vPos + i >= 0 && vPos + i < this.vTiles &&
                    hPos + j >= 0 && hPos + j < this.hTiles) {
                    var tile = this.tileArray[vPos + i][hPos + j];
                    var that = this;

                    // need to create closure with self-invoking
                    // function in order to ensure that
                    // each call of gbView.uncoverTile() gets its own
                    // copy of i and j
                    var x = function (y, x) {
                        // stagger calls to gvView.uncoverTile() by
                        // animSpeed to create cascading animation
                        // effect when revealing tiles
                        setTimeout(function () {
                            tile.covered = false;
                            BOOM.gbView.uncoverTile(y, x);
                        }, that.animTimer += that.animSpeed);
                    }(vPos + i, hPos + j);

                    // before recursing on a tile, set tileType to SAFE
                    // in order to prevent
                    // multiple recurssive calls to the same tile
                    if (tile.tileType === this.TileType.BLANK) {
                        tile.tileType = this.TileType.SAFE;
                        this.uncoverSafeZone(vPos + i, hPos + j);
                    }
                }
            }
        }
    }
};

<|endoftext|>


<commit_before>import React, {Component} from 'react'
import user from 'lib/user/user'
import t from 't-component'
import tagImages from 'lib/tags-images'
import { Link } from 'react-router'

export default class ListItem extends Component {
  constructor (props) {
    super(props)
    this.onUserStateChange = this.onUserStateChange.bind(this)
  }

  componentWillMount () {
    user.on('loaded', this.onUserStateChange)
    user.on('unloaded', this.onUserStateChange)
  }

  componentWillUnmount () {
    user.off('loaded', this.onUserStateChange)
    user.off('unloaded', this.onUserStateChange)
  }

  onUserStateChange () {
    this.forceUpdate()
  }

  createdBy (item) {
    if (item.participants.length > 0) {
      var cardinality = item.participants.length === 1 ? 'singular' : 'plural'
      return item.participants.length +
        ' ' + t('proposal-article.participant.' + cardinality)
    }
    if (item.closingAt) {
      var closed = item.closingAt && +new Date(item.closingAt) < Date.now()
      return (

            {(closed ? t('common.closed') : t('common.close'))}

      )
    }
  }

  render () {
    var voted = user.logged() && !!this.props.item.voted
    return (

        <Link
          to={this.props.item.url}
          className={(voted ? 'voted' : '') + (this.props.active ? 'active' : '')}
          activeClassName='active'>

              {
                ((voted) ?  : null)
              }

              {this.props.item.mediaTitle}

              {this.createdBy(this.props.item)}

        </Link>

    )
  }
}
<commit_msg>fix sidebar style<commit_after>import React, {Component} from 'react'
import user from 'lib/user/user'
import t from 't-component'
import tagImages from 'lib/tags-images'
import { Link } from 'react-router'

export default class ListItem extends Component {
  constructor (props) {
    super(props)
    this.onUserStateChange = this.onUserStateChange.bind(this)
  }

  componentWillMount () {
    user.on('loaded', this.onUserStateChange)
    user.on('unloaded', this.onUserStateChange)
  }

  componentWillUnmount () {
    user.off('loaded', this.onUserStateChange)
    user.off('unloaded', this.onUserStateChange)
  }

  onUserStateChange () {
    this.forceUpdate()
  }

  createdBy (item) {
    if (item.participants.length > 0) {
      var cardinality = item.participants.length === 1 ? 'singular' : 'plural'
      return item.participants.length +
        ' ' + t('proposal-article.participant.' + cardinality)
    }
    if (item.closingAt) {
      var closed = item.closingAt && +new Date(item.closingAt) < Date.now()
      return (

            {(closed ? t('common.closed') : t('common.close'))}

      )
    }
  }

  render () {
    var voted = user.logged() && !!this.props.item.voted
    return (

        <Link
          to={this.props.item.url}
          className={(voted ? 'voted' : '') + (this.props.active ? ' active' : '')}
          activeClassName='active'>

              {
                ((voted) ?  : null)
              }

              {this.props.item.mediaTitle}

              {this.createdBy(this.props.item)}

        </Link>

    )
  }
}
<|endoftext|>


<commit_before>import React, {Component} from 'react'
import {Link} from 'react-router'
import Chart from 'chart.js'
import t from 't-component'
import topicStore from 'lib/stores/topic-store/topic-store'
import userConnector from 'lib/site/connectors/user'
import voteOptions from './vote-options'

class Decision extends Component {
  constructor (props) {
    super(props)
    this.options = voteOptions

    this.state = {
      votes: {
        positives: [],
        neutrals: [],
        negatives: []
      },
      alert: {
        className: '',
        text: '',
        hide: true
      },
      voted: false,
      changingVote: false
    }
  }

  componentWillMount () {
    let newState = this.prepareState(this.props.votes)
    this.setState(newState)
  }

  componentWillReceiveProps (props) {
    let newState = this.prepareState(props.votes)
    this.setState(newState)
  }

  prepareState (votes) {
    let voted = false
    let votedValue = null
    let alertVote = null

    if (this.props.user.state.fulfilled) {
      const user = this.props.user.state.value
      Object.keys(votes).forEach((votesOpt) => {
        if (votes[votesOpt].indexOf(user.id) !== -1) {
          voted = true
          votedValue = votesOpt
          alertVote = this.options[votedValue].alert
        }
      })
    }

    let alert
    if (alertVote) {
      alert = {
        className: alertVote.className,
        text: alertVote.text,
        hide: false
      }
    } else {
      alert = {
        className: '',
        text: '',
        hide: true
      }
    }

    return {
      voted: voted,
      votes: votes,
      alert: alert
    }
  }

  handleVote = (e) => {
    if (!this.props.user.state.fulfilled) return

    let voteValue = e.currentTarget.getAttribute('data-vote')
    topicStore
      .vote(this.props.id, voteValue)
      .then(() => {
        this.setState({
          voted: true,
          changingVote: false,
          alert: Object.assign({}, this.options[voteValue].alert, {hide: false})
        })
      })
      .catch((err) => {
        console.warn('Error on vote setState', err)
        this.setState({
          alert: {
            className: 'alert-warning',
            text: 'proposal-options.error.voting',
            hide: false
          },
          voted: false
        })
      })
  }

  handleChangeVote = () => {
    this.setState({changingVote: true})
  }

  resultChartDidMount = (resultChart) => {
    if (!resultChart) return
    let votes = this.state.votes
    let votesTotal = Object.keys(votes)
      .reduce((a, b) => a + votes[b].length, 0)
    let data = []

    if (votesTotal) {
      data.push({
        value: votes.positive.length,
        color: '#a4cb53',
        label: t('proposal-options.yea'),
        labelColor: 'white',
        labelAlign: 'center'
      })
      data.push({
        value: votes.neutral.length,
        color: '#666666',
        label: t('proposal-options.abstain'),
        labelColor: 'white',
        labelAlign: 'center'
      })
      data.push({
        value: votes.negative.length,
        color: '#d95e59',
        label: t('proposal-options.nay'),
        labelColor: 'white',
        labelAlign: 'center'
      })

      new Chart(resultChart.getContext('2d'))
        .Pie(data, {animation: false}) // eslint-disable-line new-cap
    }
  }

  render () {
    const votes = this.state.votes

    const votesTotal = Object.keys(votes).reduce(function (a, b) {
      return a + votes[b].length
    }, 0)

    const closed = this.props.closed

    return (

        {
          !this.state.alert.hide &&
          !this.state.changingVote &&
          (

              {this.state.alert.text && t(this.state.alert.text)}.

          )
        }
        {
          !closed &&
          this.state.voted &&
          !this.state.changingVote && (
            <button
              className='meta-item change-vote'
              onClick={this.handleChangeVote}>

              {t('proposal-options.change-vote')}.
            </button>
          )
        }
        {
          closed && votesTotal > 0 && (
            <ResultBox
              votes={votes}
              votesTotal={votesTotal}
              options={this.options}
              resultChartDidMount={this.resultChartDidMount} />
          )
        }
        {
          !closed && (!this.state.voted || this.state.changingVote) && (
            <VoteBox options={this.options} onVote={this.handleVote} />
          )
        }

            {t('proposal-options.votes-cast', {num: votesTotal})}

        {
          !this.props.user.state.fulfilled &&
          !this.props.cantVote && (

                {t('proposal-options.must-be-signed-in') + '. '}
                <Link
                  to={{
                    pathname: '/signin',
                    query: {ref: window.location.pathname}
                  }}>
                  {t('signin.login')}
                </Link>
                 {t('common.or')}
                <Link to='/signup'>
                  {t('signin.signup')}
                </Link>.

          )
        }
        {
          this.props.cantVote && (

                {t('privileges-alert.not-can-vote-and-comment')}

          )
        }

    )
  }
}

export default userConnector(Decision)

function ResultBox (props) {
  const votesTotal = props.votesTotal
  const votes = props.votes
  const resultChartDidMount = props.resultChartDidMount
  const options = props.options

  return (

          {
            votesTotal === 0
              ? t('proposal-options.no-votes-cast')
              : t('proposal-options.votes-cast', {num: votesTotal})
          }

        <canvas
          id='results-chart'
          width='220'
          height='220'
          ref={resultChartDidMount} />

        {
          Object.keys(votes)
            .map(function (v) {
              const votesByType = votes[v]
              const option = options[v].button

              if (votesByType.length === 0) return null

              let width = votesTotal ?
                (votesByType.length / votesTotal) * 100 :
                0
              width = Math.round(width * 100) / 100

              let s = votesByType.length === 1 ? '' : 's'

              return (

{t(option.text)}

                  {width}%

                    {votesByType.length}
                    {t('proposal-options.vote-item') + s}

              )
            })
          }

  )
}

function VoteBox (props) {
  const options = props.options
  const handleVote = props.onVote

  return (

{t('proposal-options.vote')}

          {
            Object.keys(options).map(function (o) {
              const option = options[o]
              return (

                  {t(option.button.text)}

              )
            })
          }

  )
}
<commit_msg>fix topic article container<commit_after>import React, {Component} from 'react'
import {Link} from 'react-router'
import Chart from 'chart.js'
import t from 't-component'
import topicStore from 'lib/stores/topic-store/topic-store'
import userConnector from 'lib/site/connectors/user'
import voteOptions from './vote-options'

class Decision extends Component {
  constructor (props) {
    super(props)
    this.options = voteOptions

    this.state = {
      votes: {
        positives: [],
        neutrals: [],
        negatives: []
      },
      alert: {
        className: '',
        text: '',
        hide: true
      },
      voted: false,
      changingVote: false
    }
  }

  componentWillMount () {
    let newState = this.prepareState(this.props.votes)
    this.setState(newState)
  }

  componentWillReceiveProps (props) {
    let newState = this.prepareState(props.votes)
    this.setState(newState)
  }

  prepareState (votes) {
    let voted = false
    let votedValue = null
    let alertVote = null

    if (this.props.user.state.fulfilled) {
      const user = this.props.user.state.value
      Object.keys(votes).forEach((votesOpt) => {
        if (votes[votesOpt].indexOf(user.id) !== -1) {
          voted = true
          votedValue = votesOpt
          alertVote = this.options[votedValue].alert
        }
      })
    }

    let alert
    if (alertVote) {
      alert = {
        className: alertVote.className,
        text: alertVote.text,
        hide: false
      }
    } else {
      alert = {
        className: '',
        text: '',
        hide: true
      }
    }

    return {
      voted: voted,
      votes: votes,
      alert: alert
    }
  }

  handleVote = (e) => {
    if (!this.props.user.state.fulfilled) return

    let voteValue = e.currentTarget.getAttribute('data-vote')
    topicStore
      .vote(this.props.id, voteValue)
      .then(() => {
        this.setState({
          voted: true,
          changingVote: false,
          alert: Object.assign({}, this.options[voteValue].alert, {hide: false})
        })
      })
      .catch((err) => {
        console.warn('Error on vote setState', err)
        this.setState({
          alert: {
            className: 'alert-warning',
            text: 'proposal-options.error.voting',
            hide: false
          },
          voted: false
        })
      })
  }

  handleChangeVote = () => {
    this.setState({changingVote: true})
  }

  resultChartDidMount = (resultChart) => {
    if (!resultChart) return
    let votes = this.state.votes
    let votesTotal = Object.keys(votes)
      .reduce((a, b) => a + votes[b].length, 0)
    let data = []

    if (votesTotal) {
      data.push({
        value: votes.positive.length,
        color: '#a4cb53',
        label: t('proposal-options.yea'),
        labelColor: 'white',
        labelAlign: 'center'
      })
      data.push({
        value: votes.neutral.length,
        color: '#666666',
        label: t('proposal-options.abstain'),
        labelColor: 'white',
        labelAlign: 'center'
      })
      data.push({
        value: votes.negative.length,
        color: '#d95e59',
        label: t('proposal-options.nay'),
        labelColor: 'white',
        labelAlign: 'center'
      })

      new Chart(resultChart.getContext('2d'))
        .Pie(data, {animation: false}) // eslint-disable-line new-cap
    }
  }

  render () {
    const votes = this.state.votes

    const votesTotal = Object.keys(votes).reduce(function (a, b) {
      return a + votes[b].length
    }, 0)

    const closed = this.props.closed

    return (

        {
          !this.state.alert.hide &&
          !this.state.changingVote &&
          (

              {this.state.alert.text && t(this.state.alert.text)}.

          )
        }
        {
          !closed &&
          this.state.voted &&
          !this.state.changingVote && (
            <button
              className='meta-item change-vote'
              onClick={this.handleChangeVote}>

              {t('proposal-options.change-vote')}.
            </button>
          )
        }
        {
          closed && votesTotal > 0 && (
            <ResultBox
              votes={votes}
              votesTotal={votesTotal}
              options={this.options}
              resultChartDidMount={this.resultChartDidMount} />
          )
        }
        {
          !closed && (!this.state.voted || this.state.changingVote) && (
            <VoteBox options={this.options} onVote={this.handleVote} />
          )
        }

            {t('proposal-options.votes-cast', {num: votesTotal})}

        {
          !this.props.user.state.fulfilled &&
          !this.props.cantVote && (

                {t('proposal-options.must-be-signed-in') + '. '}
                <Link
                  to={{
                    pathname: '/signin',
                    query: {ref: window.location.pathname}
                  }}>
                  {t('signin.login')}
                </Link>
                 {t('common.or')}
                <Link to='/signup'>
                  {t('signin.signup')}
                </Link>.

          )
        }
        {
          this.props.cantVote && (

                {t('privileges-alert.not-can-vote-and-comment')}

          )
        }

    )
  }
}

export default userConnector(Decision)

function ResultBox (props) {
  const votesTotal = props.votesTotal
  const votes = props.votes
  const resultChartDidMount = props.resultChartDidMount
  const options = props.options

  return (

          {
            votesTotal === 0
              ? t('proposal-options.no-votes-cast')
              : t('proposal-options.votes-cast', {num: votesTotal})
          }

        <canvas
          id='results-chart'
          width='220'
          height='220'
          ref={resultChartDidMount} />

        {
          Object.keys(votes)
            .map(function (v) {
              const votesByType = votes[v]
              const option = options[v].button

              if (votesByType.length === 0) return null

              let width = votesTotal ?
                (votesByType.length / votesTotal) * 100 :
                0
              width = Math.round(width * 100) / 100

              let s = votesByType.length === 1 ? '' : 's'

              return (

{t(option.text)}

                  {width}%

                    {votesByType.length}
                    {t('proposal-options.vote-item') + s}

              )
            })
          }

  )
}

function VoteBox (props) {
  const options = props.options
  const handleVote = props.onVote

  return (

{t('proposal-options.vote')}

          {
            Object.keys(options).map(function (o) {
              const option = options[o]
              return (

                  {t(option.button.text)}

              )
            })
          }

  )
}
<|endoftext|>


<commit_before>define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function (Tone) {

	/**
	 *  @class A signal which adds the method getValueAtTime.
	 *         Code and inspiration from https://github.com/jsantell/web-audio-automation-timeline
	 */
	Tone.TimelineSignal = function(){

		//extend Tone.Signal
		Tone.Signal.apply(this, arguments);

		/**
		 *  The scheduled events
		 *  @type {Tone.Timeline}
		 *  @private
		 */
		this._events = new Tone.Timeline();

		/**
		 *  The initial scheduled value
		 *  @type {Number}
		 *  @private
		 */
		this._initial = this._value.value;
	};

	Tone.extend(Tone.TimelineSignal, Tone.Signal);

	/**
	 *  The event types of a schedulable signal.
	 *  @enum {String}
	 */
	Tone.TimelineSignal.Type = {
		Linear : "linear",
		Exponential : "exponential",
		Target : "target",
		Set : "set"
	};

	/**
	 * The current value of the signal.
	 * @memberOf Tone.TimelineSignal#
	 * @type {Number}
	 * @name value
	 */
	Object.defineProperty(Tone.TimelineSignal.prototype, "value", {
		get : function(){
			return this._toUnits(this._value.value);
		},
		set : function(value){
			var convertedVal = this._fromUnits(value);
			this._initial = convertedVal;
			this._value.value = convertedVal;
		}
	});

	///////////////////////////////////////////////////////////////////////////
	//	SCHEDULING
	///////////////////////////////////////////////////////////////////////////

	/**
	 *  Schedules a parameter value change at the given time.
	 *  @param {*}	value The value to set the signal.
	 *  @param {Time}  time The time when the change should occur.
	 *  @returns {Tone.TimelineSignal} this
	 *  @example
	 * //set the frequency to "G4" in exactly 1 second from now.
	 * freq.setValueAtTime("G4", "+1");
	 */
	Tone.TimelineSignal.prototype.setValueAtTime = function (value, startTime) {
		var unitValue = this._fromUnits(value);
		startTime = this.toSeconds(startTime);
		this._events.addEvent({
			"type" : Tone.TimelineSignal.Type.Set,
			"value" : unitValue,
			"time" : startTime
		});
		//invoke the original event
		Tone.Signal.prototype.setValueAtTime.apply(this, arguments);
		return this;
	};

	/**
	 *  Schedules a linear continuous change in parameter value from the
	 *  previous scheduled parameter value to the given value.
	 *
	 *  @param  {number} value
	 *  @param  {Time} endTime
	 *  @returns {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.linearRampToValueAtTime = function (value, endTime) {
		var unitValue = this._fromUnits(value);
		endTime = this.toSeconds(endTime);
		this._events.addEvent({
			"type" : Tone.TimelineSignal.Type.Linear,
			"value" : unitValue,
			"time" : endTime
		});
		Tone.Signal.prototype.linearRampToValueAtTime.apply(this, arguments);
		return this;
	};

	/**
	 *  Schedules an exponential continuous change in parameter value from
	 *  the previous scheduled parameter value to the given value.
	 *
	 *  @param  {number} value
	 *  @param  {Time} endTime
	 *  @returns {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) {
		var unitValue = this._fromUnits(value);
		unitValue = Math.max(this._minOutput, unitValue);
		endTime = this.toSeconds(endTime);
		this._events.addEvent({
			"type" : Tone.TimelineSignal.Type.Exponential,
			"value" : unitValue,
			"time" : endTime
		});
		Tone.Signal.prototype.exponentialRampToValueAtTime.apply(this, arguments);
		return this;
	};

	/**
	 *  Start exponentially approaching the target value at the given time with
	 *  a rate having the given time constant.
	 *  @param {number} value
	 *  @param {Time} startTime
	 *  @param {number} timeConstant
	 *  @returns {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.setTargetAtTime = function (value, startTime, timeConstant) {
		var unitValue = this._fromUnits(value);
		unitValue = Math.max(this._minOutput, unitValue);
		startTime = this.toSeconds(startTime);
		this._events.addEvent({
			"type" : Tone.TimelineSignal.Type.Target,
			"value" : unitValue,
			"time" : startTime,
			"constant" : timeConstant
		});
		Tone.Signal.prototype.setTargetAtTime.apply(this, arguments);
		return this;
	};

	/**
	 *  Cancels all scheduled parameter changes with times greater than or
	 *  equal to startTime.
	 *
	 *  @param  {Time} startTime
	 *  @returns {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.cancelScheduledValues = function (after) {
		this._events.clear(after);
		Tone.Signal.prototype.cancelScheduledValues.apply(this, arguments);
		return this;
	};

	/**
	 *  Sets the computed value at the given time. This provides
	 *  a point from which a linear or exponential curve
	 *  can be scheduled after.
	 *  @param {Time} time When to set the ramp point
	 *  @returns {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.setRampPoint = function (time) {
		time = this.toSeconds(time);
		//get the value at the given time
		var val = this.getValueAtTime(time);
		this.setValueAtTime(val, time);
		return this;
	};

	/**
	 *  Do a linear ramp to the given value between the start and finish times.
	 *  @param {Number} value The value to ramp to.
	 *  @param {Time} start The beginning anchor point to do the linear ramp
	 *  @param {Time} finish The ending anchor point by which the value of
	 *                       the signal will equal the given value.
	 */
	Tone.TimelineSignal.prototype.linearRampToValueBetween = function (value, start, finish) {
		this.setRampPoint(start);
		this.linearRampToValueAtTime(value, finish);
		return this;
	};

	/**
	 *  Do a exponential ramp to the given value between the start and finish times.
	 *  @param {Number} value The value to ramp to.
	 *  @param {Time} start The beginning anchor point to do the exponential ramp
	 *  @param {Time} finish The ending anchor point by which the value of
	 *                       the signal will equal the given value.
	 */
	Tone.TimelineSignal.prototype.exponentialRampToValueBetween = function (value, start, finish) {
		this.setRampPoint(start);
		this.exponentialRampToValueAtTime(value, finish);
		return this;
	};

	///////////////////////////////////////////////////////////////////////////
	//	GETTING SCHEDULED VALUES
	///////////////////////////////////////////////////////////////////////////

	/**
	 *  Returns the value before or equal to the given time
	 *  @param  {Number}  time  The time to query
	 *  @return  {Object}  The event at or before the given time.
	 *  @private
	 */
	Tone.TimelineSignal.prototype._searchBefore = function(time){
		return this._events.getEvent(time);
	};

	/**
	 *  The event after the given time
	 *  @param  {Number}  time  The time to query.
	 *  @return  {Object}  The next event after the given time
	 */
	Tone.TimelineSignal.prototype._searchAfter = function(time){
		return this._events.getEventAfter(time);
	};

	/**
	 *  Get the scheduled value at the given time.
	 *  @param  {Number}  time  The time in seconds.
	 *  @return  {Number}  The scheduled value at the given time.
	 */
	Tone.TimelineSignal.prototype.getValueAtTime = function(time){
		var after = this._searchAfter(time);
		var before = this._searchBefore(time);
		//if it was set by
		if (before === null){
			return this._initial;
		} else if (before.type === Tone.TimelineSignal.Type.Target){
			var previous = this._searchBefore(before.time - 0.0001);
			var previouVal;
			if (previous === null){
				previouVal = this._initial;
			} else {
				previouVal = previous.value;
			}
			return this._exponentialApproach(before.time, previouVal, before.value, before.constant, time);
		} else if (after === null){
			return before.value;
		} else if (after.type === Tone.TimelineSignal.Type.Linear){
			return this._linearInterpolate(before.time, before.value, after.time, after.value, time);
		} else if (after.type === Tone.TimelineSignal.Type.Exponential){
			return this._exponentialInterpolate(before.time, before.value, after.time, after.value, time);
		} else {
			return before.value;
		}
	};

	///////////////////////////////////////////////////////////////////////////
	//	AUTOMATION CURVE CALCULATIONS
	//	MIT License, copyright (c) 2014 Jordan Santell
	///////////////////////////////////////////////////////////////////////////

	/**
	 *  Calculates the the value along the curve produced by setTargetAtTime
	 *  @private
	 */
	Tone.TimelineSignal.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) {
		return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant);
	};

	/**
	 *  Calculates the the value along the curve produced by linearRampToValueAtTime
	 *  @private
	 */
	Tone.TimelineSignal.prototype._linearInterpolate = function (t0, v0, t1, v1, t) {
		return v0 + (v1 - v0) * ((t - t0) / (t1 - t0));
	};

	/**
	 *  Calculates the the value along the curve produced by exponentialRampToValueAtTime
	 *  @private
	 */
	Tone.TimelineSignal.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) {
		v0 = Math.max(this._minOutput, v0);
		return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
	};

	/**
	 *  Clean up.
	 *  @return {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.dispose = function(){
		Tone.Signal.prototype.dispose.call(this);
		this._events.dispose();
		this._events = null;
	};

	return Tone.TimelineSignal;
});<commit_msg>updating docs<commit_after>define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function (Tone) {

	/**
	 *  @class A signal which adds the method getValueAtTime.
	 *         Code and inspiration from https://github.com/jsantell/web-audio-automation-timeline
	 */
	Tone.TimelineSignal = function(){

		//extend Tone.Signal
		Tone.Signal.apply(this, arguments);

		/**
		 *  The scheduled events
		 *  @type {Tone.Timeline}
		 *  @private
		 */
		this._events = new Tone.Timeline();

		/**
		 *  The initial scheduled value
		 *  @type {Number}
		 *  @private
		 */
		this._initial = this._value.value;
	};

	Tone.extend(Tone.TimelineSignal, Tone.Signal);

	/**
	 *  The event types of a schedulable signal.
	 *  @enum {String}
	 */
	Tone.TimelineSignal.Type = {
		Linear : "linear",
		Exponential : "exponential",
		Target : "target",
		Set : "set"
	};

	/**
	 * The current value of the signal.
	 * @memberOf Tone.TimelineSignal#
	 * @type {Number}
	 * @name value
	 */
	Object.defineProperty(Tone.TimelineSignal.prototype, "value", {
		get : function(){
			return this._toUnits(this._value.value);
		},
		set : function(value){
			var convertedVal = this._fromUnits(value);
			this._initial = convertedVal;
			this._value.value = convertedVal;
		}
	});

	///////////////////////////////////////////////////////////////////////////
	//	SCHEDULING
	///////////////////////////////////////////////////////////////////////////

	/**
	 *  Schedules a parameter value change at the given time.
	 *  @param {*}	value The value to set the signal.
	 *  @param {Time}  time The time when the change should occur.
	 *  @returns {Tone.TimelineSignal} this
	 *  @example
	 * //set the frequency to "G4" in exactly 1 second from now.
	 * freq.setValueAtTime("G4", "+1");
	 */
	Tone.TimelineSignal.prototype.setValueAtTime = function (value, startTime) {
		var unitValue = this._fromUnits(value);
		startTime = this.toSeconds(startTime);
		this._events.addEvent({
			"type" : Tone.TimelineSignal.Type.Set,
			"value" : unitValue,
			"time" : startTime
		});
		//invoke the original event
		Tone.Signal.prototype.setValueAtTime.apply(this, arguments);
		return this;
	};

	/**
	 *  Schedules a linear continuous change in parameter value from the
	 *  previous scheduled parameter value to the given value.
	 *
	 *  @param  {number} value
	 *  @param  {Time} endTime
	 *  @returns {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.linearRampToValueAtTime = function (value, endTime) {
		var unitValue = this._fromUnits(value);
		endTime = this.toSeconds(endTime);
		this._events.addEvent({
			"type" : Tone.TimelineSignal.Type.Linear,
			"value" : unitValue,
			"time" : endTime
		});
		Tone.Signal.prototype.linearRampToValueAtTime.apply(this, arguments);
		return this;
	};

	/**
	 *  Schedules an exponential continuous change in parameter value from
	 *  the previous scheduled parameter value to the given value.
	 *
	 *  @param  {number} value
	 *  @param  {Time} endTime
	 *  @returns {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) {
		var unitValue = this._fromUnits(value);
		unitValue = Math.max(this._minOutput, unitValue);
		endTime = this.toSeconds(endTime);
		this._events.addEvent({
			"type" : Tone.TimelineSignal.Type.Exponential,
			"value" : unitValue,
			"time" : endTime
		});
		Tone.Signal.prototype.exponentialRampToValueAtTime.apply(this, arguments);
		return this;
	};

	/**
	 *  Start exponentially approaching the target value at the given time with
	 *  a rate having the given time constant.
	 *  @param {number} value
	 *  @param {Time} startTime
	 *  @param {number} timeConstant
	 *  @returns {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.setTargetAtTime = function (value, startTime, timeConstant) {
		var unitValue = this._fromUnits(value);
		unitValue = Math.max(this._minOutput, unitValue);
		startTime = this.toSeconds(startTime);
		this._events.addEvent({
			"type" : Tone.TimelineSignal.Type.Target,
			"value" : unitValue,
			"time" : startTime,
			"constant" : timeConstant
		});
		Tone.Signal.prototype.setTargetAtTime.apply(this, arguments);
		return this;
	};

	/**
	 *  Cancels all scheduled parameter changes with times greater than or
	 *  equal to startTime.
	 *
	 *  @param  {Time} startTime
	 *  @returns {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.cancelScheduledValues = function (after) {
		this._events.clear(after);
		Tone.Signal.prototype.cancelScheduledValues.apply(this, arguments);
		return this;
	};

	/**
	 *  Sets the computed value at the given time. This provides
	 *  a point from which a linear or exponential curve
	 *  can be scheduled after.
	 *  @param {Time} time When to set the ramp point
	 *  @returns {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.setRampPoint = function (time) {
		time = this.toSeconds(time);
		//get the value at the given time
		var val = this.getValueAtTime(time);
		this.setValueAtTime(val, time);
		return this;
	};

	/**
	 *  Do a linear ramp to the given value between the start and finish times.
	 *  @param {Number} value The value to ramp to.
	 *  @param {Time} start The beginning anchor point to do the linear ramp
	 *  @param {Time} finish The ending anchor point by which the value of
	 *                       the signal will equal the given value.
	 */
	Tone.TimelineSignal.prototype.linearRampToValueBetween = function (value, start, finish) {
		this.setRampPoint(start);
		this.linearRampToValueAtTime(value, finish);
		return this;
	};

	/**
	 *  Do a exponential ramp to the given value between the start and finish times.
	 *  @param {Number} value The value to ramp to.
	 *  @param {Time} start The beginning anchor point to do the exponential ramp
	 *  @param {Time} finish The ending anchor point by which the value of
	 *                       the signal will equal the given value.
	 */
	Tone.TimelineSignal.prototype.exponentialRampToValueBetween = function (value, start, finish) {
		this.setRampPoint(start);
		this.exponentialRampToValueAtTime(value, finish);
		return this;
	};

	///////////////////////////////////////////////////////////////////////////
	//	GETTING SCHEDULED VALUES
	///////////////////////////////////////////////////////////////////////////

	/**
	 *  Returns the value before or equal to the given time
	 *  @param  {Number}  time  The time to query
	 *  @return  {Object}  The event at or before the given time.
	 *  @private
	 */
	Tone.TimelineSignal.prototype._searchBefore = function(time){
		return this._events.getEvent(time);
	};

	/**
	 *  The event after the given time
	 *  @param  {Number}  time  The time to query.
	 *  @return  {Object}  The next event after the given time
	 *  @private
	 */
	Tone.TimelineSignal.prototype._searchAfter = function(time){
		return this._events.getEventAfter(time);
	};

	/**
	 *  Get the scheduled value at the given time.
	 *  @param  {Number}  time  The time in seconds.
	 *  @return  {Number}  The scheduled value at the given time.
	 */
	Tone.TimelineSignal.prototype.getValueAtTime = function(time){
		var after = this._searchAfter(time);
		var before = this._searchBefore(time);
		//if it was set by
		if (before === null){
			return this._initial;
		} else if (before.type === Tone.TimelineSignal.Type.Target){
			var previous = this._searchBefore(before.time - 0.0001);
			var previouVal;
			if (previous === null){
				previouVal = this._initial;
			} else {
				previouVal = previous.value;
			}
			return this._exponentialApproach(before.time, previouVal, before.value, before.constant, time);
		} else if (after === null){
			return before.value;
		} else if (after.type === Tone.TimelineSignal.Type.Linear){
			return this._linearInterpolate(before.time, before.value, after.time, after.value, time);
		} else if (after.type === Tone.TimelineSignal.Type.Exponential){
			return this._exponentialInterpolate(before.time, before.value, after.time, after.value, time);
		} else {
			return before.value;
		}
	};

	///////////////////////////////////////////////////////////////////////////
	//	AUTOMATION CURVE CALCULATIONS
	//	MIT License, copyright (c) 2014 Jordan Santell
	///////////////////////////////////////////////////////////////////////////

	/**
	 *  Calculates the the value along the curve produced by setTargetAtTime
	 *  @private
	 */
	Tone.TimelineSignal.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) {
		return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant);
	};

	/**
	 *  Calculates the the value along the curve produced by linearRampToValueAtTime
	 *  @private
	 */
	Tone.TimelineSignal.prototype._linearInterpolate = function (t0, v0, t1, v1, t) {
		return v0 + (v1 - v0) * ((t - t0) / (t1 - t0));
	};

	/**
	 *  Calculates the the value along the curve produced by exponentialRampToValueAtTime
	 *  @private
	 */
	Tone.TimelineSignal.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) {
		v0 = Math.max(this._minOutput, v0);
		return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
	};

	/**
	 *  Clean up.
	 *  @return {Tone.TimelineSignal} this
	 */
	Tone.TimelineSignal.prototype.dispose = function(){
		Tone.Signal.prototype.dispose.call(this);
		this._events.dispose();
		this._events = null;
	};

	return Tone.TimelineSignal;
});<|endoftext|>


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

/**
 * Property whose value must be a number.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var axon = require( 'AXON/axon' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Property = require( 'AXON/Property' );
  var NumberPropertyIO = require( 'AXON/NumberPropertyIO' );

  // valid values for options.units (or it can be null)
  var VALID_UNITS = [
    'amperes',
    'milliamperes',
    'becquerels',
    'centimeters',
    'centimeters-squared',
    'coulombs',
    'degrees Celsius',
    'farads',
    'kilograms',
    'grams',
    'gray',
    'henrys',
    'henries',
    'hertz',
    'joules',
    'katals',
    'kelvins',
    'liters',
    'liters/second',
    'lumens',
    'lux',
    'meters',
    'meters/second',
    'meters/second/second',
    'moles',
    'moles/liter',
    'nanometers',
    'newtons',
    'newtons/meters',
    'newtons-second/meters',
    'ohms',
    'ohm-centimeters',
    'pascals',
    'percent',
    'radians',
    'radians/second',
    'seconds',
    'siemens',
    'sieverts',
    'steradians',
    'tesla',
    'view-coordinates/second',
    'volts',
    'watts',
    'webers'
  ];

  // valid values for options.numberType
  var VALID_NUMBER_TYPES = [ 'FloatingPoint', 'Integer' ];

  /**
   * @param {number} value - initial value
   * @param {Object} [options]
   * @constructor
   */
  function NumberProperty( value, options ) {

    options = _.extend( {
      range: null, // {null|Range|{min:number, max:number}} range of the value
      numberType: 'FloatingPoint', // {string} see VALID_VALUE_TYPES
      phetioType: NumberPropertyIO,
      units: null // {string|null} units for the number, see VALID_UNITS
    }, options );

    assert && assert( _.filter( [ options.validValues, options.isValidValue, options.range ], function( value ) {
      return value;
    } ).length <= 1, 'validValues, isValidValue and range are mutually-exclusive options' );
    options.units && assert && assert( _.includes( VALID_UNITS, options.units ), 'invalid units: ' + options.units );
    assert && assert( _.includes( VALID_NUMBER_TYPES, options.numberType ), 'invalid numberType: ' + options.numberType );
    assert && assert( isValidForValueType( value, options.numberType ), 'initial value ' + value + ' must be of type: ' + options.numberType );

    // @public (read-only) - used by PhET-iO in NumberPropertyIO as metadata passed to the wrapper.
    this.units = options.units;
    this.range = options.range;
    this.numberType = options.numberType;

    if ( options.range ) {

      // Add a validation function that includes the range check.
      options.isValidValue = function( value ) {
        return isValidForValueType( value, options.numberType ) && ( value >= options.range.min ) && ( value <= options.range.max );
      };
    }
    else if ( options.validValues ) {

      // Verify that the values are all numbers.
      assert && assert( _.every( options.validValues, function( value ) {
          return isValidForValueType( value, options.numberType );
        }
      ), 'validValues must contain numbers of the right numberType' );
    }
    else if ( options.isValidValue ) {

      // Wrap the provided function so that we can verify that the value is a number.
      // This prevents the client from having to check (or remember to check) that the value is a number.
      var isValidValue = options.isValidValue;
      options.isValidValue = function( value ) {
        return isValidForValueType( value, options.numberType ) && isValidValue( value );
      };
    }
    else {

      // fallback to verifying that the value is a string
      options.isValidValue = function( value ) {
        return isValidForValueType( value, options.numberType );
      };
    }

    Property.call( this, value, options );
  }

  axon.register( 'NumberProperty', NumberProperty );

  /**
   * If numberType is 'Integer', then the value must be an integer.
   * @param value
   * @param numberType
   * @returns {boolean}
   */
  function isValidForValueType( value, numberType ) {
    return ( typeof value === 'number' ) && !( numberType === 'Integer' && value % 1 !== 0 );
  }

  return inherit( Property, NumberProperty );
} );<commit_msg>Added docs, see https://github.com/phetsims/axon/issues/174<commit_after>// Copyright 2016-2018, University of Colorado Boulder

/**
 * Property whose value must be a number.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var axon = require( 'AXON/axon' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Property = require( 'AXON/Property' );
  var NumberPropertyIO = require( 'AXON/NumberPropertyIO' );

  // valid values for options.units (or it can be null)
  var VALID_UNITS = [
    'amperes',
    'milliamperes',
    'becquerels',
    'centimeters',
    'centimeters-squared',
    'coulombs',
    'degrees Celsius',
    'farads',
    'kilograms',
    'grams',
    'gray',
    'henrys',
    'henries',
    'hertz',
    'joules',
    'katals',
    'kelvins',
    'liters',
    'liters/second',
    'lumens',
    'lux',
    'meters',
    'meters/second',
    'meters/second/second',
    'moles',
    'moles/liter',
    'nanometers',
    'newtons',
    'newtons/meters',
    'newtons-second/meters',
    'ohms',
    'ohm-centimeters',
    'pascals',
    'percent',
    'radians',
    'radians/second',
    'seconds',
    'siemens',
    'sieverts',
    'steradians',
    'tesla',
    'view-coordinates/second',
    'volts',
    'watts',
    'webers'
  ];

  // valid values for options.numberType to convey whether it is continuous or discrete with step size 1
  var VALID_NUMBER_TYPES = [ 'FloatingPoint', 'Integer' ];

  /**
   * @param {number} value - initial value
   * @param {Object} [options]
   * @constructor
   */
  function NumberProperty( value, options ) {

    options = _.extend( {
      range: null, // {null|Range|{min:number, max:number}} range of the value
      numberType: 'FloatingPoint', // {string} see VALID_VALUE_TYPES
      phetioType: NumberPropertyIO,
      units: null // {string|null} units for the number, see VALID_UNITS
    }, options );

    assert && assert( _.filter( [ options.validValues, options.isValidValue, options.range ], function( value ) {
      return value;
    } ).length <= 1, 'validValues, isValidValue and range are mutually-exclusive options' );
    options.units && assert && assert( _.includes( VALID_UNITS, options.units ), 'invalid units: ' + options.units );
    assert && assert( _.includes( VALID_NUMBER_TYPES, options.numberType ), 'invalid numberType: ' + options.numberType );
    assert && assert( isValidForValueType( value, options.numberType ), 'initial value ' + value + ' must be of type: ' + options.numberType );

    // @public (read-only) - used by PhET-iO in NumberPropertyIO as metadata passed to the wrapper.
    this.units = options.units;
    this.range = options.range;
    this.numberType = options.numberType;

    if ( options.range ) {

      // Add a validation function that includes the range check.
      options.isValidValue = function( value ) {
        return isValidForValueType( value, options.numberType ) && ( value >= options.range.min ) && ( value <= options.range.max );
      };
    }
    else if ( options.validValues ) {

      // Verify that the values are all numbers.
      assert && assert( _.every( options.validValues, function( value ) {
          return isValidForValueType( value, options.numberType );
        }
      ), 'validValues must contain numbers of the right numberType' );
    }
    else if ( options.isValidValue ) {

      // Wrap the provided function so that we can verify that the value is a number.
      // This prevents the client from having to check (or remember to check) that the value is a number.
      var isValidValue = options.isValidValue;
      options.isValidValue = function( value ) {
        return isValidForValueType( value, options.numberType ) && isValidValue( value );
      };
    }
    else {

      // fallback to verifying that the value is a string
      options.isValidValue = function( value ) {
        return isValidForValueType( value, options.numberType );
      };
    }

    Property.call( this, value, options );
  }

  axon.register( 'NumberProperty', NumberProperty );

  /**
   * If numberType is 'Integer', then the value must be an integer.
   * @param value
   * @param numberType
   * @returns {boolean}
   */
  function isValidForValueType( value, numberType ) {
    return ( typeof value === 'number' ) && !( numberType === 'Integer' && value % 1 !== 0 );
  }

  return inherit( Property, NumberProperty );
} );<|endoftext|>