<commit_before>// jshint ignore: start

/*

a = canvases
b = body
s = canvas size
t = text size

I = intervals
T = tick

X, Y, Z, and _ are temporary

*/

I = 'sec min hr day wk mon yr'.split(' ');

document.documentElement.style.cssText = 'width:100%;height:100%;display:table;background-color:#333';
b.style.cssText = 'display:table-cell;vertical-align:middle;text-align:center';

t = Infinity;
a = I.map(function(unit, index) {

  _ = document.createElement('canvas');
  s = _.width = _.height = innerWidth / 7;
  b.style.fontSize = (s / 8) + 'px';

  _.u = unit;

  _.m = [1000, 60000, 3600000, 86400000, 604800000, 2629800000, 31557600000][index];

  _.e = function() {
    Z = new Date;
    switch (unit) {
      case 'yr':
        Z.setMonth(0);
      case 'mon':
        Z.setDate(1);
      case 'wk':
      case 'day':
        Z.setHours(0);
      case 'hr':
        Z.setMinutes(0);
      case 'min':
        Z.setSeconds(0);
      case 'sec':
        Z.setMilliseconds(0);
    }
    if (unit === 'wk') {
      Z.setDate(Z.getDate() - Z.getDay());
    }
    Z.setTime(Z.getTime() + this.m - 1);
    return Z;
  };

  _.c = _.getContext('2d');
  _.c.fillStyle = _.c.strokeStyle = ['#fa8072', '#faad72', '#fada72', '#acda72', '#87ceeb', '#87aceb', '#a4b7eb'][index];
  _.c.lineWidth = s * 0.01;
  _.c.textAlign = 'center';
  _.c.textBaseline = 'middle';

  b.appendChild(_);

  $ = 1;
  do {
    _.c.font = '100 ' + $ + 'px sans-serif';
    $ ++;
  } while (_.c.measureText(unit).width < (s * 0.8));
  t = Math.min(t, $);

  return _;

});

(function T() {

  a.forEach(function(canvas) {

    canvas.c.clearRect(0, 0, s, s);

    canvas.c.beginPath();
    canvas.c.moveTo(s / 2, s / 2);
    canvas.c.arc(s / 2, s / 2, s * 0.45, 0, 2 * Math.PI * ((canvas.e() - new Date) / canvas.m));
    canvas.c.fill();

    canvas.c.beginPath();
    canvas.c.moveTo(s / 2, s / 2);
    canvas.c.arc(s / 2, s / 2, s * 0.45, 0, 2 * Math.PI);
    canvas.c.stroke();

    canvas.c.font = '100 ' + t + 'px sans-serif';
    canvas.c.fillText(canvas.u, s / 2, s / 2);

  });

  requestAnimationFrame(T);

})();
<commit_msg>-7 bytes (now at 1026 characters)<commit_after>// jshint ignore: start

/*

a = canvases
b = body
s = canvas size
t = text size

I = intervals
T = tick

X, Y, Z, and _ are temporary

*/

I = 'sec min hr day wk mon yr'.split(' ');

document.documentElement.style.cssText = 'width:100%;height:100%;display:table;background:#333';
b.style.cssText = 'display:table-cell;vertical-align:middle;text-align:center';

t = Infinity;
a = I.map(function(unit, index) {

  _ = document.createElement('canvas');
  s = _.width = _.height = innerWidth / 7;
  b.style.fontSize = (s / 8) + 'px';

  _.u = unit;

  _.m = [1000, 60000, 3600000, 86400000, 604800000, 2629800000, 31557600000][index];

  _.e = function() {
    Z = new Date;
    switch (unit) {
      case 'yr':
        Z.setMonth(0);
      case 'mon':
        Z.setDate(1);
      case 'wk':
      case 'day':
        Z.setHours(0);
      case 'hr':
        Z.setMinutes(0);
      case 'min':
        Z.setSeconds(0);
      case 'sec':
        Z.setMilliseconds(0);
    }
    if (unit === 'wk') {
      Z.setDate(Z.getDate() - Z.getDay());
    }
    Z.setTime(Z.getTime() + this.m - 1);
    return Z;
  };

  _.c = _.getContext('2d');
  _.c.fillStyle = _.c.strokeStyle = ['#fa8072', '#faad72', '#fada72', '#acda72', '#87ceeb', '#87aceb', '#a4b7eb'][index];
  _.c.lineWidth = s * 0.01;
  _.c.textAlign = 'center';
  _.c.textBaseline = 'middle';

  b.appendChild(_);

  $ = 1;
  do {
    _.c.font = '100 ' + $ + 'px sans-serif';
    $ ++;
  } while (_.c.measureText(unit).width < (s * 0.8));
  t = Math.min(t, $);

  return _;

});

(function T() {

  a.forEach(function(canvas) {

    canvas.c.clearRect(0, 0, s, s);

    canvas.c.beginPath();
    canvas.c.moveTo(s / 2, s / 2);
    canvas.c.arc(s / 2, s / 2, s * 0.45, 0, 2 * Math.PI * ((canvas.e() - new Date) / canvas.m));
    canvas.c.fill();

    canvas.c.beginPath();
    canvas.c.moveTo(s / 2, s / 2);
    canvas.c.arc(s / 2, s / 2, s * 0.45, 0, 2 * Math.PI);
    canvas.c.stroke();

    canvas.c.font = '100 ' + t + 'px sans-serif';
    canvas.c.fillText(canvas.u, s / 2, s / 2);

  });

  requestAnimationFrame(T);

})();
<|endoftext|>


<commit_before>'use strict';

import React from 'react';
import {connect} from 'react-redux'
import {changeLevel, fetchService} from '../../../store/actions'

class TPTop extends React.Component {

    constructor(props) {
        super(props);
    }

    cutLevelName(levelName) {
        if (levelName === 'Undergraduate (1st and 2nd year)') {
            return 'Undergrad. (yrs 1-2)'
        } else if (levelName === 'Undergraduate (3rd and 4th year)') {
            return 'Undergrad. (yrs 3-4)'
        }
        else {
            return levelName
        }
    }

    render() {
        const {discount, level, levelList} = this.props;
        let levels = levelList.map((l) => {
            return <div key={l.id} className={`${(level === l.name) ? 'active' : ''} tp-header__level-item`}>
                {this.cutLevelName(l.name)}

        });
        const dsc = (discount === 0) ?
 :

            Your first order
            {discount * 100}% OFF
            Limited time!

;

        return (

ACADEMIC LEVEL

                    {dsc}

                    {levels}

        )
    }
}

//container to match redux state to component props and dispatch redux actions to callback props
const mapStateToProps = (reduxState, ownProps) => {
    const state = reduxState.calculatorSmall[ownProps.calcId];
    return {
        discount: reduxState.discount,
        service: state.service.name,
        serviceList: state.currentServices,
        level: state.level.name,
        levelList: state.currentLevels,
    }
};

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        changeService: (id) => {
            dispatch(fetchService(id, ownProps.calcId))
        },
        changeLevel: (id) => {
            dispatch(changeLevel(id, ownProps.calcId))
        },
    }
};

export default connect(mapStateToProps, mapDispatchToProps)(TPTop);
<commit_msg>fix title TP<commit_after>'use strict';

import React from 'react';
import {connect} from 'react-redux'
import {changeLevel, fetchService} from '../../../store/actions'

class TPTop extends React.Component {

    constructor(props) {
        super(props);
    }

    cutLevelName(levelName) {
        if (levelName === 'Undergraduate (1st and 2nd year)') {
            return 'Undergrad. (yrs 1-2)'
        } else if (levelName === 'Undergraduate (3rd and 4th year)') {
            return 'Undergrad. (yrs 3-4)'
        }
        else {
            return levelName
        }
    }

    render() {
        const {discount, level, levelList} = this.props;
        let levels = levelList.map((l) => {
            return <div key={l.id} className={`${(level === l.name) ? 'active' : ''} tp-header__level-item`}>
                {this.cutLevelName(l.name)}

        });
        const dsc = (discount === 0) ?
GET A QUICK ESTIMATE
 :

            Your first order
            {discount * 100}% OFF
            Limited time!

;

        return (

 ACADEMIC LEVEL

                    {dsc}

                    {levels}

        )
    }
}

//container to match redux state to component props and dispatch redux actions to callback props
const mapStateToProps = (reduxState, ownProps) => {
    const state = reduxState.calculatorSmall[ownProps.calcId];
    return {
        discount: reduxState.discount,
        service: state.service.name,
        serviceList: state.currentServices,
        level: state.level.name,
        levelList: state.currentLevels,
    }
};

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        changeService: (id) => {
            dispatch(fetchService(id, ownProps.calcId))
        },
        changeLevel: (id) => {
            dispatch(changeLevel(id, ownProps.calcId))
        },
    }
};

export default connect(mapStateToProps, mapDispatchToProps)(TPTop);
<|endoftext|>


<commit_before>var solrClient = require( 'solr-client' ),
	when = require( 'when' ),
	_ = require( 'lodash' ),
	http = require( 'http' ),
	SolrError = require('../node_modules/solr-client/lib/error/solr-error.js');

module.exports = function( config, riak ) {

	var queryRequest = function( params, callback ) {
		var options = {
			host: params.host,
			port: params.port,
			path: params.fullPath
		};

		if ( params.agent !== undefined ) {
			options.agent = params.agent;
		}

		if ( params.authorization ) {
			var headers = {
				'authorization': params.authorization
			};
			options.headers = headers;
		}

		var callbackResponse = function( res ) {
			var buffer = '';
			var err = null;
			res.on( 'data', function( chunk ) {
				buffer += chunk;
			} );

			res.on( 'end', function() {
				if ( res.statusCode !== 200 ) {

					err = new SolrError( res.statusCode, buffer );
					if ( callback ) callback( err, null );
				} else {
					try {
						var data = JSON.parse( buffer );
					} catch ( error ) {
						err = error;
					} finally {
						if ( callback ) callback( err, data );
					}
				}
			} );
		}

		var request = http.get( options, callbackResponse );

		request.on( 'error', function( err ) {
			if ( callback ) callback( err, null );
		} );
	}

	var Index = function( index ) {
		this.solr = solrClient.createClient( {
			host: config.get( 'RIAK_SERVER', 'ubuntu' ),
			port: config.get( 'RIAK_HTTP', 8098 ),
			core: index,
			path: '/search'
		} );

		// this is kinda terrible, but Riak's URL doesn't conform to SOLR's :|
		// I figure patching vs. rolling our own solr client is the lesser of two evils.
		this.solr.search = function( query, callback ) {
			var self = this;
			// Allow to be more flexible allow query to be a string and not only a Query object
			var parameters = query.build ? query.build() : query;
			this.options.fullPath = [ this.options.path, this.options.core, '?' + parameters + '&wt=json' ]
				.filter( function( element ) {
					if ( element ) return true;
					return false;
				} )
				.join( '/' );;
			queryRequest( this.options, callback );
			return self;
		}
	};

	Index.prototype.search = function( body, params, includeStats ) {
		return when.promise( function( resolve, reject, notify ) {
			var query = this.solr.createQuery().set( 'wt=json' ).q( body ),
				useEdis = false;

			if ( params ) {
				if ( params.start ) {
					query = query.start( params.start );
				}
				if ( params.rows ) {
					query = query.rows( params.rows );
				}
				if ( params.factors ) {
					useEdis = true;
					query = query.qf( params.factors );
				}
				if ( params.minimumMatch ) {
					useEdis = true;
					query = query.mm( params.minimumMatch );
				}
				if ( params.boost ) {
					useEdis = true;
					query = query.boost( params.boost );
				}
				if( useEdis ) {
					query = query.edismax();
				}
			}

			this.solr.search( query, function( err, result ) {
				if ( err ) {
					reject( err );
				} else {
					var matches = _.map( result.response.docs, function( d ) {
						return {
							id: [ d._yz_rb, d._yz_rk ].join( ':' ),
							bucket: d._yz_rb,
							key: d._yz_rk
						};
					} );
					matches = _.uniq( matches, function( match ) {
						return match.id
					} );
					riak.getByKeys( matches )
						.then( null, reject )
						.progress( notify )
						.done( function( searchResult ) {
							var response =  includeStats
											? { docs: searchResult,
												total:result.response.numFound,
												start: result.response.start,
												maxScore: result.response.maxScore,
												qTime: result.responseHeader.QTime
												 }
											: searchResult;
							resolve( response );
						});
				}
			}.bind( this ) );
		}.bind( this ) );
	};

	return Index;
};<commit_msg>remove unneeded de-dupe code<commit_after>var solrClient = require( 'solr-client' ),
	when = require( 'when' ),
	_ = require( 'lodash' ),
	http = require( 'http' ),
	SolrError = require('../node_modules/solr-client/lib/error/solr-error.js');

module.exports = function( config, riak ) {

	var queryRequest = function( params, callback ) {
		var options = {
			host: params.host,
			port: params.port,
			path: params.fullPath
		};

		if ( params.agent !== undefined ) {
			options.agent = params.agent;
		}

		if ( params.authorization ) {
			var headers = {
				'authorization': params.authorization
			};
			options.headers = headers;
		}

		var callbackResponse = function( res ) {
			var buffer = '';
			var err = null;
			res.on( 'data', function( chunk ) {
				buffer += chunk;
			} );

			res.on( 'end', function() {
				if ( res.statusCode !== 200 ) {

					err = new SolrError( res.statusCode, buffer );
					if ( callback ) callback( err, null );
				} else {
					try {
						var data = JSON.parse( buffer );
					} catch ( error ) {
						err = error;
					} finally {
						if ( callback ) callback( err, data );
					}
				}
			} );
		}

		var request = http.get( options, callbackResponse );

		request.on( 'error', function( err ) {
			if ( callback ) callback( err, null );
		} );
	}

	var Index = function( index ) {
		this.solr = solrClient.createClient( {
			host: config.get( 'RIAK_SERVER', 'ubuntu' ),
			port: config.get( 'RIAK_HTTP', 8098 ),
			core: index,
			path: '/search'
		} );

		// this is kinda terrible, but Riak's URL doesn't conform to SOLR's :|
		// I figure patching vs. rolling our own solr client is the lesser of two evils.
		this.solr.search = function( query, callback ) {
			var self = this;
			// Allow to be more flexible allow query to be a string and not only a Query object
			var parameters = query.build ? query.build() : query;
			this.options.fullPath = [ this.options.path, this.options.core, '?' + parameters + '&wt=json' ]
				.filter( function( element ) {
					if ( element ) return true;
					return false;
				} )
				.join( '/' );;
			queryRequest( this.options, callback );
			return self;
		}
	};

	Index.prototype.search = function( body, params, includeStats ) {
		return when.promise( function( resolve, reject, notify ) {
			var query = this.solr.createQuery().set( 'wt=json' ).q( body ),
				useEdis = false;

			if ( params ) {
				if ( params.start ) {
					query = query.start( params.start );
				}
				if ( params.rows ) {
					query = query.rows( params.rows );
				}
				if ( params.factors ) {
					useEdis = true;
					query = query.qf( params.factors );
				}
				if ( params.minimumMatch ) {
					useEdis = true;
					query = query.mm( params.minimumMatch );
				}
				if ( params.boost ) {
					useEdis = true;
					query = query.boost( params.boost );
				}
				if( useEdis ) {
					query = query.edismax();
				}
			}

			this.solr.search( query, function( err, result ) {
				if ( err ) {
					reject( err );
				} else {
					var matches = _.map( result.response.docs, function( d ) {
						return {
							id: [ d._yz_rb, d._yz_rk ].join( ':' ),
							bucket: d._yz_rb,
							key: d._yz_rk
						};
					} );

					riak.getByKeys( matches )
						.then( null, reject )
						.progress( notify )
						.done( function( searchResult ) {
							var response =  includeStats
											? { docs: searchResult,
												total:result.response.numFound,
												start: result.response.start,
												maxScore: result.response.maxScore,
												qTime: result.responseHeader.QTime
												 }
											: searchResult;
							resolve( response );
						});
				}
			}.bind( this ) );
		}.bind( this ) );
	};

	return Index;
};<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var moment = require('moment');
var request = require('request');
var nconf = require('nconf');
nconf.argv();

var token = nconf.get('token') || process.env.OANDA_TOKEN;
var time = nconf.get('time');
if(time && moment().isAfter(moment(time))) {
	time = moment(time).utc().format();
} else {
	time = '';
}
var margin = parseFloat(nconf.get('margin')) || 1.0;
var target = parseFloat(nconf.get('target')) || 0.0075;

var data = nconf.get('csv') || './report.csv';

// Create the parser
var parser = require('csv-parse')();

var results = {};
var wagers = [];
var rankBase = {};
var rankTotal = {usd:0, gbp:0, aud:0, eur:0, nzd:0, chf:0, cad:0, jpy:0};

// Use the writable stream api
parser.on('readable', function(){
	var cursor;
	var record;
	var chance;
	var odds;
	var meetsCriterion;
	var currency;
	var position;

  while(record = parser.read()){
	  currency = record[0].toLowerCase();
	  position = record[1];
		chance = parseFloat(record[2]) || 0.0;
		odds = parseFloat(record[3]) || 0.0;
		meetsCriterion = record[4] === 'true';

		if(!results[currency]) {
			results[currency] = {
				hold: {
					wager: 0.0,
					total: 0
				},
				long: {
					wager: 0.0,
					total: 0
				},
				short: {
					wager: 0.0,
					total: 0
				}
			};
		}

		if(chance > 0.0 && odds > 0.0) {
			if(meetsCriterion) {
				results[currency][position]['chance'] = (results[currency][position]['chance'] || 0.0) + chance;
				results[currency][position]['odds'] = (results[currency][position]['odds'] || 0.0) + odds;

				results[currency][position]['total'] = (results[currency][position]['total'] || 0) + 1.0;
			}
		}
  }
});
// Catch any error
parser.on('error', function(err){
  console.error(err.message);
});
// When we are done, test that the parsed output matched what expected
parser.on('finish', function(){
	var currencies = _.keys(results);
	var positions = ['hold', 'long', 'short'];
	var currency, position;
	var chance = 0.0;
	var odds = 0.0;
	var wager;
	var netWager;
	var total;
	var pair;
	var multiplier;

	// console.error('results:', JSON.stringify(results, null, '\t'));

	for(var i = 0; i< currencies.length; i++) {
		currency = currencies[i];
		// console.error('-------', currency,'-------');

		total = 0.0;
		for(var j=0; j < positions.length; j++) {
			position = positions[j];
			odds = 0.0;

			if(results[currency][position]['total']) {
				chance = results[currency][position]['chance'] / results[currency][position]['total'];
				odds = (results[currency][position]['odds'] / results[currency][position]['total']) - 1.0;
			}

			if(odds > 0.0) {
				wager = chance - ((1.0 - chance) / odds);
				results[currency][position]['wager'] = wager;

				// console.error(position, ':', (wager * 100.0) + '%');
			} else {
				results[currency][position]['wager'] = 0.0;
			}

			total = total + results[currency][position]['wager'];
		}

		netWager =  results[currency]['long']['wager'] - results[currency]['short']['wager'];
		multiplier = netWager > 0.0 ? 1.0 : -1.0;

		pair = getPair(currency);

		rankBase[pair.base] = (rankBase[pair.base] || 0.0) + netWager;
		rankBase[pair.other] = (rankBase[pair.other] || 0.0) - netWager;
		rankTotal[pair.base]++;
		rankTotal[pair.other]++;

		// if(results[currency]['hold']['wager'] < results[currency][netWager > 0.0 ? 'long' : 'short']['wager'])
		wagers.push({currency: currency, wager: netWager});
	}

	var rankings = {};
	_.each(_.keys(rankBase), function(key) {
		rankings[key] = 100.0 * (rankBase[key] || 0.0) / rankTotal[key];
	});

	_.each(_.keys(results), function (currencyPair) {
		var newPair = getPair(currencyPair);

		var sum = rankings[newPair.base] - rankings[newPair.other];

		switch(currencyPair) {
			case 'audjpy':
			case 'audusd':
			case 'euraud':
			case 'eurchf':
			case 'eurgbp':
			case 'eurjpy':
			case 'eurusd':
			case 'gbpchf':
			case 'gbpjpy':
			case 'gbpusd':
			case 'nzdusd':
			case 'usdcad':
			case 'usdchf':
			case 'usdjpy':
				fetchOrders(currencyPair, time, function(orderResult) {
					fetchOpenPositions(currencyPair, time, function(openPositionsResult) {
						console.error('open positions:', openPositionsResult);
						console.error('orders:', orderResult);

						var bullish = openPositionsResult.bullish && orderResult.bullish
							? 'yes'
							: openPositionsResult.bullish || orderResult.bullish
								? 'neutral'
								: 'no'
						var sl = sum > 0.0
 							? Math.min(orderResult.bid, openPositionsResult.bid)
							: Math.max(orderResult.ask, openPositionsResult.ask);
						var el = sum > 0.0
							? (orderResult.bid + openPositionsResult.ask) / 2.0
							: (orderResult.ask + openPositionsResult.bid) / 2.0;
						var risk = el - sl;
						var tp1 = el + risk;
						var tp2 = sum > 0.0
							? orderResult.ask
							: orderResult.bid;

						console.log(
							sum,
							currencyPair,
							'bullish:'+bullish,
							'risk:'+Math.abs(risk),
							'sl:'+sl, 'el:'+el, 'tp1:'+tp1, 'tp2:'+tp2
						);
					});
				});
				break;
			default:
		};
	});
});

var lineReader = require('readline').createInterface({
  input: require('fs').createReadStream(data)
});

var lineCount = 0;
lineReader.on('line', function (line) {
	if(lineCount) {
		parser.write(line + '\n');
	}

	lineCount++;
});

lineReader.on('close', function() {
	parser.end();
})

function getPair(currency) {
	var base = currency.substring(0, 3);
	var other = currency.substring(3, 6);

	var pair = {
		base: base,
		other: other
	};

	return pair;
}

var currencyPairMapping = {
	audjpy: 'AUD_JPY',
	audusd: 'AUD_USD',
	euraud: 'EUR_AUD',
	eurchf: 'EUR_CHF',
	eurgbp: 'EUR_GBP',
	eurjpy: 'EUR_JPY',
	eurusd: 'EUR_USD',
	gbpchf: 'GBP_CHF',
	gbpjpy: 'GBP_JPY',
	gbpusd: 'GBP_USD',
	nzdusd: 'NZD_USD',
	usdcad: 'USD_CAD',
	usdchf: 'USD_CHF',
	usdjpy: 'USD_JPY'
};

function fetchOrders(currencyPair, time, done) {
	var instrument = currencyPairMapping[currencyPair];
	var requestUrl = 'https://api-fxpractice.oanda.com/v3/instruments/' + instrument + '/orderBook';

	if(time) {
		requestUrl = requestUrl + '?time=' + time;
	}

	var requestOpts = {
		url: requestUrl,
		gzip: true,
		headers: {
			'Authorization': 'Bearer ' + token
		}
	};

	var results = {};

	request(requestOpts, function(error, response, body) {
		var data = JSON.parse(body);
		if(error || _.has(data, 'errorMessage')) {
			return console.error('error:', error || data.errorMessage, requestUrl);
		}

		var rate = parseFloat(data.orderBook.price);
		var sum = { bid: 0.0, ask: 0.0 };
		var total = { bid: 0, ask: 0 };

		_.each(_.sortBy(data.orderBook.buckets, 'price'), function(pricePoint) {
			pricePoint = _.reduce(_.keys(pricePoint), function(acc, key) {
				acc[key] = parseFloat(pricePoint[key]);

				return acc;
			}, {});

			var net = (pricePoint.longCountPercent - pricePoint.shortCountPercent);
			var distOrig = pricePoint.price - rate;
			var dist = Math.abs(distOrig);

			if(dist < margin * rate) {
				if(distOrig < 0.0) {
						sum.bid = sum.bid + (net * dist);
						total.bid = total.bid + Math.abs(net);
				} else {
					sum.ask = sum.ask + (net * dist);
					total.ask = total.ask + Math.abs(net);
				}
			}
		});

		var bid = rate - Math.abs(sum.bid / total.bid);
		var ask = rate + Math.abs(sum.ask / total.ask);
		var average = (bid + ask) / 2.0;
		var bullish = (sum.bid / (Math.abs(sum.bid) + Math.abs(sum.ask))) > 0.5;

		done({
			instrument: instrument,
			ask: ask,
			rate: rate,
			bid: bid,
			average: average,
			bullish: bullish
		});
	});
}

function fetchOpenPositions(currencyPair, time, done) {
	var instrument = currencyPairMapping[currencyPair];
	var requestUrl = 'https://api-fxpractice.oanda.com/v3/instruments/' + instrument + '/positionBook';

	if(time) {
		requestUrl = requestUrl + '?time=' + time;
	}

	var requestOpts = {
		url: requestUrl,
		gzip: true,
		headers: {
			'Authorization': 'Bearer ' + token
		}
	};

	var results = {};

	request(requestOpts, function(error, response, body) {
		var data = JSON.parse(body);
		if(error || _.has(data, 'errorMessage')) {
			return console.error('error:', error || data.errorMessage, requestUrl);
		}

		var rate = parseFloat(data.positionBook.price);
		var sum = { bid: 0.0, ask: 0.0 };
		var total = { bid: 0, ask: 0 };

		_.each(_.sortBy(data.positionBook.buckets, 'price'), function(pricePoint) {
			pricePoint = _.reduce(_.keys(pricePoint), function(acc, key) {
				acc[key] = parseFloat(pricePoint[key]);

				return acc;
			}, {});

			var d = Math.abs(pricePoint.price - rate);

			if(d/rate <= margin) {
				if(pricePoint.longCountPercent) {
					sum.ask = sum.ask + (pricePoint.longCountPercent * pricePoint.price);
					total.ask = total.ask + pricePoint.longCountPercent;
				}

				if(pricePoint.shortCountPercent) {
					//console.error(pricePoint);
					sum.bid = sum.bid + (pricePoint.shortCountPercent * pricePoint.price);
					total.bid = total.bid + pricePoint.shortCountPercent;
				}
			}
		});

		var bid = sum.bid / total.bid;
		var ask = sum.ask / total.ask;
		var average = (bid + ask) / 2.0;
		var bullish = average < rate;

		done({
			instrument: instrument,
			ask: ask,
			rate: rate,
			bid: bid,
			average: average,
			bullish: bullish
		});
	});
}
<commit_msg>fix: percentize risk<commit_after>'use strict';

var _ = require('lodash');
var moment = require('moment');
var request = require('request');
var nconf = require('nconf');
nconf.argv();

var token = nconf.get('token') || process.env.OANDA_TOKEN;
var time = nconf.get('time');
if(time && moment().isAfter(moment(time))) {
	time = moment(time).utc().format();
} else {
	time = '';
}
var margin = parseFloat(nconf.get('margin')) || 1.0;
var target = parseFloat(nconf.get('target')) || 0.0075;

var data = nconf.get('csv') || './report.csv';

// Create the parser
var parser = require('csv-parse')();

var results = {};
var wagers = [];
var rankBase = {};
var rankTotal = {usd:0, gbp:0, aud:0, eur:0, nzd:0, chf:0, cad:0, jpy:0};

// Use the writable stream api
parser.on('readable', function(){
	var cursor;
	var record;
	var chance;
	var odds;
	var meetsCriterion;
	var currency;
	var position;

  while(record = parser.read()){
	  currency = record[0].toLowerCase();
	  position = record[1];
		chance = parseFloat(record[2]) || 0.0;
		odds = parseFloat(record[3]) || 0.0;
		meetsCriterion = record[4] === 'true';

		if(!results[currency]) {
			results[currency] = {
				hold: {
					wager: 0.0,
					total: 0
				},
				long: {
					wager: 0.0,
					total: 0
				},
				short: {
					wager: 0.0,
					total: 0
				}
			};
		}

		if(chance > 0.0 && odds > 0.0) {
			if(meetsCriterion) {
				results[currency][position]['chance'] = (results[currency][position]['chance'] || 0.0) + chance;
				results[currency][position]['odds'] = (results[currency][position]['odds'] || 0.0) + odds;

				results[currency][position]['total'] = (results[currency][position]['total'] || 0) + 1.0;
			}
		}
  }
});
// Catch any error
parser.on('error', function(err){
  console.error(err.message);
});
// When we are done, test that the parsed output matched what expected
parser.on('finish', function(){
	var currencies = _.keys(results);
	var positions = ['hold', 'long', 'short'];
	var currency, position;
	var chance = 0.0;
	var odds = 0.0;
	var wager;
	var netWager;
	var total;
	var pair;
	var multiplier;

	// console.error('results:', JSON.stringify(results, null, '\t'));

	for(var i = 0; i< currencies.length; i++) {
		currency = currencies[i];
		// console.error('-------', currency,'-------');

		total = 0.0;
		for(var j=0; j < positions.length; j++) {
			position = positions[j];
			odds = 0.0;

			if(results[currency][position]['total']) {
				chance = results[currency][position]['chance'] / results[currency][position]['total'];
				odds = (results[currency][position]['odds'] / results[currency][position]['total']) - 1.0;
			}

			if(odds > 0.0) {
				wager = chance - ((1.0 - chance) / odds);
				results[currency][position]['wager'] = wager;

				// console.error(position, ':', (wager * 100.0) + '%');
			} else {
				results[currency][position]['wager'] = 0.0;
			}

			total = total + results[currency][position]['wager'];
		}

		netWager =  results[currency]['long']['wager'] - results[currency]['short']['wager'];
		multiplier = netWager > 0.0 ? 1.0 : -1.0;

		pair = getPair(currency);

		rankBase[pair.base] = (rankBase[pair.base] || 0.0) + netWager;
		rankBase[pair.other] = (rankBase[pair.other] || 0.0) - netWager;
		rankTotal[pair.base]++;
		rankTotal[pair.other]++;

		// if(results[currency]['hold']['wager'] < results[currency][netWager > 0.0 ? 'long' : 'short']['wager'])
		wagers.push({currency: currency, wager: netWager});
	}

	var rankings = {};
	_.each(_.keys(rankBase), function(key) {
		rankings[key] = 100.0 * (rankBase[key] || 0.0) / rankTotal[key];
	});

	_.each(_.keys(results), function (currencyPair) {
		var newPair = getPair(currencyPair);

		var sum = rankings[newPair.base] - rankings[newPair.other];

		switch(currencyPair) {
			case 'audjpy':
			case 'audusd':
			case 'euraud':
			case 'eurchf':
			case 'eurgbp':
			case 'eurjpy':
			case 'eurusd':
			case 'gbpchf':
			case 'gbpjpy':
			case 'gbpusd':
			case 'nzdusd':
			case 'usdcad':
			case 'usdchf':
			case 'usdjpy':
				fetchOrders(currencyPair, time, function(orderResult) {
					fetchOpenPositions(currencyPair, time, function(openPositionsResult) {
						console.error('open positions:', openPositionsResult);
						console.error('orders:', orderResult);

						var bullish = openPositionsResult.bullish && orderResult.bullish
							? 'yes'
							: openPositionsResult.bullish || orderResult.bullish
								? 'neutral'
								: 'no'
						var sl = sum > 0.0
 							? Math.min(orderResult.bid, openPositionsResult.bid)
							: Math.max(orderResult.ask, openPositionsResult.ask);
						var el = sum > 0.0
							? (orderResult.bid + openPositionsResult.ask) / 2.0
							: (orderResult.ask + openPositionsResult.bid) / 2.0;
						var risk = el - sl;
						var tp1 = el + risk;
						var tp2 = sum > 0.0
							? orderResult.ask
							: orderResult.bid;

						console.log(
							sum,
							currencyPair,
							'bullish:'+bullish,
							'risk:'+(Math.abs(risk) * 100.0 / openPositionsResult.rate),
							'sl:'+sl, 'el:'+el, 'tp1:'+tp1, 'tp2:'+tp2
						);
					});
				});
				break;
			default:
		};
	});
});

var lineReader = require('readline').createInterface({
  input: require('fs').createReadStream(data)
});

var lineCount = 0;
lineReader.on('line', function (line) {
	if(lineCount) {
		parser.write(line + '\n');
	}

	lineCount++;
});

lineReader.on('close', function() {
	parser.end();
})

function getPair(currency) {
	var base = currency.substring(0, 3);
	var other = currency.substring(3, 6);

	var pair = {
		base: base,
		other: other
	};

	return pair;
}

var currencyPairMapping = {
	audjpy: 'AUD_JPY',
	audusd: 'AUD_USD',
	euraud: 'EUR_AUD',
	eurchf: 'EUR_CHF',
	eurgbp: 'EUR_GBP',
	eurjpy: 'EUR_JPY',
	eurusd: 'EUR_USD',
	gbpchf: 'GBP_CHF',
	gbpjpy: 'GBP_JPY',
	gbpusd: 'GBP_USD',
	nzdusd: 'NZD_USD',
	usdcad: 'USD_CAD',
	usdchf: 'USD_CHF',
	usdjpy: 'USD_JPY'
};

function fetchOrders(currencyPair, time, done) {
	var instrument = currencyPairMapping[currencyPair];
	var requestUrl = 'https://api-fxpractice.oanda.com/v3/instruments/' + instrument + '/orderBook';

	if(time) {
		requestUrl = requestUrl + '?time=' + time;
	}

	var requestOpts = {
		url: requestUrl,
		gzip: true,
		headers: {
			'Authorization': 'Bearer ' + token
		}
	};

	var results = {};

	request(requestOpts, function(error, response, body) {
		var data = JSON.parse(body);
		if(error || _.has(data, 'errorMessage')) {
			return console.error('error:', error || data.errorMessage, requestUrl);
		}

		var rate = parseFloat(data.orderBook.price);
		var sum = { bid: 0.0, ask: 0.0 };
		var total = { bid: 0, ask: 0 };

		_.each(_.sortBy(data.orderBook.buckets, 'price'), function(pricePoint) {
			pricePoint = _.reduce(_.keys(pricePoint), function(acc, key) {
				acc[key] = parseFloat(pricePoint[key]);

				return acc;
			}, {});

			var net = (pricePoint.longCountPercent - pricePoint.shortCountPercent);
			var distOrig = pricePoint.price - rate;
			var dist = Math.abs(distOrig);

			if(dist < margin * rate) {
				if(distOrig < 0.0) {
						sum.bid = sum.bid + (net * dist);
						total.bid = total.bid + Math.abs(net);
				} else {
					sum.ask = sum.ask + (net * dist);
					total.ask = total.ask + Math.abs(net);
				}
			}
		});

		var bid = rate - Math.abs(sum.bid / total.bid);
		var ask = rate + Math.abs(sum.ask / total.ask);
		var average = (bid + ask) / 2.0;
		var bullish = (sum.bid / (Math.abs(sum.bid) + Math.abs(sum.ask))) > 0.5;

		done({
			instrument: instrument,
			ask: ask,
			rate: rate,
			bid: bid,
			average: average,
			bullish: bullish
		});
	});
}

function fetchOpenPositions(currencyPair, time, done) {
	var instrument = currencyPairMapping[currencyPair];
	var requestUrl = 'https://api-fxpractice.oanda.com/v3/instruments/' + instrument + '/positionBook';

	if(time) {
		requestUrl = requestUrl + '?time=' + time;
	}

	var requestOpts = {
		url: requestUrl,
		gzip: true,
		headers: {
			'Authorization': 'Bearer ' + token
		}
	};

	var results = {};

	request(requestOpts, function(error, response, body) {
		var data = JSON.parse(body);
		if(error || _.has(data, 'errorMessage')) {
			return console.error('error:', error || data.errorMessage, requestUrl);
		}

		var rate = parseFloat(data.positionBook.price);
		var sum = { bid: 0.0, ask: 0.0 };
		var total = { bid: 0, ask: 0 };

		_.each(_.sortBy(data.positionBook.buckets, 'price'), function(pricePoint) {
			pricePoint = _.reduce(_.keys(pricePoint), function(acc, key) {
				acc[key] = parseFloat(pricePoint[key]);

				return acc;
			}, {});

			var d = Math.abs(pricePoint.price - rate);

			if(d/rate <= margin) {
				if(pricePoint.longCountPercent) {
					sum.ask = sum.ask + (pricePoint.longCountPercent * pricePoint.price);
					total.ask = total.ask + pricePoint.longCountPercent;
				}

				if(pricePoint.shortCountPercent) {
					//console.error(pricePoint);
					sum.bid = sum.bid + (pricePoint.shortCountPercent * pricePoint.price);
					total.bid = total.bid + pricePoint.shortCountPercent;
				}
			}
		});

		var bid = sum.bid / total.bid;
		var ask = sum.ask / total.ask;
		var average = (bid + ask) / 2.0;
		var bullish = average < rate;

		done({
			instrument: instrument,
			ask: ask,
			rate: rate,
			bid: bid,
			average: average,
			bullish: bullish
		});
	});
}
<|endoftext|>


<commit_before>var router = getRouter();
var app = getApp();
var User = require('../users/model');
var Url = require('./model');

/**
* Create a new shortened URL
* POST /urls
* Auth -> admin, staff
*/
router.post('/urls', User.auth('admin', 'staff'), function (req, res) {
  var errors = Url.validate(req.body);
  if (errors.length) return res.multiError(errors);
  var url = new Url(req.body);
  url.save(function (err, url) {
    if (err) return res.singleError('The URL must be unique');
    return res.json(url);
  });
});

/**
* Resolve a shortened URL
* GET /urls/go/:short
*/
router.get('/urls/go/:url', function (req, res) {
  Url
    .findOne({short: req.params.url})
    .exec(function (err, url) {
      if (err) return res.status(404).send();
      return res.redirect(url.full);
    });
});

/**
* Get a single URL
* GET /urls/:id
* Auth -> admin, staff
*/
router.get('/urls/:id', function (req, res) {
  Url
    .findById(req.params.id)
    .exec(function (err, url) {
      if (err) return res.internalError();
      return res.json(url);
    });
});

/**
* Get a list of URLs
* GET /urls
* Auth -> admin, staff
*/
router.get('/urls', User.auth('admin', 'staff'), function (req, res) {
  Url
    .find()
    .exec(function (err, urls) {
      if (err) return res.internalError();
      return res.json({urls: urls});
    });
});

/**
* Delete a URL
* DELETE /urls/:id
* Auth -> admin, staff
*/
router.delete('/urls/:id', User.auth('admin', 'staff'), function (req, res) {
  Url
    .findByIdAndRemove(req.params.id)
    .exec(function (err, url) {
      if (err) return res.internalError();
      return res.json({
        _id: url._id
      });
    });
});<commit_msg>Add sockets to urls<commit_after>var router = getRouter();
var socket = rootRequire('app/helpers/socket');
var io = socket('/urls', ['admin', 'staff']);
var User = require('../users/model');
var Url = require('./model');

/**
* Create a new shortened URL
* POST /urls
* Auth -> admin, staff
*/
router.post('/urls', User.auth('admin', 'staff'), function (req, res) {
  var errors = Url.validate(req.body);
  if (errors.length) return res.multiError(errors);
  var url = new Url(req.body);
  url.save(function (err, url) {
    if (err) return res.singleError('The URL must be unique');
    io.emit('create', url);
    return res.json(url);
  });
});

/**
* Resolve a shortened URL
* GET /urls/go/:short
*/
router.get('/urls/go/:url', function (req, res) {
  Url
    .findOne({short: req.params.url})
    .exec(function (err, url) {
      if (err) return res.status(404).send();
      return res.redirect(url.full);
    });
});

/**
* Get a single URL
* GET /urls/:id
* Auth -> admin, staff
*/
router.get('/urls/:id', function (req, res) {
  Url
    .findById(req.params.id)
    .exec(function (err, url) {
      if (err) return res.internalError();
      return res.json(url);
    });
});

/**
* Get a list of URLs
* GET /urls
* Auth -> admin, staff
*/
router.get('/urls', User.auth('admin', 'staff'), function (req, res) {
  Url
    .find()
    .exec(function (err, urls) {
      if (err) return res.internalError();
      return res.json({urls: urls});
    });
});

/**
* Delete a URL
* DELETE /urls/:id
* Auth -> admin, staff
*/
router.delete('/urls/:id', User.auth('admin', 'staff'), function (req, res) {
  Url
    .findByIdAndRemove(req.params.id)
    .exec(function (err, url) {
      if (err) return res.internalError();
      var response = {
        _id: url._id
      };
      io.emit('delete', response);
      return res.json(response);
    });
});<|endoftext|>


<commit_before>/*
Copyright 2017 Vector Creations Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require('react');

var MatrixClientPeg = require("../../../MatrixClientPeg");
var ContentRepo = require("matrix-js-sdk").ContentRepo;
import { _t, _tJsx } from '../../../languageHandler';
import sdk from '../../../index';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';

module.exports = React.createClass({
    displayName: 'RoomAvatarEvent',

    propTypes: {
        /* the MatrixEvent to show */
        mxEvent: React.PropTypes.object.isRequired,
    },

    onAvatarClick: function(name) {
        var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(this.props.mxEvent.getContent().url);
        var ImageView = sdk.getComponent("elements.ImageView");
        var params = {
            src: httpUrl,
            name: name,
        };
        Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
    },

    render: function() {
        var ev = this.props.mxEvent;
        var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
        var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");

        var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
        var name = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', {
                senderDisplayName: senderDisplayName,
                roomName: room ? room.name : '',
        });

        if (!ev.getContent().url || ev.getContent().url.trim().length === 0) {
            return (

                    { _t('%(senderDisplayName)s removed the room avatar.', {senderDisplayName: senderDisplayName}) }

            );
        }

        var url = ContentRepo.getHttpUriForMxc(
                    MatrixClientPeg.get().getHomeserverUrl(),
                    ev.getContent().url,
                    14 * window.devicePixelRatio,
                    14 * window.devicePixelRatio,
                    'crop'
                );

        // it sucks that _tJsx doesn't support normal _t substitutions :((
        return (

                { _tJsx('$senderDisplayName changed the room avatar to ',
                         [
                            /\$senderDisplayName/,
                            //,
                         ],
                         [
                            (sub) => senderDisplayName,
                            (sub) =>
                                <AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar"
                                                  onClick={ this.onAvatarClick.bind(this, name) }>
                                    <BaseAvatar width={14} height={14} url={ url }
                                                name={ name } />
                                </AccessibleButton>,
                         ]
                    )
                }

        );
    },
});
<commit_msg>switch to imports<commit_after>/*
Copyright 2017 Vector Creations Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { ContentRepo } from 'matrix-js-sdk';
import { _t, _tJsx } from '../../../languageHandler';
import sdk from '../../../index';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';

module.exports = React.createClass({
    displayName: 'RoomAvatarEvent',

    propTypes: {
        /* the MatrixEvent to show */
        mxEvent: React.PropTypes.object.isRequired,
    },

    onAvatarClick: function(name) {
        var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(this.props.mxEvent.getContent().url);
        var ImageView = sdk.getComponent("elements.ImageView");
        var params = {
            src: httpUrl,
            name: name,
        };
        Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
    },

    render: function() {
        var ev = this.props.mxEvent;
        var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
        var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");

        var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
        var name = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', {
                senderDisplayName: senderDisplayName,
                roomName: room ? room.name : '',
        });

        if (!ev.getContent().url || ev.getContent().url.trim().length === 0) {
            return (

                    { _t('%(senderDisplayName)s removed the room avatar.', {senderDisplayName: senderDisplayName}) }

            );
        }

        var url = ContentRepo.getHttpUriForMxc(
                    MatrixClientPeg.get().getHomeserverUrl(),
                    ev.getContent().url,
                    14 * window.devicePixelRatio,
                    14 * window.devicePixelRatio,
                    'crop'
                );

        // it sucks that _tJsx doesn't support normal _t substitutions :((
        return (

                { _tJsx('$senderDisplayName changed the room avatar to ',
                         [
                            /\$senderDisplayName/,
                            //,
                         ],
                         [
                            (sub) => senderDisplayName,
                            (sub) =>
                                <AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar"
                                                  onClick={ this.onAvatarClick.bind(this, name) }>
                                    <BaseAvatar width={14} height={14} url={ url }
                                                name={ name } />
                                </AccessibleButton>,
                         ]
                    )
                }

        );
    },
});
<|endoftext|>


<commit_before>/*
options = {
    onPlayerConnect: function()
    onPlayerDisconnect: function (),
    onPlayerReconnect: function (),
    onPlayerUpdate: function (state),
    onEntityUpdate: function (state),
    onPlayerRemove: function (id),
    onGameState: function (state),
    onPing: function (pingDelay),
    onUpdatePlayerPhysics: function (id, state, inputs, deltaTime),
    onUpdate: function (),
    onInterpolation: function(id, previousState, targetState, amount)
    logging: true,
    clientSidePrediction: true,
    interpolation: true,
    interpolationDelay: 100,
    pingInterval: 2000
}
*/

window.GarageServerIO = (function (window, socketio) {

    function StateController() {
        this.state = {};
        this.clientTime;
        this.renderTime;
        this.physicsDelta;
        this.physicsIntervalId;
        this.currentTime;
        this.accumulator = 0.0;
        this.playerId;
        this.pingDelay = 100;
        this.interpolationDelay = 100;
    }
    StateController.prototype = {
        setTime: function (serverTime) {
            this.clientTime = serverTime;
            this.renderTime = this.clientTime - this.interpolationDelay;
        },
        accumulate: function () {
            var newTime = new Date().getTime(),
                frameTime = newTime - this.currentTime;
            if (frameTime  > 250) {
                frameTime = 250;
            }
            this.currentTime = newTime;
            this.accumulator += frameTime;
        }
    };

    function Input(input, seq) {
        this.input = input;
        this.seq = seq;
    }

    function InputController() {
        this.inputs = [];
        this.sequenceNumber = 1;
    }
    InputController.prototype = {
        any: function () {
            return this.inputs.length > 0;
        },
        addInput: function (input) {
            this.sequenceNumber += 1;
            this.inputs.push(new Input(input, this.sequenceNumber));
        },
        removeUpToSequence: function (seq) {
            for (var i = 0; i < this.inputs.length; i ++) {
                if (this.inputs[i].seq === seq) {
                    this.inputs.splice(0, i + 1);
                    break;
                }
            }
        }
    };

    function Update(state, seq, time) {
        this.state = state;
        this.seq = seq;
        this.time = time;
    }

    function Entity() {
        this.updates = [];
        this.id;
        this.currentState = {};
    }
    Entity.prototype = {
        anyUpdates: function () {
            return this.updates.length > 0;
        },
        addUpate: function (state, seq, time) {
            var newUpdate = new Update(state, seq, time);
            if (this.updates.length === 0) {
                this.currentState = newUpdate.state;
            }
            this.updates.push(newUpdate);
            if (this.updates.length > 120) {
                this.updates.splice(0, 1);
            }
        },
        getLatestUpdate: function () {
            return this.updates[this.updates.length - 1];
        },
        processState: function (state, seq, time) {
            var updateFound = false;
            this.updates.some(function (update) {
               if (update.seq === seq) {
                   update.state = state;
                   updateFound = true;
                   return true;
               }
            });
            if (!updateFound) {
                this.addUpate(state, seq, time);
            }
        },
        getSurroundingPositions: function (time) {
            var positions = {};
            for (var i = 0; i < this.updates.length; i ++) {
                var previous = this.updates[i];
                var target = this.updates[i + 1];

                if(previous && target && time > previous.time && time < target.time) {
                    positions.previous = previous;
                    positions.target = target;
                    break;
                }
            }
            return positions;
        }
    };

    function Player(id) {
        Entity.call(this);
        this.id = id;
    }
    Player.prototype = Object.create(Entity.prototype);

    function PlayerController() {
        this.players = [];
    }
    PlayerController.prototype = {
        addPlayer: function (id) {
            var player = new Player(id);
            this.players.push(player);
            return player;
        },
        removePlayer: function (id) {
            for (var i = 0; i < this.players.length; i ++) {
                this.players.splice(i, 1);
                return;
            }
        }
    };

    var _io = socketio,
        _socket = null,
        _options = null,
        _stateController = new StateController(),
        _inputController = new InputController(),
        _playerController = new PlayerController(),

        initializeGarageServer = function (path, opts) {
            _options = opts;
            _socket = _io.connect(path + '/garageserver.io');
            registerSocketEvents();
            registerPinger();
        },

        registerSocketEvents = function () {
            _socket.on('connect', function () {
                _stateController.playerId = _socket.id;
                _stateController.interpolationDelay = _options.interpolationDelay ? _options.interpolationDelay : 100;
                if (_options.onPlayerConnect) {
                    _options.onPlayerConnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket connect');
                }
            });
            _socket.on('state', function(data) {
                if (_options.onGameState) {
                    _options.onGameState(data);
                }
                _stateController.physicsDelta = data.physicsDelta;
            });
            _socket.on('disconnect', function () {
                if (_options.onPlayerDisconnect) {
                    _options.onPlayerDisconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket disconnect');
                }
            });
            _socket.on('reconnect', function () {
                if (_options.onPlayerReconnect) {
                    _options.onPlayerReconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket reconnect');
                }
            });
            _socket.on('update', function(data) {
                updateState(data);
            });
            _socket.on('ping', function(data) {
                _stateController.pingDelay = new Date().getTime() - data;
                if (_options.onPing) {
                    _options.onPing(_stateController.pingDelay);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket ping delay ' + _stateController.pingDelay);
                }
            });
            _socket.on('removePlayer', function(id) {
                removePlayer(id);
                if (_options.onPlayerRemove) {
                    _options.onPlayerRemove(id);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket removePlayer ' + id);
                }
            });
        },

        registerPinger = function () {
            var interval = 2000;
            if (_options.pingInterval) {
                interval = _options.pingInterval;
            }
            setInterval(function (){
                _socket.emit('ping', new Date().getTime());
            }, interval);
        },

        start = function () {
            var self = this;
            _stateController.currentTime = new Date().getTime();
            _stateController.physicsIntervalId = setInterval(function () { self.update(); }, this.physicsDelta * 1000);
        },

        getPlayerId = function () {
            return _stateController.playerId;
        },

        setPlayerState = function (state) {
            _socket.emit('playerState', state);
        },

        removePlayer = function (id) {
            _playerController.removePlayer(id);
        },

        update = function () {
            if (_options.onUpdate) {
                _stateController.accumulate();
                while (_stateController.accumulator >= (_stateController.physicsDelta * 1000))
                {
                    _options.onUpdate();
                    _stateController.accumulator -= (_stateController.physicsDelta * 1000);
                }
            }
        },

        addPlayerInput = function (clientInput) {
            _inputController.addInput(clientInput);
            if (_options.clientSidePrediction && _options.onUpdatePlayerPhysics) {
                _stateController.state = _options.onUpdatePlayerPhysics(_stateController.playerId, _stateController.state, [{ input: clientInput }], _stateController.physicsDelta);
            }
            _socket.emit('input', [ clientInput, _inputController.sequenceNumber, _stateController.renderTime ]);
        },

        updateState = function (data) {
            _stateController.setTime(data.time);

            updatePlayersState(data);
            updateEntitiesState(data);
        },

        updatePlayersState = function (data) {
            data.playerStates.forEach(function (playerState) {
                if (_socket.socket.sessionid === playerState[0]) {
                    updatePlayerState(playerState);
                } else {
                    updateOtherPlayersState(playerState, data.time);
                }

                if (_options.onPlayerUpdate) {
                    _options.onPlayerUpdate(playerState[1]);
                }
            });
        },

        updatePlayerState = function (playerState) {
            _stateController.state = playerState[1];
            _inputController.removeUpToSequence(playerState[2]);

            if (_options.clientSidePrediction && _inputController.any()) {
                _stateController.state = _options.onUpdatePlayerPhysics(_stateController.playerId, _stateController.state, _inputController.inputs, _stateController.physicsDelta);
            }
        },

        updateOtherPlayersState = function (playerState, time) {
            var playerFound = false;
            _playerController.players.some(function (player) {
                if (player.id === playerState[0]) {
                    playerFound = true;
                    player.processState(playerState[1], playerState[2], time);
                    return true;
                }
            });
            if (!playerFound) {
                var newPlayer = _playerController.addPlayer(playerState[0]);
                newPlayer.addUpate(playerState[1], playerState[2], time);
            }
        },

        updateEntitiesState = function (data) {
            data.entityStates.forEach(function (entityState) {

                if (_options.onEntityUpdate) {
                    _options.onEntityUpdate(entityState[1]);
                }
            });
        },

        getPlayerStates = function (stateCallback) {
            if(_options.interpolation && _options.onInterpolation) {
                getPlayerStatesInterpolated(stateCallback);
            }
            else {
                getPlayerStatesCurrent(stateCallback);
            }
            stateCallback(_stateController.state);
        },

        getPlayerStatesCurrent = function (stateCallback) {
            _playerController.players.forEach(function (player) {
                if (player.anyUpdates()) {
                    stateCallback(player.getLatestUpdate().state);
                }
            });
        },

        getPlayerStatesInterpolated = function (stateCallback) {
            var positions, amount, newState;
            _playerController.players.forEach(function (player) {
                if (player.anyUpdates()) {
                    positions = player.getSurroundingPositions(_stateController.renderTime);
                    if (positions.previous && positions.target) {
                        amount = getInterpolatedAmount(positions.previous.time, positions.target.time);
                        newState = _options.onInterpolation(player.id, positions.previous.state, positions.target.state, amount);
                        player.currentState = newState = _options.onInterpolation(player.id, player.currentState, newState, _stateController.physicsDelta * 20);
                        stateCallback(player.currentState);
                    }
                    else {
                        stateCallback(player.currentState);
                    }
                }
            });
        },

        getInterpolatedAmount = function (previousTime, targetTime) {
            var range = targetTime - previousTime,
                difference = _stateController.renderTime - previousTime,
                amount = parseFloat((difference / range).toFixed(3));

            return amount;
        };

    return {
        initializeGarageServer: initializeGarageServer,
        start: start,
        update: update,
        addPlayerInput: addPlayerInput,
        getPlayerStates: getPlayerStates,
        getPlayerId: getPlayerId,
        setPlayerState: setPlayerState
    };

}) (window, io);<commit_msg>Progress: #3<commit_after>/*
options = {
    onPlayerConnect: function()
    onPlayerDisconnect: function (),
    onPlayerReconnect: function (),
    onPlayerUpdate: function (state),
    onEntityUpdate: function (state),
    onPlayerRemove: function (id),
    onGameState: function (state),
    onPing: function (pingDelay),
    onUpdatePlayerPhysics: function (id, state, inputs, deltaTime),
    onUpdate: function (),
    onInterpolation: function(id, previousState, targetState, amount)
    logging: true,
    clientSidePrediction: true,
    interpolation: true,
    interpolationDelay: 100,
    pingInterval: 2000
}
*/

window.GarageServerIO = (function (window, socketio) {

    function StateController() {
        this.state = {};
        this.clientTime;
        this.renderTime;
        this.physicsDelta;
        this.physicsIntervalId;
        this.currentTime;
        this.accumulator = 0.0;
        this.playerId;
        this.pingDelay = 100;
        this.interpolationDelay = 100;
    }
    StateController.prototype = {
        setTime: function (serverTime) {
            this.clientTime = serverTime;
            this.renderTime = this.clientTime - this.interpolationDelay;
        },
        accumulate: function () {
            var newTime = new Date().getTime(),
                frameTime = newTime - this.currentTime;
            if (frameTime  > 250) {
                frameTime = 250;
            }
            this.currentTime = newTime;
            this.accumulator += frameTime;
        }
    };

    function Input(input, seq) {
        this.input = input;
        this.seq = seq;
    }

    function InputController() {
        this.inputs = [];
        this.sequenceNumber = 1;
    }
    InputController.prototype = {
        any: function () {
            return this.inputs.length > 0;
        },
        add: function (input) {
            this.sequenceNumber += 1;
            this.inputs.push(new Input(input, this.sequenceNumber));
        },
        remove: function (seq) {
            for (var i = 0; i < this.inputs.length; i ++) {
                if (this.inputs[i].seq === seq) {
                    this.inputs.splice(0, i + 1);
                    break;
                }
            }
        }
    };

    function Update(state, seq, time) {
        this.state = state;
        this.seq = seq;
        this.time = time;
    }

    function Entity() {
        this.updates = [];
        this.id;
        this.currentState = {};
    }
    Entity.prototype = {
        addUpate: function (state, seq, time) {
            var newUpdate = new Update(state, seq, time);
            if (this.updates.length === 0) {
                this.currentState = newUpdate.state;
            }
            this.updates.push(newUpdate);
            if (this.updates.length > 120) {
                this.updates.splice(0, 1);
            }
        },
        updateState: function (state, seq, time) {
            var updateFound = false;
            this.updates.some(function (update) {
               if (update.seq === seq) {
                   update.state = state;
                   updateFound = true;
                   return true;
               }
            });
            if (!updateFound) {
                this.addUpate(state, seq, time);
            }
        },
        anyUpdates: function () {
            return this.updates.length > 0;
        },
        latestUpdate: function () {
            return this.updates[this.updates.length - 1];
        },
        surroundingPositions: function (time) {
            var positions = {};
            for (var i = 0; i < this.updates.length; i ++) {
                var previous = this.updates[i];
                var target = this.updates[i + 1];

                if(previous && target && time > previous.time && time < target.time) {
                    positions.previous = previous;
                    positions.target = target;
                    break;
                }
            }
            return positions;
        }
    };

    function Player(id) {
        Entity.call(this);
        this.id = id;
    }
    Player.prototype = Object.create(Entity.prototype);

    function PlayerController() {
        this.players = [];
    }
    PlayerController.prototype = {
        add: function (id) {
            var player = new Player(id);
            this.players.push(player);
            return player;
        },
        remove: function (id) {
            for (var i = 0; i < this.players.length; i ++) {
                this.players.splice(i, 1);
                return;
            }
        }
    };

    var _io = socketio,
        _socket = null,
        _options = null,
        _stateController = new StateController(),
        _inputController = new InputController(),
        _playerController = new PlayerController(),

        initializeGarageServer = function (path, opts) {
            _options = opts;
            _socket = _io.connect(path + '/garageserver.io');
            registerSocketEvents();
            registerPinger();
        },

        registerSocketEvents = function () {
            _socket.on('connect', function () {
                _stateController.playerId = _socket.id;
                _stateController.interpolationDelay = _options.interpolationDelay ? _options.interpolationDelay : 100;
                if (_options.onPlayerConnect) {
                    _options.onPlayerConnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket connect');
                }
            });
            _socket.on('state', function(data) {
                if (_options.onGameState) {
                    _options.onGameState(data);
                }
                _stateController.physicsDelta = data.physicsDelta;
            });
            _socket.on('disconnect', function () {
                if (_options.onPlayerDisconnect) {
                    _options.onPlayerDisconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket disconnect');
                }
            });
            _socket.on('reconnect', function () {
                if (_options.onPlayerReconnect) {
                    _options.onPlayerReconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket reconnect');
                }
            });
            _socket.on('update', function(data) {
                updateState(data);
            });
            _socket.on('ping', function(data) {
                _stateController.pingDelay = new Date().getTime() - data;
                if (_options.onPing) {
                    _options.onPing(_stateController.pingDelay);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket ping delay ' + _stateController.pingDelay);
                }
            });
            _socket.on('removePlayer', function(id) {
                removePlayer(id);
                if (_options.onPlayerRemove) {
                    _options.onPlayerRemove(id);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket removePlayer ' + id);
                }
            });
        },

        registerPinger = function () {
            var interval = 2000;
            if (_options.pingInterval) {
                interval = _options.pingInterval;
            }
            setInterval(function (){
                _socket.emit('ping', new Date().getTime());
            }, interval);
        },

        start = function () {
            var self = this;
            _stateController.currentTime = new Date().getTime();
            _stateController.physicsIntervalId = setInterval(function () { self.update(); }, this.physicsDelta * 1000);
        },

        getPlayerId = function () {
            return _stateController.playerId;
        },

        setPlayerState = function (state) {
            _socket.emit('playerState', state);
        },

        removePlayer = function (id) {
            _playerController.remove(id);
        },

        update = function () {
            if (_options.onUpdate) {
                _stateController.accumulate();
                while (_stateController.accumulator >= (_stateController.physicsDelta * 1000))
                {
                    _options.onUpdate();
                    _stateController.accumulator -= (_stateController.physicsDelta * 1000);
                }
            }
        },

        addPlayerInput = function (clientInput) {
            _inputController.add(clientInput);
            if (_options.clientSidePrediction && _options.onUpdatePlayerPhysics) {
                _stateController.state = _options.onUpdatePlayerPhysics(_stateController.playerId, _stateController.state, [{ input: clientInput }], _stateController.physicsDelta);
            }
            _socket.emit('input', [ clientInput, _inputController.sequenceNumber, _stateController.renderTime ]);
        },

        updateState = function (data) {
            _stateController.setTime(data.time);

            updatePlayersState(data);
            updateEntitiesState(data);
        },

        updatePlayersState = function (data) {
            data.playerStates.forEach(function (playerState) {
                if (_socket.socket.sessionid === playerState[0]) {
                    updatePlayerState(playerState);
                } else {
                    updateOtherPlayersState(playerState, data.time);
                }

                if (_options.onPlayerUpdate) {
                    _options.onPlayerUpdate(playerState[1]);
                }
            });
        },

        updatePlayerState = function (playerState) {
            _stateController.state = playerState[1];
            _inputController.remove(playerState[2]);

            if (_options.clientSidePrediction && _inputController.any()) {
                _stateController.state = _options.onUpdatePlayerPhysics(_stateController.playerId, _stateController.state, _inputController.inputs, _stateController.physicsDelta);
            }
        },

        updateOtherPlayersState = function (playerState, time) {
            var playerFound = false;
            _playerController.players.some(function (player) {
                if (player.id === playerState[0]) {
                    playerFound = true;
                    player.updateState(playerState[1], playerState[2], time);
                    return true;
                }
            });
            if (!playerFound) {
                var newPlayer = _playerController.add(playerState[0]);
                newPlayer.addUpate(playerState[1], playerState[2], time);
            }
        },

        updateEntitiesState = function (data) {
            data.entityStates.forEach(function (entityState) {

                if (_options.onEntityUpdate) {
                    _options.onEntityUpdate(entityState[1]);
                }
            });
        },

        getPlayerStates = function (stateCallback) {
            if(_options.interpolation && _options.onInterpolation) {
                getPlayerStatesInterpolated(stateCallback);
            }
            else {
                getPlayerStatesCurrent(stateCallback);
            }
            stateCallback(_stateController.state);
        },

        getPlayerStatesCurrent = function (stateCallback) {
            _playerController.players.forEach(function (player) {
                if (player.anyUpdates()) {
                    stateCallback(player.latestUpdate().state);
                }
            });
        },

        getPlayerStatesInterpolated = function (stateCallback) {
            var positions, amount, newState;
            _playerController.players.forEach(function (player) {
                if (player.anyUpdates()) {
                    positions = player.surroundingPositions(_stateController.renderTime);
                    if (positions.previous && positions.target) {
                        amount = getInterpolatedAmount(positions.previous.time, positions.target.time);
                        newState = _options.onInterpolation(player.id, positions.previous.state, positions.target.state, amount);
                        player.currentState = newState = _options.onInterpolation(player.id, player.currentState, newState, _stateController.physicsDelta * 20);
                        stateCallback(player.currentState);
                    }
                    else {
                        stateCallback(player.currentState);
                    }
                }
            });
        },

        getInterpolatedAmount = function (previousTime, targetTime) {
            var range = targetTime - previousTime,
                difference = _stateController.renderTime - previousTime,
                amount = parseFloat((difference / range).toFixed(3));

            return amount;
        };

    return {
        initializeGarageServer: initializeGarageServer,
        start: start,
        update: update,
        addPlayerInput: addPlayerInput,
        getPlayerStates: getPlayerStates,
        getPlayerId: getPlayerId,
        setPlayerState: setPlayerState
    };

}) (window, io);<|endoftext|>


<commit_before>/*
Copyright 2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';

import { _t } from '../../../languageHandler';
import SdkConfig from "../../../SdkConfig";
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from "../elements/AccessibleButton";
import {formatBytes, formatCountLong} from "../../../utils/FormattingUtils";
import EventIndexPeg from "../../../indexing/EventIndexPeg";
import {SettingLevel} from "../../../settings/SettingLevel";
import {replaceableComponent} from "../../../utils/replaceableComponent";

@replaceableComponent("views.settings.EventIndexPanel")
export default class EventIndexPanel extends React.Component {
    constructor() {
        super();

        this.state = {
            enabling: false,
            eventIndexSize: 0,
            roomCount: 0,
            eventIndexingEnabled:
                SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing'),
        };
    }

    updateCurrentRoom = async (room) => {
        const eventIndex = EventIndexPeg.get();
        let stats;

        try {
            stats = await eventIndex.getStats();
        } catch {
            // This call may fail if sporadically, not a huge issue as we will
            // try later again and probably succeed.
            return;
        }

        this.setState({
            eventIndexSize: stats.size,
            roomCount: stats.roomCount,
        });
    };

    componentWillUnmount(): void {
        const eventIndex = EventIndexPeg.get();

        if (eventIndex !== null) {
            eventIndex.removeListener("changedCheckpoint", this.updateCurrentRoom);
        }
    }

    async componentDidMount(): void {
        this.updateState();
    }

    async updateState() {
        const eventIndex = EventIndexPeg.get();
        const eventIndexingEnabled = SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing');
        const enabling = false;

        let eventIndexSize = 0;
        let roomCount = 0;

        if (eventIndex !== null) {
            eventIndex.on("changedCheckpoint", this.updateCurrentRoom);

            try {
                const stats = await eventIndex.getStats();
                eventIndexSize = stats.size;
                roomCount = stats.roomCount;
            } catch {
                // This call may fail if sporadically, not a huge issue as we
                // will try later again in the updateCurrentRoom call and
                // probably succeed.
            }
        }

        this.setState({
            enabling,
            eventIndexSize,
            roomCount,
            eventIndexingEnabled,
        });
    }

    _onManage = async () => {
        Modal.createTrackedDialogAsync('Message search', 'Message search',
            import('../../../async-components/views/dialogs/eventindex/ManageEventIndexDialog'),
            {
                onFinished: () => {},
            }, null, /* priority = */ false, /* static = */ true,
        );
    }

    _onEnable = async () => {
        this.setState({
            enabling: true,
        });

        await EventIndexPeg.initEventIndex();
        await EventIndexPeg.get().addInitialCheckpoints();
        await EventIndexPeg.get().startCrawler();
        await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, true);
        await this.updateState();
    }

    render() {
        let eventIndexingSettings = null;
        const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
        const brand = SdkConfig.get().brand;

        if (EventIndexPeg.get() !== null) {
            eventIndexingSettings = (

                        {_t("Securely cache encrypted messages locally for them " +
                            "to appear in search results, using %(size)s to store messages from %(rooms)s rooms.",
                            {
                                size: formatBytes(this.state.eventIndexSize, 0),
                                // This drives the singular / plural string
                                // selection for "room" / "rooms" only.
                                count: this.state.roomCount,
                                rooms: formatCountLong(this.state.roomCount),
                            },
                        )}

                        <AccessibleButton kind="primary" onClick={this._onManage}>
                            {_t("Manage")}
                        </AccessibleButton>

            );
        } else if (!this.state.eventIndexingEnabled && EventIndexPeg.supportIsInstalled()) {
            eventIndexingSettings = (

                        {_t( "Securely cache encrypted messages locally for them to " +
                             "appear in search results.")}

                        <AccessibleButton kind="primary" disabled={this.state.enabling}
                            onClick={this._onEnable}>
                            {_t("Enable")}
                        </AccessibleButton>
                        {this.state.enabling ? <InlineSpinner /> :
}

            );
        } else if (EventIndexPeg.platformHasSupport() && !EventIndexPeg.supportIsInstalled()) {
            const nativeLink = (
                "https://github.com/vector-im/element-web/blob/develop/" +
                "docs/native-node-modules.md#" +
                "adding-seshat-for-search-in-e2e-encrypted-rooms"
            );

            eventIndexingSettings = (

                    {
                        _t( "%(brand)s is missing some components required for securely " +
                            "caching encrypted messages locally. If you'd like to " +
                            "experiment with this feature, build a custom %(brand)s Desktop " +
                            "with <nativeLink>search components added</nativeLink>.",
                            {
                                brand,
                            },
                            {
                                'nativeLink': (sub) => {sub},
                            },
                        )
                    }

            );
        } else if (!EventIndexPeg.platformHasSupport()) {
            eventIndexingSettings = (

                    {
                        _t( "%(brand)s can't securely cache encrypted messages locally " +
                            "while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> " +
                            "for encrypted messages to appear in search results.",
                            {
                                brand,
                            },
                            {
                                'desktopLink': (sub) => {sub},
                            },
                        )
                    }

            );
        } else {
            eventIndexingSettings = (

                        {_t("Message search initilisation failed")}

                    {EventIndexPeg.error && (

                        {_t("Advanced")}

                            {EventIndexPeg.error.message}

                    )}

            );
        }

        return eventIndexingSettings;
    }
}
<commit_msg>Change copy to point to native node modules docs in element desktop<commit_after>/*
Copyright 2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';

import { _t } from '../../../languageHandler';
import SdkConfig from "../../../SdkConfig";
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from "../elements/AccessibleButton";
import {formatBytes, formatCountLong} from "../../../utils/FormattingUtils";
import EventIndexPeg from "../../../indexing/EventIndexPeg";
import {SettingLevel} from "../../../settings/SettingLevel";
import {replaceableComponent} from "../../../utils/replaceableComponent";

@replaceableComponent("views.settings.EventIndexPanel")
export default class EventIndexPanel extends React.Component {
    constructor() {
        super();

        this.state = {
            enabling: false,
            eventIndexSize: 0,
            roomCount: 0,
            eventIndexingEnabled:
                SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing'),
        };
    }

    updateCurrentRoom = async (room) => {
        const eventIndex = EventIndexPeg.get();
        let stats;

        try {
            stats = await eventIndex.getStats();
        } catch {
            // This call may fail if sporadically, not a huge issue as we will
            // try later again and probably succeed.
            return;
        }

        this.setState({
            eventIndexSize: stats.size,
            roomCount: stats.roomCount,
        });
    };

    componentWillUnmount(): void {
        const eventIndex = EventIndexPeg.get();

        if (eventIndex !== null) {
            eventIndex.removeListener("changedCheckpoint", this.updateCurrentRoom);
        }
    }

    async componentDidMount(): void {
        this.updateState();
    }

    async updateState() {
        const eventIndex = EventIndexPeg.get();
        const eventIndexingEnabled = SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing');
        const enabling = false;

        let eventIndexSize = 0;
        let roomCount = 0;

        if (eventIndex !== null) {
            eventIndex.on("changedCheckpoint", this.updateCurrentRoom);

            try {
                const stats = await eventIndex.getStats();
                eventIndexSize = stats.size;
                roomCount = stats.roomCount;
            } catch {
                // This call may fail if sporadically, not a huge issue as we
                // will try later again in the updateCurrentRoom call and
                // probably succeed.
            }
        }

        this.setState({
            enabling,
            eventIndexSize,
            roomCount,
            eventIndexingEnabled,
        });
    }

    _onManage = async () => {
        Modal.createTrackedDialogAsync('Message search', 'Message search',
            import('../../../async-components/views/dialogs/eventindex/ManageEventIndexDialog'),
            {
                onFinished: () => {},
            }, null, /* priority = */ false, /* static = */ true,
        );
    }

    _onEnable = async () => {
        this.setState({
            enabling: true,
        });

        await EventIndexPeg.initEventIndex();
        await EventIndexPeg.get().addInitialCheckpoints();
        await EventIndexPeg.get().startCrawler();
        await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, true);
        await this.updateState();
    }

    render() {
        let eventIndexingSettings = null;
        const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
        const brand = SdkConfig.get().brand;

        if (EventIndexPeg.get() !== null) {
            eventIndexingSettings = (

                        {_t("Securely cache encrypted messages locally for them " +
                            "to appear in search results, using %(size)s to store messages from %(rooms)s rooms.",
                            {
                                size: formatBytes(this.state.eventIndexSize, 0),
                                // This drives the singular / plural string
                                // selection for "room" / "rooms" only.
                                count: this.state.roomCount,
                                rooms: formatCountLong(this.state.roomCount),
                            },
                        )}

                        <AccessibleButton kind="primary" onClick={this._onManage}>
                            {_t("Manage")}
                        </AccessibleButton>

            );
        } else if (!this.state.eventIndexingEnabled && EventIndexPeg.supportIsInstalled()) {
            eventIndexingSettings = (

                        {_t( "Securely cache encrypted messages locally for them to " +
                             "appear in search results.")}

                        <AccessibleButton kind="primary" disabled={this.state.enabling}
                            onClick={this._onEnable}>
                            {_t("Enable")}
                        </AccessibleButton>
                        {this.state.enabling ? <InlineSpinner /> :
}

            );
        } else if (EventIndexPeg.platformHasSupport() && !EventIndexPeg.supportIsInstalled()) {
            const nativeLink = (
                "https://github.com/vector-im/element-desktop/blob/develop/" +
                "docs/native-node-modules.md#" +
                "adding-seshat-for-search-in-e2e-encrypted-rooms"
            );

            eventIndexingSettings = (

                    {
                        _t( "%(brand)s is missing some components required for securely " +
                            "caching encrypted messages locally. If you'd like to " +
                            "experiment with this feature, build a custom %(brand)s Desktop " +
                            "with <nativeLink>search components added</nativeLink>.",
                            {
                                brand,
                            },
                            {
                                'nativeLink': (sub) => {sub},
                            },
                        )
                    }

            );
        } else if (!EventIndexPeg.platformHasSupport()) {
            eventIndexingSettings = (

                    {
                        _t( "%(brand)s can't securely cache encrypted messages locally " +
                            "while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> " +
                            "for encrypted messages to appear in search results.",
                            {
                                brand,
                            },
                            {
                                'desktopLink': (sub) => {sub},
                            },
                        )
                    }

            );
        } else {
            eventIndexingSettings = (

                        {_t("Message search initilisation failed")}

                    {EventIndexPeg.error && (

                        {_t("Advanced")}

                            {EventIndexPeg.error.message}

                    )}

            );
        }

        return eventIndexingSettings;
    }
}
<|endoftext|>


<commit_before>/* globals qq */
/**
 * Module that handles support for existing forms.
 *
 * @param options Options passed from the integrator-supplied options related to form support.
 * @param startUpload Callback to invoke when files "stored" should be uploaded.
 * @param log Proxy for the logger
 * @constructor
 */
qq.FormSupport = function(options, startUpload, log) {
    "use strict";
    var self  = this,
        interceptSubmit = options.interceptSubmit,
        formEl = options.element,
        autoUpload = options.autoUpload;

    // Available on the public API associated with this module.
    qq.extend(this, {
        // To be used by the caller to determine if the endpoint will be determined by some processing
        // that occurs in this module, such as if the form has an action attribute.
        // Ignore if `attachToForm === false`.
        newEndpoint: null,

        // To be used by the caller to determine if auto uploading should be allowed.
        // Ignore if `attachToForm === false`.
        newAutoUpload: autoUpload,

        // true if a form was detected and is being tracked by this module
        attachedToForm: false,

        // Returns an object with names and values for all valid form elements associated with the attached form.
        getFormInputsAsObject: function() {
            /* jshint eqnull:true */
            if (formEl == null) {
                return null;
            }

            return self._form2Obj(formEl);
        }
    });

    // If the form contains an action attribute, this should be the new upload endpoint.
    function determineNewEndpoint(formEl) {
        if (formEl.getAttribute("action")) {
            self.newEndpoint = formEl.getAttribute("action");
        }
    }

    // Return true only if the form is valid, or if we cannot make this determination.
    // If the form is invalid, ensure invalid field(s) are highlighted in the UI.
    function validateForm(formEl, nativeSubmit) {
        if (formEl.checkValidity && !formEl.checkValidity()) {
            log("Form did not pass validation checks - will not upload.", "error");
            nativeSubmit();
        }
        else {
            return true;
        }
    }

    // Intercept form submit attempts, unless the integrator has told us not to do this.
    function maybeUploadOnSubmit(formEl) {
        var nativeSubmit = formEl.submit;

        // Intercept and squelch submit events.
        qq(formEl).attach("submit", function(event) {
            event = event || window.event;

            if (event.preventDefault) {
                event.preventDefault();
            }
            else {
                event.returnValue = false;
            }

            validateForm(formEl, nativeSubmit) && startUpload();
        });

        // The form's `submit()` function may be called instead (i.e. via jQuery.submit()).
        // Intercept that too.
        formEl.submit = function() {
            validateForm(formEl, nativeSubmit) && startUpload();
        };
    }

    // If the element value passed from the uploader is a string, assume it is an element ID - select it.
    // The rest of the code in this module depends on this being an HTMLElement.
    function determineFormEl(formEl) {
        if (formEl) {
            if (qq.isString(formEl)) {
                formEl = document.getElementById(formEl);
            }

            if (formEl) {
                log("Attaching to form element.");
                determineNewEndpoint(formEl);
                interceptSubmit && maybeUploadOnSubmit(formEl);
            }
        }

        return formEl;
    }

    formEl = determineFormEl(formEl);
    this.attachedToForm = !!formEl;
};

qq.extend(qq.FormSupport.prototype, {
    // Converts all relevant form fields to key/value pairs.  This is meant to mimic the data a browser will
    // construct from a given form when the form is submitted.
    _form2Obj: function(form) {
        "use strict";
        var obj = {},
            notIrrelevantType = function(type) {
                var irrelevantTypes = [
                    "button",
                    "image",
                    "reset",
                    "submit"
                ];

                return qq.indexOf(irrelevantTypes, type.toLowerCase()) < 0;
            },
            radioOrCheckbox = function(type) {
                return qq.indexOf(["checkbox", "radio"], type.toLowerCase()) >= 0;
            },
            ignoreValue = function(el) {
                if (radioOrCheckbox(el.type) && !qq(el).hasAttribute("checked")) {
                    return true;
                }

                return qq(el).hasAttribute("disabled") && el.type.toLowerCase() !== "hidden";
            },
            selectValue = function(select) {
                var value = null;

                qq.each(qq(select).children(), function(idx, child) {
                    if (child.tagName.toLowerCase() === "option" && qq(child).hasAttribute("selected")) {
                        value = child.value;
                        return false;
                    }
                });

                return value;
            };

        qq.each(form.elements, function(idx, el) {
            if (qq.isInput(el, true) && notIrrelevantType(el.type) && !ignoreValue(el)) {
                obj[el.name] = el.value;
            }
            else if (el.tagName.toLowerCase() === "select" && !ignoreValue(el)) {
                var value = selectValue(el);

                if (value !== null) {
                    obj[el.name] = value;
                }
            }
        });

        return obj;
    }
});
<commit_msg>fix(js/form-support): don't use qq.hasAttribute - it has issues in IE #781<commit_after>/* globals qq */
/**
 * Module that handles support for existing forms.
 *
 * @param options Options passed from the integrator-supplied options related to form support.
 * @param startUpload Callback to invoke when files "stored" should be uploaded.
 * @param log Proxy for the logger
 * @constructor
 */
qq.FormSupport = function(options, startUpload, log) {
    "use strict";
    var self  = this,
        interceptSubmit = options.interceptSubmit,
        formEl = options.element,
        autoUpload = options.autoUpload;

    // Available on the public API associated with this module.
    qq.extend(this, {
        // To be used by the caller to determine if the endpoint will be determined by some processing
        // that occurs in this module, such as if the form has an action attribute.
        // Ignore if `attachToForm === false`.
        newEndpoint: null,

        // To be used by the caller to determine if auto uploading should be allowed.
        // Ignore if `attachToForm === false`.
        newAutoUpload: autoUpload,

        // true if a form was detected and is being tracked by this module
        attachedToForm: false,

        // Returns an object with names and values for all valid form elements associated with the attached form.
        getFormInputsAsObject: function() {
            /* jshint eqnull:true */
            if (formEl == null) {
                return null;
            }

            return self._form2Obj(formEl);
        }
    });

    // If the form contains an action attribute, this should be the new upload endpoint.
    function determineNewEndpoint(formEl) {
        if (formEl.getAttribute("action")) {
            self.newEndpoint = formEl.getAttribute("action");
        }
    }

    // Return true only if the form is valid, or if we cannot make this determination.
    // If the form is invalid, ensure invalid field(s) are highlighted in the UI.
    function validateForm(formEl, nativeSubmit) {
        if (formEl.checkValidity && !formEl.checkValidity()) {
            log("Form did not pass validation checks - will not upload.", "error");
            nativeSubmit();
        }
        else {
            return true;
        }
    }

    // Intercept form submit attempts, unless the integrator has told us not to do this.
    function maybeUploadOnSubmit(formEl) {
        var nativeSubmit = formEl.submit;

        // Intercept and squelch submit events.
        qq(formEl).attach("submit", function(event) {
            event = event || window.event;

            if (event.preventDefault) {
                event.preventDefault();
            }
            else {
                event.returnValue = false;
            }

            validateForm(formEl, nativeSubmit) && startUpload();
        });

        // The form's `submit()` function may be called instead (i.e. via jQuery.submit()).
        // Intercept that too.
        formEl.submit = function() {
            validateForm(formEl, nativeSubmit) && startUpload();
        };
    }

    // If the element value passed from the uploader is a string, assume it is an element ID - select it.
    // The rest of the code in this module depends on this being an HTMLElement.
    function determineFormEl(formEl) {
        if (formEl) {
            if (qq.isString(formEl)) {
                formEl = document.getElementById(formEl);
            }

            if (formEl) {
                log("Attaching to form element.");
                determineNewEndpoint(formEl);
                interceptSubmit && maybeUploadOnSubmit(formEl);
            }
        }

        return formEl;
    }

    formEl = determineFormEl(formEl);
    this.attachedToForm = !!formEl;
};

qq.extend(qq.FormSupport.prototype, {
    // Converts all relevant form fields to key/value pairs.  This is meant to mimic the data a browser will
    // construct from a given form when the form is submitted.
    _form2Obj: function(form) {
        "use strict";
        var obj = {},
            notIrrelevantType = function(type) {
                var irrelevantTypes = [
                    "button",
                    "image",
                    "reset",
                    "submit"
                ];

                return qq.indexOf(irrelevantTypes, type.toLowerCase()) < 0;
            },
            radioOrCheckbox = function(type) {
                return qq.indexOf(["checkbox", "radio"], type.toLowerCase()) >= 0;
            },
            ignoreValue = function(el) {
                if (radioOrCheckbox(el.type) && !el.checked) {
                    return true;
                }

                return el.disabled && el.type.toLowerCase() !== "hidden";
            },
            selectValue = function(select) {
                var value = null;

                qq.each(qq(select).children(), function(idx, child) {
                    if (child.tagName.toLowerCase() === "option" && child.selected) {
                        value = child.value;
                        return false;
                    }
                });

                return value;
            };

        qq.each(form.elements, function(idx, el) {
            if (qq.isInput(el, true) && notIrrelevantType(el.type) && !ignoreValue(el)) {
                obj[el.name] = el.value;
            }
            else if (el.tagName.toLowerCase() === "select" && !ignoreValue(el)) {
                var value = selectValue(el);

                if (value !== null) {
                    obj[el.name] = value;
                }
            }
        });

        return obj;
    }
});
<|endoftext|>