<commit_before>var redis = require('redis');
var async = require('async');

var Stratum = require('stratum-pool');

module.exports = function(logger){

    var poolConfigs = JSON.parse(process.env.pools);

    Object.keys(poolConfigs).forEach(function(coin) {
        SetupForPool(logger, poolConfigs[coin]);
    });

};

function SetupForPool(logger, poolOptions){

    var coin = poolOptions.coin.name;

    var processingConfig = poolOptions.shareProcessing.internal;

    if (!processingConfig.enabled) return;

    var logIdentify = 'Payment Processor (' + coin + ')';

    var paymentLogger = {
        debug: function(key, text){
            logger.logDebug(logIdentify, key, text);
        },
        warning: function(key, text){
            logger.logWarning(logIdentify, key, text);
        },
        error: function(key, text){
            logger.logError(logIdentify, key, text);
        }
    };

    var daemon = new Stratum.daemon.interface([processingConfig.daemon]);
    daemon.once('online', function(){
        paymentLogger.debug('system', 'Connected to daemon for payment processing');

        daemon.cmd('validateaddress', [poolOptions.address], function(result){
            if (!result[0].response.ismine){
                paymentLogger.error('system', 'Daemon does not own pool address - payment processing can not be done with this daemon');
            }
        });
    }).once('connectionFailed', function(error){
        paymentLogger.error('system', 'Failed to connect to daemon for payment processing: ' + JSON.stringify(error));
    }).on('error', function(error){
        paymentLogger.error('system', error);
    }).init();

    var redisClient;

    var connectToRedis = function(){
        var reconnectTimeout;
        redisClient = redis.createClient(processingConfig.redis.port, processingConfig.redis.host);
        redisClient.on('ready', function(){
            clearTimeout(reconnectTimeout);
            paymentLogger.debug('redis', 'Successfully connected to redis database');
        }).on('error', function(err){
                paymentLogger.error('redis', 'Redis client had an error: ' + JSON.stringify(err))
        }).on('end', function(){
            paymentLogger.error('redis', 'Connection to redis database as been ended');
            paymentLogger.warning('redis', 'Trying reconnection in 3 seconds...');
            reconnectTimeout = setTimeout(function(){
                connectToRedis();
            }, 3000);
        });
    };
    connectToRedis();

    var processPayments = function(){
        async.waterfall([

            /* Call redis to get an array of rounds - which are coinbase transactions and block heights from submitted
               blocks. */
            function(callback){

                redisClient.smembers(coin + '_blocksPending', function(error, results){

                    if (error){
                        logger.error('redis', 'Could get blocks from redis ' + JSON.stringify(error));
                        callback('done - redis error for getting blocks');
                        return;
                    }
                    if (results.length === 0){
                        callback('done - no pending blocks in redis');
                        return;
                    }

                    var rounds = results.map(function(r){
                        var details = r.split(':');
                        return {txHash: details[0], height: details[1], reward: details[2], serialized: r};
                    });

                    callback(null, rounds);
                });
            },

            /* Does a batch rpc call to daemon with all the transaction hashes to see if they are confirmed yet.
               It also adds the block reward amount to the round object - which the daemon gives also gives us. */
            function(rounds, callback){

                var batchRPCcommand = rounds.map(function(r){
                    return ['gettransaction', [r.txHash]];
                });

                daemon.batchCmd(batchRPCcommand, function(error, txDetails){

                    if (error || !txDetails){
                        callback('done - daemon rpc error with batch gettransactions ' + JSON.stringify(error));
                        return;
                    }

                    txDetails = txDetails.filter(function(tx){
                        if (tx.error || !tx.result){
                            console.log('error with requesting transaction from block daemon: ' + JSON.stringify(t));
                            return false;
                        }
                        return true;
                    });

                    var orphanedRounds = [];
                    var confirmedRounds = [];
                    //Rounds that are not confirmed yet are removed from the round array
                    //We also get reward amount for each block from daemon reply
                    rounds.forEach(function(r){

                        var tx = txDetails.filter(function(tx){return tx.result.txid === r.txHash})[0];

                        if (!tx){
                            console.log('daemon did not give us back a transaction that we asked for: ' + r.txHash);
                            return;
                        }

                        r.category = tx.result.details[0].category;

                        if (r.category === 'orphan'){
                            orphanedRounds.push(r);

                        }
                        else if (r.category === 'generate'){
                            r.amount = tx.result.amount;
                            r.magnitude = r.reward / r.amount;
                            confirmedRounds.push(r);
                        }

                    });

                    if (orphanedRounds.length === 0 && confirmedRounds.length === 0){
                        callback('done - no confirmed or orhpaned rounds');
                    }
                    else{
                        callback(null, confirmedRounds, orphanedRounds);
                    }
                });
            },

            /* Does a batch redis call to get shares contributed to each round. Then calculates the reward
               amount owned to each miner for each round. */
            function(confirmedRounds, orphanedRounds, callback){

                var rounds = [];
                for (var i = 0; i < orphanedRounds.length; i++) rounds.push(orphanedRounds[i]);
                for (var i = 0; i < confirmedRounds.length; i++) rounds.push(confirmedRounds[i]);

                var shareLookups = rounds.map(function(r){
                    return ['hgetall', coin + '_shares:round' + r.height]
                });

                redisClient.multi(shareLookups).exec(function(error, allWorkerShares){
                    if (error){
                        callback('done - redis error with multi get rounds share')
                        return;
                    }

                    // Iterate through the beginning of the share results which are for the orphaned rounds
                    var orphanMergeCommands = []
                    for (var i = 0; i < orphanedRounds.length; i++){
                        var workerShares = allWorkerShares[i];
                        Object.keys(workerShares).forEach(function(worker){
                            orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', worker, workerShares[worker]]);
                        });
                        orphanMergeCommands.push([]);
                    }

                    // Iterate through the rest of the share results which are for the worker rewards
                    var workerRewards = {};
                    for (var i = orphanedRounds.length; i < allWorkerShares.length; i++){

                        var round = rounds[i];
                        var workerShares = allWorkerShares[i];

                        var reward = round.reward * (1 - processingConfig.feePercent);

                        var totalShares = Object.keys(workerShares).reduce(function(p, c){
                            return p + parseInt(workerShares[c])
                        }, 0);

                        for (var worker in workerShares){
                            var percent = parseInt(workerShares[worker]) / totalShares;
                            var workerRewardTotal = Math.floor(reward * percent);
                            if (!(worker in workerRewards)) workerRewards[worker] = 0;
                            workerRewards[worker] += workerRewardTotal;
                        }
                    }

                    //this calculates profit if you wanna see it
                    /*
                    var workerTotalRewards = Object.keys(workerRewards).reduce(function(p, c){
                        return p + workerRewards[c];
                    }, 0);

                    var poolTotalRewards = rounds.reduce(function(p, c){
                        return p + c.amount * c.magnitude;
                    }, 0);

                    console.log(workerRewards);
                    console.log('pool profit percent' + ((poolTotalRewards - workerTotalRewards) / poolTotalRewards));
                    */

                    callback(null, rounds, workerRewards, orphanMergeCommands);
                });
            },

            /* Does a batch call to redis to get worker existing balances from coin_balances*/
            function(rounds, workerRewards, orphanMergeCommands, callback){

                var workers = Object.keys(workerRewards);

                redisClient.hmget([coin + '_balances'].concat(workers), function(error, results){
                    if (error){
                        callback('done - redis error with multi get balances');
                        return;
                    }

                    var workerBalances = {};

                    for (var i = 0; i < workers.length; i++){
                        workerBalances[workers[i]] = parseInt(results[i]) || 0;
                    }

                    callback(null, rounds, workerRewards, workerBalances, orphanMergeCommands);
                });

            },

            /* Calculate if any payments are ready to be sent and trigger them sending
             Get balance different for each address and pass it along as object of latest balances such as
             {worker1: balance1, worker2, balance2}
             when deciding the sent balance, it the difference should be -1*amount they had in db,
             if not sending the balance, the differnce should be +(the amount they earned this round)
             */
            function(rounds, workerRewards, workerBalances, orphanMergeCommands, callback){

                var magnitude = rounds[0].magnitude;

                daemon.cmd('getbalance', [], function(results){

                    var totalBalance = results[0].response * magnitude;
                    var toBePaid = 0;
                    var workerPayments = {};

                    var balanceUpdateCommands = [];
                    var workerPayoutsCommand = [];

                    for (var worker in workerRewards){
                        workerPayments[worker] = (workerPayments[worker] || 0) + workerRewards[worker];
                    }
                    for (var worker in workerBalances){
                        workerPayments[worker] = (workerPayments[worker] || 0) + workerBalances[worker];
                    }
                    for (var worker in workerPayments){
                        if (workerPayments[worker] < processingConfig.minimumPayment * magnitude){
                            balanceUpdateCommands.push(['hincrby', coin + '_balances', worker, workerRewards[worker]]);
                            delete workerPayments[worker];
                        }
                        else{
                            if (workerBalances[worker] !== 0)
                                balanceUpdateCommands.push(['hincrby', coin + '_balances', worker, -1 * workerBalances[worker]]);
                            workerPayoutsCommand.push(['hincrby', coin + '_balances', worker, workerRewards[worker]]);
                            toBePaid += workerPayments[worker];
                        }
                    }

                    var balanceLeftOver = totalBalance - toBePaid;
                    var minReserveSatoshis = processingConfig.minimumReserve * magnitude;
                    if (balanceLeftOver < minReserveSatoshis){

                        callback('done - payments would wipe out minimum reserve, tried to pay out ' + toBePaid +
                            ' but only have ' + totalBalance + '. Left over balance would be ' + balanceLeftOver +
                            ', needs to be at least ' + minReserveSatoshis);
                        return;
                    }

                    var movePendingCommands = [];
                    var deleteRoundsCommand = ['del'];
                    rounds.forEach(function(r){
                        var destinationSet = r.category === 'orphan' ? '_blocksOrphaned' : '_blocksConfirmed';
                        movePendingCommands.push(['smove', coin + '_blocksPending', coin + destinationSet, r.serialized]);
                        deleteRoundsCommand.push(coin + '_shares:round' + r.height)
                    });

                    var finalRedisCommands = [];

                    finalRedisCommands = finalRedisCommands.concat(
                        movePendingCommands,
                        orphanMergeCommands,
                        balanceUpdateCommands,
                        workerPayoutsCommand
                    );

                    finalRedisCommands.push(deleteRoundsCommand);
                    finalRedisCommands.push(['hincrby', coin + '_stats', 'totalPaid', toBePaid]);

                    callback(null, magnitude, workerPayments, finalRedisCommands);

                });
            },

            function(magnitude, workerPayments, finalRedisCommands, callback){

                var sendManyCmd = ['', {}];
                for (var address in workerPayments){
                    sendManyCmd[1][address] = workerPayments[address] / magnitude;
                }

                console.log(JSON.stringify(finalRedisCommands, null, 4));
                console.log(JSON.stringify(workerPayments, null, 4));
                console.log(JSON.stringify(sendManyCmd, null, 4));

                return; //not yet...
                daemon.cmd(sendManyCmd, function(error, result){
                    //if successful then do finalRedisCommands
                });

            }
        ], function(error, result){
            console.log(error);
            //log error completion
        });
    };

    setInterval(processPayments, processingConfig.paymentInterval * 1000);
    setTimeout(processPayments, 100);

};<commit_msg>Payment processing is 98% done...<commit_after>var redis = require('redis');
var async = require('async');

var Stratum = require('stratum-pool');

module.exports = function(logger){

    var poolConfigs = JSON.parse(process.env.pools);

    Object.keys(poolConfigs).forEach(function(coin) {
        SetupForPool(logger, poolConfigs[coin]);
    });

};

function SetupForPool(logger, poolOptions){

    var coin = poolOptions.coin.name;

    var processingConfig = poolOptions.shareProcessing.internal;

    if (!processingConfig.enabled) return;

    var logIdentify = 'Payment Processor (' + coin + ')';

    var paymentLogger = {
        debug: function(key, text){
            logger.logDebug(logIdentify, key, text);
        },
        warning: function(key, text){
            logger.logWarning(logIdentify, key, text);
        },
        error: function(key, text){
            logger.logError(logIdentify, key, text);
        }
    };

    var daemon = new Stratum.daemon.interface([processingConfig.daemon]);
    daemon.once('online', function(){
        paymentLogger.debug('system', 'Connected to daemon for payment processing');

        daemon.cmd('validateaddress', [poolOptions.address], function(result){
            if (!result[0].response.ismine){
                paymentLogger.error('system', 'Daemon does not own pool address - payment processing can not be done with this daemon');
            }
        });
    }).once('connectionFailed', function(error){
        paymentLogger.error('system', 'Failed to connect to daemon for payment processing: ' + JSON.stringify(error));
    }).on('error', function(error){
        paymentLogger.error('system', error);
    }).init();

    var redisClient;

    var connectToRedis = function(){
        var reconnectTimeout;
        redisClient = redis.createClient(processingConfig.redis.port, processingConfig.redis.host);
        redisClient.on('ready', function(){
            clearTimeout(reconnectTimeout);
            paymentLogger.debug('redis', 'Successfully connected to redis database');
        }).on('error', function(err){
                paymentLogger.error('redis', 'Redis client had an error: ' + JSON.stringify(err))
        }).on('end', function(){
            paymentLogger.error('redis', 'Connection to redis database as been ended');
            paymentLogger.warning('redis', 'Trying reconnection in 3 seconds...');
            reconnectTimeout = setTimeout(function(){
                connectToRedis();
            }, 3000);
        });
    };
    connectToRedis();

    var processPayments = function(){
        async.waterfall([

            /* Call redis to get an array of rounds - which are coinbase transactions and block heights from submitted
               blocks. */
            function(callback){

                redisClient.smembers(coin + '_blocksPending', function(error, results){

                    if (error){
                        logger.error('redis', 'Could get blocks from redis ' + JSON.stringify(error));
                        callback('done - redis error for getting blocks');
                        return;
                    }
                    if (results.length === 0){
                        callback('done - no pending blocks in redis');
                        return;
                    }

                    var rounds = results.map(function(r){
                        var details = r.split(':');
                        return {txHash: details[0], height: details[1], reward: details[2], serialized: r};
                    });

                    callback(null, rounds);
                });
            },

            /* Does a batch rpc call to daemon with all the transaction hashes to see if they are confirmed yet.
               It also adds the block reward amount to the round object - which the daemon gives also gives us. */
            function(rounds, callback){

                var batchRPCcommand = rounds.map(function(r){
                    return ['gettransaction', [r.txHash]];
                });

                daemon.batchCmd(batchRPCcommand, function(error, txDetails){

                    if (error || !txDetails){
                        callback('done - daemon rpc error with batch gettransactions ' + JSON.stringify(error));
                        return;
                    }

                    txDetails = txDetails.filter(function(tx){
                        if (tx.error || !tx.result){
                            console.log('error with requesting transaction from block daemon: ' + JSON.stringify(t));
                            return false;
                        }
                        return true;
                    });

                    var orphanedRounds = [];
                    var confirmedRounds = [];
                    //Rounds that are not confirmed yet are removed from the round array
                    //We also get reward amount for each block from daemon reply
                    rounds.forEach(function(r){

                        var tx = txDetails.filter(function(tx){return tx.result.txid === r.txHash})[0];

                        if (!tx){
                            console.log('daemon did not give us back a transaction that we asked for: ' + r.txHash);
                            return;
                        }

                        r.category = tx.result.details[0].category;

                        if (r.category === 'orphan'){
                            orphanedRounds.push(r);

                        }
                        else if (r.category === 'generate'){
                            r.amount = tx.result.amount;
                            r.magnitude = r.reward / r.amount;
                            confirmedRounds.push(r);
                        }

                    });

                    if (orphanedRounds.length === 0 && confirmedRounds.length === 0){
                        callback('done - no confirmed or orhpaned rounds');
                    }
                    else{
                        callback(null, confirmedRounds, orphanedRounds);
                    }
                });
            },

            /* Does a batch redis call to get shares contributed to each round. Then calculates the reward
               amount owned to each miner for each round. */
            function(confirmedRounds, orphanedRounds, callback){

                var rounds = [];
                for (var i = 0; i < orphanedRounds.length; i++) rounds.push(orphanedRounds[i]);
                for (var i = 0; i < confirmedRounds.length; i++) rounds.push(confirmedRounds[i]);

                var shareLookups = rounds.map(function(r){
                    return ['hgetall', coin + '_shares:round' + r.height]
                });

                redisClient.multi(shareLookups).exec(function(error, allWorkerShares){
                    if (error){
                        callback('done - redis error with multi get rounds share')
                        return;
                    }

                    // Iterate through the beginning of the share results which are for the orphaned rounds
                    var orphanMergeCommands = []
                    for (var i = 0; i < orphanedRounds.length; i++){
                        var workerShares = allWorkerShares[i];
                        Object.keys(workerShares).forEach(function(worker){
                            orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', worker, workerShares[worker]]);
                        });
                        orphanMergeCommands.push([]);
                    }

                    // Iterate through the rest of the share results which are for the worker rewards
                    var workerRewards = {};
                    for (var i = orphanedRounds.length; i < allWorkerShares.length; i++){

                        var round = rounds[i];
                        var workerShares = allWorkerShares[i];

                        var reward = round.reward * (1 - processingConfig.feePercent);

                        var totalShares = Object.keys(workerShares).reduce(function(p, c){
                            return p + parseInt(workerShares[c])
                        }, 0);

                        for (var worker in workerShares){
                            var percent = parseInt(workerShares[worker]) / totalShares;
                            var workerRewardTotal = Math.floor(reward * percent);
                            if (!(worker in workerRewards)) workerRewards[worker] = 0;
                            workerRewards[worker] += workerRewardTotal;
                        }
                    }

                    //this calculates profit if you wanna see it
                    /*
                    var workerTotalRewards = Object.keys(workerRewards).reduce(function(p, c){
                        return p + workerRewards[c];
                    }, 0);

                    var poolTotalRewards = rounds.reduce(function(p, c){
                        return p + c.amount * c.magnitude;
                    }, 0);

                    console.log(workerRewards);
                    console.log('pool profit percent' + ((poolTotalRewards - workerTotalRewards) / poolTotalRewards));
                    */

                    callback(null, rounds, workerRewards, orphanMergeCommands);
                });
            },

            /* Does a batch call to redis to get worker existing balances from coin_balances*/
            function(rounds, workerRewards, orphanMergeCommands, callback){

                var workers = Object.keys(workerRewards);

                redisClient.hmget([coin + '_balances'].concat(workers), function(error, results){
                    if (error){
                        callback('done - redis error with multi get balances');
                        return;
                    }

                    var workerBalances = {};

                    for (var i = 0; i < workers.length; i++){
                        workerBalances[workers[i]] = parseInt(results[i]) || 0;
                    }

                    callback(null, rounds, workerRewards, workerBalances, orphanMergeCommands);
                });

            },

            /* Calculate if any payments are ready to be sent and trigger them sending
             Get balance different for each address and pass it along as object of latest balances such as
             {worker1: balance1, worker2, balance2}
             when deciding the sent balance, it the difference should be -1*amount they had in db,
             if not sending the balance, the differnce should be +(the amount they earned this round)
             */
            function(rounds, workerRewards, workerBalances, orphanMergeCommands, callback){

                var magnitude = rounds[0].magnitude;

                daemon.cmd('getbalance', [], function(results){

                    var totalBalance = results[0].response * magnitude;
                    var toBePaid = 0;
                    var workerPayments = {};

                    var balanceUpdateCommands = [];
                    var workerPayoutsCommand = [];

                    for (var worker in workerRewards){
                        workerPayments[worker] = (workerPayments[worker] || 0) + workerRewards[worker];
                    }
                    for (var worker in workerBalances){
                        workerPayments[worker] = (workerPayments[worker] || 0) + workerBalances[worker];
                    }
                    for (var worker in workerPayments){
                        if (workerPayments[worker] < processingConfig.minimumPayment * magnitude){
                            balanceUpdateCommands.push(['hincrby', coin + '_balances', worker, workerRewards[worker]]);
                            delete workerPayments[worker];
                        }
                        else{
                            if (workerBalances[worker] !== 0)
                                balanceUpdateCommands.push(['hincrby', coin + '_balances', worker, -1 * workerBalances[worker]]);
                            workerPayoutsCommand.push(['hincrby', coin + '_balances', worker, workerRewards[worker]]);
                            toBePaid += workerPayments[worker];
                        }
                    }

                    var balanceLeftOver = totalBalance - toBePaid;
                    var minReserveSatoshis = processingConfig.minimumReserve * magnitude;
                    if (balanceLeftOver < minReserveSatoshis){

                        callback('done - payments would wipe out minimum reserve, tried to pay out ' + toBePaid +
                            ' but only have ' + totalBalance + '. Left over balance would be ' + balanceLeftOver +
                            ', needs to be at least ' + minReserveSatoshis);
                        return;
                    }

                    var movePendingCommands = [];
                    var deleteRoundsCommand = ['del'];
                    rounds.forEach(function(r){
                        var destinationSet = r.category === 'orphan' ? '_blocksOrphaned' : '_blocksConfirmed';
                        movePendingCommands.push(['smove', coin + '_blocksPending', coin + destinationSet, r.serialized]);
                        deleteRoundsCommand.push(coin + '_shares:round' + r.height)
                    });

                    var finalRedisCommands = [];

                    finalRedisCommands = finalRedisCommands.concat(
                        movePendingCommands,
                        orphanMergeCommands,
                        balanceUpdateCommands,
                        workerPayoutsCommand
                    );

                    finalRedisCommands.push(deleteRoundsCommand);
                    finalRedisCommands.push(['hincrby', coin + '_stats', 'totalPaid', toBePaid]);

                    callback(null, magnitude, workerPayments, finalRedisCommands);

                });
            },

            function(magnitude, workerPayments, finalRedisCommands, callback){

                var sendManyCmd = ['', {}];
                for (var address in workerPayments){
                    sendManyCmd[1][address] = workerPayments[address] / magnitude;
                }

                console.log(JSON.stringify(finalRedisCommands, null, 4));
                console.log(JSON.stringify(workerPayments, null, 4));
                console.log(JSON.stringify(sendManyCmd, null, 4));

                return; //not yet...
                daemon.cmd('sendmany', sendManyCmd, function(results){
                    if (results[0].error){
                        callback('done - error with sendmany ' + JSON.stringify(results[0].error));
                        return;
                    }
                    redisClient.multi(finalRedisCommands).exec(function(error, results){
                        if (error){
                            callback('done - error with final redis commands for cleaning up ' + JSON.stringify(error));
                            return;
                        }
                        callback(null, 'Payments sent');
                    });
                });

            }
        ], function(error, result){
            if (error)
                logger.debug(error)

            else{
                logger.debug(result);
            }
        });
    };

    setInterval(processPayments, processingConfig.paymentInterval * 1000);
    setTimeout(processPayments, 100);

};<|endoftext|>


<commit_before>
import { E2EGlobal } from './E2EGlobal'
import { E2EApp } from './E2EApp'

export class E2EMeetingSeries {
    static countMeetingSeries () {
        E2EApp.gotoStartPage();
        try {
            browser.waitForExist('li.meeting-series-item');
        } catch (e) {
            return 0;   // we have no meeting series <li> => "zero" result
        }
        const elements = browser.elements('li.meeting-series-item');
        return elements.value.length;
    };

    static editMeetingSeriesForm (aProj, aName, switchInput) {
        E2EApp.gotoStartPage();

        // is "create MeetingSeries dialog" closed?
        if (!browser.isVisible('input[id="id_meetingproject"]')) {
            browser.click('#btnNewMeetingSeries');  // open
            E2EGlobal.waitSomeTime();
            browser.waitForVisible('input[id="id_meetingproject"]');
        }

        if (switchInput) {
            browser.setValue('input[id="id_meetingname"]', aName);
            browser.setValue('input[id="id_meetingproject"]', aProj);
        } else {
            browser.setValue('input[id="id_meetingproject"]', aProj);
            browser.setValue('input[id="id_meetingname"]', aName);
        }
    };

    static createMeetingSeries (aProj, aName, keepOpenMSEditor, switchInput) {
        this.editMeetingSeriesForm(aProj, aName,  switchInput);

        browser.click('#btnAddInvite');
        E2EGlobal.waitSomeTime(500);  // double time for dialog + panel switch!

        if (! keepOpenMSEditor) {
            browser.click('#btnMeetinSeriesEditCancel');
            E2EGlobal.waitSomeTime();
            E2EApp.gotoStartPage();
        }
    };

    static getMeetingSeriesId (aProj, aName) {
        E2EApp.gotoStartPage();

        try {
            browser.waitForExist('li.meeting-series-item');
        } catch (e) {
            return false;   // we have no meeting series at all!
        }
        let compareText = aProj+": "+aName;

        const elements = browser.elements('li.meeting-series-item a');

        for (let i in elements.value) {
            let elemId = elements.value[i].ELEMENT;
            let visibleText = browser.elementIdText(elemId).value;
            if (visibleText == compareText) {
                let linkTarget = browser.elementIdAttribute(elemId, 'href').value;
                return linkTarget.slice(linkTarget.lastIndexOf("/")+1);
            }
        }
        return false;
    };

    static gotoMeetingSeries (aProj, aName) {
        E2EApp.gotoStartPage();

        let selector = 'li.meeting-series-item a';
        try {
            browser.waitForExist(selector);
        } catch (e) {
            return false;   // we have no meeting series at all!
        }
        let compareText = aProj+": "+aName;

        const elements = browser.elements(selector);

        for (let i in elements.value) {
            let elemId = elements.value[i].ELEMENT;
            let visibleText = browser.elementIdText(elemId).value;
            if (visibleText == compareText) {
                browser.elementIdClick(elemId);
                E2EGlobal.waitSomeTime();
                return true;
            }
        }
        throw new Error("Could not find Meeting Series '"+compareText+"'");
    };

    static gotoTabTopics() {
        let selector = '#tab_topics';
        try {
            browser.waitForExist(selector);
        } catch (e) {
            return false;   // we have no meeting series at all!
        }
        browser.click(selector);
    }
}
<commit_msg>Fix: E2E with empty projectname or meeting name was broken, as now MS editor is expected to be open after create<commit_after>
import { E2EGlobal } from './E2EGlobal'
import { E2EApp } from './E2EApp'

export class E2EMeetingSeries {
    static countMeetingSeries () {
        E2EApp.gotoStartPage();
        try {
            browser.waitForExist('li.meeting-series-item');
        } catch (e) {
            return 0;   // we have no meeting series <li> => "zero" result
        }
        const elements = browser.elements('li.meeting-series-item');
        return elements.value.length;
    };

    static editMeetingSeriesForm (aProj, aName, switchInput) {
        E2EApp.gotoStartPage();

        // is "create MeetingSeries dialog" closed?
        if (!browser.isVisible('input[id="id_meetingproject"]')) {
            browser.click('#btnNewMeetingSeries');  // open
            E2EGlobal.waitSomeTime();
            browser.waitForVisible('input[id="id_meetingproject"]');
        }

        if (switchInput) {
            browser.setValue('input[id="id_meetingname"]', aName);
            browser.setValue('input[id="id_meetingproject"]', aProj);
        } else {
            browser.setValue('input[id="id_meetingproject"]', aProj);
            browser.setValue('input[id="id_meetingname"]', aName);
        }
    };

    static createMeetingSeries (aProj, aName, keepOpenMSEditor, switchInput) {
        this.editMeetingSeriesForm(aProj, aName,  switchInput);

        browser.click('#btnAddInvite');
        E2EGlobal.waitSomeTime(500);  // double time for dialog + panel switch!

        if (! keepOpenMSEditor && browser.isVisible("#btnMeetinSeriesEditCancel")) {
            browser.click('#btnMeetinSeriesEditCancel');
            E2EGlobal.waitSomeTime();
            E2EApp.gotoStartPage();
        }
    };

    static getMeetingSeriesId (aProj, aName) {
        E2EApp.gotoStartPage();

        try {
            browser.waitForExist('li.meeting-series-item');
        } catch (e) {
            return false;   // we have no meeting series at all!
        }
        let compareText = aProj+": "+aName;

        const elements = browser.elements('li.meeting-series-item a');

        for (let i in elements.value) {
            let elemId = elements.value[i].ELEMENT;
            let visibleText = browser.elementIdText(elemId).value;
            if (visibleText == compareText) {
                let linkTarget = browser.elementIdAttribute(elemId, 'href').value;
                return linkTarget.slice(linkTarget.lastIndexOf("/")+1);
            }
        }
        return false;
    };

    static gotoMeetingSeries (aProj, aName) {
        E2EApp.gotoStartPage();

        let selector = 'li.meeting-series-item a';
        try {
            browser.waitForExist(selector);
        } catch (e) {
            return false;   // we have no meeting series at all!
        }
        let compareText = aProj+": "+aName;

        const elements = browser.elements(selector);

        for (let i in elements.value) {
            let elemId = elements.value[i].ELEMENT;
            let visibleText = browser.elementIdText(elemId).value;
            if (visibleText == compareText) {
                browser.elementIdClick(elemId);
                E2EGlobal.waitSomeTime();
                return true;
            }
        }
        throw new Error("Could not find Meeting Series '"+compareText+"'");
    };

    static gotoTabTopics() {
        let selector = '#tab_topics';
        try {
            browser.waitForExist(selector);
        } catch (e) {
            return false;   // we have no meeting series at all!
        }
        browser.click(selector);
    }
}
<|endoftext|>


<commit_before>import React, { Fragment } from 'react'
import {
  Heading,
  Container,
  Box,
  Text,
  Section,
  Link as A
} from '@hackclub/design-system'
import Helmet from 'react-helmet'
import Link from 'gatsby-link'
import Nav from 'components/Nav'
import Start from 'components/Start'
import Footer from 'components/Footer'

A.link = A.withComponent(Link)

const Header = Section.withComponent('header').extend`
  background-color: ${({ theme }) => theme.colors.fuschia[6]};
  background-image: linear-gradient(
    32deg,
    ${({ theme }) => theme.colors.fuschia[5]} 0%,
    ${({ theme }) => theme.colors.red[5]} 64%,
    ${({ theme }) => theme.colors.red[6]} 100%
  );
  clip-path: polygon(0% 0%, 100% 0, 100% 100%, 0% 90%);
  > div { position: relative; }
`

const Seal = Box.extend`
  border-radius: 9999px;
  background-color: white;
  color: black;
  mix-blend-mode: screen;
  text-align: center;
  width: 12rem;
  height: 12rem;
  position: absolute;
  margin-top: -1rem;
  transform: rotate(3deg);
  ${({ theme }) => theme.mediaQueries.md} {
    margin-top: -3rem;
  }
`

const HeadLine = Heading.h1.extend.attrs({ f: [5, 7, 8] })`
  line-height: 1.125 !important;
  text-transform: uppercase;
  &:nth-of-type(2) {
    padding-left: 1.5rem;
    ${({ theme }) => theme.mediaQueries.md} {
      padding-left: 6rem;
    }
  }
  &:nth-of-type(3) {
    text-align: center;
  }
  &:nth-of-type(4) {
    text-align: right;
    position: relative;
    &:before {
      content: '';
      position: absolute;
      clip-path: polygon(8% 0%, 100% 0%, 92% 100%, 0% 100%);
      background-color: rgba(252, 252, 252, 0.625);
      mix-blend-mode: overlay;
      right: -0.5rem;
      width: 9.5rem;
      height: 2.5rem;
      ${({ theme }) => theme.mediaQueries.sm} {
        width: 20rem;
        height: 5.5rem;
      }
    }
  }
`

const Row = Container.extend.attrs({ px: 3, maxWidth: 56 })`
  display: grid;
  grid-gap: ${({ theme }) => theme.space[3]}px;
  text-align: left;
  h2 {
    line-height: 1;
  }
  ${({ theme }) => theme.mediaQueries.md} {
    grid-template-columns: 2fr 3fr;
  }
`

const Super = Text.withComponent('mark').extend`
  background-color: ${({ theme }) => theme.colors.warning};
  clip-path: polygon(4% 0%, 100% 0%, 96% 100%, 0% 100%);
  color: ${({ theme }) => theme.colors.white};
  display: inline-block;
  padding-bottom: ${({ theme }) => theme.space[2]}px;
  padding-left: ${({ theme }) => theme.space[3]}px;
  padding-right: ${({ theme }) => theme.space[3]}px;
`

const title = 'Hack Club Philosophy'
const desc =
  'Read about Hack Club, a network of high school computer science clubs. ' +
  'We want to make building apps and websites accessible to everyone through programming clubs at every high school.'

export default () => (
  <Fragment>
    <Helmet
      title={title}
      meta={[
        { name: 'twitter:title', content: title },
        { name: 'description', content: desc },
        { name: 'twitter:description', content: desc },
        { property: 'og:title', content: title },
        { property: 'og:description', content: desc },
        { property: 'og:url', content: 'https://hackclub.com/philosophy' }
      ]}
    />

      <Container w={1} maxWidth={56} pt={4} pb={[4, 3]} px={3} align="left">
        <HeadLine children="We’re" />
        <HeadLine children="at our best" />
        <HeadLine children="when we’re" />
        <HeadLine children="making." />
        <Seal pt={[3, 4]}>
          <Text f={[1, 2]} caps>
            The Hack Club
          </Text>
          <Text f={[3, 4]} bold caps>
            Philosophy
          </Text>
        </Seal>
      </Container>

    <Row py={4}>
      <Heading.h2 f={[5, 6]} color="red.5">
        Coding is a <Super>superpower.</Super>
      </Heading.h2>
      <Text f={3}>
        Learning to code is uniquely like gaining a superpower: it converts you
        from a consumer to a creator. Suddenly, computers become a tool for
        creating.
      </Text>
    </Row>
    <Row py={4}>
      <Heading.h2 f={[5, 6]} color="fuschia.5">
        Make, from anywhere.
      </Heading.h2>
      <Text f={3}>
        There’s never been a better time for making: anywhere in the world,
        anyone with a laptop and an internet connection can learn to make an
        app. Building things has never been so globally democratized.
      </Text>
    </Row>
    <Row py={4}>
      <Heading.h2 f={[5, 6]} color="violet.5">
        Hack, hack, hack.
      </Heading.h2>
      <Text f={3}>
        The goal of Hack Club is to help you become a hacker.{' '}
        We want a space at every school where people are making interesting
        things with code, every week. Schools don’t provide that, so we’re
        creating it in every school to make building things accessible to
        everyone.
      </Text>
    </Row>
    <Row py={4}>
      <Heading.h2 f={[5, 6]} color="indigo.5">
        Start building.
      </Heading.h2>
      <Text f={3}>
        Most coding classes teach you programming concepts instead of how to
        write real code—it’s like trying to learn carpentry without any wood. So
        at Hack Club, you learn to code entirely through building things. You
        start with no experience and build and ship a project every meeting.
      </Text>
    </Row>
    <Row py={4}>
      <Heading.h2 f={[5, 6]} color="blue.6">
        Learn as you build.
      </Heading.h2>
      <Text f={3}>
        Just as the best carpenters didn’t learn in the classroom, neither did
        the best programmers. Through our{' '}
        workshops, you’ll be walked through
        building projects. Starting out, you won’t understand how the code
        works, but you’ll build understanding as you go. You’ll get stuck along
        the way, but we’re here to help.
      </Text>
    </Row>
    <Row py={4}>
      <Heading.h2 f={[5, 6]} color="cyan.7">
        Be part of a community.
      </Heading.h2>
      <Text f={3}>
        Hack Club gives you a worldwide community of thousands of other young
        makers to talk to. We’re artists, writers, engineers, tinkerers,
        campers, filmmakers, volunteers. We make things. We help one another. We
        have fun. Join us.
      </Text>
    </Row>
    <Start mt={3} />

  </Fragment>
)
<commit_msg>Update typography on Philosophy<commit_after>import React, { Fragment } from 'react'
import styled from 'styled-components'
import {
  Heading,
  Container,
  Box,
  Text,
  Section,
  Link as A,
  theme
} from '@hackclub/design-system'
import Helmet from 'react-helmet'
import Link from 'gatsby-link'
import Nav from 'components/Nav'
import Start from 'components/Start'
import Footer from 'components/Footer'

A.link = A.withComponent(Link)

const Header = styled(Section.withComponent('header')).attrs({
  bg: 'fuschia.6'
})`
  background-image: linear-gradient(
    32deg,
    ${theme.colors.fuschia[5]} 0%,
    ${theme.colors.red[5]} 64%,
    ${theme.colors.red[6]} 100%
  );
  clip-path: polygon(0% 0%, 100% 0, 100% 100%, 0% 90%);
  > div {
    position: relative;
  }
`

const Seal = styled(Box)`
  border-radius: 9999px;
  background-color: white;
  color: black;
  mix-blend-mode: screen;
  text-align: center;
  width: 12rem;
  height: 12rem;
  position: absolute;
  margin-top: -1rem;
  transform: rotate(4deg);
  ${theme.mediaQueries.sm} {
    transform: rotate(3deg);
    margin-top: -${theme.space[5]}px;
  }
`

const HeadLine = styled(Heading.h1).attrs({
  f: [5, 7, 8],
  color: 'white',
  caps: true
})`
  line-height: 1.125 !important;
  &:nth-of-type(2) {
    padding-left: 1.5rem;
    ${theme.mediaQueries.md} {
      padding-left: 6rem;
    }
  }
  &:nth-of-type(3) {
    text-align: center;
  }
  &:nth-of-type(4) {
    text-align: right;
    position: relative;
    &:before {
      content: '';
      position: absolute;
      clip-path: polygon(8% 0%, 100% 0%, 92% 100%, 0% 100%);
      background-color: rgba(252, 252, 252, 0.625);
      mix-blend-mode: overlay;
      right: -0.5rem;
      width: 9.5rem;
      height: 2.5rem;
      ${theme.mediaQueries.sm} {
        width: 20rem;
        height: 5.5rem;
      }
    }
  }
`

const Row = styled(Container).attrs({ px: 3, color: 'black' })`
  display: grid;
  text-align: left;
  h2 {
    line-height: 1;
  }
  ${theme.mediaQueries.md} {
    grid-gap: ${theme.space[3]}px;
    grid-template-columns: 2fr 3fr;
  }
`

const Super = styled(Text.withComponent('mark'))`
  background-color: ${theme.colors.warning};
  clip-path: polygon(4% 0%, 100% 0%, 96% 100%, 0% 100%);
  color: ${theme.colors.white};
  display: inline-block;
  padding-bottom: ${theme.space[2]}px;
  padding-left: ${theme.space[3]}px;
  padding-right: ${theme.space[3]}px;
`

const title = 'Hack Club Philosophy'
const desc =
  'Read about Hack Club, a network of high school computer science clubs. ' +
  'We want to make building apps and websites accessible to everyone through programming clubs at every high school.'

export default () => (
  <Fragment>
    <Helmet
      title={title}
      meta={[
        { name: 'twitter:title', content: title },
        { name: 'description', content: desc },
        { name: 'twitter:description', content: desc },
        { property: 'og:title', content: title },
        { property: 'og:description', content: desc },
        { property: 'og:url', content: 'https://hackclub.com/philosophy' }
      ]}
    />

      <Container w={1} maxWidth={56} py={5} px={3} align="left">
        <HeadLine children="We’re" />
        <HeadLine children="at our best" />
        <HeadLine children="when we’re" />
        <HeadLine children="making." />
        <Seal pt={[3, 4]}>
          <Text f={[1, 2]} caps>
            The Hack Club
          </Text>
          <Text f={[3, 4]} bold caps>
            Philosophy
          </Text>
        </Seal>
      </Container>

    <Row py={4} mt={[0, 4]}>
      <Heading.h2 f={[5, 6]} color="red.5">
        Coding is a <Super>superpower.</Super>
      </Heading.h2>
      <Text f={[3, 4]}>
        Learning to code is uniquely like gaining a superpower: it converts you
        from a consumer to a creator. Suddenly, computers become a tool for
        creating.
      </Text>
    </Row>
    <Row py={4}>
      <Heading.h2 f={[5, 6]} color="fuschia.5">
        Make, from anywhere.
      </Heading.h2>
      <Text f={[3, 4]}>
        There’s never been a better time for making: anywhere in the world,
        anyone with a laptop and an internet connection can learn to make an
        app. Building things has never been so globally democratized.
      </Text>
    </Row>
    <Row py={4}>
      <Heading.h2 f={[5, 6]} color="violet.5">
        Hack, hack, hack.
      </Heading.h2>
      <Text f={[3, 4]}>
        The goal of Hack Club is to help you become a hacker.{' '}
        We want a space at every school where people are making interesting
        things with code, every week. Schools don’t provide that, so we’re
        creating it in every school to make building things accessible to
        everyone.
      </Text>
    </Row>
    <Row py={4}>
      <Heading.h2 f={[5, 6]} color="indigo.5">
        Start building.
      </Heading.h2>
      <Text f={[3, 4]}>
        Most coding classes teach you programming concepts instead of how to
        write real code—it’s like trying to learn carpentry without any wood. So
        at Hack Club, you learn to code entirely through building things. You
        start with no experience and build and ship a project every meeting.
      </Text>
    </Row>
    <Row py={4}>
      <Heading.h2 f={[5, 6]} color="blue.6">
        Learn as you build.
      </Heading.h2>
      <Text f={[3, 4]}>
        Just as the best carpenters didn’t learn in the classroom, neither did
        the best programmers. Through our{' '}
        workshops, you’ll be walked through
        building projects. Starting out, you won’t understand how the code
        works, but you’ll build understanding as you go. You’ll get stuck along
        the way, but we’re here to help.
      </Text>
    </Row>
    <Row py={4}>
      <Heading.h2 f={[5, 6]} color="cyan.7">
        Be part of a community.
      </Heading.h2>
      <Text f={[3, 4]}>
        Hack Club gives you a worldwide community of thousands of other young
        makers to talk to. We’re artists, writers, engineers, tinkerers,
        campers, filmmakers, volunteers. We make things. We help one another. We
        have fun. Join us.
      </Text>
    </Row>
    <Start mt={3} />

  </Fragment>
)
<|endoftext|>


<commit_before>#!/usr/bin/env node
const validateNodeEnvironment = require("../src/cli/validate-node-environment");

try {
  validateNodeEnvironment();

  // importing things only after the validateNodeEnv check so we can show an understandable
  // error. Otherwise, on unsupported platforms we would show a stack trace, which is
  // not so nice
  /* eslint-disable node/global-require */
  const program = require("commander");
  const { version } = require("../src/meta.js");
  const cli = require("../src/cli");

  program
    .description(
      "Validate and visualize dependencies.\nDetails: https://github.com/sverweij/dependency-cruiser"
    )
    .option(
      "--init [oneshot]",
      "set up dependency-cruiser for use in your environment (<<< recommended!)"
    )
    .option(
      "-c, --config [file]",
      "read rules and options from [file] (e.g. .dependency-cruiser.js)"
    )
    .option(
      "-T, --output-type <type>",
      "output type; e.g. err, err-html, dot, ddot, archi, flat, baseline or json\n(default: err)"
    )
    .option("-m, --metrics", "calculate stability metrics", false)
    .option(
      "-f, --output-to <file>",
      "file to write output to; - for stdout",
      "-"
    )
    .option(
      "-I, --include-only <regex>",
      "only include modules matching the regex"
    )
    .option(
      "-F, --focus <regex>",
      "only include modules matching the regex + their direct neighbours"
    )
    .option("-x, --exclude <regex>", "exclude all modules matching the regex")
    .option(
      "-X, --do-not-follow <regex>",
      "include modules matching the regex, but don't follow their dependencies"
    )
    .option(
      "--ignore-known [file]",
      "ignore known violations as saved in [file] (default: .dependency-cruiser-known-violations.json)"
    )
    .option(
      "--ts-config [file]",
      "use a typescript configuration (e.g. tsconfig.json)"
    )
    .option(
      "--webpack-config [file]",
      "use a webpack configuration (e.g. webpack.config.js)"
    )
    .option(
      "--ts-pre-compilation-deps",
      "detect dependencies that only exist before typescript-to-javascript " +
        "compilation (off by default)"
    )
    .option(
      "-S, --collapse <regex>",
      "collapse a to a folder depth by passing a single digit (e.g. 2). When passed a " +
        "regex collapses to that pattern E.g. ^packages/[^/]+/ would collapse to " +
        "modules/ folders directly under your packages folder. "
    )
    .option(
      "-p, --progress [type]",
      "show progress while dependency-cruiser is busy. Possible values: cli-feedback, performance-log, none"
    )
    .option(
      "-d, --max-depth <n>",
      "You probably want to use --collapse instead of --max-depth. " +
        "(max-depth would limit the cruise depth; 0 <= n <= 99 (default: 0 - no limit))."
    )
    .option(
      "-M, --module-systems <items>",
      "list of module systems (default: amd, cjs, es6, tsd)"
    )
    .option(
      "-P, --prefix <prefix>",
      "prefix to use for links in the dot and err-html reporters"
    )
    .option("--preserve-symlinks", `leave symlinks unchanged (off by default)`)
    .option("-v, --validate [file]", `alias for --config`)
    .option(
      "-i, --info",
      "shows what languages and extensions dependency-cruiser supports"
    )
    .version(version)
    .arguments("[files-or-directories]")
    .parse(process.argv);

  if (Boolean(program.args[0]) || program.opts().info || program.opts().init) {
    process.exitCode = cli(program.args, program.opts());
  } else {
    program.help();
  }
} catch (pError) {
  process.stderr.write(pError.message);
  process.exitCode = 1;
}
<commit_msg>doc(cli): to the --ts-config cli description adds it's possible to pass a jsconfig.json<commit_after>#!/usr/bin/env node
const validateNodeEnvironment = require("../src/cli/validate-node-environment");

try {
  validateNodeEnvironment();

  // importing things only after the validateNodeEnv check so we can show an understandable
  // error. Otherwise, on unsupported platforms we would show a stack trace, which is
  // not so nice
  /* eslint-disable node/global-require */
  const program = require("commander");
  const { version } = require("../src/meta.js");
  const cli = require("../src/cli");

  program
    .description(
      "Validate and visualize dependencies.\nDetails: https://github.com/sverweij/dependency-cruiser"
    )
    .option(
      "--init [oneshot]",
      "set up dependency-cruiser for use in your environment (<<< recommended!)"
    )
    .option(
      "-c, --config [file]",
      "read rules and options from [file] (e.g. .dependency-cruiser.js)"
    )
    .option(
      "-T, --output-type <type>",
      "output type; e.g. err, err-html, dot, ddot, archi, flat, baseline or json\n(default: err)"
    )
    .option("-m, --metrics", "calculate stability metrics", false)
    .option(
      "-f, --output-to <file>",
      "file to write output to; - for stdout",
      "-"
    )
    .option(
      "-I, --include-only <regex>",
      "only include modules matching the regex"
    )
    .option(
      "-F, --focus <regex>",
      "only include modules matching the regex + their direct neighbours"
    )
    .option("-x, --exclude <regex>", "exclude all modules matching the regex")
    .option(
      "-X, --do-not-follow <regex>",
      "include modules matching the regex, but don't follow their dependencies"
    )
    .option(
      "--ignore-known [file]",
      "ignore known violations as saved in [file] (default: .dependency-cruiser-known-violations.json)"
    )
    .option(
      "--ts-config [file]",
      "use a TypeScript configuration (e.g. tsconfig.json) or it's JavaScript counterpart (e.g. jsconfig.json)"
    )
    .option(
      "--webpack-config [file]",
      "use a webpack configuration (e.g. webpack.config.js)"
    )
    .option(
      "--ts-pre-compilation-deps",
      "detect dependencies that only exist before typescript-to-javascript " +
        "compilation (off by default)"
    )
    .option(
      "-S, --collapse <regex>",
      "collapse a to a folder depth by passing a single digit (e.g. 2). When passed a " +
        "regex collapses to that pattern E.g. ^packages/[^/]+/ would collapse to " +
        "modules/ folders directly under your packages folder. "
    )
    .option(
      "-p, --progress [type]",
      "show progress while dependency-cruiser is busy. Possible values: cli-feedback, performance-log, none"
    )
    .option(
      "-d, --max-depth <n>",
      "You probably want to use --collapse instead of --max-depth. " +
        "(max-depth would limit the cruise depth; 0 <= n <= 99 (default: 0 - no limit))."
    )
    .option(
      "-M, --module-systems <items>",
      "list of module systems (default: amd, cjs, es6, tsd)"
    )
    .option(
      "-P, --prefix <prefix>",
      "prefix to use for links in the dot and err-html reporters"
    )
    .option("--preserve-symlinks", `leave symlinks unchanged (off by default)`)
    .option("-v, --validate [file]", `alias for --config`)
    .option(
      "-i, --info",
      "shows what languages and extensions dependency-cruiser supports"
    )
    .version(version)
    .arguments("[files-or-directories]")
    .parse(process.argv);

  if (Boolean(program.args[0]) || program.opts().info || program.opts().init) {
    process.exitCode = cli(program.args, program.opts());
  } else {
    program.help();
  }
} catch (pError) {
  process.stderr.write(pError.message);
  process.exitCode = 1;
}
<|endoftext|>


<commit_before>jQuery(document).ready(function($) {

    var budget = {};

    // utils
    // simple validations
    $(".alert").hide();
    $(".alert .close").on('click', function (e) {
        e.preventDefault();
        $(this).parent().hide("slow");
    });
    var isEmail = function (email) {
        var regex = /^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
        return regex.test(email);
    }
    // taken from http://www.mredkj.com/javascript/numberFormat.html#addcommas
    var addCommas = function (nStr) {
        nStr += '';
        x = nStr.split('.');
        x1 = x[0];
        x2 = x.length > 1 ? '.' + x[1] : '';
        var rgx = /(\d+)(\d{3})/;
        while (rgx.test(x1)) {
            x1 = x1.replace(rgx, '$1' + ',' + '$2');
        }
        return x1 + x2;
    }

    // form utils
    // popovers
    $(".budget-builder .details")
    .popover()
    $(".budget-builder .clear")
    .popover()
    .on("click", function () {
        var optionGroup = $(this).attr("name");
        $("input[name='" + optionGroup + "']").prop("checked", false);
        updateBudget();
    });

    $(".budget-change").on("change", function() {
        updateBudget();
    });

    // update budget tally
    var updateBudget = function () {
        budget = {
            "options" : [],
            "gap": 161000000,
            "filled": 0
        };
        $(".budget-change").each(function (){
            if ($(this).prop("checked")) {
                budget["options"].push(parseInt($(this).attr("id").split("_")[1]));
                budget["filled"] += parseInt($(this).val());
                budget["gap"] -= parseInt($(this).val());
            }
        });
        $(".tally .budget-filled").html("$ " + addCommas(budget["filled"]));
        $(".tally .budget-gap").html("$ " + addCommas(budget["gap"]));
        if (budget["gap"] < 0) $(".tally .budget-gap").css("color", "#468847");
        if ($("#error-nobudget").is(':visible')) $("#error-nobudget").hide("slow");
    }

    // submit budget
    $("#budget-form").on("submit", function (e) {
        e.preventDefault();

        budget["email"] = $("form #email").val();

        // some simple validations
        if (!budget["filled"]) {
            $("#error-nobudget").show("slow");
            return false;
        }
        if (!isEmail(budget["email"])) {
            $("#error-noemail").show("slow");
            return false;
        }

        budget["csrfmiddlewaretoken"] = $("input[name='csrfmiddlewaretoken']").val();
        budget["options"] = (typeof budget["options"] === 'object') ? JSON.stringify(budget["options"]) : budget["options"];

        $.post($(this).attr("action"),
            budget,
            function (data) {
                // some result stats
                $(".page-header .lead").html("You are one of " + data["budget__count"] + " users to come up with your own plan to fix the T's budget. Budget proposals so far have had an average combined revenue and savings of $ " + addCommas(parseInt(data["budget__avg"])) + ".
See how many people chose each option below.");
                $.each(data["options"], function(option, nr) {
                    if ($("#option_nr_" + option).length === 0) {
                        $("#option_" + option).parent().prepend("× " + nr + "");
                    } else {
                        $("#option_nr_" + option).html("× " + nr);
                    }
                });
                $(".alert").hide("slow");
                $(".social-media").appendTo("header.page-header");

                try {
                    $(".twitter-share-button").remove();
                    $(".twitterwidget").prepend("Tweet");
                    // re-evaluate twitter widget
                    twttr.widgets.load();
                } catch (e) {
                    // twitter widget not available
                    $(".twitterwidget").remove();
                }
                $(".email-link .btn").attr("href","mailto:?subject=MBTA Budget Calculator&body=I worked on the MBTA Budget and filled it by $ " + addCommas(budget['filled']) + " - Try yourself at http://fixthet.mapc.org");
                $("html, body").animate({scrollTop:0}, "slow");
            },
            "json"
        );
    });

});<commit_msg>updates language for tweets and email<commit_after>jQuery(document).ready(function($) {

    var budget = {};

    // utils
    // simple validations
    $(".alert").hide();
    $(".alert .close").on('click', function (e) {
        e.preventDefault();
        $(this).parent().hide("slow");
    });
    var isEmail = function (email) {
        var regex = /^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
        return regex.test(email);
    }
    // taken from http://www.mredkj.com/javascript/numberFormat.html#addcommas
    var addCommas = function (nStr) {
        nStr += '';
        x = nStr.split('.');
        x1 = x[0];
        x2 = x.length > 1 ? '.' + x[1] : '';
        var rgx = /(\d+)(\d{3})/;
        while (rgx.test(x1)) {
            x1 = x1.replace(rgx, '$1' + ',' + '$2');
        }
        return x1 + x2;
    }

    // form utils
    // popovers
    $(".budget-builder .details")
    .popover()
    $(".budget-builder .clear")
    .popover()
    .on("click", function () {
        var optionGroup = $(this).attr("name");
        $("input[name='" + optionGroup + "']").prop("checked", false);
        updateBudget();
    });

    $(".budget-change").on("change", function() {
        updateBudget();
    });

    // update budget tally
    var updateBudget = function () {
        budget = {
            "options" : [],
            "gap": 161000000,
            "filled": 0
        };
        $(".budget-change").each(function (){
            if ($(this).prop("checked")) {
                budget["options"].push(parseInt($(this).attr("id").split("_")[1]));
                budget["filled"] += parseInt($(this).val());
                budget["gap"] -= parseInt($(this).val());
            }
        });
        $(".tally .budget-filled").html("$ " + addCommas(budget["filled"]));
        $(".tally .budget-gap").html("$ " + addCommas(budget["gap"]));
        if (budget["gap"] < 0) $(".tally .budget-gap").css("color", "#468847");
        if ($("#error-nobudget").is(':visible')) $("#error-nobudget").hide("slow");
    }

    // submit budget
    $("#budget-form").on("submit", function (e) {
        e.preventDefault();

        budget["email"] = $("form #email").val();

        // some simple validations
        if (!budget["filled"]) {
            $("#error-nobudget").show("slow");
            return false;
        }
        if (!isEmail(budget["email"])) {
            $("#error-noemail").show("slow");
            return false;
        }

        budget["csrfmiddlewaretoken"] = $("input[name='csrfmiddlewaretoken']").val();
        budget["options"] = (typeof budget["options"] === 'object') ? JSON.stringify(budget["options"]) : budget["options"];

        $.post($(this).attr("action"),
            budget,
            function (data) {
                // some result stats
                $(".page-header .lead").html("You are one of " + data["budget__count"] + " users to come up with your own plan to fix the T's budget. Budget proposals so far have had an average combined revenue and savings of $ " + addCommas(parseInt(data["budget__avg"])) + ".
See how many people chose each option below.");
                $.each(data["options"], function(option, nr) {
                    if ($("#option_nr_" + option).length === 0) {
                        $("#option_" + option).parent().prepend("× " + nr + "");
                    } else {
                        $("#option_nr_" + option).html("× " + nr);
                    }
                });
                $(".alert").hide("slow");
                $(".social-media").appendTo("header.page-header");

                try {
                    $(".twitter-share-button").remove();
                    $(".twitterwidget").prepend("Tweet");
                    // re-evaluate twitter widget
                    twttr.widgets.load();
                } catch (e) {
                    // twitter widget not available
                    $(".twitterwidget").remove();
                }
                $(".email-link .btn").attr("href","mailto:?subject=MBTA Budget Calculator&body=I came up with my own plan to fix the MBTA Budget and filled the gap by $ " + addCommas(budget['filled']) + ". Try yourself at http://fixthet.mapc.org");
                $("html, body").animate({scrollTop:0}, "slow");
            },
            "json"
        );
    });

});<|endoftext|>


<commit_before>import React, {Component} from 'react'
import Dropzone from 'react-dropzone'
import _ from 'lodash'

class DataDrop extends Component {

    constructor(props) {
        super(props)

        if (!window.FileReader) {
            alert('Please use modern browser.')
        }

        // deferred object
        DataDrop.data = new Promise((res, rej) => {
            DataDrop.resolve = res
            DataDrop.reject = rej
        })
    }

    static onDrop(files) {
        let reader = new FileReader()
        reader.readAsText(files[0])
        reader.onload = DataDrop.loadHandler
        reader.onerror = DataDrop.errorHandler
    }

    // ISO 3166-1 country code,FIPS 5-2 subdivision code, GNS FD, GNS UFI,ISO 639-1 language code, language script, name, latitude, longitude
    static loadHandler(event) {
        let allData = event.target.result.split(/\r\n|\n/)

        // drop header
        allData.shift()

        DataDrop.resolve(allData
            .map(r =>
                // here should be ;
                r.split(',')
            ).map(r => ({
                count: _.parseInt(r[1]),
                name: r[6],
                lat: Number(r[7]),
                lng: Number(r[8])
            })))
    }

    static errorHandler(evt) {
        if ("NotReadableError" === evt.target.error.name) {
            return alert("File not redable")
        }
        return alert(evt.target.error)
    }

    static getData() {
        return DataDrop.data
    }

    render() {
        return (

                <Dropzone
                    onDrop={DataDrop.onDrop}
                    accept={"text/csv"}>

Drop your data, or click to select files to upload.

                </Dropzone>

        )
    }
}

export default DataDrop<commit_msg>typo :(<commit_after>import React, {Component} from 'react'
import Dropzone from 'react-dropzone'
import _ from 'lodash'

class DataDrop extends Component {

    constructor(props) {
        super(props)

        if (!window.FileReader) {
            alert('Please use modern browser.')
        }

        // deferred object
        DataDrop.data = new Promise((res, rej) => {
            DataDrop.resolve = res
            DataDrop.reject = rej
        })
    }

    static onDrop(files) {
        let reader = new FileReader()
        reader.readAsText(files[0])
        reader.onload = DataDrop.loadHandler
        reader.onerror = DataDrop.errorHandler
    }

    // ISO 3166-1 country code,FIPS 5-2 subdivision code, GNS FD, GNS UFI,ISO 639-1 language code, language script, name, latitude, longitude
    static loadHandler(event) {
        let allData = event.target.result.split(/\r\n|\n/)

        // drop header
        allData.shift()

        DataDrop.resolve(allData
            .map(r =>
                // here should be ;
                r.split(',')
            ).map(r => ({
                count: _.parseInt(r[1]),
                name: r[6],
                lat: Number(r[7]),
                lng: Number(r[8])
            })))
    }

    static errorHandler(evt) {
        if ("NotReadableError" === evt.target.error.name) {
            return alert("File not readable")
        }
        return alert(evt.target.error)
    }

    static getData() {
        return DataDrop.data
    }

    render() {
        return (

                <Dropzone
                    onDrop={DataDrop.onDrop}
                    accept={"text/csv"}>

Drop your data, or click to select files to upload.

                </Dropzone>

        )
    }
}

export default DataDrop<|endoftext|>


<commit_before>var redis = require('redis');
var config = require("konfig")();
var port=config.app.redis.port;
var host=config.app.redis.host;
var client = redis.createClient(port,host);

module.exports = {
    get: function(cookie, callback) {
        client.hgetall(cookie, function (error, val) {
            if (error !== null) {
                console.log("error: " + error);
                callback(null, error);
            }
            else {
                console.log(val);
                callback(val, null);
            }
        });
    },
    set: function(cookie, username, stateObject, callback){
        client.hmset(cookie,"Username",username,"stateObject" ,stateObject,function (error, result) {
            if (error !== null) {
                console.log("error: " + error);
                callback(null, error)
            }
            else {
                console.log("Success");
                callback(result, null);
            }
        });
    }
};
<commit_msg>Correctly starting and stoping the redis client<commit_after>var redis = require('redis');
var config = require("konfig")();
var port=config.app.redis.port;
var host=config.app.redis.host;

module.exports = {
    get: function(cookie, callback) {
        var client = redis.createClient(port,host);
        client.hgetall(cookie, function (error, val) {
            if (error !== null) {
                console.log("error: " + error);
                client.quit();
                callback(null, error);
            }
            else {
                console.log(val);
                client.quit();
                callback(val, null);
            }
        });
    },
    set: function(cookie, username, stateObject, callback){
        var client = redis.createClient(port,host);
        client.hmset(cookie,"Username",username,"stateObject" ,stateObject,function (error, result) {
            if (error !== null) {
                console.log("error: " + error);
                client.quit();
                callback(null, error)
            }
            else {
                console.log("Success");
                client.quit();
                callback(result, null);
            }
        });
    }
};
<|endoftext|>


<commit_before>angular.module('dplaceMapDirective', [])
    .directive('dplaceMap', function() {
        function link(scope, element, attrs) {
            element.append("

");
            scope.updatesEnabled = true;
            scope.map = $('#mapdiv').vectorMap({
                map: 'world_mill_en',
                backgroundColor: 'white',
                regionStyle: {
                  initial: {
                    fill: '#428bca',
                    "fill-opacity": 1,
                    stroke: '#357ebd',
                    "stroke-width": 0,
                    "stroke-opacity": 1
                  },
                  hover: {
                    "fill-opacity": 0.8
                  },
                  selected: {
                    fill: '#113'
                  },
                  selectedHover: {
                  }
                },
                onRegionOver: function(e, code) {
                    if(attrs.region) {
                        scope.$apply(function () {
                            scope.region = code;
                        });
                    }
                },
                onRegionSelected: function(e, code, isSelected, selectedRegionIds) {
                    if(attrs.selectedRegionIds && scope.updatesEnabled) {
                        scope.$apply(function() {
                            scope.selectedRegionIds = angular.copy(selectedRegionIds);
                        });
                    }
                },
                regionsSelectable: true
            }).vectorMap('get','mapObject');

            scope.addMarkers = function() {
                scope.map.removeAllMarkers();
                if(!scope.societies) {
                    return;
                }
                scope.societies.forEach(function(societyResult) {
                    var society = societyResult.society;
                    // Add a marker for each point
                    var marker = {latLng: society.location.coordinates.reverse(), name: society.name}
                    scope.map.addMarker(society.id, marker);
                });
            };

            // Update markers when societies change
            scope.$watchCollection('societies', function(oldvalue, newvalue) {
                scope.addMarkers();
            });

            scope.$watchCollection('selectedRegionIds', function(oldvalue, newvalue) {
                // Then update the UI
                scope.updatesEnabled = false;
                scope.map.setSelectedRegions(angular.copy(newvalue));
                scope.updatesEnabled = true;
            });
            scope.$on('mapTabActivated', function(event, args) {
                scope.map.setSize();
            });
        }

        return {
            restrict: 'E',
            scope: {
                societies: '=',
                region: '=',
                selectedRegionIds: '='
            },
            link: link
        };
    });<commit_msg>Tweak to fix 2 way sync of regions<commit_after>angular.module('dplaceMapDirective', [])
    .directive('dplaceMap', function() {
        function link(scope, element, attrs) {
            element.append("

");
            scope.updatesEnabled = true;
            scope.map = $('#mapdiv').vectorMap({
                map: 'world_mill_en',
                backgroundColor: 'white',
                regionStyle: {
                  initial: {
                    fill: '#428bca',
                    "fill-opacity": 1,
                    stroke: '#357ebd',
                    "stroke-width": 0,
                    "stroke-opacity": 1
                  },
                  hover: {
                    "fill-opacity": 0.8
                  },
                  selected: {
                    fill: '#113'
                  },
                  selectedHover: {
                  }
                },
                onRegionOver: function(e, code) {
                    if(attrs.region) {
                        scope.$apply(function () {
                            scope.region = code;
                        });
                    }
                },
                onRegionSelected: function(e, code, isSelected, selectedRegionIds) {
                    if(attrs.selectedRegionIds && scope.updatesEnabled) {
                        scope.$apply(function() {
                            scope.selectedRegionIds = angular.copy(selectedRegionIds);
                        });
                    }
                },
                regionsSelectable: true
            }).vectorMap('get','mapObject');

            scope.addMarkers = function() {
                scope.map.removeAllMarkers();
                if(!scope.societies) {
                    return;
                }
                scope.societies.forEach(function(societyResult) {
                    var society = societyResult.society;
                    // Add a marker for each point
                    var marker = {latLng: society.location.coordinates.reverse(), name: society.name}
                    scope.map.addMarker(society.id, marker);
                });
            };

            // Update markers when societies change
            scope.$watchCollection('societies', function(oldvalue, newvalue) {
                scope.addMarkers();
            });

            scope.$watchCollection('selectedRegionIds', function(oldvalue, newvalue) {
                // Then update the UI
                // If the regions in the map are already correct, do not set them
                if(!angular.equals(scope.map.getSelectedRegions(), scope.selectedRegionIds)) {
                    // Prevent this update from triggering another change
                    scope.updatesEnabled = false;
                    scope.map.setSelectedRegions(angular.copy(scope.selectedRegionIds));
                    scope.updatesEnabled = true;
                }
            });
            scope.$on('mapTabActivated', function(event, args) {
                scope.map.setSize();
            });
        }

        return {
            restrict: 'E',
            scope: {
                societies: '=',
                region: '=',
                selectedRegionIds: '='
            },
            link: link
        };
    });<|endoftext|>


<commit_before>import {
	Color,
	LinearFilter,
	MathUtils,
	Matrix4,
	Mesh,
	PerspectiveCamera,
	Plane,
	RGBFormat,
	ShaderMaterial,
	UniformsUtils,
	Vector3,
	Vector4,
	WebGLRenderTarget,
	DepthTexture,
	UnsignedShortType,
	NearestFilter
} from '../../../build/three.module.js';

var Reflector = function ( geometry, options ) {

	Mesh.call( this, geometry );

	this.type = 'Reflector';

	var scope = this;

	options = options || {};

	var color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F );
	var textureWidth = options.textureWidth || 512;
	var textureHeight = options.textureHeight || 512;
	var clipBias = options.clipBias || 0;
	var shader = options.shader || Reflector.ReflectorShader;
	var useDepthTexture = options.useDepthTexture === true;
	var yAxis = new Vector3(0, 1, 0);
	var vecTemp0 = new Vector3();
	var vecTemp1 = new Vector3();

	//

	scope.needsUpdate = false;
	scope.maxDistance = Reflector.ReflectorShader.uniforms.maxDistance.value
	scope.opacity = Reflector.ReflectorShader.uniforms.opacity.value

	Object.defineProperty(scope, 'color', {
		get() {
			return scope.material.uniforms[ 'color' ].value;
		},
		set(val) {
			scope.material.uniforms[ 'color' ].value = new Color( val );
		}
	});

  scope._isDistanceAttenuation = Reflector.ReflectorShader.defines.isDistanceAttenuation
  Object.defineProperty(scope, 'isDistanceAttenuation', {
    get() {
      return scope._isDistanceAttenuation
    },
    set(val) {
      if (scope._isDistanceAttenuation === val) return
      scope._isDistanceAttenuation = val
      scope.material.defines.isDistanceAttenuation = val
      scope.material.needsUpdate = true
    }
	})

  scope._isFresnel = Reflector.ReflectorShader.defines.isFresnel
  Object.defineProperty(scope, 'isFresnel', {
    get() {
      return scope._isFresnel
    },
    set(val) {
      if (scope._isFresnel === val) return
      scope._isFresnel = val
      scope.material.defines.isFresnel = val
      scope.material.needsUpdate = true
    }
	})

	var reflectorPlane = new Plane();
	var normal = new Vector3();
	var reflectorWorldPosition = new Vector3();
	var cameraWorldPosition = new Vector3();
	var rotationMatrix = new Matrix4();
	var lookAtPosition = new Vector3( 0, 0, - 1 );
	var clipPlane = new Vector4();

	var view = new Vector3();
	var target = new Vector3();
	var q = new Vector4();

	var textureMatrix = new Matrix4();
	var virtualCamera = new PerspectiveCamera();

	if( useDepthTexture ){
		var depthTexture = new DepthTexture();
		depthTexture.type = UnsignedShortType;
		depthTexture.minFilter = NearestFilter;
		depthTexture.maxFilter = NearestFilter;
	}

	var parameters = {
		minFilter: LinearFilter,
		magFilter: LinearFilter,
		format: RGBFormat,
    depthTexture: useDepthTexture ? depthTexture : null,
	};

	var renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, parameters );

	if ( ! MathUtils.isPowerOfTwo( textureWidth ) || ! MathUtils.isPowerOfTwo( textureHeight ) ) {

		renderTarget.texture.generateMipmaps = false;

	}

	var material = new ShaderMaterial( {
		transparent: useDepthTexture,
    defines: Object.assign( {}, Reflector.ReflectorShader.defines, {
      useDepthTexture
    } ),
		uniforms: UniformsUtils.clone( shader.uniforms ),
		fragmentShader: shader.fragmentShader,
		vertexShader: shader.vertexShader
	} );

	material.uniforms[ 'tDiffuse' ].value = renderTarget.texture;
	material.uniforms[ 'color' ].value = color;
	material.uniforms[ 'textureMatrix' ].value = textureMatrix;
	if (useDepthTexture) {
		material.uniforms[ 'tDepth' ].value = renderTarget.depthTexture;
	}

	this.material = material;

	this.doRender = function ( renderer, scene, camera ) {

		material.uniforms['maxDistance'].value = scope.maxDistance * (camera.position.length() / camera.position.y);
		///todo: Temporary hack,
		// need precise calculation like this https://github.com/mrdoob/three.js/pull/20156/commits/8181946068e386d14a283cbd4f8877bc7ae066d3 ,
		// after fully understand http://www.terathon.com/lengyel/Lengyel-Oblique.pdf .

		material.uniforms['opacity'].value = scope.opacity;

		vecTemp0.copy(camera.position).normalize();
		vecTemp1.copy(vecTemp0).reflect(yAxis);
		material.uniforms['fresnel'].value = (vecTemp0.dot( vecTemp1 ) + 1.) / 2.; ///todo: Also need to use glsl viewPosition and viewNormal per pixel.
		// console.log(material.uniforms['fresnel'].value)

		reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld );
		cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );

		rotationMatrix.extractRotation( scope.matrixWorld );

		normal.set( 0, 0, 1 );
		normal.applyMatrix4( rotationMatrix );

		view.subVectors( reflectorWorldPosition, cameraWorldPosition );

		// Avoid rendering when reflector is facing away

		if ( view.dot( normal ) > 0 ) return;

		view.reflect( normal ).negate();
		view.add( reflectorWorldPosition );

		rotationMatrix.extractRotation( camera.matrixWorld );

		lookAtPosition.set( 0, 0, - 1 );
		lookAtPosition.applyMatrix4( rotationMatrix );
		lookAtPosition.add( cameraWorldPosition );

		target.subVectors( reflectorWorldPosition, lookAtPosition );
		target.reflect( normal ).negate();
		target.add( reflectorWorldPosition );

		virtualCamera.position.copy( view );
		virtualCamera.up.set( 0, 1, 0 );
		virtualCamera.up.applyMatrix4( rotationMatrix );
		virtualCamera.up.reflect( normal );
		virtualCamera.lookAt( target );

		virtualCamera.far = camera.far; // Used in WebGLBackground

		virtualCamera.updateMatrixWorld();
		virtualCamera.projectionMatrix.copy( camera.projectionMatrix );

		// Update the texture matrix
		textureMatrix.set(
			0.5, 0.0, 0.0, 0.5,
			0.0, 0.5, 0.0, 0.5,
			0.0, 0.0, 0.5, 0.5,
			0.0, 0.0, 0.0, 1.0
		);
		textureMatrix.multiply( virtualCamera.projectionMatrix );
		textureMatrix.multiply( virtualCamera.matrixWorldInverse );
		textureMatrix.multiply( scope.matrixWorld );

		// Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
		// Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
		reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition );
		reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse );

		clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant );

		var projectionMatrix = virtualCamera.projectionMatrix;

		q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ];
		q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ];
		q.z = - 1.0;
		q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ];

		// Calculate the scaled plane vector
		clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) );

		// Replacing the third row of the projection matrix
		projectionMatrix.elements[ 2 ] = clipPlane.x;
		projectionMatrix.elements[ 6 ] = clipPlane.y;
		projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias;
		projectionMatrix.elements[ 14 ] = clipPlane.w;

		// Render

		renderTarget.texture.encoding = renderer.outputEncoding;

		// scope.visible = false;

		var currentRenderTarget = renderer.getRenderTarget();

		var currentXrEnabled = renderer.xr.enabled;
		var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;

		renderer.xr.enabled = false; // Avoid camera modification
		renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows

		renderer.setRenderTarget( renderTarget );

		renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897

		if ( renderer.autoClear === false ) renderer.clear();
		renderer.render( scene, virtualCamera );

		renderer.xr.enabled = currentXrEnabled;
		renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;

		renderer.setRenderTarget( currentRenderTarget );

		// Restore viewport

		var viewport = camera.viewport;

		if ( viewport !== undefined ) {

			renderer.state.viewport( viewport );

		}

		// scope.visible = true;

	};

	this.getRenderTarget = function () {

		return renderTarget;

	};

};

Reflector.prototype = Object.create( Mesh.prototype );
Reflector.prototype.constructor = Reflector;

Reflector.ReflectorShader = { ///todo: Will conflict with Reflector.js?

  defines: {
    isDistanceAttenuation: true,
    isFresnel: true,
  },

	uniforms: {

		color: { value: null },
		tDiffuse: { value: null },
		tDepth: { value: null },
		textureMatrix: { value: null },
    maxDistance: { value: 180 },
    opacity: { value: .5 },
    fresnel: { value: null },

	},

	vertexShader: [
		'uniform mat4 textureMatrix;',
		'varying vec4 vUv;',

		'void main() {',

		'	vUv = textureMatrix * vec4( position, 1.0 );',

		'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',

		'}'
	].join( '\n' ),

	fragmentShader: `
		uniform vec3 color;
		uniform sampler2D tDiffuse;
		uniform sampler2D tDepth;
		uniform float maxDistance;
		uniform float opacity;
		uniform float fresnel;
		varying vec4 vUv;
		float blendOverlay( float base, float blend ) {
			return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );
		}
		vec3 blendOverlay( vec3 base, vec3 blend ) {
			return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) );
		}
		void main() {
			vec4 base = texture2DProj( tDiffuse, vUv );
			#ifdef useDepthTexture
				float op=opacity;
				float depth = texture2DProj( tDepth, vUv ).r;
				if(depth>maxDistance) discard;
				#ifdef isDistanceAttenuation
					float ratio=1.-(depth/maxDistance);
					float attenuation=ratio*ratio;
					op=opacity*attenuation;
				#endif
				#ifdef isFresnel
					op*=fresnel;
				#endif
				gl_FragColor = vec4( blendOverlay( base.rgb, color ), op );
			#else
				gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 );
			#endif
		}
	`,
};

export { Reflector };
<commit_msg>Examples: Clean up. (#21499)<commit_after>import {
	Color,
	LinearFilter,
	MathUtils,
	Matrix4,
	Mesh,
	PerspectiveCamera,
	Plane,
	RGBFormat,
	ShaderMaterial,
	UniformsUtils,
	Vector3,
	Vector4,
	WebGLRenderTarget,
	DepthTexture,
	UnsignedShortType,
	NearestFilter
} from '../../../build/three.module.js';

var Reflector = function ( geometry, options ) {

	Mesh.call( this, geometry );

	this.type = 'Reflector';

	var scope = this;

	options = options || {};

	var color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F );
	var textureWidth = options.textureWidth || 512;
	var textureHeight = options.textureHeight || 512;
	var clipBias = options.clipBias || 0;
	var shader = options.shader || Reflector.ReflectorShader;
	var useDepthTexture = options.useDepthTexture === true;
	var yAxis = new Vector3( 0, 1, 0 );
	var vecTemp0 = new Vector3();
	var vecTemp1 = new Vector3();

	//

	scope.needsUpdate = false;
	scope.maxDistance = Reflector.ReflectorShader.uniforms.maxDistance.value;
	scope.opacity = Reflector.ReflectorShader.uniforms.opacity.value;

	Object.defineProperty( scope, 'color', {
		get() {

			return scope.material.uniforms[ 'color' ].value;

		},
		set( val ) {

			scope.material.uniforms[ 'color' ].value = new Color( val );

		}
	} );

	scope._isDistanceAttenuation = Reflector.ReflectorShader.defines.isDistanceAttenuation;
	Object.defineProperty( scope, 'isDistanceAttenuation', {
		get() {

			return scope._isDistanceAttenuation;

		},
		set( val ) {

			if ( scope._isDistanceAttenuation === val ) return;
			scope._isDistanceAttenuation = val;
			scope.material.defines.isDistanceAttenuation = val;
			scope.material.needsUpdate = true;

		}
	} );

	scope._isFresnel = Reflector.ReflectorShader.defines.isFresnel;
	Object.defineProperty( scope, 'isFresnel', {
		get() {

			return scope._isFresnel;

		},
		set( val ) {

			if ( scope._isFresnel === val ) return;
			scope._isFresnel = val;
			scope.material.defines.isFresnel = val;
			scope.material.needsUpdate = true;

		}
	} );

	var reflectorPlane = new Plane();
	var normal = new Vector3();
	var reflectorWorldPosition = new Vector3();
	var cameraWorldPosition = new Vector3();
	var rotationMatrix = new Matrix4();
	var lookAtPosition = new Vector3( 0, 0, - 1 );
	var clipPlane = new Vector4();

	var view = new Vector3();
	var target = new Vector3();
	var q = new Vector4();

	var textureMatrix = new Matrix4();
	var virtualCamera = new PerspectiveCamera();

	if ( useDepthTexture ) {

		var depthTexture = new DepthTexture();
		depthTexture.type = UnsignedShortType;
		depthTexture.minFilter = NearestFilter;
		depthTexture.maxFilter = NearestFilter;

	}

	var parameters = {
		minFilter: LinearFilter,
		magFilter: LinearFilter,
		format: RGBFormat,
		depthTexture: useDepthTexture ? depthTexture : null,
	};

	var renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, parameters );

	if ( ! MathUtils.isPowerOfTwo( textureWidth ) || ! MathUtils.isPowerOfTwo( textureHeight ) ) {

		renderTarget.texture.generateMipmaps = false;

	}

	var material = new ShaderMaterial( {
		transparent: useDepthTexture,
		defines: Object.assign( {}, Reflector.ReflectorShader.defines, {
			useDepthTexture
		} ),
		uniforms: UniformsUtils.clone( shader.uniforms ),
		fragmentShader: shader.fragmentShader,
		vertexShader: shader.vertexShader
	} );

	material.uniforms[ 'tDiffuse' ].value = renderTarget.texture;
	material.uniforms[ 'color' ].value = color;
	material.uniforms[ 'textureMatrix' ].value = textureMatrix;
	if ( useDepthTexture ) {

		material.uniforms[ 'tDepth' ].value = renderTarget.depthTexture;

	}

	this.material = material;

	this.doRender = function ( renderer, scene, camera ) {

		material.uniforms[ 'maxDistance' ].value = scope.maxDistance * ( camera.position.length() / camera.position.y );
		///todo: Temporary hack,
		// need precise calculation like this https://github.com/mrdoob/three.js/pull/20156/commits/8181946068e386d14a283cbd4f8877bc7ae066d3 ,
		// after fully understand http://www.terathon.com/lengyel/Lengyel-Oblique.pdf .

		material.uniforms[ 'opacity' ].value = scope.opacity;

		vecTemp0.copy( camera.position ).normalize();
		vecTemp1.copy( vecTemp0 ).reflect( yAxis );
		material.uniforms[ 'fresnel' ].value = ( vecTemp0.dot( vecTemp1 ) + 1. ) / 2.; ///todo: Also need to use glsl viewPosition and viewNormal per pixel.
		// console.log(material.uniforms['fresnel'].value)

		reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld );
		cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );

		rotationMatrix.extractRotation( scope.matrixWorld );

		normal.set( 0, 0, 1 );
		normal.applyMatrix4( rotationMatrix );

		view.subVectors( reflectorWorldPosition, cameraWorldPosition );

		// Avoid rendering when reflector is facing away

		if ( view.dot( normal ) > 0 ) return;

		view.reflect( normal ).negate();
		view.add( reflectorWorldPosition );

		rotationMatrix.extractRotation( camera.matrixWorld );

		lookAtPosition.set( 0, 0, - 1 );
		lookAtPosition.applyMatrix4( rotationMatrix );
		lookAtPosition.add( cameraWorldPosition );

		target.subVectors( reflectorWorldPosition, lookAtPosition );
		target.reflect( normal ).negate();
		target.add( reflectorWorldPosition );

		virtualCamera.position.copy( view );
		virtualCamera.up.set( 0, 1, 0 );
		virtualCamera.up.applyMatrix4( rotationMatrix );
		virtualCamera.up.reflect( normal );
		virtualCamera.lookAt( target );

		virtualCamera.far = camera.far; // Used in WebGLBackground

		virtualCamera.updateMatrixWorld();
		virtualCamera.projectionMatrix.copy( camera.projectionMatrix );

		// Update the texture matrix
		textureMatrix.set(
			0.5, 0.0, 0.0, 0.5,
			0.0, 0.5, 0.0, 0.5,
			0.0, 0.0, 0.5, 0.5,
			0.0, 0.0, 0.0, 1.0
		);
		textureMatrix.multiply( virtualCamera.projectionMatrix );
		textureMatrix.multiply( virtualCamera.matrixWorldInverse );
		textureMatrix.multiply( scope.matrixWorld );

		// Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
		// Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
		reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition );
		reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse );

		clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant );

		var projectionMatrix = virtualCamera.projectionMatrix;

		q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ];
		q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ];
		q.z = - 1.0;
		q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ];

		// Calculate the scaled plane vector
		clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) );

		// Replacing the third row of the projection matrix
		projectionMatrix.elements[ 2 ] = clipPlane.x;
		projectionMatrix.elements[ 6 ] = clipPlane.y;
		projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias;
		projectionMatrix.elements[ 14 ] = clipPlane.w;

		// Render

		renderTarget.texture.encoding = renderer.outputEncoding;

		// scope.visible = false;

		var currentRenderTarget = renderer.getRenderTarget();

		var currentXrEnabled = renderer.xr.enabled;
		var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;

		renderer.xr.enabled = false; // Avoid camera modification
		renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows

		renderer.setRenderTarget( renderTarget );

		renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897

		if ( renderer.autoClear === false ) renderer.clear();
		renderer.render( scene, virtualCamera );

		renderer.xr.enabled = currentXrEnabled;
		renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;

		renderer.setRenderTarget( currentRenderTarget );

		// Restore viewport

		var viewport = camera.viewport;

		if ( viewport !== undefined ) {

			renderer.state.viewport( viewport );

		}

		// scope.visible = true;

	};

	this.getRenderTarget = function () {

		return renderTarget;

	};

};

Reflector.prototype = Object.create( Mesh.prototype );
Reflector.prototype.constructor = Reflector;

Reflector.ReflectorShader = { ///todo: Will conflict with Reflector.js?

	defines: {
		isDistanceAttenuation: true,
		isFresnel: true,
	},

	uniforms: {

		color: { value: null },
		tDiffuse: { value: null },
		tDepth: { value: null },
		textureMatrix: { value: null },
		maxDistance: { value: 180 },
		opacity: { value: 0.5 },
		fresnel: { value: null },

	},

	vertexShader: [
		'uniform mat4 textureMatrix;',
		'varying vec4 vUv;',

		'void main() {',

		'	vUv = textureMatrix * vec4( position, 1.0 );',

		'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',

		'}'
	].join( '\n' ),

	fragmentShader: `
		uniform vec3 color;
		uniform sampler2D tDiffuse;
		uniform sampler2D tDepth;
		uniform float maxDistance;
		uniform float opacity;
		uniform float fresnel;
		varying vec4 vUv;
		float blendOverlay( float base, float blend ) {
			return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );
		}
		vec3 blendOverlay( vec3 base, vec3 blend ) {
			return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) );
		}
		void main() {
			vec4 base = texture2DProj( tDiffuse, vUv );
			#ifdef useDepthTexture
				float op=opacity;
				float depth = texture2DProj( tDepth, vUv ).r;
				if(depth>maxDistance) discard;
				#ifdef isDistanceAttenuation
					float ratio=1.-(depth/maxDistance);
					float attenuation=ratio*ratio;
					op=opacity*attenuation;
				#endif
				#ifdef isFresnel
					op*=fresnel;
				#endif
				gl_FragColor = vec4( blendOverlay( base.rgb, color ), op );
			#else
				gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 );
			#endif
		}
	`,
};

export { Reflector };
<|endoftext|>