<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');
var time = nconf.get('time');
if(time) {
	time = moment(time).utc().format();
}
var margin = parseFloat(nconf.get('margin')) || 1.0;
var target = parseFloat(nconf.get('target')) || 0.01;

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':
				fetchResults(sum, target, currencyPair, time, margin, function(result) {
					console.log(
						sum,
						currencyPair,
						'risk:'+Math.abs(result.el - result.sl),
						'sl:'+result.sl, 'el:'+result.el, 'tp:'+result.tp
					);
				});
				break;
			case 'eurcad':
			case 'gbpcad':
			case 'nzdcad':
			case 'audcad':
			case 'nzdjpy':
			case 'nzdchf':
			case 'audchf':
				fetchResults(sum, target, newPair.base + 'usd', time, margin, function(baseResult) {
					fetchResults(sum, target, 'usd' + newPair.other, time, margin, function(otherResult) {
						var el = baseResult.el * otherResult.el;
						var sl = baseResult.sl * otherResult.sl;
						var tp = baseResult.tp * otherResult.tp;

						console.log(
							sum,
							currencyPair,
							'risk:'+Math.abs(el - sl),
							'sl:'+sl, 'el:'+el, 'tp:'+tp
						);
					});
				});
				break;
			case 'eurnzd':
			case 'gbpaud':
			case 'gbpnzd':
			case 'audnzd':
				fetchResults(sum, target, newPair.base + 'usd', time, margin, function(baseResult) {
					fetchResults(-sum, target, newPair.other + 'usd', time, margin, function(otherResult) {
						var el = baseResult.el / otherResult.el;
						var sl = baseResult.sl / otherResult.sl;
						var tp = baseResult.tp / otherResult.tp;

						console.log(
							sum,
							currencyPair,
							'risk:'+Math.abs(el - sl),
							'sl:'+sl, 'el:'+el, 'tp:'+tp
						);
					});
				});
				break;
			case 'chfjpy':
			case 'cadjpy':
			case 'cadchf':
				fetchResults(-sum, target, 'usd' + newPair.base, time, margin, function(baseResult) {
					fetchResults(sum, target, 'usd' + newPair.other, time, margin, function(otherResult) {
						var el = otherResult.el / baseResult.el;
						var sl = otherResult.sl / baseResult.sl;
						var tp = otherResult.tp / baseResult.tp;

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

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 fetchResults(bias, target, currencyPair, time, margin, 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 };
		var max = { os: 0.0, ask: 0.0 };
		var min = { ol: 0.0, bid: 0.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) {
					if(net > min.ol) {
						min.ol = net;
						min.bid = rate - dist;
					}
					sum.bid = sum.bid + (net * dist);
					total.bid++;
				} else {
					if(net < max.os) {
						max.os = net;
						max.ask = rate + dist;
					}
					sum.ask = sum.ask + (net * dist);
					total.ask++;
				}
			}
		});

		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;

		var buyEntryLimit = ((bullish ? average : bid) + rate) / 2.0;
		var sellEntryLimit = ((!bullish ? average : ask) + rate) / 2.0;

		var result = bias > 0.0
			? { sl: bid, el: buyEntryLimit, tp: buyEntryLimit * (1.0 + target) }
			: { sl: ask, el: sellEntryLimit, tp: sellEntryLimit * (1.0 - target) };

		done(result);
	});
}
<commit_msg>fix: decrease default target<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');
var time = nconf.get('time');
if(time) {
	time = moment(time).utc().format();
}
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':
				fetchResults(sum, target, currencyPair, time, margin, function(result) {
					console.log(
						sum,
						currencyPair,
						'risk:'+Math.abs(result.el - result.sl),
						'sl:'+result.sl, 'el:'+result.el, 'tp:'+result.tp
					);
				});
				break;
			case 'eurcad':
			case 'gbpcad':
			case 'nzdcad':
			case 'audcad':
			case 'nzdjpy':
			case 'nzdchf':
			case 'audchf':
				fetchResults(sum, target, newPair.base + 'usd', time, margin, function(baseResult) {
					fetchResults(sum, target, 'usd' + newPair.other, time, margin, function(otherResult) {
						var el = baseResult.el * otherResult.el;
						var sl = baseResult.sl * otherResult.sl;
						var tp = baseResult.tp * otherResult.tp;

						console.log(
							sum,
							currencyPair,
							'risk:'+Math.abs(el - sl),
							'sl:'+sl, 'el:'+el, 'tp:'+tp
						);
					});
				});
				break;
			case 'eurnzd':
			case 'gbpaud':
			case 'gbpnzd':
			case 'audnzd':
				fetchResults(sum, target, newPair.base + 'usd', time, margin, function(baseResult) {
					fetchResults(-sum, target, newPair.other + 'usd', time, margin, function(otherResult) {
						var el = baseResult.el / otherResult.el;
						var sl = baseResult.sl / otherResult.sl;
						var tp = baseResult.tp / otherResult.tp;

						console.log(
							sum,
							currencyPair,
							'risk:'+Math.abs(el - sl),
							'sl:'+sl, 'el:'+el, 'tp:'+tp
						);
					});
				});
				break;
			case 'chfjpy':
			case 'cadjpy':
			case 'cadchf':
				fetchResults(-sum, target, 'usd' + newPair.base, time, margin, function(baseResult) {
					fetchResults(sum, target, 'usd' + newPair.other, time, margin, function(otherResult) {
						var el = otherResult.el / baseResult.el;
						var sl = otherResult.sl / baseResult.sl;
						var tp = otherResult.tp / baseResult.tp;

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

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 fetchResults(bias, target, currencyPair, time, margin, 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 };
		var max = { os: 0.0, ask: 0.0 };
		var min = { ol: 0.0, bid: 0.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) {
					if(net > min.ol) {
						min.ol = net;
						min.bid = rate - dist;
					}
					sum.bid = sum.bid + (net * dist);
					total.bid++;
				} else {
					if(net < max.os) {
						max.os = net;
						max.ask = rate + dist;
					}
					sum.ask = sum.ask + (net * dist);
					total.ask++;
				}
			}
		});

		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;

		var buyEntryLimit = ((bullish ? average : bid) + rate) / 2.0;
		var sellEntryLimit = ((!bullish ? average : ask) + rate) / 2.0;

		var result = bias > 0.0
			? { sl: bid, el: buyEntryLimit, tp: buyEntryLimit * (1.0 + target) }
			: { sl: ask, el: sellEntryLimit, tp: sellEntryLimit * (1.0 - target) };

		done(result);
	});
}
<|endoftext|>


<commit_before>"use strict";

var SuperJS = require('../../index');

var Promise = require('bluebird');
var path = require('path');

/**
 * The base controller class is the basic building block for controllers.
 *
 * @exports Controller
 * @namespace SuperJS
 * @extends SuperJS.Class
 */

module.exports = SuperJS.Class.extend({

  init: function(app) {

    //store reference to express
    this.app = app;

  },

  //can be overridden by the controller extension to manipulate the request or response
  beforeAction: function(req) {

    //return promise
    return new Promise(function(resolve, reject) {
      resolve({});
    });

  },

  //verify the request by transforming, validating, and sanitizing parameters
  verifyRequest: function(req) {

    //maintain reference to the currrent instance
    var self = this;

    //return promise which is resolved or rejected depending on completion
    return new Promise(function (resolve, reject) {

      //maintain context object of parameters
      var parameters = {};

      //maintain list of closures which contain promises for each process
      var transforms = [];
      var validations = [];
      var sanitizations = [];
      var modelValidations = [];

      //loop through all the parameters for this action and
      //append transforms, validations, and sanitizations to their respective lists
      for (var param in self.blueprint.actions[req.action].params) {

        //store the parameters value
        parameters[param] = req.param(param);

        //set value to default if its not passed
        if( !parameters[param] ) {
          parameters[param] = self.blueprint.actions[req.action].params[param].default;
        }

        //setup transforms
        transforms = transforms.concat(self.app.services.transform.setup(self.blueprint.actions[req.action].params[param].transform, parameters, param));

        //setup validations
        validations = validations.concat(self.app.services.validate.setup(self.blueprint.actions[req.action].params[param].validate, param, parameters[param], 'parameter'));

        //setup sanitizations
        //sanitizations = sanitizations.concat(self.app.services.sanitize.setup(self.blueprint.actions[req.action].params[param].sanitize, parameters, param));

        if( self.blueprint.actions[req.action].params[param].model ) {

          //if the parameter is a model but the value is null/undefined, default parameter to empty object
          if( Object.keys(self.blueprint.actions[req.action].params[param].model.validate).length > 0 && !parameters[param]) {
            parameters[param] = {};
          }

          for( var attribute in self.blueprint.actions[req.action].params[param].model.validate ) {

            //set attribute value to default if its not passed
            if( Object.keys(parameters[param]).indexOf(attribute) === -1 ) {
              parameters[param][attribute] = self.app.models[self.name].attributes[attribute].defaultsTo;
            }

            modelValidations = modelValidations.concat(self.app.services.validate.setup(self.blueprint.actions[req.action].params[param].model.validate[attribute], attribute, parameters[param][attribute], 'attribute'));

          }

        }
      }

      var numberOfValidations = validations.length + modelValidations.length;
      self.app.log.debug("performing " + transforms.length + " transforms, " + numberOfValidations + " validations, and " + sanitizations.length + " sanitizations...");

      //exceute transforms
      self.app.services.transform.process(transforms)

        //execute validations
        .then(function() {
          return self.app.services.validate.process(validations);
        })

        //exectute sanitizations
        //.then(function() {
        //  return self.services.sanitize.process(sanitizations)
        //})

        //execute validations
        .then(function() {
          return self.app.services.validate.process(modelValidations);
        })

        //resolve if all passed without errors
        .then(function() {

          //store the parameters on the request object
          req.parameters = parameters;
          self.app.log.debug('verified parameters:', parameters);

          resolve();
        })

        //reject if we caught any errors
        .catch(function(err) {

          reject(err);
        });

    });

  },

  Describe: function(req) {

    //maintain reference to instance
    var self = this;

    //return promise to resolve or reject
    return new Promise(function(resolve, reject) {

      //package response
      var response = {meta: {success: true}, blueprint: self.blueprint};

      //resolve response
      resolve(response);

    });

  },

  //can be overridden by the controller extension to manipulate the request or response
  afterAction: function(req, response, next) {

    //return promise
    return new Promise(function(resolve, reject) {
      resolve({});
    });
  }

});<commit_msg>Added check to prevent running unnecessary validations.<commit_after>"use strict";

var SuperJS = require('../../index');

var Promise = require('bluebird');
var path = require('path');

/**
 * The base controller class is the basic building block for controllers.
 *
 * @exports Controller
 * @namespace SuperJS
 * @extends SuperJS.Class
 */

module.exports = SuperJS.Class.extend({

  init: function(app) {

    //store reference to express
    this.app = app;

  },

  //can be overridden by the controller extension to manipulate the request or response
  beforeAction: function(req) {

    //return promise
    return new Promise(function(resolve, reject) {
      resolve({});
    });

  },

  //verify the request by transforming, validating, and sanitizing parameters
  verifyRequest: function(req) {

    //maintain reference to the currrent instance
    var self = this;

    //return promise which is resolved or rejected depending on completion
    return new Promise(function (resolve, reject) {

      //maintain context object of parameters
      var parameters = {};

      //maintain list of closures which contain promises for each process
      var transforms = [];
      var validations = [];
      var sanitizations = [];
      var modelValidations = [];

      //loop through all the parameters for this action and
      //append transforms, validations, and sanitizations to their respective lists
      for (var param in self.blueprint.actions[req.action].params) {

        //store the parameters value
        parameters[param] = req.param(param);

        //set value to default if its not passed
        if( !parameters[param] ) {
          parameters[param] = self.blueprint.actions[req.action].params[param].default;
        }

        //setup transforms
        transforms = transforms.concat(self.app.services.transform.setup(self.blueprint.actions[req.action].params[param].transform, parameters, param));

        //setup validations
        validations = validations.concat(self.app.services.validate.setup(self.blueprint.actions[req.action].params[param].validate, param, parameters[param], 'parameter'));

        //setup sanitizations
        //sanitizations = sanitizations.concat(self.app.services.sanitize.setup(self.blueprint.actions[req.action].params[param].sanitize, parameters, param));

        if( self.blueprint.actions[req.action].params[param].model ) {

          //if the parameter is a model but the value is null/undefined, default parameter to empty object
          if( Object.keys(self.blueprint.actions[req.action].params[param].model.validate).length > 0 && !parameters[param]) {
            parameters[param] = {};
          }

          //loop through model validations
          for( var attribute in self.blueprint.actions[req.action].params[param].model.validate ) {

            //set attribute value to default if its not passed
            if( Object.keys(parameters[param]).indexOf(attribute) === -1 ) {
              parameters[param][attribute] = self.app.models[self.name].attributes[attribute].defaultsTo;
            }

            //don't run validations if the attribute has not been provided and its not required
            if( self.blueprint.actions[req.action].params[param].model.validate[attribute].required === true || typeof parameters[param][attribute] !== 'undefined' ) {
              modelValidations = modelValidations.concat(self.app.services.validate.setup(self.blueprint.actions[req.action].params[param].model.validate[attribute], attribute, parameters[param][attribute], 'attribute'));
            }

          }

        }
      }

      var numberOfValidations = validations.length + modelValidations.length;
      self.app.log.debug("performing " + transforms.length + " transforms, " + numberOfValidations + " validations, and " + sanitizations.length + " sanitizations...");

      //exceute transforms
      self.app.services.transform.process(transforms)

        //execute validations
        .then(function() {
          return self.app.services.validate.process(validations);
        })

        //exectute sanitizations
        //.then(function() {
        //  return self.services.sanitize.process(sanitizations)
        //})

        //execute validations
        .then(function() {
          return self.app.services.validate.process(modelValidations);
        })

        //resolve if all passed without errors
        .then(function() {

          //store the parameters on the request object
          req.parameters = parameters;
          self.app.log.debug('verified parameters:', parameters);

          resolve();
        })

        //reject if we caught any errors
        .catch(function(err) {

          reject(err);
        });

    });

  },

  Describe: function(req) {

    //maintain reference to instance
    var self = this;

    //return promise to resolve or reject
    return new Promise(function(resolve, reject) {

      //package response
      var response = {meta: {success: true}, blueprint: self.blueprint};

      //resolve response
      resolve(response);

    });

  },

  //can be overridden by the controller extension to manipulate the request or response
  afterAction: function(req, response, next) {

    //return promise
    return new Promise(function(resolve, reject) {
      resolve({});
    });
  }

});<|endoftext|>


<commit_before>import { event as d3_event, select as d3_select } from 'd3-selection';

import { geoVecLength } from '../geo';
import { modeBrowse } from '../modes/browse';
import { modeSelect } from '../modes/select';
import { modeSelectData } from '../modes/select_data';
import { modeSelectNote } from '../modes/select_note';
import { modeSelectError } from '../modes/select_error';
import { osmEntity, osmNote, QAItem } from '../osm';
import { utilFastMouse } from '../util/util';

export function behaviorSelect(context) {
    var _tolerancePx = 4; // see also behaviorDrag
    var _lastMouseEvent = null;
    var _showMenu = false;
    var _downPointers = {};
    var _longPressTimeout = null;
    var _lastInteractionType = null;
    // the id of the down pointer that's enabling multiselection while down
    var _multiselectionPointerId = null;

    // use pointer events on supported platforms; fallback to mouse events
    var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';

    function keydown() {

        if (d3_event.keyCode === 32) {
            // don't react to spacebar events during text input
            var activeNode = document.activeElement;
            if (activeNode && new Set(['INPUT', 'TEXTAREA']).has(activeNode.nodeName)) return;
        }

        if (d3_event.keyCode === 93 ||  // context menu key
            d3_event.keyCode === 32) {  // spacebar
            d3_event.preventDefault();
        }

        if (d3_event.repeat) return; // ignore repeated events for held keys

        // if any key is pressed the user is probably doing something other than long-pressing
        cancelLongPress();

        if (d3_event.shiftKey) {
            context.surface()
                .classed('behavior-multiselect', true);
        }

        if (d3_event.keyCode === 32) {  // spacebar
            if (!_downPointers.spacebar && _lastMouseEvent) {
                cancelLongPress();
                _longPressTimeout = window.setTimeout(didLongPress, 500, 'spacebar', 'spacebar');

                _downPointers.spacebar = {
                    firstEvent: _lastMouseEvent,
                    lastEvent: _lastMouseEvent
                };
            }
        }
    }

    function keyup() {
        cancelLongPress();

        if (!d3_event.shiftKey) {
            context.surface()
                .classed('behavior-multiselect', false);
        }

        if (d3_event.keyCode === 93) {  // context menu key
            d3_event.preventDefault();
            _lastInteractionType = 'menukey';
            contextmenu();
        } else if (d3_event.keyCode === 32) {  // spacebar
            var pointer = _downPointers.spacebar;
            if (pointer) {
                delete _downPointers.spacebar;

                if (pointer.done) return;

                d3_event.preventDefault();
                _lastInteractionType = 'spacebar';
                click(pointer.firstEvent, pointer.lastEvent);
            }
        }
    }

    function pointerdown() {
        var id = (d3_event.pointerId || 'mouse').toString();

        cancelLongPress();

        if (d3_event.buttons && d3_event.buttons !== 1) return;

        _longPressTimeout = window.setTimeout(didLongPress, 500, id, 'longdown-' + (d3_event.pointerType || 'mouse'));

        _downPointers[id] = {
            firstEvent: d3_event,
            lastEvent: d3_event
        };
    }

    function didLongPress(id, interactionType) {
        var pointer = _downPointers[id];
        if (!pointer) return;

        for (var i in _downPointers) {
            // don't allow this or any currently down pointer to trigger another click
            _downPointers[i].done = true;
        }

        // treat long presses like right-clicks
        _longPressTimeout = null;
        _lastInteractionType = interactionType;
        _showMenu = true;

        click(pointer.firstEvent, pointer.lastEvent);
    }

    function pointermove() {
        var id = (d3_event.pointerId || 'mouse').toString();
        if (_downPointers[id]) {
            _downPointers[id].lastEvent = d3_event;
        }
        if (!d3_event.pointerType || d3_event.pointerType === 'mouse') {
            _lastMouseEvent = d3_event;
            if (_downPointers.spacebar) {
                _downPointers.spacebar.lastEvent = d3_event;
            }
        }
    }

    function pointerup() {
        var id = (d3_event.pointerId || 'mouse').toString();
        var pointer = _downPointers[id];
        if (!pointer) return;

        delete _downPointers[id];

        if (_multiselectionPointerId === id) {
            _multiselectionPointerId = null;
        }

        if (pointer.done) return;

        click(pointer.firstEvent, d3_event);
    }

    function contextmenu() {
        var e = d3_event;
        e.preventDefault();

        if (!+e.clientX && !+e.clientY) {
            if (_lastMouseEvent) {
                e.sourceEvent = _lastMouseEvent;
            } else {
                return;
            }
        } else {
            _lastMouseEvent = d3_event;
            _lastInteractionType = 'rightclick';
        }

        _showMenu = true;
        click(d3_event, d3_event);
    }

    function click(firstEvent, lastEvent) {
        cancelLongPress();

        var mapNode = context.container().select('.main-map').node();

        // Use the `main-map` coordinate system since the surface and supersurface
        // are transformed when drag-panning.
        var pointGetter = utilFastMouse(mapNode);
        var p1 = pointGetter(firstEvent);
        var p2 = pointGetter(lastEvent);
        var dist = geoVecLength(p1, p2);

        if (dist > _tolerancePx ||
            !mapContains(lastEvent)) {

            resetProperties();
            return;
        }

        var targetDatum = lastEvent.target.__data__;

        if (!_multiselectionPointerId) {
            // If a different pointer than the one triggering this click is down on the
            // selection, treat this and all future clicks as multiselection until that
            // pointer is raised.
            var selectionPointer = context.mode().id === 'select' && pointerDownOnSelection();
            if (selectionPointer) {
                _multiselectionPointerId = selectionPointer;
            }
        }

        // only support multiselect if data is already selected
        var isMultiselect = context.mode().id === 'select' && (
            (d3_event && d3_event.shiftKey) ||
            context.surface().select('.lasso').node() ||
            _multiselectionPointerId
        );

        processClick(targetDatum, isMultiselect, p2);

        function mapContains(event) {
            var rect = mapNode.getBoundingClientRect();
            return event.clientX >= rect.left &&
                event.clientX <= rect.right &&
                event.clientY >= rect.top &&
                event.clientY <= rect.bottom;
        }
    }

    function pointerDownOnSelection() {
        var selectedIds = context.mode().id === 'select' && context.mode().selectedIDs();
        for (var id in _downPointers) {
            if (id === 'spacebar') continue;

            var datum = _downPointers[id].firstEvent.target.__data__;
            var entity = (datum && datum.properties && datum.properties.entity) || datum;
            if (selectedIds.indexOf(entity.id) !== -1) return id;
        }
        return null;
    }

    function processClick(datum, isMultiselect, point) {
        var mode = context.mode();
        var showMenu = _showMenu;
        var interactionType = _lastInteractionType;

        var entity = datum && datum.properties && datum.properties.entity;
        if (entity) datum = entity;

        if (datum && datum.type === 'midpoint') {
            // treat targeting midpoints as if targeting the parent way
            datum = datum.parents[0];
        }

        var newMode;

        if (datum instanceof osmEntity) {
            // targeting an entity
            var selectedIDs = context.selectedIDs();
            context.selectedNoteID(null);
            context.selectedErrorID(null);

            if (!isMultiselect) {
                if (selectedIDs.length <= 1 || !showMenu) {
                    // always enter modeSelect even if the entity is already
                    // selected since listeners may expect `context.enter` events,
                    // e.g. in the walkthrough
                    newMode = mode.id === 'select' ? mode.selectedIDs([datum.id]) : modeSelect(context, [datum.id]).selectBehavior(behavior);
                    context.enter(newMode);
                }

            } else {
                if (selectedIDs.indexOf(datum.id) !== -1) {
                    // clicked entity is already in the selectedIDs list..
                    if (!showMenu) {
                        // deselect clicked entity, then reenter select mode or return to browse mode..
                        selectedIDs = selectedIDs.filter(function(id) { return id !== datum.id; });
                        newMode = selectedIDs.length ? mode.selectedIDs(selectedIDs) : modeBrowse(context).selectBehavior(behavior);
                        context.enter(newMode);
                    }
                } else {
                    // clicked entity is not in the selected list, add it..
                    selectedIDs = selectedIDs.concat([datum.id]);
                    newMode = mode.selectedIDs(selectedIDs);
                    context.enter(newMode);
                }
            }

        } else if (datum && datum.__featurehash__ && !isMultiselect) {
            // targeting custom data
            context
                .selectedNoteID(null)
                .enter(modeSelectData(context, datum));

        } else if (datum instanceof osmNote && !isMultiselect) {
            // targeting a note
            context
                .selectedNoteID(datum.id)
                .enter(modeSelectNote(context, datum.id));

        } else if (datum instanceof QAItem & !isMultiselect) {
            // targeting an external QA issue
            context
                .selectedErrorID(datum.id)
                .enter(modeSelectError(context, datum.id, datum.service));

        } else {
            // targeting nothing
            context.selectedNoteID(null);
            context.selectedErrorID(null);
            if (!isMultiselect && mode.id !== 'browse') {
                context.enter(modeBrowse(context));
            }
        }

        context.ui().closeEditMenu();

        // always request to show the edit menu in case the mode needs it
        if (showMenu) context.ui().showEditMenu(point, interactionType);

        resetProperties();
    }

    function cancelLongPress() {
        if (_longPressTimeout) window.clearTimeout(_longPressTimeout);
        _longPressTimeout = null;
    }

    function resetProperties() {
        cancelLongPress();
        _showMenu = false;
        _lastInteractionType = null;
        // don't reset _lastMouseEvent since it might still be useful
    }

    function behavior(selection) {
        resetProperties();
        _lastMouseEvent = context.map().lastPointerEvent();

        d3_select(window)
            .on('keydown.select', keydown)
            .on('keyup.select', keyup)
            .on(_pointerPrefix + 'move.select', pointermove, true)
            .on(_pointerPrefix + 'up.select', pointerup, true)
            .on('contextmenu.select-window', function() {
                // Edge and IE really like to show the contextmenu on the
                // menubar when user presses a keyboard menu button
                // even after we've already preventdefaulted the key event.
                var e = d3_event;
                if (+e.clientX === 0 && +e.clientY === 0) {
                    d3_event.preventDefault();
                }
            });

        selection
            .on(_pointerPrefix + 'down.select', pointerdown)
            .on('contextmenu.select', contextmenu);

        if (d3_event && d3_event.shiftKey) {
            context.surface()
                .classed('behavior-multiselect', true);
        }
    }

    behavior.off = function(selection) {
        cancelLongPress();

        d3_select(window)
            .on('keydown.select', null)
            .on('keyup.select', null)
            .on('contextmenu.select-window', null)
            .on(_pointerPrefix + 'move.select', null, true)
            .on(_pointerPrefix + 'up.select', null, true);

        selection
            .on(_pointerPrefix + 'down.select', null)
            .on('contextmenu.select', null);

        context.surface()
            .classed('behavior-multiselect', false);
    };

    return behavior;
}
<commit_msg>Account for canceled pointers in behaviorSelect<commit_after>import { event as d3_event, select as d3_select } from 'd3-selection';

import { geoVecLength } from '../geo';
import { modeBrowse } from '../modes/browse';
import { modeSelect } from '../modes/select';
import { modeSelectData } from '../modes/select_data';
import { modeSelectNote } from '../modes/select_note';
import { modeSelectError } from '../modes/select_error';
import { osmEntity, osmNote, QAItem } from '../osm';
import { utilFastMouse } from '../util/util';

export function behaviorSelect(context) {
    var _tolerancePx = 4; // see also behaviorDrag
    var _lastMouseEvent = null;
    var _showMenu = false;
    var _downPointers = {};
    var _longPressTimeout = null;
    var _lastInteractionType = null;
    // the id of the down pointer that's enabling multiselection while down
    var _multiselectionPointerId = null;

    // use pointer events on supported platforms; fallback to mouse events
    var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';

    function keydown() {

        if (d3_event.keyCode === 32) {
            // don't react to spacebar events during text input
            var activeNode = document.activeElement;
            if (activeNode && new Set(['INPUT', 'TEXTAREA']).has(activeNode.nodeName)) return;
        }

        if (d3_event.keyCode === 93 ||  // context menu key
            d3_event.keyCode === 32) {  // spacebar
            d3_event.preventDefault();
        }

        if (d3_event.repeat) return; // ignore repeated events for held keys

        // if any key is pressed the user is probably doing something other than long-pressing
        cancelLongPress();

        if (d3_event.shiftKey) {
            context.surface()
                .classed('behavior-multiselect', true);
        }

        if (d3_event.keyCode === 32) {  // spacebar
            if (!_downPointers.spacebar && _lastMouseEvent) {
                cancelLongPress();
                _longPressTimeout = window.setTimeout(didLongPress, 500, 'spacebar', 'spacebar');

                _downPointers.spacebar = {
                    firstEvent: _lastMouseEvent,
                    lastEvent: _lastMouseEvent
                };
            }
        }
    }

    function keyup() {
        cancelLongPress();

        if (!d3_event.shiftKey) {
            context.surface()
                .classed('behavior-multiselect', false);
        }

        if (d3_event.keyCode === 93) {  // context menu key
            d3_event.preventDefault();
            _lastInteractionType = 'menukey';
            contextmenu();
        } else if (d3_event.keyCode === 32) {  // spacebar
            var pointer = _downPointers.spacebar;
            if (pointer) {
                delete _downPointers.spacebar;

                if (pointer.done) return;

                d3_event.preventDefault();
                _lastInteractionType = 'spacebar';
                click(pointer.firstEvent, pointer.lastEvent);
            }
        }
    }

    function pointerdown() {
        var id = (d3_event.pointerId || 'mouse').toString();

        cancelLongPress();

        if (d3_event.buttons && d3_event.buttons !== 1) return;

        _longPressTimeout = window.setTimeout(didLongPress, 500, id, 'longdown-' + (d3_event.pointerType || 'mouse'));

        _downPointers[id] = {
            firstEvent: d3_event,
            lastEvent: d3_event
        };
    }

    function didLongPress(id, interactionType) {
        var pointer = _downPointers[id];
        if (!pointer) return;

        for (var i in _downPointers) {
            // don't allow this or any currently down pointer to trigger another click
            _downPointers[i].done = true;
        }

        // treat long presses like right-clicks
        _longPressTimeout = null;
        _lastInteractionType = interactionType;
        _showMenu = true;

        click(pointer.firstEvent, pointer.lastEvent);
    }

    function pointermove() {
        var id = (d3_event.pointerId || 'mouse').toString();
        if (_downPointers[id]) {
            _downPointers[id].lastEvent = d3_event;
        }
        if (!d3_event.pointerType || d3_event.pointerType === 'mouse') {
            _lastMouseEvent = d3_event;
            if (_downPointers.spacebar) {
                _downPointers.spacebar.lastEvent = d3_event;
            }
        }
    }

    function pointerup() {
        var id = (d3_event.pointerId || 'mouse').toString();
        var pointer = _downPointers[id];
        if (!pointer) return;

        delete _downPointers[id];

        if (_multiselectionPointerId === id) {
            _multiselectionPointerId = null;
        }

        if (pointer.done) return;

        click(pointer.firstEvent, d3_event);
    }

    function pointercancel() {
        var id = (d3_event.pointerId || 'mouse').toString();
        if (!_downPointers[id]) return;

        delete _downPointers[id];

        if (_multiselectionPointerId === id) {
            _multiselectionPointerId = null;
        }
    }

    function contextmenu() {
        var e = d3_event;
        e.preventDefault();

        if (!+e.clientX && !+e.clientY) {
            if (_lastMouseEvent) {
                e.sourceEvent = _lastMouseEvent;
            } else {
                return;
            }
        } else {
            _lastMouseEvent = d3_event;
            _lastInteractionType = 'rightclick';
        }

        _showMenu = true;
        click(d3_event, d3_event);
    }

    function click(firstEvent, lastEvent) {
        cancelLongPress();

        var mapNode = context.container().select('.main-map').node();

        // Use the `main-map` coordinate system since the surface and supersurface
        // are transformed when drag-panning.
        var pointGetter = utilFastMouse(mapNode);
        var p1 = pointGetter(firstEvent);
        var p2 = pointGetter(lastEvent);
        var dist = geoVecLength(p1, p2);

        if (dist > _tolerancePx ||
            !mapContains(lastEvent)) {

            resetProperties();
            return;
        }

        var targetDatum = lastEvent.target.__data__;

        if (!_multiselectionPointerId) {
            // If a different pointer than the one triggering this click is down on the
            // selection, treat this and all future clicks as multiselection until that
            // pointer is raised.
            var selectionPointer = context.mode().id === 'select' && pointerDownOnSelection();
            if (selectionPointer) {
                _multiselectionPointerId = selectionPointer;
            }
        }

        // only support multiselect if data is already selected
        var isMultiselect = context.mode().id === 'select' && (
            (d3_event && d3_event.shiftKey) ||
            context.surface().select('.lasso').node() ||
            _multiselectionPointerId
        );

        processClick(targetDatum, isMultiselect, p2);

        function mapContains(event) {
            var rect = mapNode.getBoundingClientRect();
            return event.clientX >= rect.left &&
                event.clientX <= rect.right &&
                event.clientY >= rect.top &&
                event.clientY <= rect.bottom;
        }
    }

    function pointerDownOnSelection() {
        var selectedIds = context.mode().id === 'select' && context.mode().selectedIDs();
        for (var id in _downPointers) {
            if (id === 'spacebar') continue;

            var datum = _downPointers[id].firstEvent.target.__data__;
            var entity = (datum && datum.properties && datum.properties.entity) || datum;
            if (selectedIds.indexOf(entity.id) !== -1) return id;
        }
        return null;
    }

    function processClick(datum, isMultiselect, point) {
        var mode = context.mode();
        var showMenu = _showMenu;
        var interactionType = _lastInteractionType;

        var entity = datum && datum.properties && datum.properties.entity;
        if (entity) datum = entity;

        if (datum && datum.type === 'midpoint') {
            // treat targeting midpoints as if targeting the parent way
            datum = datum.parents[0];
        }

        var newMode;

        if (datum instanceof osmEntity) {
            // targeting an entity
            var selectedIDs = context.selectedIDs();
            context.selectedNoteID(null);
            context.selectedErrorID(null);

            if (!isMultiselect) {
                if (selectedIDs.length <= 1 || !showMenu) {
                    // always enter modeSelect even if the entity is already
                    // selected since listeners may expect `context.enter` events,
                    // e.g. in the walkthrough
                    newMode = mode.id === 'select' ? mode.selectedIDs([datum.id]) : modeSelect(context, [datum.id]).selectBehavior(behavior);
                    context.enter(newMode);
                }

            } else {
                if (selectedIDs.indexOf(datum.id) !== -1) {
                    // clicked entity is already in the selectedIDs list..
                    if (!showMenu) {
                        // deselect clicked entity, then reenter select mode or return to browse mode..
                        selectedIDs = selectedIDs.filter(function(id) { return id !== datum.id; });
                        newMode = selectedIDs.length ? mode.selectedIDs(selectedIDs) : modeBrowse(context).selectBehavior(behavior);
                        context.enter(newMode);
                    }
                } else {
                    // clicked entity is not in the selected list, add it..
                    selectedIDs = selectedIDs.concat([datum.id]);
                    newMode = mode.selectedIDs(selectedIDs);
                    context.enter(newMode);
                }
            }

        } else if (datum && datum.__featurehash__ && !isMultiselect) {
            // targeting custom data
            context
                .selectedNoteID(null)
                .enter(modeSelectData(context, datum));

        } else if (datum instanceof osmNote && !isMultiselect) {
            // targeting a note
            context
                .selectedNoteID(datum.id)
                .enter(modeSelectNote(context, datum.id));

        } else if (datum instanceof QAItem & !isMultiselect) {
            // targeting an external QA issue
            context
                .selectedErrorID(datum.id)
                .enter(modeSelectError(context, datum.id, datum.service));

        } else {
            // targeting nothing
            context.selectedNoteID(null);
            context.selectedErrorID(null);
            if (!isMultiselect && mode.id !== 'browse') {
                context.enter(modeBrowse(context));
            }
        }

        context.ui().closeEditMenu();

        // always request to show the edit menu in case the mode needs it
        if (showMenu) context.ui().showEditMenu(point, interactionType);

        resetProperties();
    }

    function cancelLongPress() {
        if (_longPressTimeout) window.clearTimeout(_longPressTimeout);
        _longPressTimeout = null;
    }

    function resetProperties() {
        cancelLongPress();
        _showMenu = false;
        _lastInteractionType = null;
        // don't reset _lastMouseEvent since it might still be useful
    }

    function behavior(selection) {
        resetProperties();
        _lastMouseEvent = context.map().lastPointerEvent();

        d3_select(window)
            .on('keydown.select', keydown)
            .on('keyup.select', keyup)
            .on(_pointerPrefix + 'move.select', pointermove, true)
            .on(_pointerPrefix + 'up.select', pointerup, true)
            .on('pointercancel.select', pointercancel, true)
            .on('contextmenu.select-window', function() {
                // Edge and IE really like to show the contextmenu on the
                // menubar when user presses a keyboard menu button
                // even after we've already preventdefaulted the key event.
                var e = d3_event;
                if (+e.clientX === 0 && +e.clientY === 0) {
                    d3_event.preventDefault();
                }
            });

        selection
            .on(_pointerPrefix + 'down.select', pointerdown)
            .on('contextmenu.select', contextmenu);

        if (d3_event && d3_event.shiftKey) {
            context.surface()
                .classed('behavior-multiselect', true);
        }
    }

    behavior.off = function(selection) {
        cancelLongPress();

        d3_select(window)
            .on('keydown.select', null)
            .on('keyup.select', null)
            .on('contextmenu.select-window', null)
            .on(_pointerPrefix + 'move.select', null, true)
            .on(_pointerPrefix + 'up.select', null, true)
            .on('pointercancel.select', null, true);

        selection
            .on(_pointerPrefix + 'down.select', null)
            .on('contextmenu.select', null);

        context.surface()
            .classed('behavior-multiselect', false);
    };

    return behavior;
}
<|endoftext|>


<commit_before>define(["dojo/_base/declare", "ppwcode/contracts/_Mixin",
        "dojo/store/util/QueryResults", "dojo/store/util/SimpleQueryEngine",
        "dojo/store/Observable" /*=====, "./api/Store" =====*/],
  function(declare, _ContractsMixin,
           QueryResults, SimpleQueryEngine,
           Observable /*=====, Store =====*/) {

    // No base class, but for purposes of documentation, the base class is dojo/store/api/Store
    var Base = null;
    /*===== Base = Store; =====*/

    var OurStore = declare([_ContractsMixin, Base], {
      // summary:
      //		An in-memory object store like dojo/store/Memory, but with the following
      //    differences:
      //    1) We don't change the objects that are kept in the store. If you are not very
      //       carefull, dojo/store/Memory inserts an "id" property for its own benefit,
      //       whose behavior depends on chance (random).
      //    2) We are aware of Stateful.
      //       If an element changes, we also send changed events. You do not have
      //       to re-put.
      //    3) removeAll, loadAll
      // description:
      //    On development we immediately need constructor, put, removeAll, loadAll, and query.
      //    get, getIdentity, and remove are optional.
      //    Query needs an array to be returned. It therefor is wasteful to keep an index, which
      //    only helps with get and remove.
      //    idProperty is wasteful and stupid, but used in many uses of Store.
      //    Therefor, we will wrap the real objects in a helper object. The helper object
      //    has the id-attribute. It's value is calculated and kept in sync by a
      //    idPropertyDerivation function.
      //    If idProperty changes, all bets are off.
      //
      //    Based on dojo/store/Memory

      _c_invar: [
        function() {return this._c_prop_mandatory("idProperty");},
        function() {return this._c_prop_string("idProperty");},
        function() {return this._c_prop_mandatory("idPropertyDerivation");},
        function() {return this._c_prop_function("idPropertyDerivation");}
      ],

      // _data: Array
      //   The internal representation of the data
      _data: null,

      // idProperty: String
      //		Indicates the property to use as the identity property. Don't change this.
      idProperty: "id",

      // idPropertyDerivation: Function
      //    Stateful --> String
      //    Derives a unique id from a Stateful object as argument.
      idPropertyDerivation: null,

      // queryEngine: Function
      //		Defines the query engine to use for querying the data store
      queryEngine: SimpleQueryEngine,

      _wrap: function(/*Stateful*/ s) {
        var thisStore = this;

        function addWatcher(wrapper) {
          var watcher = function(name, oldValue, newValue) {
            // on change, see if the id has changed
            // if it has, signal removal, and a new addition
            // if is has not, signal change
            var oldId = wrapper[thisStore.idProperty];
            var newId = thisStore.idPropertyDerivation();
            if (oldId != newId) {
              wrapper[thisStore.idProperty] = newId;
              thisStore.notify(null, oldId);
              thisStore.notify(wrapper.data, null);
            }
            else {
              thisStore.notify(wrapper.data, oldId);
            }
          };
          wrapper.watcher = wrapper.data.watch(watcher);
        }

        var id = thisStore.idPropertyDerivation(s);
        var result = {};
        result[thisStore.idProperty] = id;
        result.data = s;
        addWatcher(result);
        return result;
      },

      constructor: function(options){
        // summary:
        //		Creates a store of stateful objects.
        // options: dojo/store/Memory
        //		This provides any configuration information that will be mixed into the store.
        //		This should generally include the data property to provide the starting set of data.

        this._c_pre(function() {return options;});
        this._c_pre(function() {return this._c_prop_mandatory(options, "idPropertyDerivation");});

        var thisStore = this;
        Object.keys(options).
          filter(function(key) {return key !== "data";}).
          forEach(function(key) {thisStore[key] = options[key];});
        if (options && options.data) {
          thisStore._data = options.data.reduce(
            function(acc, element) {
              acc.push(thisStore._wrap(element));
              return acc;
            },
            []
          );
        }
        else {
          thisStore._data = [];
        }
      },

      get: function(id){
        // summary:
        //		Retrieves an object by its identity
        // id: Number
        //		The identity to use to lookup the object
        // returns: Object
        //		The object in the store that matches the given id.

        var filterResult = this._data.filter(function(wrapper) {
          return wrapper[this.idProperty] === id;
        });
        if (filterResult.length > 1) {
          throw "ERROR: duplicate id in store (id: " + id + ", store: " + this + ")";
        }
        else if (filterResult.length === 0) {
          return null;
        }
        else {
          return filterResult[0].data;
        }
      },

      getIdentity: function(object){
        // summary:
        //		Returns an object's identity
        // object: Object
        //		The object to get the identity from
        // returns: Number

        var filterResult = this._data.filter(function(wrapper) {
          return wrapper[data] === object;
        });
        if (filterResult.length > 1) {
          throw "ERROR: object in store more then once (object: " + object + ", store: " + this + ")";
        }
        else if (filterResult.length === 0) {
          return null;
        }
        else {
          return filterResult[0][this.idProperty];
        }
      },

      put: function(object) {
        // summary:
        //		Stores an object. Options are ignored.
        // object: Object
        //		The object to store.
        // returns: Number

        var wrapper = this._wrap(object);
        this._data.push(wrapper); // Store spec doesn't say what we should return. We return nothing.
      },

      add: function(object){
        // summary:
        //		Creates an object, throws an error if the object already exists
        //		Options are ignored.
        // object: Object
        //		The object to store.
        // returns: Number

        if (this.getIdentity(object)) {
          throw new Error("Object already exists");
        }
        this.put(object); // Store spec doesn't say what we should return. We return nothing.
      },

      remove: function(id){
        // summary:
        //		Deletes an object by its identity
        // id: Number
        //		The identity to use to delete the object
        // returns: Boolean
        //		Returns true if an object was removed, falsy (undefined) if no object matched the id

        var thisStore = this;
        var foundAtIndex = this._data.reduce(
          function(acc, element, index) {
            if (element[thisStore.idProperty] === id) {
              if (acc > -1) {
                throw "ERROR: duplicate id in store (id: " + id + ", store: " + thisStore + ")";
              }
              else {
                element.watcher.unwatch();
                return index;
              }
            }
            else {
              return acc;
            }
          },
          -1
        );
        if (foundAtIndex > -1) {
          thisStore._data.splice(foundAtIndex, 1);
        }
        // Store spec doesn't say what we should return. We return nothing.
      },

      query: function(query, options){
        // summary:
        //		Queries the store for objects.
        // query: Object
        //		The query to use for retrieving objects from the store.
        // options: dojo/store/api/Store.QueryOptions?
        //		The optional arguments to apply to the resultset.
        // returns: dojo/store/api/Store.QueryResults
        //		The results of the query, extended with iterative methods.
        //
        // example:
        //		Given the following store:
        //
        // 	|	var store = new Memory({
        // 	|		data: [
        // 	|			{id: 1, name: "one", prime: false },
        //	|			{id: 2, name: "two", even: true, prime: true},
        //	|			{id: 3, name: "three", prime: true},
        //	|			{id: 4, name: "four", even: true, prime: false},
        //	|			{id: 5, name: "five", prime: true}
        //	|		]
        //	|	});
        //
        //	...find all items where "prime" is true:
        //
        //	|	var results = store.query({ prime: true });
        //
        //	...or find all items where "even" is true:
        //
        //	|	var results = store.query({ even: true });
        return QueryResults(this.queryEngine(query, options)(this.data));
      },

      loadAll: function(data) {
        // summary:
        //   replaces current data with new data; common objects
        //   are not signalled as removed and added again

        var thisStore = this;
        var inData = data.slice(0);
        thisStore._data = thisStore._data.reduce(
          function (acc, wrapper) {
            var indexInData = inData.indexOf(wrapper.data);
            if (indexInData < 0) {
              // not in inData; don't add to acc, signal removal
              wrapper.watcher.unwatch();
              thisStore.notify(null, wrapper[thisStore.idProperty]);
            }
            else {
              // keep the element (add to acc), and note handled.
              inData.splice(indexInData, 1);
              acc.push(wrapper);
            }
            return acc;
          },
          []
        );
        // what is left in inData needs to be added
        inData.forEach(function(element) {
          thisStore.put(element);
        });
      },

      removeAll: function() {
        var thisStore = this;

        var oldData = thisStore._data;
        thisStore._data = [];
        oldData.forEach(function(wrapper) {
          wrapper.watcher.unwatch();
          thisStore.notify(null, wrapper[thisStore.idProperty]);
        });
      }

    });

    var OurObservableStore = Observable(OurStore);

    return OurObservableStore;

  });
<commit_msg>comment change<commit_after>define(["dojo/_base/declare", "ppwcode/contracts/_Mixin",
        "dojo/store/util/QueryResults", "dojo/store/util/SimpleQueryEngine",
        "dojo/store/Observable" /*=====, "./api/Store" =====*/],
  function(declare, _ContractsMixin,
           QueryResults, SimpleQueryEngine,
           Observable /*=====, Store =====*/) {

    // No base class, but for purposes of documentation, the base class is dojo/store/api/Store
    var Base = null;
    /*===== Base = Store; =====*/

    var OurStore = declare([_ContractsMixin, Base], {
      // summary:
      //		An in-memory Observable object store like dojo/store/Memory wrapped in dojo/store/Observable,
      //    but with the following differences:
      //    1) We don't change the objects that are kept in the store. If you are not very
      //       carefull, dojo/store/Memory inserts an "id" property for its own benefit,
      //       whose behavior depends on chance (random).
      //    2) We are aware of Stateful.
      //       If an element changes, we also send changed events. You do not have
      //       to re-put.
      //    3) removeAll, loadAll
      // description:
      //    On development we immediately need constructor, put, removeAll, loadAll, and query.
      //    get, getIdentity, and remove are optional.
      //    Query needs an array to be returned. It therefor is wasteful to keep an index, which
      //    only helps with get and remove.
      //    idProperty is wasteful and stupid, but used in many uses of Store.
      //    Therefor, we will wrap the real objects in a helper object. The helper object
      //    has the id-attribute. It's value is calculated and kept in sync by a
      //    idPropertyDerivation function.
      //    If idProperty changes, all bets are off.
      //
      //    Based on dojo/store/Memory

      _c_invar: [
        function() {return this._c_prop_mandatory("idProperty");},
        function() {return this._c_prop_string("idProperty");},
        function() {return this._c_prop_mandatory("idPropertyDerivation");},
        function() {return this._c_prop_function("idPropertyDerivation");}
      ],

      // _data: Array
      //   The internal representation of the data
      _data: null,

      // idProperty: String
      //		Indicates the property to use as the identity property. Don't change this.
      idProperty: "id",

      // idPropertyDerivation: Function
      //    Stateful --> String
      //    Derives a unique id from a Stateful object as argument.
      idPropertyDerivation: null,

      // queryEngine: Function
      //		Defines the query engine to use for querying the data store
      queryEngine: SimpleQueryEngine,

      _wrap: function(/*Stateful*/ s) {
        var thisStore = this;

        function addWatcher(wrapper) {
          var watcher = function(name, oldValue, newValue) {
            // on change, see if the id has changed
            // if it has, signal removal, and a new addition
            // if is has not, signal change
            var oldId = wrapper[thisStore.idProperty];
            var newId = thisStore.idPropertyDerivation();
            if (oldId != newId) {
              wrapper[thisStore.idProperty] = newId;
              thisStore.notify(null, oldId);
              thisStore.notify(wrapper.data, null);
            }
            else {
              thisStore.notify(wrapper.data, oldId);
            }
          };
          wrapper.watcher = wrapper.data.watch(watcher);
        }

        var id = thisStore.idPropertyDerivation(s);
        var result = {};
        result[thisStore.idProperty] = id;
        result.data = s;
        addWatcher(result);
        return result;
      },

      constructor: function(options){
        // summary:
        //		Creates a store of stateful objects.
        // options: dojo/store/Memory
        //		This provides any configuration information that will be mixed into the store.
        //		This should generally include the data property to provide the starting set of data.

        this._c_pre(function() {return options;});
        this._c_pre(function() {return this._c_prop_mandatory(options, "idPropertyDerivation");});

        var thisStore = this;
        Object.keys(options).
          filter(function(key) {return key !== "data";}).
          forEach(function(key) {thisStore[key] = options[key];});
        if (options && options.data) {
          thisStore._data = options.data.reduce(
            function(acc, element) {
              acc.push(thisStore._wrap(element));
              return acc;
            },
            []
          );
        }
        else {
          thisStore._data = [];
        }
      },

      get: function(id){
        // summary:
        //		Retrieves an object by its identity
        // id: Number
        //		The identity to use to lookup the object
        // returns: Object
        //		The object in the store that matches the given id.

        var filterResult = this._data.filter(function(wrapper) {
          return wrapper[this.idProperty] === id;
        });
        if (filterResult.length > 1) {
          throw "ERROR: duplicate id in store (id: " + id + ", store: " + this + ")";
        }
        else if (filterResult.length === 0) {
          return null;
        }
        else {
          return filterResult[0].data;
        }
      },

      getIdentity: function(object){
        // summary:
        //		Returns an object's identity
        // object: Object
        //		The object to get the identity from
        // returns: Number

        var filterResult = this._data.filter(function(wrapper) {
          return wrapper[data] === object;
        });
        if (filterResult.length > 1) {
          throw "ERROR: object in store more then once (object: " + object + ", store: " + this + ")";
        }
        else if (filterResult.length === 0) {
          return null;
        }
        else {
          return filterResult[0][this.idProperty];
        }
      },

      put: function(object) {
        // summary:
        //		Stores an object. Options are ignored.
        // object: Object
        //		The object to store.
        // returns: Number

        var wrapper = this._wrap(object);
        this._data.push(wrapper); // Store spec doesn't say what we should return. We return nothing.
      },

      add: function(object){
        // summary:
        //		Creates an object, throws an error if the object already exists
        //		Options are ignored.
        // object: Object
        //		The object to store.
        // returns: Number

        if (this.getIdentity(object)) {
          throw new Error("Object already exists");
        }
        this.put(object); // Store spec doesn't say what we should return. We return nothing.
      },

      remove: function(id){
        // summary:
        //		Deletes an object by its identity
        // id: Number
        //		The identity to use to delete the object
        // returns: Boolean
        //		Returns true if an object was removed, falsy (undefined) if no object matched the id

        var thisStore = this;
        var foundAtIndex = this._data.reduce(
          function(acc, element, index) {
            if (element[thisStore.idProperty] === id) {
              if (acc > -1) {
                throw "ERROR: duplicate id in store (id: " + id + ", store: " + thisStore + ")";
              }
              else {
                element.watcher.unwatch();
                return index;
              }
            }
            else {
              return acc;
            }
          },
          -1
        );
        if (foundAtIndex > -1) {
          thisStore._data.splice(foundAtIndex, 1);
        }
        // Store spec doesn't say what we should return. We return nothing.
      },

      query: function(query, options){
        // summary:
        //		Queries the store for objects.
        // query: Object
        //		The query to use for retrieving objects from the store.
        // options: dojo/store/api/Store.QueryOptions?
        //		The optional arguments to apply to the resultset.
        // returns: dojo/store/api/Store.QueryResults
        //		The results of the query, extended with iterative methods.
        //
        // example:
        //		Given the following store:
        //
        // 	|	var store = new Memory({
        // 	|		data: [
        // 	|			{id: 1, name: "one", prime: false },
        //	|			{id: 2, name: "two", even: true, prime: true},
        //	|			{id: 3, name: "three", prime: true},
        //	|			{id: 4, name: "four", even: true, prime: false},
        //	|			{id: 5, name: "five", prime: true}
        //	|		]
        //	|	});
        //
        //	...find all items where "prime" is true:
        //
        //	|	var results = store.query({ prime: true });
        //
        //	...or find all items where "even" is true:
        //
        //	|	var results = store.query({ even: true });
        return QueryResults(this.queryEngine(query, options)(this.data));
      },

      loadAll: function(data) {
        // summary:
        //   replaces current data with new data; common objects
        //   are not signalled as removed and added again

        var thisStore = this;
        var inData = data.slice(0);
        thisStore._data = thisStore._data.reduce(
          function (acc, wrapper) {
            var indexInData = inData.indexOf(wrapper.data);
            if (indexInData < 0) {
              // not in inData; don't add to acc, signal removal
              wrapper.watcher.unwatch();
              thisStore.notify(null, wrapper[thisStore.idProperty]);
            }
            else {
              // keep the element (add to acc), and note handled.
              inData.splice(indexInData, 1);
              acc.push(wrapper);
            }
            return acc;
          },
          []
        );
        // what is left in inData needs to be added
        inData.forEach(function(element) {
          thisStore.put(element);
        });
      },

      removeAll: function() {
        var thisStore = this;

        var oldData = thisStore._data;
        thisStore._data = [];
        oldData.forEach(function(wrapper) {
          wrapper.watcher.unwatch();
          thisStore.notify(null, wrapper[thisStore.idProperty]);
        });
      }

    });

    var OurObservableStore = Observable(OurStore);

    return OurObservableStore;

  });
<|endoftext|>


<commit_before>import EditFormRoute from 'prototype-ember-cli-application/routes/edit-form';
import Model from 'prototype-ember-cli-application/models/employee';

export default EditFormRoute.extend({
    view: Model.Views.EmployeeeE,
    modelTypeKey: 'employee'
});
<commit_msg>Fix Edit Form opening (fixed a typo)<commit_after>import EditFormRoute from 'prototype-ember-cli-application/routes/edit-form';
import Model from 'prototype-ember-cli-application/models/employee';

export default EditFormRoute.extend({
    view: Model.Views.EmployeeE,
    modelTypeKey: 'employee'
});
<|endoftext|>


<commit_before>import AuthenticatedRoute from '../-authenticated-route';

export default AuthenticatedRoute.extend({
  actions: {
    willTransition: function () {
      this.controller
        .setProperties({
          emailNotificationsSuccess: false,
          emailNotificationsError: false,
        })
        .clear();
    },
  },
  async model() {
    let { ownedCrates, currentUser: user } = this.session;

    if (!ownedCrates) {
      await this.session.fetchUser();
      ({ ownedCrates } = this.session);
    }

    let apiTokens = this.store.findAll('api-token');

    return { user, ownedCrates, api_tokens: apiTokens };
  },
});
<commit_msg>me/index: Move `controller.setProperties()` call into `setupController()`<commit_after>import AuthenticatedRoute from '../-authenticated-route';

export default AuthenticatedRoute.extend({
  async model() {
    let { ownedCrates, currentUser: user } = this.session;

    if (!ownedCrates) {
      await this.session.fetchUser();
      ({ ownedCrates } = this.session);
    }

    let apiTokens = this.store.findAll('api-token');

    return { user, ownedCrates, api_tokens: apiTokens };
  },

  setupController(controller) {
    this._super(...arguments);

    controller.setProperties({
      emailNotificationsSuccess: false,
      emailNotificationsError: false,
    });
  },
});
<|endoftext|>


<commit_before>var _ = require("underscore");

Parse.Cloud.define("searchAll", function(request, response) {
	var Scope = {
		All: 0,
		Users: 1,
		Posts: 2,
		Albums: 3,
		Groups: 4
	};
	var filter = request.params.filter;
	var probe = request.params.probe;
	var user = request.user;
	var searchResults = [];
	var promises = [];
	function getQuery(className) {
		return new Parse.Query(Parse.Object.extend(className));
	}
	if (filter == Scope.All || filter == Scope.Posts) {
		// search posts and photos from roll
		function queryPostField(field) {
			var query = getQuery("Post");
			query.equalTo("createdBy", user).matches("title", "(?i)" + probe).matches("title", "(?i)" + probe);
			return query.find();
		}
		var tagQuery = getQuery("Tag");
		tagQuery.matches("name", "(?i)" + probe)
		//promises.push(tagQuery);		//TODO: This isn't right. Must find posts that contain these tags
		promises.push(queryPostField("title"));
		promises.push(queryPostField("notes"));
	}
	if (filter == Scope.All || filter == Scope.Albums || filter == Scope.Groups) {
		// search groups and albums
		function queryAlbumGroupField(type, field) {
			var query = getQuery("Album");
			query.equalTo("type", type);
			if (type == "album")
				query.equalTo("createdBy", user);
			query.matches(field,  "(?i)" + probe);
			return query.find()
		}
		if (filter != Scope.Groups) {
			promises.push(queryAlbumGroupField("album", "title"));
			promises.push(queryAlbumGroupField("album", "notes"));
		}
		if (filter != Scope.Albums) {
			promises.push(queryAlbumGroupField("group", "title"));
			promises.push(queryAlbumGroupField("group", "notes"));
		}
	}
	if (filter == Scope.All || filter == Scope.Users) {
		// search users
		function queryUserField(field) {
			var query = getQuery("_User");
			query.matches(field,  "(?i)" + probe);
			return query.find();
		}
		promises.push(queryUserField("username"));
		promises.push(queryUserField("email"));
		promises.push(queryUserField("name"));
	}
	Parse.Promise.when(promises).then(function(results) {
		// TODO: sort the results
		var finalResults = [];
		_.each(results, function accum(r) { finalResults = finalResults.concat(r) });
		console.log("[0]="+ Date.parse(finalResults[0].get("createdAt")));
		try {
		finalResults = _.uniq(finalResults);
		_.sortBy(finalResults, function(a) { return a.get("createdAt") });
		}
		catch (e) {
			console.log(e);
		}
		response.success(finalResults);
		console.log("********");
	});
});
<commit_msg>Search in back end<commit_after>var _ = require("underscore");

Parse.Cloud.define("searchAll", function(request, response) {
	var Scope = {
		All: 0,
		Users: 1,
		Posts: 2,
		Albums: 3,
		Groups: 4
	};
	var filter = request.params.filter;
	var probe = request.params.probe;
	var user = request.user;
	var searchResults = [];
	var promises = [];
	function getQuery(className) {
		return new Parse.Query(Parse.Object.extend(className));
	}
	if (filter == Scope.All || filter == Scope.Posts) {
		// search posts and photos from roll
		function queryPostField(field) {
			var query = getQuery("Post");
			query.equalTo("createdBy", user).matches("title", "(?i)" + probe).matches("title", "(?i)" + probe);
			return query.find();
		}
		var tagQuery = getQuery("Tag");
		tagQuery.matches("name", "(?i)" + probe)
		//promises.push(tagQuery);		//TODO: This isn't right. Must find posts that contain these tags
		promises.push(queryPostField("title"));
		promises.push(queryPostField("notes"));
	}
	if (filter == Scope.All || filter == Scope.Albums || filter == Scope.Groups) {
		// search groups and albums
		function queryAlbumGroupField(type, field) {
			var query = getQuery("Album");
			query.equalTo("type", type);
			if (type == "album")
				query.equalTo("createdBy", user);
			query.matches(field,  "(?i)" + probe);
			return query.find()
		}
		if (filter != Scope.Groups) {
			promises.push(queryAlbumGroupField("album", "title"));
			promises.push(queryAlbumGroupField("album", "notes"));
		}
		if (filter != Scope.Albums) {
			promises.push(queryAlbumGroupField("group", "title"));
			promises.push(queryAlbumGroupField("group", "notes"));
		}
	}
	if (filter == Scope.All || filter == Scope.Users) {
		// search users
		function queryUserField(field) {
			var query = getQuery("_User");
			query.matches(field,  "(?i)" + probe);
			return query.find();
		}
		promises.push(queryUserField("username"));
		promises.push(queryUserField("email"));
		promises.push(queryUserField("name"));
	}
	Parse.Promise.when(promises).then(function(results) {
		var finalResults = [];
		try {
			_.each(results, function accum(r) { finalResults = finalResults.concat(r) });
			finalResults = _.uniq(finalResults);
			_.sortBy(finalResults, function(a) { return Date.parse(a.get("createdAt")) });
		}
		catch (e) {
			console.log(e);
		}
		response.success(finalResults);
	});
});
<|endoftext|>


<commit_before>import jQuery from 'jquery';

import anime from 'animejs';
import { Client } from 'chomex';
import numeral from 'numeral';

import { ERROR, TextColor } from './constants';

const messageClient = new Client(chrome.runtime);

function colorFromPercentage(percentage) {
  switch (true) {
    case (percentage < 2):
      return TextColor.RED;
    case (percentage >= 2 && percentage < 48):
      return TextColor.ORANGE;
    default:
      return TextColor.GREEN;
  }
}

async function fetchAccessTokenAsync(elems) {
  const { data } = await messageClient.message('/access-token/get');

  if (data === ERROR) {
    return elems.ACCESS_TOKEN_FIELD.val('N/A');
  }

  return elems.ACCESS_TOKEN_FIELD.val(data);
}

async function fetchRateLimitAsync(elems) {
  const $accessTokenSaveButton = elems.ACCESS_TOKEN_SAVE_BUTTON;
  const $progressBarFilled = elems.PROGRESS_BAR_FILLED;
  $accessTokenSaveButton.attr('disabled', true);
  $progressBarFilled.css({ width: '0%' });

  const { data } = await messageClient.message('/rate-limit');

  if (data === ERROR) {
    elems.PROGRESS_BAR_TEXT.css({ color: colorFromPercentage(0) }).text('N/A');
    return false;
  }

  const { remaining, limit } = data;
  const formattedRateLimit = numeral(remaining).format('0,0');
  const percentage = limit > 0 ? parseInt((remaining / limit) * 100, 10) : 0;
  const finalbackgroundColor = colorFromPercentage(percentage);
  const textStyle = { color: finalbackgroundColor };

  $progressBarFilled.css({ background: colorFromPercentage(0) });

  // NOTE returns finished Promise of animation
  await anime({
    backgroundColor: finalbackgroundColor,
    duration: 750,
    easing: 'easeInOutQuad',
    targets: $progressBarFilled.get(0),
    width: `${percentage}%`,
    begin: () => elems.PROGRESS_BAR_TEXT.css(textStyle).text(formattedRateLimit),
  }).finished;

  return $accessTokenSaveButton.attr('disabled', false);
}

async function sendAccessTokenAsync(elems) {
  const $accessTokenSaveButton = elems.ACCESS_TOKEN_SAVE_BUTTON;
  const accessToken = elems.ACCESS_TOKEN_FIELD.val();
  const originLabel = $accessTokenSaveButton.text();

  $accessTokenSaveButton.text('saved!');

  await messageClient.message('/access-token/set', { accessToken });
  fetchAccessTokenAsync(elems);
  await fetchRateLimitAsync(elems);

  return $accessTokenSaveButton.text(originLabel);
}

jQuery(document).ready(() => {
  const ELEMENTS = {
    ACCESS_TOKEN_FIELD: jQuery('#access-token-field'),
    ACCESS_TOKEN_SAVE_BUTTON: jQuery('#access-token-save-button'),
    PROGRESS_BAR_FILLED: jQuery('.progress-bar-filled'),
    PROGRESS_BAR_TEXT: jQuery('.progress-bar-text'),
  };

  ELEMENTS.ACCESS_TOKEN_SAVE_BUTTON.click(() => sendAccessTokenAsync(ELEMENTS));

  fetchAccessTokenAsync(ELEMENTS);
  fetchRateLimitAsync(ELEMENTS);
});
<commit_msg>fix(options): re-enable save button if access token is invalid<commit_after>import jQuery from 'jquery';

import anime from 'animejs';
import { Client } from 'chomex';
import numeral from 'numeral';

import { ERROR, TextColor } from './constants';

const messageClient = new Client(chrome.runtime);

function colorFromPercentage(percentage) {
  switch (true) {
    case (percentage < 2):
      return TextColor.RED;
    case (percentage >= 2 && percentage < 48):
      return TextColor.ORANGE;
    default:
      return TextColor.GREEN;
  }
}

async function fetchAccessTokenAsync(elems) {
  const { data } = await messageClient.message('/access-token/get');

  if (data === ERROR) {
    return elems.ACCESS_TOKEN_FIELD.val('N/A');
  }

  return elems.ACCESS_TOKEN_FIELD.val(data);
}

async function fetchRateLimitAsync(elems) {
  const $accessTokenSaveButton = elems.ACCESS_TOKEN_SAVE_BUTTON;
  const $progressBarFilled = elems.PROGRESS_BAR_FILLED;
  $accessTokenSaveButton.attr('disabled', true);
  $progressBarFilled.css({ width: '0%' });

  const { data } = await messageClient.message('/rate-limit');

  if (data === ERROR) {
    $accessTokenSaveButton.attr('disabled', false);
    elems.PROGRESS_BAR_TEXT.css({ color: colorFromPercentage(0) }).text('N/A');
    return false;
  }

  const { remaining, limit } = data;
  const formattedRateLimit = numeral(remaining).format('0,0');
  const percentage = limit > 0 ? parseInt((remaining / limit) * 100, 10) : 0;
  const finalbackgroundColor = colorFromPercentage(percentage);
  const textStyle = { color: finalbackgroundColor };

  $progressBarFilled.css({ background: colorFromPercentage(0) });

  // NOTE returns finished Promise of animation
  await anime({
    backgroundColor: finalbackgroundColor,
    duration: 750,
    easing: 'easeInOutQuad',
    targets: $progressBarFilled.get(0),
    width: `${percentage}%`,
    begin: () => elems.PROGRESS_BAR_TEXT.css(textStyle).text(formattedRateLimit),
  }).finished;

  return $accessTokenSaveButton.attr('disabled', false);
}

async function sendAccessTokenAsync(elems) {
  const $accessTokenSaveButton = elems.ACCESS_TOKEN_SAVE_BUTTON;
  const accessToken = elems.ACCESS_TOKEN_FIELD.val();
  const originLabel = $accessTokenSaveButton.text();

  $accessTokenSaveButton.text('saved!');

  await messageClient.message('/access-token/set', { accessToken });
  fetchAccessTokenAsync(elems);
  await fetchRateLimitAsync(elems);

  return $accessTokenSaveButton.text(originLabel);
}

jQuery(document).ready(() => {
  const ELEMENTS = {
    ACCESS_TOKEN_FIELD: jQuery('#access-token-field'),
    ACCESS_TOKEN_SAVE_BUTTON: jQuery('#access-token-save-button'),
    PROGRESS_BAR_FILLED: jQuery('.progress-bar-filled'),
    PROGRESS_BAR_TEXT: jQuery('.progress-bar-text'),
  };

  ELEMENTS.ACCESS_TOKEN_SAVE_BUTTON.click(() => sendAccessTokenAsync(ELEMENTS));

  fetchAccessTokenAsync(ELEMENTS);
  fetchRateLimitAsync(ELEMENTS);
});
<|endoftext|>