<commit_before>const Kraken = require('kraken-api');
const moment = require('moment');
const _ = require('lodash');
const exchangeUtils = require('../exchangeUtils');
const retry = exchangeUtils.retry;
const scientificToDecimal = exchangeUtils.scientificToDecimal;

const marketData = require('./kraken-markets.json');

const Trader = function(config) {
  _.bindAll(this);

  if(_.isObject(config)) {
    this.key = config.key;
    this.secret = config.secret;
    this.currency = config.currency.toUpperCase()
    this.asset = config.asset.toUpperCase();
  }

  this.name = 'kraken';
  this.since = null;

  this.market = _.find(Trader.getCapabilities().markets, (market) => {
    return market.pair[0] === this.currency && market.pair[1] === this.asset
  });
  this.pair = this.market.book;

  this.interval = 3100;

  this.kraken = new Kraken(
    this.key,
    this.secret,
    {timeout: +moment.duration(60, 'seconds')}
  );
}

const recoverableErrors = [
  'SOCKETTIMEDOUT',
  'TIMEDOUT',
  'CONNRESET',
  'CONNREFUSED',
  'NOTFOUND',
  'Service:Unavailable',
  'Request timed out',
  'Empty response',
  'API:Invalid nonce',
  'General:Temporary lockout',
  'Response code 525',
  'Service:Busy'
];

// errors that might mean
// the API call succeeded.
const unknownResultErrors = [
  'Response code 502',
  'Response code 504',
  'Response code 522',
  'Response code 520',
]

const includes = (str, list) => {
  if(!_.isString(str))
    return false;

  return _.some(list, item => str.includes(item));
}

Trader.prototype.handleResponse = function(funcName, callback, nonMutating, payload) {
  return (error, body) => {

    if(!error && !body) {
      error = new Error('Empty response');
    }

    if(error) {
      if(includes(error.message, recoverableErrors)) {
        error.notFatal = true;
      }

      if(includes(error.message, ['Rate limit exceeded'])) {
        error.notFatal = true;
        error.backoffDelay = 2500;
      }

      if(nonMutating && includes(error.message, unknownResultErrors)) {
        // this call only tried to retrieve data, safe to redo
        error.notFatal = true;
      }

      if(funcName === 'addOrder' && includes(error.message, unknownResultErrors)) {

        const { tradeType, amount, price } = payload;

        return setTimeout(() => {
          this.getRawOpenOrders((err2, orders) => {
            if(err2) {
              console.log('err2', err2);
              return callback(err2);
            }

            _.each(orders, (o, id) => {
              o.id = id;
            });

            const order = _.find(orders, o => {
              if(o.descr.type !== tradeType) {
                return false;
              }

              const ts = moment.unix((o.opentm + '').split('.')[0])
              if(moment().diff(ts, 'm') > 10) {
                return false;
              }

              // string vs float
              if(o.descr.price != price) {
                return false;
              }

              // string vs float
              if(o.vol != amount) {
                return false;
              }

              return true;
            });

            if(!order) {
              console.log('broken add order, appears not created:', {payload, orders: JSON.stringify(orders)});
              return this.addOrder(tradeType, amount, price, callback);
            }

            return callback(undefined, { catched: true, id: order.id });
          });
        }, 5000);
      }

      if(funcName === 'cancelOrder' && includes(error.message, unknownResultErrors)) {
        console.log('broken cancel');

        return setTimeout(() => {
          const handle = (err2, data) => {
            if(err2) {
              console.log('err2', err2);
              return callback(err2);
            }

            const order = _.get(data, `result["${payload}"]`);

            if(!_.isObject(order)) {
              console.log('refetched broken cancel, cannot find order...', data);
              throw 'a';
            }

            console.log(order);

            if(order.status !== 'canceled') {
              console.log(new Date, 'it still exists, retrying cancel');
              return this.cancelOrder(payload, callback);
            }

            console.log(new Date, 'it was canceled');
            return callback(undefined, true, { catched: true, filled: parseFloat(order.vol_exec) });

          };

          const reqData = {txid: payload};

          const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('checkOrder', cb, true));
          retry(null, fetch, handle);
        }, 5000);
      }

      return callback(error);
    }

    return callback(undefined, body);
  }
};

Trader.prototype.getTrades = function(since, callback, descending) {
  const startTs = since ? moment(since).valueOf() : null;

  const handle = (err, trades) => {
    if (err) return callback(err);

    var parsedTrades = [];
    _.each(trades.result[this.pair], function(trade) {
      // Even when you supply 'since' you can still get more trades than you asked for, it needs to be filtered
      if (_.isNull(startTs) || startTs < moment.unix(trade[2]).valueOf()) {
        parsedTrades.push({
          tid: moment.unix(trade[2]).valueOf() * 1000000,
          date: parseInt(Math.round(trade[2]), 10),
          price: parseFloat(trade[0]),
          amount: parseFloat(trade[1])
        });
      }
    }, this);

    if(descending)
      callback(undefined, parsedTrades.reverse());
    else
      callback(undefined, parsedTrades);
  };

  const reqData = {
    pair: this.pair
  };

  if(since) {
    // Kraken wants a tid, which is found to be timestamp_ms * 1000000 in practice. No clear documentation on this though
    reqData.since = startTs * 1000000;
  }

  const fetch = cb => this.kraken.api('Trades', reqData, this.handleResponse('getTrades', cb, true));
  retry(null, fetch, handle);
};

Trader.prototype.getPortfolio = function(callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    let assetAmount = parseFloat( data.result[this.market.prefixed[1]] );
    let currencyAmount = parseFloat( data.result[this.market.prefixed[0]] );

    if(!_.isNumber(assetAmount) || _.isNaN(assetAmount)) {
      console.log(`Kraken did not return portfolio for ${this.asset}, assuming 0.`);
      assetAmount = 0;
    }

    if(!_.isNumber(currencyAmount) || _.isNaN(currencyAmount)) {
      console.log(`Kraken did not return portfolio for ${this.currency}, assuming 0.`);
      currencyAmount = 0;
    }

    const portfolio = [
      { name: this.asset, amount: assetAmount },
      { name: this.currency, amount: currencyAmount }
    ];

    return callback(undefined, portfolio);
  };

  const fetch = cb => this.kraken.api('Balance', {}, this.handleResponse('getPortfolio', cb, true));
  retry(null, fetch, handle);
};

// This assumes that only limit orders are being placed with standard assets pairs
// It does not take into account volume discounts.
// Base maker fee is 0.16%, taker fee is 0.26%.
Trader.prototype.getFee = function(callback) {
  const makerFee = 0.16;
  callback(undefined, makerFee / 100);
};

Trader.prototype.getTicker = function(callback) {
  const handle = (err, data) => {
    if (err) return callback(err);

    const result = data.result[this.pair];
    const ticker = {
      ask: result.a[0],
      bid: result.b[0]
    };
    callback(undefined, ticker);
  };

  const reqData = {pair: this.pair}
  const fetch = cb => this.kraken.api('Ticker', reqData, this.handleResponse('getTicker', cb, true));
  retry(null, fetch, handle);
};

Trader.prototype.roundAmount = function(amount) {
  return _.floor(amount, this.market.amountPrecision);
};

Trader.prototype.roundPrice = function(amount) {
  return scientificToDecimal(_.round(amount, this.market.pricePrecision));
};

Trader.prototype.addOrder = function(tradeType, amount, price, callback) {
  price = this.roundAmount(price); // only round price, not amount

  const handle = (err, data) => {
    if(err) {
      return callback(err);
    }

    let txid;

    if(data.catched) {
      // handled timeout, but order was created
      txid = data.id;
    } else if(_.isString(data)) {
      // handled timeout, order was NOT created
      txid = data;
    } else {
      // normal flow
      txid = data.result.txid[0];
    }

    callback(undefined, txid);
  };

  const reqData = {
    pair: this.pair,
    type: tradeType.toLowerCase(),
    ordertype: 'limit',
    price: price,
    volume: amount
  };

  const fetch = cb => this.kraken.api('AddOrder', reqData, this.handleResponse('addOrder', cb, false, { tradeType, amount, price }));
  retry(null, fetch, handle);
};

Trader.prototype.buy = function(amount, price, callback) {
  this.addOrder('buy', amount, price, callback);
};

Trader.prototype.sell = function(amount, price, callback) {
  this.addOrder('sell', amount, price, callback);
};

Trader.prototype.getOrder = function(order, callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    const price = parseFloat( data.result[ order ].price );
    const amount = parseFloat( data.result[ order ].vol_exec );
    const date = moment.unix( data.result[ order ].closetm );

    // TODO: figure out fees, kraken is reporting 0 fees:
    // { 'OF6L2D-6LIKD-4OOHR7':
    //   { refid: null,
    //     userref: 0,
    //     status: 'closed',
    //     reason: null,
    //     opentm: 1530694339.8116,
    //     closetm: 1530694402.9572,
    //     starttm: 0,
    //     expiretm: 0,
    //     descr: [Object],
    //     vol: '0.00500000',
    //     vol_exec: '0.00500000',
    //     cost: '27.9',
    //     fee: '0',
    //     price: '5595.0',
    //     stopprice: '0.00000',
    //     limitprice: '0.00000',
    //     misc: '',
    //     oflags: 'fciq' } } }

    callback(undefined, {
      price,
      amount,
      date,
      feePercent: 0.16 // default for now
    });
  };

  const reqData = {txid: order};

  const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('getOrder', cb, true));
  retry(null, fetch, handle);
}

Trader.prototype.checkOrder = function(order, callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    const result = data.result[order];

    callback(undefined, {
      executed: result.vol === result.vol_exec,
      open: result.status === 'open',
      filledAmount: +result.vol_exec
    });
  };

  const reqData = {txid: order};

  const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('checkOrder', cb, true));
  retry(null, fetch, handle);
};

Trader.prototype.cancelOrder = function(order, callback) {
  const reqData = {txid: order};

  const handle = (err, data) => {
    if(err) {
      if(err.message.includes('Unknown order')) {
        return callback(undefined, true);
      }

      // catch race condition:
      // if we cancel, that request times out
      // we recheck: it's live BUT then the
      // timeout request goes through nonetheless
      // (only times out behind cloudflare)
      if(err.message.includes('Invalid order')) {
        return callback(undefined, true);
      }

      return callback(err)
    }

    if(data.filled) {
      callback(undefined, false, data);
    }

    callback(undefined, false);
  }

  const fetch = cb => this.kraken.api('CancelOrder', reqData, this.handleResponse('cancelOrder', cb, false, order));
  retry(null, fetch, handle);
};

Trader.prototype.getRawOpenOrders = function(callback) {
  const handle = (err, data) => {
    if(err) {
      return callback(err)
    }

    callback(undefined, data.result.open);
  }

  const fetch = cb => this.kraken.api('OpenOrders', {}, this.handleResponse('getOpenOrders', cb, true));
  retry(null, fetch, handle);
}

Trader.prototype.getOpenOrders = function(callback) {

  this.getRawOpenOrders((err, allOrders) => {
    if(err) {
      console.log(err);

      return callback(err)
    }

    const orders = [];

    _.each(allOrders, (o, id) => {
      if(o.descr.pair === this.pair) {
        orders.push(id);
      }
    });

    callback(undefined, orders);
  });
}

Trader.getCapabilities = function () {
  return {
    name: 'Kraken',
    slug: 'kraken',
    currencies: marketData.currencies,
    assets: marketData.assets,
    markets: marketData.markets,
    requires: ['key', 'secret'],
    providesHistory: 'date',
    providesFullHistory: true,
    tid: 'date',
    tradable: true,
    gekkoBroker: 0.6
  };
}

module.exports = Trader;
<commit_msg>[GB] make sure roundPrice is used<commit_after>const Kraken = require('kraken-api');
const moment = require('moment');
const _ = require('lodash');
const exchangeUtils = require('../exchangeUtils');
const retry = exchangeUtils.retry;
const scientificToDecimal = exchangeUtils.scientificToDecimal;

const marketData = require('./kraken-markets.json');

const Trader = function(config) {
  _.bindAll(this);

  if(_.isObject(config)) {
    this.key = config.key;
    this.secret = config.secret;
    this.currency = config.currency.toUpperCase()
    this.asset = config.asset.toUpperCase();
  }

  this.name = 'kraken';
  this.since = null;

  this.market = _.find(Trader.getCapabilities().markets, (market) => {
    return market.pair[0] === this.currency && market.pair[1] === this.asset
  });
  this.pair = this.market.book;

  this.interval = 3100;

  this.kraken = new Kraken(
    this.key,
    this.secret,
    {timeout: +moment.duration(60, 'seconds')}
  );
}

const recoverableErrors = [
  'SOCKETTIMEDOUT',
  'TIMEDOUT',
  'CONNRESET',
  'CONNREFUSED',
  'NOTFOUND',
  'Service:Unavailable',
  'Request timed out',
  'Empty response',
  'API:Invalid nonce',
  'General:Temporary lockout',
  'Response code 525',
  'Service:Busy'
];

// errors that might mean
// the API call succeeded.
const unknownResultErrors = [
  'Response code 502',
  'Response code 504',
  'Response code 522',
  'Response code 520',
]

const includes = (str, list) => {
  if(!_.isString(str))
    return false;

  return _.some(list, item => str.includes(item));
}

Trader.prototype.handleResponse = function(funcName, callback, nonMutating, payload) {
  return (error, body) => {

    if(!error && !body) {
      error = new Error('Empty response');
    }

    if(error) {
      if(includes(error.message, recoverableErrors)) {
        error.notFatal = true;
      }

      if(includes(error.message, ['Rate limit exceeded'])) {
        error.notFatal = true;
        error.backoffDelay = 2500;
      }

      if(nonMutating && includes(error.message, unknownResultErrors)) {
        // this call only tried to retrieve data, safe to redo
        error.notFatal = true;
      }

      if(funcName === 'addOrder' && includes(error.message, unknownResultErrors)) {

        const { tradeType, amount, price } = payload;

        return setTimeout(() => {
          this.getRawOpenOrders((err2, orders) => {
            if(err2) {
              console.log('err2', err2);
              return callback(err2);
            }

            _.each(orders, (o, id) => {
              o.id = id;
            });

            const order = _.find(orders, o => {
              if(o.descr.type !== tradeType) {
                return false;
              }

              const ts = moment.unix((o.opentm + '').split('.')[0])
              if(moment().diff(ts, 'm') > 10) {
                return false;
              }

              // string vs float
              if(o.descr.price != price) {
                return false;
              }

              // string vs float
              if(o.vol != amount) {
                return false;
              }

              return true;
            });

            if(!order) {
              console.log('broken add order, appears not created:', {payload, orders: JSON.stringify(orders)});
              return this.addOrder(tradeType, amount, price, callback);
            }

            return callback(undefined, { catched: true, id: order.id });
          });
        }, 5000);
      }

      if(funcName === 'cancelOrder' && includes(error.message, unknownResultErrors)) {
        console.log('broken cancel');

        return setTimeout(() => {
          const handle = (err2, data) => {
            if(err2) {
              console.log('err2', err2);
              return callback(err2);
            }

            const order = _.get(data, `result["${payload}"]`);

            if(!_.isObject(order)) {
              console.log('refetched broken cancel, cannot find order...', data);
              throw 'a';
            }

            console.log(order);

            if(order.status !== 'canceled') {
              console.log(new Date, 'it still exists, retrying cancel');
              return this.cancelOrder(payload, callback);
            }

            console.log(new Date, 'it was canceled');
            return callback(undefined, true, { catched: true, filled: parseFloat(order.vol_exec) });

          };

          const reqData = {txid: payload};

          const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('checkOrder', cb, true));
          retry(null, fetch, handle);
        }, 5000);
      }

      return callback(error);
    }

    return callback(undefined, body);
  }
};

Trader.prototype.getTrades = function(since, callback, descending) {
  const startTs = since ? moment(since).valueOf() : null;

  const handle = (err, trades) => {
    if (err) return callback(err);

    var parsedTrades = [];
    _.each(trades.result[this.pair], function(trade) {
      // Even when you supply 'since' you can still get more trades than you asked for, it needs to be filtered
      if (_.isNull(startTs) || startTs < moment.unix(trade[2]).valueOf()) {
        parsedTrades.push({
          tid: moment.unix(trade[2]).valueOf() * 1000000,
          date: parseInt(Math.round(trade[2]), 10),
          price: parseFloat(trade[0]),
          amount: parseFloat(trade[1])
        });
      }
    }, this);

    if(descending)
      callback(undefined, parsedTrades.reverse());
    else
      callback(undefined, parsedTrades);
  };

  const reqData = {
    pair: this.pair
  };

  if(since) {
    // Kraken wants a tid, which is found to be timestamp_ms * 1000000 in practice. No clear documentation on this though
    reqData.since = startTs * 1000000;
  }

  const fetch = cb => this.kraken.api('Trades', reqData, this.handleResponse('getTrades', cb, true));
  retry(null, fetch, handle);
};

Trader.prototype.getPortfolio = function(callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    let assetAmount = parseFloat( data.result[this.market.prefixed[1]] );
    let currencyAmount = parseFloat( data.result[this.market.prefixed[0]] );

    if(!_.isNumber(assetAmount) || _.isNaN(assetAmount)) {
      console.log(`Kraken did not return portfolio for ${this.asset}, assuming 0.`);
      assetAmount = 0;
    }

    if(!_.isNumber(currencyAmount) || _.isNaN(currencyAmount)) {
      console.log(`Kraken did not return portfolio for ${this.currency}, assuming 0.`);
      currencyAmount = 0;
    }

    const portfolio = [
      { name: this.asset, amount: assetAmount },
      { name: this.currency, amount: currencyAmount }
    ];

    return callback(undefined, portfolio);
  };

  const fetch = cb => this.kraken.api('Balance', {}, this.handleResponse('getPortfolio', cb, true));
  retry(null, fetch, handle);
};

// This assumes that only limit orders are being placed with standard assets pairs
// It does not take into account volume discounts.
// Base maker fee is 0.16%, taker fee is 0.26%.
Trader.prototype.getFee = function(callback) {
  const makerFee = 0.16;
  callback(undefined, makerFee / 100);
};

Trader.prototype.getTicker = function(callback) {
  const handle = (err, data) => {
    if (err) return callback(err);

    const result = data.result[this.pair];
    const ticker = {
      ask: result.a[0],
      bid: result.b[0]
    };
    callback(undefined, ticker);
  };

  const reqData = {pair: this.pair}
  const fetch = cb => this.kraken.api('Ticker', reqData, this.handleResponse('getTicker', cb, true));
  retry(null, fetch, handle);
};

Trader.prototype.roundAmount = function(amount) {
  return _.floor(amount, this.market.amountPrecision);
};

Trader.prototype.roundPrice = function(amount) {
  return scientificToDecimal(_.round(amount, this.market.pricePrecision));
};

Trader.prototype.addOrder = function(tradeType, amount, price, callback) {
  price = this.roundPrice(price); // only round price, not amount

  const handle = (err, data) => {
    if(err) {
      return callback(err);
    }

    let txid;

    if(data.catched) {
      // handled timeout, but order was created
      txid = data.id;
    } else if(_.isString(data)) {
      // handled timeout, order was NOT created
      txid = data;
    } else {
      // normal flow
      txid = data.result.txid[0];
    }

    callback(undefined, txid);
  };

  const reqData = {
    pair: this.pair,
    type: tradeType.toLowerCase(),
    ordertype: 'limit',
    price: price,
    volume: amount
  };

  const fetch = cb => this.kraken.api('AddOrder', reqData, this.handleResponse('addOrder', cb, false, { tradeType, amount, price }));
  retry(null, fetch, handle);
};

Trader.prototype.buy = function(amount, price, callback) {
  this.addOrder('buy', amount, price, callback);
};

Trader.prototype.sell = function(amount, price, callback) {
  this.addOrder('sell', amount, price, callback);
};

Trader.prototype.getOrder = function(order, callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    const price = parseFloat( data.result[ order ].price );
    const amount = parseFloat( data.result[ order ].vol_exec );
    const date = moment.unix( data.result[ order ].closetm );

    // TODO: figure out fees, kraken is reporting 0 fees:
    // { 'OF6L2D-6LIKD-4OOHR7':
    //   { refid: null,
    //     userref: 0,
    //     status: 'closed',
    //     reason: null,
    //     opentm: 1530694339.8116,
    //     closetm: 1530694402.9572,
    //     starttm: 0,
    //     expiretm: 0,
    //     descr: [Object],
    //     vol: '0.00500000',
    //     vol_exec: '0.00500000',
    //     cost: '27.9',
    //     fee: '0',
    //     price: '5595.0',
    //     stopprice: '0.00000',
    //     limitprice: '0.00000',
    //     misc: '',
    //     oflags: 'fciq' } } }

    callback(undefined, {
      price,
      amount,
      date,
      feePercent: 0.16 // default for now
    });
  };

  const reqData = {txid: order};

  const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('getOrder', cb, true));
  retry(null, fetch, handle);
}

Trader.prototype.checkOrder = function(order, callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    const result = data.result[order];

    callback(undefined, {
      executed: result.vol === result.vol_exec,
      open: result.status === 'open',
      filledAmount: +result.vol_exec
    });
  };

  const reqData = {txid: order};

  const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('checkOrder', cb, true));
  retry(null, fetch, handle);
};

Trader.prototype.cancelOrder = function(order, callback) {
  const reqData = {txid: order};

  const handle = (err, data) => {
    if(err) {
      if(err.message.includes('Unknown order')) {
        return callback(undefined, true);
      }

      // catch race condition:
      // if we cancel, that request times out
      // we recheck: it's live BUT then the
      // timeout request goes through nonetheless
      // (only times out behind cloudflare)
      if(err.message.includes('Invalid order')) {
        return callback(undefined, true);
      }

      return callback(err)
    }

    if(data.filled) {
      callback(undefined, false, data);
    }

    callback(undefined, false);
  }

  const fetch = cb => this.kraken.api('CancelOrder', reqData, this.handleResponse('cancelOrder', cb, false, order));
  retry(null, fetch, handle);
};

Trader.prototype.getRawOpenOrders = function(callback) {
  const handle = (err, data) => {
    if(err) {
      return callback(err)
    }

    callback(undefined, data.result.open);
  }

  const fetch = cb => this.kraken.api('OpenOrders', {}, this.handleResponse('getOpenOrders', cb, true));
  retry(null, fetch, handle);
}

Trader.prototype.getOpenOrders = function(callback) {

  this.getRawOpenOrders((err, allOrders) => {
    if(err) {
      console.log(err);

      return callback(err)
    }

    const orders = [];

    _.each(allOrders, (o, id) => {
      if(o.descr.pair === this.pair) {
        orders.push(id);
      }
    });

    callback(undefined, orders);
  });
}

Trader.getCapabilities = function () {
  return {
    name: 'Kraken',
    slug: 'kraken',
    currencies: marketData.currencies,
    assets: marketData.assets,
    markets: marketData.markets,
    requires: ['key', 'secret'],
    providesHistory: 'date',
    providesFullHistory: true,
    tid: 'date',
    tradable: true,
    gekkoBroker: 0.6
  };
}

module.exports = Trader;
<|endoftext|>


<commit_before>import path from 'path';
import webpack from 'webpack';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import CompressionPlugin from 'compression-webpack-plugin';
import AssetMapPlugin from 'asset-map-webpack-plugin';
import StatsPlugin from 'stats-webpack-plugin';
import SentrySourcemapPlugin from 'webpack-sentry-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import envRules from './webpack/rules';
import { devServer, publicPath } from './webpack/dev';
import buildEnv from './webpack/env';
import runtime from './webpack/runtime';
import vendor from './webpack/vendor';

const {
  TARGET: target,
  BUNDLE_ANALYZE: bundleAnalyze,
  SENTRY_KEY: sentryKey,
  SENTRY_JS_DSN: sentryDsn,
  SOURCE_VERSION: commit,
} = process.env;
const { deployTarget, namePattern, cssNamePattern } = buildEnv(target);

const resolvedRules = envRules(deployTarget);

const config = {
  entry: {
    application: './app/assets/webpack/application',
    runtime,
    vendor,
  },

  output: {
    path: path.join(__dirname, '..', 'public', 'assets'),
    publicPath: '/assets/',
    filename: `${namePattern}.js`,
  },

  resolve: {
    modules: [
      path.join('app', 'assets'),
      path.join('vendor', 'assets'),
      path.join('app', 'assets', 'stylesheets'),
      path.join('app'),
      'node_modules',
    ],
    extensions: ['.js', '.css'],
  },

  module: {
    rules: resolvedRules,
  },

  plugins: [
    new webpack.optimize.CommonsChunkPlugin({ names: ['vendor', 'runtime'], minChunks: Infinity }),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new StatsPlugin('webpack_manifest.json', {
      chunkModules: false,
      source: false,
      chunks: false,
      modules: false,
      assets: true,
      warnings: target === 'development',
    }),
  ],
};

if (deployTarget) {
  config.plugins.push(
    new ExtractTextPlugin({ filename: `${cssNamePattern}.css` }),
    new webpack.NoEmitOnErrorsPlugin(),
    new AssetMapPlugin('asset_map.json'),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production'),
      },
      SENTRY_DSN: JSON.stringify(sentryDsn),
      GITSHA: JSON.stringify(commit),
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        screw_ie8: true,
      },
      mangle: {
        screw_ie8: true,
      },
      output: {
        comments: false,
        screw_ie8: true,
      },
    }),
    new webpack.SourceMapDevToolPlugin({
      module: true,
      columns: true,
      filename: '[file].map',
      moduleFilenameTemplate: 'file://[resource-path]',
      fallbackModuleFilenameTemplate: 'file://[resource-path]?[hash]',
      append: '\n//# sourceMappingURL=/assets/[url]',
    }),
    new CompressionPlugin({
      asset: '[path].gz',
      test: /\.(css|js)$/,
    })
  );

  if(bundleAnalyze) {
    config.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'static' }));
  }

  if(sentryKey) {
    config.plugins.push(
      new SentrySourcemapPlugin({
        organisation: 'schneidmaster',
        project: 'debatevidio',
        apiKey: sentryKey,
        release: commit,
        include: /\.js(\.map)?$/,
        filenameTransform(filename) {
          return `~/assets/${filename}`;
        },
        releaseBody(version) {
          return {
            version,
            refs: [
              {
                repository: 'schneidmaster/debatevid.io',
                commit: commit,
              },
            ],
            projects: ['debatevidio'],
          };
        },
      })
    );
  }
} else {
  config.devServer = devServer;
  config.output.publicPath = publicPath;
  config.plugins.push(
    new webpack.HotModuleReplacementPlugin(),
    new webpack.SourceMapDevToolPlugin({
      module: true,
      columns: false,
    }),
    new AssetMapPlugin('asset_map.json', path.resolve()),
  );
}

export default config;
<commit_msg>Fix sourcemaps<commit_after>import path from 'path';
import webpack from 'webpack';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import CompressionPlugin from 'compression-webpack-plugin';
import AssetMapPlugin from 'asset-map-webpack-plugin';
import StatsPlugin from 'stats-webpack-plugin';
import SentrySourcemapPlugin from 'webpack-sentry-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import envRules from './webpack/rules';
import { devServer, publicPath } from './webpack/dev';
import buildEnv from './webpack/env';
import runtime from './webpack/runtime';
import vendor from './webpack/vendor';

const {
  TARGET: target,
  BUNDLE_ANALYZE: bundleAnalyze,
  SENTRY_KEY: sentryKey,
  SENTRY_JS_DSN: sentryDsn,
  SOURCE_VERSION: commit,
} = process.env;
const { deployTarget, namePattern, cssNamePattern } = buildEnv(target);

const resolvedRules = envRules(deployTarget);

const config = {
  entry: {
    application: './app/assets/webpack/application',
    runtime,
    vendor,
  },

  output: {
    path: path.join(__dirname, '..', 'public', 'assets'),
    publicPath: '/assets/',
    filename: `${namePattern}.js`,
  },

  resolve: {
    modules: [
      path.join('app', 'assets'),
      path.join('vendor', 'assets'),
      path.join('app', 'assets', 'stylesheets'),
      path.join('app'),
      'node_modules',
    ],
    extensions: ['.js', '.css'],
  },

  module: {
    rules: resolvedRules,
  },

  plugins: [
    new webpack.optimize.CommonsChunkPlugin({ names: ['vendor', 'runtime'], minChunks: Infinity }),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new StatsPlugin('webpack_manifest.json', {
      chunkModules: false,
      source: false,
      chunks: false,
      modules: false,
      assets: true,
      warnings: target === 'development',
    }),
  ],
};

if (deployTarget) {
  config.plugins.push(
    new ExtractTextPlugin({ filename: `${cssNamePattern}.css` }),
    new webpack.NoEmitOnErrorsPlugin(),
    new AssetMapPlugin('asset_map.json'),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production'),
      },
      SENTRY_DSN: JSON.stringify(sentryDsn),
      GITSHA: JSON.stringify(commit),
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        screw_ie8: true,
      },
      mangle: {
        screw_ie8: true,
      },
      output: {
        comments: false,
        screw_ie8: true,
      },
    }),
    new webpack.SourceMapDevToolPlugin({
      filename: `${namePattern}.js.map`,
      moduleFilenameTemplate: 'file://[resource-path]',
      fallbackModuleFilenameTemplate: 'file://[resource-path]?[hash]',
      append: '\n//# sourceMappingURL=/assets/[url]',
    }),
    new CompressionPlugin({
      asset: '[path].gz',
      test: /\.(css|js)$/,
    })
  );

  if(bundleAnalyze) {
    config.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'static' }));
  }

  if(sentryKey) {
    config.plugins.push(
      new SentrySourcemapPlugin({
        organisation: 'schneidmaster',
        project: 'debatevidio',
        apiKey: sentryKey,
        release: commit,
        include: /\.js(\.map)?$/,
        filenameTransform(filename) {
          return `~/assets/${filename}`;
        },
        releaseBody(version) {
          return {
            version,
            refs: [
              {
                repository: 'schneidmaster/debatevid.io',
                commit: commit,
              },
            ],
            projects: ['debatevidio'],
          };
        },
      })
    );
  }
} else {
  config.devServer = devServer;
  config.output.publicPath = publicPath;
  config.plugins.push(
    new webpack.HotModuleReplacementPlugin(),
    new webpack.SourceMapDevToolPlugin({
      module: true,
      columns: false,
    }),
    new AssetMapPlugin('asset_map.json', path.resolve()),
  );
}

export default config;
<|endoftext|>


<commit_before>var Luhn = require('./luhn');

describe('Luhn',function() {

  it('check digit',function() {
    var luhn = new Luhn(34567);
    expect(luhn.checkDigit).toEqual(7);
  });

  xit('check digit again',function() {
    var luhn = new Luhn(91370);
    expect(luhn.checkDigit).toEqual(0);
  });

  xit('addends',function() {
    var luhn = new Luhn(12121);
    expect(luhn.addends).toEqual([1, 4, 1, 4, 1]);
  });

  xit('too large added',function() {
    var luhn = new Luhn(8631);
    expect(luhn.addends).toEqual([7, 6, 6, 1]);
  });

  xit('checksum',function() {
    var luhn = new Luhn(4913);
    expect(luhn.checksum).toEqual(22);
  });

  xit('checksum again',function() {
    var luhn = new Luhn(201773);
    expect(luhn.checksum).toEqual(21);
  });

  xit('invalid number',function() {
    var luhn = new Luhn(738);
    expect(luhn.valid).toEqual(false);
  });

  xit('invalid number',function() {
    var luhn = new Luhn(8739567);
    expect(luhn.valid).toEqual(true);
  });

  xit('create valid number',function() {
    var number = Luhn.create(123);
    expect(number).toEqual(1230);
  });

  xit('create other valid number',function() {
    var number = Luhn.create(873956);
    expect(number).toEqual(8739567);
  });

  xit('create yet another valid number',function() {
    var number = Luhn.create(837263756);
    expect(number).toEqual(8372637564);
  });

});<commit_msg>Fix typo<commit_after>var Luhn = require('./luhn');

describe('Luhn',function() {

  it('check digit',function() {
    var luhn = new Luhn(34567);
    expect(luhn.checkDigit).toEqual(7);
  });

  xit('check digit again',function() {
    var luhn = new Luhn(91370);
    expect(luhn.checkDigit).toEqual(0);
  });

  xit('addends',function() {
    var luhn = new Luhn(12121);
    expect(luhn.addends).toEqual([1, 4, 1, 4, 1]);
  });

  xit('too large addend',function() {
    var luhn = new Luhn(8631);
    expect(luhn.addends).toEqual([7, 6, 6, 1]);
  });

  xit('checksum',function() {
    var luhn = new Luhn(4913);
    expect(luhn.checksum).toEqual(22);
  });

  xit('checksum again',function() {
    var luhn = new Luhn(201773);
    expect(luhn.checksum).toEqual(21);
  });

  xit('invalid number',function() {
    var luhn = new Luhn(738);
    expect(luhn.valid).toEqual(false);
  });

  xit('invalid number',function() {
    var luhn = new Luhn(8739567);
    expect(luhn.valid).toEqual(true);
  });

  xit('create valid number',function() {
    var number = Luhn.create(123);
    expect(number).toEqual(1230);
  });

  xit('create other valid number',function() {
    var number = Luhn.create(873956);
    expect(number).toEqual(8739567);
  });

  xit('create yet another valid number',function() {
    var number = Luhn.create(837263756);
    expect(number).toEqual(8372637564);
  });

});
<|endoftext|>


<commit_before>module.exports = {
  "extends": "justinlocsei/configurations/es6",
  "ecmaFeatures": {
    "jsx": true
  },
  "env": {
    "browser": true
  },
  "plugins": [
    "react"
  ],
  "rules": {
    "jsx-quotes": [2, "prefer-double"],
    "react/display-name": [2, {"acceptTranspilerName": true}],
    "react/jsx-boolean-value": [2, "always"],
    "react/jsx-curly-spacing": [2, "never"],
    "react/jsx-handler-names": 2,
    "react/jsx-no-bind": 2,
    "react/jsx-no-duplicate-props": [2, {"ignoreCase": true}],
    "react/jsx-no-undef": 2,
    "react/jsx-pascal-case": 2,
    "react/jsx-uses-vars": 2,
    "react/no-danger": 2,
    "react/no-did-mount-set-state": [2, "allow-in-func"],
    "react/no-did-update-set-state": [2, "allow-in-func"],
    "react/no-direct-mutation-state": 2,
    "react/no-multi-comp": 2,
    "react/no-unknown-property": 2,
    "react/prop-types": 2,
    "react/require-extension": 2,
    "react/self-closing-comp": 2,
    "react/wrap-multilines": 2
  }
};

<commit_msg>Remove the react display-name rule<commit_after>module.exports = {
  "extends": "justinlocsei/configurations/es6",
  "ecmaFeatures": {
    "jsx": true
  },
  "env": {
    "browser": true
  },
  "plugins": [
    "react"
  ],
  "rules": {
    "jsx-quotes": [2, "prefer-double"],
    "react/jsx-boolean-value": [2, "always"],
    "react/jsx-curly-spacing": [2, "never"],
    "react/jsx-handler-names": 2,
    "react/jsx-no-bind": 2,
    "react/jsx-no-duplicate-props": [2, {"ignoreCase": true}],
    "react/jsx-no-undef": 2,
    "react/jsx-pascal-case": 2,
    "react/jsx-uses-vars": 2,
    "react/no-danger": 2,
    "react/no-did-mount-set-state": [2, "allow-in-func"],
    "react/no-did-update-set-state": [2, "allow-in-func"],
    "react/no-direct-mutation-state": 2,
    "react/no-multi-comp": 2,
    "react/no-unknown-property": 2,
    "react/prop-types": 2,
    "react/require-extension": 2,
    "react/self-closing-comp": 2,
    "react/wrap-multilines": 2
  }
};

<|endoftext|>


<commit_before>/*
 * It provides a abstraction layer over native javascript Websocket.
 *
 * Provide additional functionality like if connection is close, open
 * it again and process the buffered requests
 *
 *
 * Usage:
 *
 * `TradeSocket.init()` to initiate the connection
 * `TradeSocket.send({contracts_for : 1})` to send message to server
 */
var TradeSocket = (function () {
    'use strict';

    var tradeSocket,
        socketUrl = "wss://"+window.location.host+"/websockets/v3",
        bufferedSends = [],
        isClosedOnNavigation = false;

    if (page.language()) {
        socketUrl += '?l=' + page.language();
    }

    var status = function () {
        return tradeSocket && tradeSocket.readyState;
    };

    var isReady = function () {
        return tradeSocket && tradeSocket.readyState === 1;
    };

    var isClose = function () {
        return !tradeSocket || tradeSocket.readyState === 3;
    };

    var sendBufferedSends = function () {
        while (bufferedSends.length > 0) {
            tradeSocket.send(JSON.stringify(bufferedSends.shift()));
        }
    };

    var init = function () {
        if(!isClose()){
            return;
        }

        if(typeof tradeSocket === 'undefined'){
            tradeSocket = new WebSocket(socketUrl);

            tradeSocket.onopen = function (){
                var loginToken = getCookieItem('login');
                if(loginToken) {
                    tradeSocket.send(JSON.stringify({authorize: loginToken}));
                } else {
                    tradeSocket.send(JSON.stringify({ payout_currencies: 1 }));
                }
                sendBufferedSends();
            };

            tradeSocket.onmessage = function (msg){
                Message.process(msg);
            };

            tradeSocket.onclose = function (e) {
                // clear buffer ids of price and ticks as connection is closed
                Price.clearMapping();
                Price.clearFormId();

                if(e===1){
                    isClosedOnNavigation = true;
                }
                // if not closed on navigation start it again as server may have closed it
                else if(!isClosedOnNavigation){
                    processMarketUnderlying();
                }
                // set it again to false as it class variables
                else{
                    isClosedOnNavigation = false;
                }
            };

            tradeSocket.onerror = function (error) {
                console.log('socket error', error);
            };
        }
        else {
            tradeSocket.onopen();
        }
    };

    var send = function(data) {
        if (isClose()) {
            bufferedSends.push(data);
            init();
        } else if (isReady()) {
            tradeSocket.send(JSON.stringify(data));
        } else {
            bufferedSends.push(data);
        }
    };

    var close = function () {
        if (tradeSocket) {
            tradeSocket.close();
        }
    };

    return {
        init: init,
        send: send,
        close: close,
        socket: function () { return tradeSocket; },
        setClosedFlag: function (flag) { isClosedOnNavigation = flag; }
    };

})();
<commit_msg>should reconnect onclose<commit_after>/*
 * It provides a abstraction layer over native javascript Websocket.
 *
 * Provide additional functionality like if connection is close, open
 * it again and process the buffered requests
 *
 *
 * Usage:
 *
 * `TradeSocket.init()` to initiate the connection
 * `TradeSocket.send({contracts_for : 1})` to send message to server
 */
var TradeSocket = (function () {
    'use strict';

    var tradeSocket,
        socketUrl = "wss://"+window.location.host+"/websockets/v3",
        bufferedSends = [],
        isClosedOnNavigation = false;

    if (page.language()) {
        socketUrl += '?l=' + page.language();
    }

    var status = function () {
        return tradeSocket && tradeSocket.readyState;
    };

    var isReady = function () {
        return tradeSocket && tradeSocket.readyState === 1;
    };

    var isClose = function () {
        return !tradeSocket || tradeSocket.readyState === 3;
    };

    var sendBufferedSends = function () {
        while (bufferedSends.length > 0) {
            tradeSocket.send(JSON.stringify(bufferedSends.shift()));
        }
    };

    var init = function () {
        if(!isClose()){
            return;
        }

        tradeSocket = new WebSocket(socketUrl);

        tradeSocket.onopen = function (){
            var loginToken = getCookieItem('login');
            if(loginToken) {
                tradeSocket.send(JSON.stringify({authorize: loginToken}));
            } else {
                tradeSocket.send(JSON.stringify({ payout_currencies: 1 }));
            }
            sendBufferedSends();
        };

        tradeSocket.onmessage = function (msg){
            Message.process(msg);
        };

        tradeSocket.onclose = function (e) {
            // clear buffer ids of price and ticks as connection is closed
            Price.clearMapping();
            Price.clearFormId();

            if(e===1){
                isClosedOnNavigation = true;
            }
            // if not closed on navigation start it again as server may have closed it
            else if(!isClosedOnNavigation){
                processMarketUnderlying();
            }
            // set it again to false as it class variables
            else{
                isClosedOnNavigation = false;
            }

            init();
        };

        tradeSocket.onerror = function (error) {
            console.log('socket error', error);
        };

    };

    var send = function(data) {
        if (isClose()) {
            bufferedSends.push(data);
            init();
        } else if (isReady()) {
            tradeSocket.send(JSON.stringify(data));
        } else {
            bufferedSends.push(data);
        }
    };

    var close = function () {
        if (tradeSocket) {
            tradeSocket.close();
        }
    };

    return {
        init: init,
        send: send,
        close: close,
        socket: function () { return tradeSocket; },
        setClosedFlag: function (flag) { isClosedOnNavigation = flag; }
    };

})();
<|endoftext|>


<commit_before>'use strict';

// Vendor imports
import { TweenMax } from 'gsap';
import $ from 'jquery';
import _ from 'lodash';

// Module imports
const BREWSER = require('brewser/dist/brewser.min').br;

// Singleton innit
var instance = null;

export default class VideoOverlay {
  constructor() {
    if (!instance) {
      instance = this;
      this._init();
    }

    return instance;
  }

////////////////////////////////////
/////// Private methods
////////////////

  _init() {
    var dom =  `

                      <svg viewBox="0 0 32 32"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="images/icons.svg#x--white"></use>
                      </svg>

`;

    this.$el = $(dom);
    $('body').append(this.$el);

    this.$bg = this.$el.find('.video-overlay__background');
    this.$embedWrapper = this.$el.find('.video-overlay__embed-wrapper');
    this.$iframe = this.$el.find('iframe');
    this.$closeButton = this.$el.find('.close-screen-button');

    this._initTriggers();

    this._reset();
  }

  _initTriggers() {
    this.triggers = [];
    $('.vo-trigger').each(_.bind(function(index, el) {
      if (typeof $(el).attr('data-vo-id') !== 'undefined' && typeof $(el).attr('data-vo-type') !== 'undefined') {
        this.triggers.push({
          $el: $(el),
          id: $(el).attr('data-vo-id'),
          type: $(el).attr('data-vo-type'),
          width: $(el).attr('data-vo-width'),
          height: $(el).attr('data-vo-height')
        });
      } else {
        $(el).addClass('vo-trigger--invalid');
      }
    }, this));

    _.each(this.triggers, _.bind(function(trigger, index) {
      trigger.$el.on('click tap', _.bind(function(e) {
        e.preventDefault();
        this._open(trigger.id, trigger.type);
      }, this));
    }, this));
  }

  _reset () {
    TweenMax.set(this.$closeButton, {
      opacity: 0,
      rotationZ: -180,
    });

    TweenMax.set(this.$bg, { opacity: 0 });
    this.$el.addClass('is-hidden');
    $('html').removeClass('is-video-overlay-open');

    this.$embedWrapper.hide();
    TweenMax.set(this.$embedWrapper, {
      opacity: 0,
      y: 20
    });

    this.$iframe.attr('src', '');
  }

  _setupIframe(videoID, embedType) {
    let iframeSource = `//player.vimeo.com/video/${videoID}?&badge=0&byline=0&title=0&loop=0&autoplay=1`;
    if(embedType  === VideoOverlay.EmbedTypes.YOUTUBE) {
      iframeSource = `//youtube.com/embed/${videoID}?&rel=0&showinfo=0&loop=0&autoplay=1&controls=1`;
    }

    this.$iframe.attr('src', iframeSource);
  }

  _open (id, type) {
    this.resize();

    $(window).on('keyup', _.bind(this._handleKeyUp, this));

    this.$el.removeClass('is-hidden');
    $('html').addClass('is-video-overlay-open');

    TweenMax.to(this.$bg, 0.9, {
      opacity: 1,
      ease: Strong.easeOut
    });

    this.$bg.one('click tap', _.bind(function() {
      this._close();
    }, this));

    TweenMax.to(this.$closeButton, 1.2, {
      opacity: 1,
      rotationZ: 0,
      ease: Strong.easeInOut,
      delay: 0
    });
    this.$closeButton.one('click tap', _.bind(this._handleCloseButtonClick, this));

    TweenMax.to(this.$embedWrapper, 0.9, {
      opacity: 1,
      y: 0,
      ease: Strong.easeInOut,
      onStart: _.bind(function() {
        this.$embedWrapper.show();
      }, this),
      onComplete: _.bind(function() {
        this._setupIframe(id, type);
      }, this)
    });
  }

  _close () {
    $(window).off('keyup', _.bind(this._handleKeyUp, this));

    TweenMax.to(this.$closeButton, 0.9, {
      opacity: 0,
      rotationZ: 180,
      ease: Strong.easeOut,
    });

    TweenMax.to(this.$embedWrapper, 0.6, {
      opacity: 0,
      y: 20,
      ease: Strong.easeInOut
    });

    TweenMax.to(this.$bg, 0.4, {
      opacity: 0,
      delay: 0.3,
      ease: Strong.easeOut,
      onComplete: _.bind(function(){
        this._reset();
      }, this)
    });
  }

  _handleCloseButtonClick(e) {
    $('html').removeClass('is-video-overlay-open');
    this._close();
  }

  _handleKeyUp(e) {
    if (e.keyCode === 27) {
      this._close();
    }
  }

////////////////////////////////////
/////// Public methods
////////////////

  resize() {
    const winWidth = BREWSER.windowWidth;
    const winHeight = BREWSER.windowHeight;
    const screenWidth = BREWSER.screenWidth;
    const screenHeight = BREWSER.screenHeight;

    this.cachedWinWidth = winWidth;
    this.cachedWinHeight = winHeight;

    var boxWidth = BREWSER.device.touch ? screenWidth : winWidth;
    var boxHeight = BREWSER.device.touch ? screenHeight : winHeight;

    // Resize EMBEDDED VIDEO
    var minElWidth = 480;
    var minElHeight = 270;
    if(BREWSER.device.phone && winWidth <= 400) {
      minElWidth = 280;
      minElHeight = 156;
    }

    // Resize the video to fit within the box of the window with 100px padding around the edges and keeping the aspect ratio
    VideoOverlay.containInBox({
      $el: this.$embedWrapper,
      elWidth: VideoOverlay.VideoDimensions.WIDTH,
      elHeight: VideoOverlay.VideoDimensions.HEIGHT,
      boxWidth: boxWidth,
      boxHeight: boxHeight,
      minElWidth: minElWidth,
      minElHeight: minElHeight,
      padding: 100
    });
  }

////////////////////////////////////
/////// Event handlers
////////////////

}

////////////////////////////////////
/////// Static class vars
////////////////

/**
 * Resize media to contain within the box
 *
 * @param {Object}  options
 * @param {Number}  options.$el             The element to be resized
 * @param {Number}  options.elWidth         The original width of the media
 * @param {Number}  options.elHeight        The original height of the media
 * @param {Boolean} options.boxWidth        The width of the box
 * @param {Boolean} options.boxHeight       The height of the box
 * @param {Boolean} options.padding         Padding
 * @param {Boolean} options.scale           Adds extra scale
*/
VideoOverlay.containInBox = function(options) {
  if(!_.size(options)) {
    console.error('[VideoOverlay] - containInBox() - missing some arguments for proper resize, quitting.');
    return;
  }

  // Calculate the image dimensions based on the window dimensions
  var $el = options.$el;
  var scale = options.scale || 1;
  var naturalWidth = options.elWidth;
  var naturalHeight = options.elHeight;
  var padding = options.padding || 0;
  var boxWidth = options.boxWidth - padding * 2;
  var boxHeight = options.boxHeight - padding * 2;
  var elWidth = Math.ceil(boxWidth);
  var dimensionRatio = naturalWidth / naturalHeight;
  var elHeight = Math.ceil(boxWidth / dimensionRatio);

  var minElWidth = options.minElWidth || 480;
  var minElHeight = options.minElHeight || 270;

  if(elHeight > boxHeight) {
    elHeight = Math.ceil(boxHeight);
    elWidth = Math.ceil(elHeight * dimensionRatio);
  }

  // Multiply the calculated values (to cover off gaps around edges for instance)
  elWidth *= scale;
  elHeight *= scale;

  if(elWidth < minElWidth || elHeight < minElHeight) {
    elWidth = minElWidth;
    elHeight = minElHeight;
  }

  elWidth = Math.round(elWidth);
  elHeight = Math.round(elHeight);

  $el.css({
    'position': 'absolute',
    'top': '50%',
    'left': '50%',
    'width': elWidth,
    'height': elHeight,
    'margin-top': Math.round(elHeight * -0.5),
    'margin-left': Math.round(elWidth * -0.5)
  });
}

/**
 * Resize media to cover the box (like background-size: cover)
 *
 * @param {Object}  options
 * @param {Number}  options.$el             The element to be resized
 * @param {Number}  options.elWidth         The original width of the media
 * @param {Number}  options.elHeight        The original height of the media
 * @param {Boolean} options.boxWidth        The width of the box
 * @param {Boolean} options.boxHeight       The height of the box
 * @param {Boolean} options.padding         Padding
 * @param {Boolean} options.scale           Adds extra scale
*/
VideoOverlay.fitToBox = function(options) {
  if(!_.size(options)) {
    console.error('[VideoOverlay] - fitToBox() - missing some arguments for proper resize, quitting.');
    return;
  }

  // Calculate the image dimensions based on the window dimensions
  var $el = options.$el;
  var scale = options.scale || 1;
  var naturalWidth = options.elWidth;
  var naturalHeight = options.elHeight;
  var padding = options.padding || 0;
  var boxWidth = options.boxWidth - padding * 2;
  var boxHeight = options.boxHeight - padding * 2;
  var elWidth = Math.ceil(boxWidth);
  var dimensionRatio = naturalWidth / naturalHeight;
  var elHeight = Math.ceil(elWidth / dimensionRatio);

  if(boxWidth / boxHeight < dimensionRatio) {
    elHeight = Math.ceil(boxHeight);
    elWidth = Math.ceil(elHeight * dimensionRatio);
  }

  // Multiply the calculated values (to cover off gaps around edges for instance)
  elWidth *= scale;
  elHeight *= scale;

  elWidth = Math.round(elWidth);
  elHeight = Math.round(elHeight);

  $el.css({
    'position': 'absolute',
    'top': '50%',
    'left': '50%',
    'width': elWidth,
    'height': elHeight,
    'margin-top': Math.round(elHeight * -0.5),
    'margin-left': Math.round(elWidth * -0.5)
  });
}

//
VideoOverlay.EmbedTypes = {
  VIMEO: 'vimeo',
  YOUTUBE: 'youtube'
};

VideoOverlay.VideoDimensions = {
    WIDTH: 1920,
    HEIGHT: 1080
  };<commit_msg>Help Video overlay to find the SVG sprite<commit_after>'use strict';

// Vendor imports
import { TweenMax } from 'gsap';
import $ from 'jquery';
import _ from 'lodash';

// Module imports
const BREWSER = require('brewser/dist/brewser.min').br;

import json from '../../html/data/global.json';

// Singleton innit
var instance = null;

export default class VideoOverlay {
  constructor() {
    if (!instance) {
      instance = this;
      this._init();
    }

    return instance;
  }

////////////////////////////////////
/////// Private methods
////////////////

  _init() {
    const icon = `${json.doc_root}/images/icons.svg#x--white`;
    const dom =  `

                      <svg viewBox="0 0 32 32"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${icon}"></use>
                      </svg>

`;

    this.$el = $(dom);
    $('body').append(this.$el);

    this.$bg = this.$el.find('.video-overlay__background');
    this.$embedWrapper = this.$el.find('.video-overlay__embed-wrapper');
    this.$iframe = this.$el.find('iframe');
    this.$closeButton = this.$el.find('.close-screen-button');

    this._initTriggers();

    this._reset();
  }

  _initTriggers() {
    this.triggers = [];
    $('.vo-trigger').each(_.bind(function(index, el) {
      if (typeof $(el).attr('data-vo-id') !== 'undefined' && typeof $(el).attr('data-vo-type') !== 'undefined') {
        this.triggers.push({
          $el: $(el),
          id: $(el).attr('data-vo-id'),
          type: $(el).attr('data-vo-type'),
          width: $(el).attr('data-vo-width'),
          height: $(el).attr('data-vo-height')
        });
      } else {
        $(el).addClass('vo-trigger--invalid');
      }
    }, this));

    _.each(this.triggers, _.bind(function(trigger, index) {
      trigger.$el.on('click tap', _.bind(function(e) {
        e.preventDefault();
        this._open(trigger.id, trigger.type);
      }, this));
    }, this));
  }

  _reset () {
    TweenMax.set(this.$closeButton, {
      opacity: 0,
      rotationZ: -180,
    });

    TweenMax.set(this.$bg, { opacity: 0 });
    this.$el.addClass('is-hidden');
    $('html').removeClass('is-video-overlay-open');

    this.$embedWrapper.hide();
    TweenMax.set(this.$embedWrapper, {
      opacity: 0,
      y: 20
    });

    this.$iframe.attr('src', '');
  }

  _setupIframe(videoID, embedType) {
    let iframeSource = `//player.vimeo.com/video/${videoID}?&badge=0&byline=0&title=0&loop=0&autoplay=1`;
    if(embedType  === VideoOverlay.EmbedTypes.YOUTUBE) {
      iframeSource = `//youtube.com/embed/${videoID}?&rel=0&showinfo=0&loop=0&autoplay=1&controls=1`;
    }

    this.$iframe.attr('src', iframeSource);
  }

  _open (id, type) {
    this.resize();

    $(window).on('keyup', _.bind(this._handleKeyUp, this));

    this.$el.removeClass('is-hidden');
    $('html').addClass('is-video-overlay-open');

    TweenMax.to(this.$bg, 0.9, {
      opacity: 1,
      ease: Strong.easeOut
    });

    this.$bg.one('click tap', _.bind(function() {
      this._close();
    }, this));

    TweenMax.to(this.$closeButton, 1.2, {
      opacity: 1,
      rotationZ: 0,
      ease: Strong.easeInOut,
      delay: 0
    });
    this.$closeButton.one('click tap', _.bind(this._handleCloseButtonClick, this));

    TweenMax.to(this.$embedWrapper, 0.9, {
      opacity: 1,
      y: 0,
      ease: Strong.easeInOut,
      onStart: _.bind(function() {
        this.$embedWrapper.show();
      }, this),
      onComplete: _.bind(function() {
        this._setupIframe(id, type);
      }, this)
    });
  }

  _close () {
    $(window).off('keyup', _.bind(this._handleKeyUp, this));

    TweenMax.to(this.$closeButton, 0.9, {
      opacity: 0,
      rotationZ: 180,
      ease: Strong.easeOut,
    });

    TweenMax.to(this.$embedWrapper, 0.6, {
      opacity: 0,
      y: 20,
      ease: Strong.easeInOut
    });

    TweenMax.to(this.$bg, 0.4, {
      opacity: 0,
      delay: 0.3,
      ease: Strong.easeOut,
      onComplete: _.bind(function(){
        this._reset();
      }, this)
    });
  }

  _handleCloseButtonClick(e) {
    $('html').removeClass('is-video-overlay-open');
    this._close();
  }

  _handleKeyUp(e) {
    if (e.keyCode === 27) {
      this._close();
    }
  }

////////////////////////////////////
/////// Public methods
////////////////

  resize() {
    const winWidth = BREWSER.windowWidth;
    const winHeight = BREWSER.windowHeight;
    const screenWidth = BREWSER.screenWidth;
    const screenHeight = BREWSER.screenHeight;

    this.cachedWinWidth = winWidth;
    this.cachedWinHeight = winHeight;

    var boxWidth = BREWSER.device.touch ? screenWidth : winWidth;
    var boxHeight = BREWSER.device.touch ? screenHeight : winHeight;

    // Resize EMBEDDED VIDEO
    var minElWidth = 480;
    var minElHeight = 270;
    if(BREWSER.device.phone && winWidth <= 400) {
      minElWidth = 280;
      minElHeight = 156;
    }

    // Resize the video to fit within the box of the window with 100px padding around the edges and keeping the aspect ratio
    VideoOverlay.containInBox({
      $el: this.$embedWrapper,
      elWidth: VideoOverlay.VideoDimensions.WIDTH,
      elHeight: VideoOverlay.VideoDimensions.HEIGHT,
      boxWidth: boxWidth,
      boxHeight: boxHeight,
      minElWidth: minElWidth,
      minElHeight: minElHeight,
      padding: 100
    });
  }

////////////////////////////////////
/////// Event handlers
////////////////

}

////////////////////////////////////
/////// Static class vars
////////////////

/**
 * Resize media to contain within the box
 *
 * @param {Object}  options
 * @param {Number}  options.$el             The element to be resized
 * @param {Number}  options.elWidth         The original width of the media
 * @param {Number}  options.elHeight        The original height of the media
 * @param {Boolean} options.boxWidth        The width of the box
 * @param {Boolean} options.boxHeight       The height of the box
 * @param {Boolean} options.padding         Padding
 * @param {Boolean} options.scale           Adds extra scale
*/
VideoOverlay.containInBox = function(options) {
  if(!_.size(options)) {
    console.error('[VideoOverlay] - containInBox() - missing some arguments for proper resize, quitting.');
    return;
  }

  // Calculate the image dimensions based on the window dimensions
  var $el = options.$el;
  var scale = options.scale || 1;
  var naturalWidth = options.elWidth;
  var naturalHeight = options.elHeight;
  var padding = options.padding || 0;
  var boxWidth = options.boxWidth - padding * 2;
  var boxHeight = options.boxHeight - padding * 2;
  var elWidth = Math.ceil(boxWidth);
  var dimensionRatio = naturalWidth / naturalHeight;
  var elHeight = Math.ceil(boxWidth / dimensionRatio);

  var minElWidth = options.minElWidth || 480;
  var minElHeight = options.minElHeight || 270;

  if(elHeight > boxHeight) {
    elHeight = Math.ceil(boxHeight);
    elWidth = Math.ceil(elHeight * dimensionRatio);
  }

  // Multiply the calculated values (to cover off gaps around edges for instance)
  elWidth *= scale;
  elHeight *= scale;

  if(elWidth < minElWidth || elHeight < minElHeight) {
    elWidth = minElWidth;
    elHeight = minElHeight;
  }

  elWidth = Math.round(elWidth);
  elHeight = Math.round(elHeight);

  $el.css({
    'position': 'absolute',
    'top': '50%',
    'left': '50%',
    'width': elWidth,
    'height': elHeight,
    'margin-top': Math.round(elHeight * -0.5),
    'margin-left': Math.round(elWidth * -0.5)
  });
}

/**
 * Resize media to cover the box (like background-size: cover)
 *
 * @param {Object}  options
 * @param {Number}  options.$el             The element to be resized
 * @param {Number}  options.elWidth         The original width of the media
 * @param {Number}  options.elHeight        The original height of the media
 * @param {Boolean} options.boxWidth        The width of the box
 * @param {Boolean} options.boxHeight       The height of the box
 * @param {Boolean} options.padding         Padding
 * @param {Boolean} options.scale           Adds extra scale
*/
VideoOverlay.fitToBox = function(options) {
  if(!_.size(options)) {
    console.error('[VideoOverlay] - fitToBox() - missing some arguments for proper resize, quitting.');
    return;
  }

  // Calculate the image dimensions based on the window dimensions
  var $el = options.$el;
  var scale = options.scale || 1;
  var naturalWidth = options.elWidth;
  var naturalHeight = options.elHeight;
  var padding = options.padding || 0;
  var boxWidth = options.boxWidth - padding * 2;
  var boxHeight = options.boxHeight - padding * 2;
  var elWidth = Math.ceil(boxWidth);
  var dimensionRatio = naturalWidth / naturalHeight;
  var elHeight = Math.ceil(elWidth / dimensionRatio);

  if(boxWidth / boxHeight < dimensionRatio) {
    elHeight = Math.ceil(boxHeight);
    elWidth = Math.ceil(elHeight * dimensionRatio);
  }

  // Multiply the calculated values (to cover off gaps around edges for instance)
  elWidth *= scale;
  elHeight *= scale;

  elWidth = Math.round(elWidth);
  elHeight = Math.round(elHeight);

  $el.css({
    'position': 'absolute',
    'top': '50%',
    'left': '50%',
    'width': elWidth,
    'height': elHeight,
    'margin-top': Math.round(elHeight * -0.5),
    'margin-left': Math.round(elWidth * -0.5)
  });
}

//
VideoOverlay.EmbedTypes = {
  VIMEO: 'vimeo',
  YOUTUBE: 'youtube'
};

VideoOverlay.VideoDimensions = {
    WIDTH: 1920,
    HEIGHT: 1080
  };<|endoftext|>


<commit_before>var _ = require('underscore');
var fs = require('fs');
var path = require('path');

var cache = {};

// Set the default template extension. Override as necessary.
_.templateExtension = 'tpl';

// Set the special express property for templating to work.
_.__express = function (abs, options, cb) {
  var sync = !cb;
  try {

    // Helper function for sub-templating, store the original value for nested
    // sub-templates.
    var dir = path.dirname(abs);
    options.include = function (rel) {
      var resolved = path.resolve(dir, rel + '.' + _.templateExtension);
      var include = options.include;
      var str = _.__express(resolved, options);
      options.include = include;
      return str;
    };

    // Check cache...
    var fn = options.cache && cache[abs];
    if (!fn) {
      if (sync) {
        var data = fs.readFileSync(abs, 'utf8');
        fn = cache[abs] = _.template(data, null, options);
      } else {
        return fs.readFile(abs, 'utf8', function (er, data) {
          if (er) return cb(er);
          fn = cache[abs] = _.template(data, null, options);
          cb(null, fn(options));
        });
      }
    }

    // Run and return template
    var str = fn(options);
    if (sync) return str;
    cb(null, str);
  } catch (er) {
    if (sync) throw er;
    cb(er);
  }
};
<commit_msg>JK, using `tmpl`<commit_after>var _ = require('underscore');
var fs = require('fs');
var path = require('path');

var cache = {};

// Set the default template extension. Override as necessary.
_.templateExtension = 'tmpl';

// Set the special express property for templating to work.
_.__express = function (abs, options, cb) {
  var sync = !cb;
  try {

    // Helper function for sub-templating, store the original value for nested
    // sub-templates.
    var dir = path.dirname(abs);
    options.include = function (rel) {
      var resolved = path.resolve(dir, rel + '.' + _.templateExtension);
      var include = options.include;
      var str = _.__express(resolved, options);
      options.include = include;
      return str;
    };

    // Check cache...
    var fn = options.cache && cache[abs];
    if (!fn) {
      if (sync) {
        var data = fs.readFileSync(abs, 'utf8');
        fn = cache[abs] = _.template(data, null, options);
      } else {
        return fs.readFile(abs, 'utf8', function (er, data) {
          if (er) return cb(er);
          fn = cache[abs] = _.template(data, null, options);
          cb(null, fn(options));
        });
      }
    }

    // Run and return template
    var str = fn(options);
    if (sync) return str;
    cb(null, str);
  } catch (er) {
    if (sync) throw er;
    cb(er);
  }
};
<|endoftext|>


<commit_before>import { Component, PropTypes } from "react";
import ReactMixin from "react-mixin";
import { connect } from "react-apollo";
import gql from "graphql-tag";

import Loading, { FeedItemSkeleton } from "../../components/loading";

import Headerable from "../../mixins/mixins.Header";
import Pageable from "../../mixins/mixins.Pageable";

import { nav as navActions } from "../../store";
import ApollosPullToRefresh from "../../components/pullToRefresh";

import Single from "./articles.Single";

import FeedItem from "../../components/cards/cards.FeedItem";

const mapQueriesToProps = ({ ownProps, state }) => ({
  data: {
    query: gql`
      query getArticles($limit: Int!, $skip: Int!) {
        content(channel: "articles", limit: $limit, skip: $skip) {
          entryId: id
          title
          status
          channelName
          meta {
            urlTitle
            siteId
            date
            channelId
          }
          content {
            body
            scripture {
              book
              passage
            }
            images(sizes: ["large"]) {
              fileName
              fileType
              fileLabel
              url
            }
          }
        }
      }
    `,
    variables: {
      limit: state.paging.pageSize * state.paging.page,
      skip: state.paging.skip,
    },
    forceFetch: false,
    returnPartialData: false,
  },
});

const mapStateToProps = state => ({ paging: state.paging });

@connect({ mapQueriesToProps, mapStateToProps })
@ReactMixin.decorate(Pageable)
@ReactMixin.decorate(Headerable)
class Template extends Component {

  componentWillMount() {
    this.props.dispatch(navActions.setLevel("TOP"));
    this.headerAction({ title: "All Articles" });
  }

  handleRefresh = (resolve, reject) => {
    this.props.data.refetch()
      .then(resolve)
      .catch(reject);
  }

  renderItems = () => {
    const { content } = this.props.data;
    let articles = [1, 2, 3, 4, 5];
    if (content) articles = content;
    return (
      articles.map((article, i) => {
        return (

            {(() => {
              if (typeof article === "number") return <FeedItemSkeleton />;
              return <FeedItem item={article} />;
            })()}

        );
      })
    );
  }

  render() {
    return (
      <ApollosPullToRefresh handleRefresh={this.handleRefresh}>

              {this.renderItems()}

      </ApollosPullToRefresh>
    );
  }

}

const Routes = [
  { path: "articles", component: Template },
  { path: "articles/:id", component: Single },
];

export default {
  Template,
  Routes,
};
<commit_msg>lint articles index<commit_after>import { Component, PropTypes } from "react";
import ReactMixin from "react-mixin";
import { connect } from "react-apollo";
import gql from "graphql-tag";

import { FeedItemSkeleton } from "../../components/loading";

import Headerable from "../../mixins/mixins.Header";
import Pageable from "../../mixins/mixins.Pageable";

import { nav as navActions } from "../../store";
import ApollosPullToRefresh from "../../components/pullToRefresh";

import Single from "./articles.Single";

import FeedItem from "../../components/cards/cards.FeedItem";

const mapQueriesToProps = ({ state }) => ({
  data: {
    query: gql`
      query getArticles($limit: Int!, $skip: Int!) {
        content(channel: "articles", limit: $limit, skip: $skip) {
          entryId: id
          title
          status
          channelName
          meta {
            urlTitle
            siteId
            date
            channelId
          }
          content {
            body
            scripture {
              book
              passage
            }
            images(sizes: ["large"]) {
              fileName
              fileType
              fileLabel
              url
            }
          }
        }
      }
    `,
    variables: {
      limit: state.paging.pageSize * state.paging.page,
      skip: state.paging.skip,
    },
    forceFetch: false,
    returnPartialData: false,
  },
});

const mapStateToProps = state => ({ paging: state.paging });

@connect({ mapQueriesToProps, mapStateToProps })
@ReactMixin.decorate(Pageable)
@ReactMixin.decorate(Headerable)
class Template extends Component {

  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    data: {
      refetch: PropTypes.func.isRequired,
      content: PropTypes.object.isRequired,
    },
  }

  componentWillMount() {
    this.props.dispatch(navActions.setLevel("TOP"));
    this.headerAction({ title: "All Articles" });
  }

  handleRefresh = (resolve, reject) => {
    this.props.data.refetch()
      .then(resolve)
      .catch(reject);
  }

  renderItems = () => {
    const { content } = this.props.data;
    let articles = [1, 2, 3, 4, 5];
    if (content) articles = content;
    return (
      articles.map((article, i) => (

          {(() => {
            if (typeof article === "number") return <FeedItemSkeleton />;
            return <FeedItem item={article} />;
          })()}

      ))
    );
  }

  render() {
    return (
      <ApollosPullToRefresh handleRefresh={this.handleRefresh}>

              {this.renderItems()}

      </ApollosPullToRefresh>
    );
  }

}

const Routes = [
  { path: "articles", component: Template },
  { path: "articles/:id", component: Single },
];

export default {
  Template,
  Routes,
};
<|endoftext|>


<commit_before>/*
 * Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
 */

define([
    'settings/js/widgets/aci-widget',
    'settings/js/controls/enable-view',
    'text!settings/templates/widgets/query-manipulation-widget.html'
], function(AciWidget, EnableView, queryManipulationTemplate) {

    return AciWidget.extend({

        queryManipulationTemplate: _.template(queryManipulationTemplate),

        initialize: function() {
            AciWidget.prototype.initialize.apply(this, arguments);
        },

        render: function() {
            AciWidget.prototype.render.apply(this, arguments);

            var $validateButtonParent = this.$('button[name=validate]').parent();

            $validateButtonParent.before(this.queryManipulationTemplate({
                strings: this.strings
            }));

            this.$typeahead = this.$('.typeahead-input');
        },

        getConfig: function() {
            return {
                server: AciWidget.prototype.getConfig.call(this),
                typeAheadMode: this.$typeahead.val()
            }
        },

        updateConfig: function(config) {
            AciWidget.prototype.updateConfig.call(this, config.server);

            this.$typeahead.val(config.typeAheadMode);
        }
    });

});<commit_msg>[IDA-649] Added enable view to query manipulation widget.<commit_after>/*
 * Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
 */

define([
    'settings/js/widgets/aci-widget',
    'settings/js/controls/enable-view',
    'text!settings/templates/widgets/query-manipulation-widget.html'
], function(AciWidget, EnableView, queryManipulationTemplate) {

    return AciWidget.extend({
        EnableView: EnableView,
        queryManipulationTemplate: _.template(queryManipulationTemplate),

        initialize: function() {
            AciWidget.prototype.initialize.apply(this, arguments);

            this.enableView = new this.EnableView({
                enableIcon: 'fa fa-file',
                strings: this.strings
            });

            this.listenTo(this.enableView, 'change', function() {
                this.$('.settings-required-flag').toggleClass('hide', !this.enableView.getConfig());
            });
        },

        render: function() {
            AciWidget.prototype.render.apply(this, arguments);

            var $validateButtonParent = this.$('button[name=validate]').parent();

            $validateButtonParent.before(this.queryManipulationTemplate({
                strings: this.strings
            }));

            this.$typeahead = this.$('.typeahead-input');

            this.enableView.render();
            $validateButtonParent.before(this.enableView.el);
        },

        getConfig: function() {
            return {
                enabled: this.enableView.getConfig(),
                server: AciWidget.prototype.getConfig.call(this),
                typeAheadMode: this.$typeahead.val()
            };
        },

        updateConfig: function(config) {
            AciWidget.prototype.updateConfig.call(this, config.server);

            this.$typeahead.val(config.typeAheadMode);
            this.enableView.updateConfig(config.enabled);
        },

        validateInputs: function() {
            return !this.enableView.getConfig() || AciWidget.prototype.validateInputs.call(this);
        },

        shouldValidate: function() {
            return this.enableView.getConfig();
        }
    });

});<|endoftext|>


<commit_before>import { Component, PropTypes } from "react";
import ReactMixin from "react-mixin";
import { connect } from "react-apollo";
import gql from "graphql-tag";

import { FeedItemSkeleton } from "../../components/loading";

import Headerable from "../../mixins/mixins.Header";
import Pageable from "../../mixins/mixins.Pageable";

import { nav as navActions } from "../../store";
import ApollosPullToRefresh from "../../components/pullToRefresh";

import Single from "./articles.Single";

import FeedItem from "../../components/cards/cards.FeedItem";

const mapQueriesToProps = ({ state }) => ({
  data: {
    query: gql`
      query getArticles($limit: Int!, $skip: Int!) {
        content(channel: "articles", limit: $limit, skip: $skip) {
          entryId: id
          title
          status
          channelName
          meta {
            urlTitle
            siteId
            date
            channelId
          }
          content {
            body
            scripture {
              book
              passage
            }
            images(sizes: ["large"]) {
              fileName
              fileType
              fileLabel
              url
            }
          }
        }
      }
    `,
    variables: {
      limit: state.paging.pageSize * state.paging.page,
      skip: state.paging.skip,
    },
    forceFetch: false,
    returnPartialData: false,
  },
});

const mapStateToProps = state => ({ paging: state.paging });

@connect({ mapQueriesToProps, mapStateToProps })
@ReactMixin.decorate(Pageable)
@ReactMixin.decorate(Headerable)
class Template extends Component {

  static propTypes = {
    dispatch: PropTypes.oneOfType([
      PropTypes.object,
      PropTypes.func,
    ]).isRequired,
    data: PropTypes.object.isRequired,
  }

  componentWillMount() {
    this.props.dispatch(navActions.setLevel("TOP"));
    this.headerAction({ title: "All Articles" });
  }

  handleRefresh = (resolve, reject) => {
    this.props.data.refetch()
      .then(resolve)
      .catch(reject);
  }

  renderItems = () => {
    const { content } = this.props.data;
    let articles = [1, 2, 3, 4, 5];
    if (content) articles = content;
    return (
      articles.map((article, i) => (

          {(() => {
            if (typeof article === "number") return <FeedItemSkeleton />;
            return <FeedItem item={article} />;
          })()}

      ))
    );
  }

  render() {
    return (
      <ApollosPullToRefresh handleRefresh={this.handleRefresh}>

              {this.renderItems()}

      </ApollosPullToRefresh>
    );
  }

}

const Routes = [
  { path: "articles", component: Template },
  { path: "articles/:id", component: Single },
];

export default {
  Template,
  Routes,
};
<commit_msg>refactored graphql query<commit_after>import { Component, PropTypes } from "react";
import ReactMixin from "react-mixin";
import { connect } from "react-redux";
import { graphql } from "react-apollo";
import gql from "graphql-tag";

import { FeedItemSkeleton } from "../../components/loading";

import Headerable from "../../mixins/mixins.Header";
import Pageable from "../../mixins/mixins.Pageable";

import { nav as navActions } from "../../store";
import ApollosPullToRefresh from "../../components/pullToRefresh";

import Single from "./articles.Single";

import FeedItem from "../../components/cards/cards.FeedItem";

const ARTICLES_QUERY = gql`
  query getArticles($limit: Int!, $skip: Int!) {
    content(channel: "articles", limit: $limit, skip: $skip) {
      entryId: id
      title
      status
      channelName
      meta {
        urlTitle
        siteId
        date
        channelId
      }
      content {
        body
        scripture {
          book
          passage
        }
        images(sizes: ["large"]) {
          fileName
          fileType
          fileLabel
          url
        }
      }
    }
  }
`;

const withArticles = graphql(ARTICLES_QUERY, {
  options: state => ({
    variables: {
      limit: state.paging.pageSize * state.paging.page,
      skip: state.paging.skip,
    },
  }),
});

const mapStateToProps = state => ({ paging: state.paging });

@withArticles
@connect(mapStateToProps)
@ReactMixin.decorate(Pageable)
@ReactMixin.decorate(Headerable)
class Template extends Component {

  static propTypes = {
    dispatch: PropTypes.oneOfType([
      PropTypes.object,
      PropTypes.func,
    ]).isRequired,
    data: PropTypes.object.isRequired,
  }

  componentWillMount() {
    this.props.dispatch(navActions.setLevel("TOP"));
    this.headerAction({ title: "All Articles" });
  }

  handleRefresh = (resolve, reject) => {
    this.props.data.refetch()
      .then(resolve)
      .catch(reject);
  }

  renderItems = () => {
    const { content } = this.props.data;
    let articles = [1, 2, 3, 4, 5];
    if (content) articles = content;
    return (
      articles.map((article, i) => (

          {(() => {
            if (typeof article === "number") return <FeedItemSkeleton />;
            return <FeedItem item={article} />;
          })()}

      ))
    );
  }

  render() {
    return (
      <ApollosPullToRefresh handleRefresh={this.handleRefresh}>

              {this.renderItems()}

      </ApollosPullToRefresh>
    );
  }

}

const Routes = [
  { path: "articles", component: Template },
  { path: "articles/:id", component: Single },
];

export default {
  Template,
  Routes,
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';

import style from './style.css';
import CategoryLabel from './categoryLabel';
import DetailList from './detailList';

const DEFAULT_FIELDS = ['symbol', 'gene_symbol', 'name', 'gene_synonyms', 'synonyms', 'sourceHref', 'gene_id', 'species', 'type'];

class ResultsList extends Component {
  renderHighlightedValues(highlight) {
    let _data = highlight;
    let _fields = Object.keys(_data).filter( d => {
      return (DEFAULT_FIELDS.indexOf(d) < 0);
    });
    return <DetailList data={_data} fields={_fields} />;
  }

  renderHeader(d) {
    return (

        <CategoryLabel category={d.category} />

    );
  }

  renderDetailFromFields(d, fields) {
    return <DetailList data={d} fields={fields} />;
  }

  renderNonGeneEntry(d, i, fields) {
    return (
      <div className={style.resultContainer} key={`sr${i}`}>
        {this.renderHeader(d)}
        {this.renderDetailFromFields(d, fields)}
        {this.renderHighlightedValues(d.highlight)}

    );
  }

  renderOrthologs(orthologs, label) {
    label = label || 'Orthologs';
    if (orthologs.length === 0) return null;
    let nodes = orthologs.map( (d, i) => {
      let commaNode = (i === orthologs.length - 1) ? null : ', ';
      let evidenceNodes = d.evidences.map( (_d, _i) => {
        let commaNode = (_i === d.evidences.length - 1) ? null : ', ';
        return <span className={style.evidenceFootnote} key={`oe.${i}.${_i}`}>{_d.name}{commaNode};
      });
      return (

          {d.symbol} {evidenceNodes}{commaNode}

      );
    });
    return (

        {label}: {nodes}

    );
  }

  renderHomologyGroup(d, i, fields) {
    return (
      <div className={style.resultContainer} key={`sr${i}`}>
        {this.renderHeader(d)}
        {this.renderDetailFromFields(d, fields)}
        {this.renderHighlightedValues(d.highlight)}
        {this.renderOrthologs(d.member_genes, 'Member Genes')}

    );
  }

  renderGeneEntry(d, i) {
    let topFields = ['name', 'synonyms'];
    let bottomFields = ['species', 'gene_type'];
    return (
      <div className={style.resultContainer} key={`sr${i}`}>
        {this.renderHeader(d)}
          {this.renderDetailFromFields(d, topFields)}

            Source:

          {this.renderDetailFromFields(d, bottomFields)}
          {this.renderOrthologs(d.orthologs)}
          {this.renderHighlightedValues(d.highlight)}

    );
  }

  renderRows() {
    return this.props.entries.map( (d, i) => {
      if (d.category === 'gene') {
        return this.renderGeneEntry(d, i);
      } else {
        let fieldVals = {
          'disease': ['synonyms', 'omim_id'],
          'go': ['synonyms', 'go_branch'],
          'homology_group': ['associated_genes']
        };
        let fields = fieldVals[d.category] || [];
        return this.renderNonGeneEntry(d, i, fields);
      }
    });
  }

  render() {
    return (

        {this.renderRows()}

    );
  }
}

ResultsList.propTypes = {
  entries: React.PropTypes.array
};

export default ResultsList;
<commit_msg>render member genes<commit_after>import React, { Component } from 'react';

import style from './style.css';
import CategoryLabel from './categoryLabel';
import DetailList from './detailList';

const DEFAULT_FIELDS = ['symbol', 'gene_symbol', 'name', 'gene_synonyms', 'synonyms', 'sourceHref', 'gene_id', 'species', 'type'];

class ResultsList extends Component {
  renderHighlightedValues(highlight) {
    let _data = highlight;
    let _fields = Object.keys(_data).filter( d => {
      return (DEFAULT_FIELDS.indexOf(d) < 0);
    });
    return <DetailList data={_data} fields={_fields} />;
  }

  renderHeader(d) {
    return (

        <CategoryLabel category={d.category} />

    );
  }

  renderDetailFromFields(d, fields) {
    return <DetailList data={d} fields={fields} />;
  }

  renderNonGeneEntry(d, i, fields) {
    return (
      <div className={style.resultContainer} key={`sr${i}`}>
        {this.renderHeader(d)}
        {this.renderDetailFromFields(d, fields)}
        {this.renderHighlightedValues(d.highlight)}

    );
  }

  renderOrthologs(orthologs, label) {
    orthologs = orthologs || [];
    label = label || 'Orthologs';
    if (orthologs.length === 0) return null;
    let nodes = orthologs.map( (d, i) => {
      let commaNode = (i === orthologs.length - 1) ? null : ', ';
      let evidences = d.evidences || [];
      let evidenceNodes = evidences.map( (_d, _i) => {
        let commaNode = (_i === evidences.length - 1) ? null : ', ';
        return <span className={style.evidenceFootnote} key={`oe.${i}.${_i}`}>{_d.name}{commaNode};
      });
      return (

          {d.symbol}{evidenceNodes}{commaNode}

      );
    });
    return (

        {label}: {nodes}

    );
  }

  renderHomologyGroup(d, i) {
    let fields = [];
    return (
      <div className={style.resultContainer} key={`sr${i}`}>
        {this.renderHeader(d)}
        {this.renderDetailFromFields(d, fields)}
        {this.renderHighlightedValues(d.highlight)}
        {this.renderOrthologs(d.member_genes, 'Member Genes')}

    );
  }

  renderGeneEntry(d, i) {
    let topFields = ['name', 'synonyms'];
    let bottomFields = ['species', 'gene_type'];
    return (
      <div className={style.resultContainer} key={`sr${i}`}>
        {this.renderHeader(d)}
          {this.renderDetailFromFields(d, topFields)}

            Source:

          {this.renderDetailFromFields(d, bottomFields)}
          {this.renderOrthologs(d.orthologs)}
          {this.renderHighlightedValues(d.highlight)}

    );
  }

  renderRows() {
    return this.props.entries.map( (d, i) => {
      if (d.category === 'gene') {
        return this.renderGeneEntry(d, i);
      } else if (d.category === 'homology_group') {
        return this.renderHomologyGroup(d, i);
      } else {
        let fieldVals = {
          'disease': ['synonyms', 'omim_id'],
          'go': ['synonyms', 'go_branch'],
        };
        let fields = fieldVals[d.category] || [];
        return this.renderNonGeneEntry(d, i, fields);
      }
    });
  }

  render() {
    return (

        {this.renderRows()}

    );
  }
}

ResultsList.propTypes = {
  entries: React.PropTypes.array
};

export default ResultsList;
<|endoftext|>


<commit_before>import { Component, PropTypes } from "react";
import { graphql } from "react-apollo";
import { connect } from "react-redux";
import gql from "graphql-tag";
import { serverWatch } from "meteor/bjwiley2:server-watch";

import createContainer from "../../../blocks/meteor/react-meteor-data";
import { header as headerActions } from "../../../store";

import Layout from "./Layout";

class PageWithoutData extends Component {

  static propTypes = {
    dispatch: PropTypes.func,
    setRightProps: PropTypes.func,
  }

  componentWillMount() {
    this.props.setRightProps({
      background: "//s3.amazonaws.com/ns.assets/apollos/42835.marketing.cen.webad.scheduleyourgiving_1x2.jpg",
    });
  }

  componentDidMount() {
    if (process.env.NATIVE) {
      const item = { title: "Give Now" };
      this.props.dispatch(headerActions.set(item));
    }
  }

  render() {
    return <Layout {...this.props} />;
  }
}

const ACCOUNTS_QUERY = gql`
  query GetFinancialAccounts {
    accounts {
      description
      name
      id: entityId
      summary
      image
      order
      images { fileName, fileType, fileLabel, s3, cloudfront }
    }
  }
`;

const withAccounts = graphql(ACCOUNTS_QUERY, { name: "accounts" });

const Page = connect()(
  withAccounts(
    PageWithoutData
  )
);

const IsAlive = () => {
  let alive = true;
  try { alive = serverWatch.isAlive("ROCK"); } catch (e) { /* do nothing */ }
  return { alive };
};

// Bind reactive data to component
const TemplateWithData = createContainer(IsAlive,
  ((props) => <Page {...props} />)
);

const Routes = [
  { path: "now", component: TemplateWithData },
];

export default {
  TemplateWithData,
  Routes,
};

export {
  PageWithoutData,
  IsAlive,
};
<commit_msg>added link<commit_after>import { Component, PropTypes } from "react";
import { graphql } from "react-apollo";
import { connect } from "react-redux";
import gql from "graphql-tag";
import { serverWatch } from "meteor/bjwiley2:server-watch";

import createContainer from "../../../blocks/meteor/react-meteor-data";
import { header as headerActions } from "../../../store";

import Layout from "./Layout";

class PageWithoutData extends Component {

  static propTypes = {
    dispatch: PropTypes.func,
    setRightProps: PropTypes.func,
  }

  componentWillMount() {
    this.props.setRightProps({
      background: "//s3.amazonaws.com/ns.assets/apollos/42835.marketing.cen.webad.scheduleyourgiving_1x2.jpg",
      link: "/give/schedules",
    });
  }

  componentDidMount() {
    if (process.env.NATIVE) {
      const item = { title: "Give Now" };
      this.props.dispatch(headerActions.set(item));
    }
  }

  render() {
    return <Layout {...this.props} />;
  }
}

const ACCOUNTS_QUERY = gql`
  query GetFinancialAccounts {
    accounts {
      description
      name
      id: entityId
      summary
      image
      order
      images { fileName, fileType, fileLabel, s3, cloudfront }
    }
  }
`;

const withAccounts = graphql(ACCOUNTS_QUERY, { name: "accounts" });

const Page = connect()(
  withAccounts(
    PageWithoutData
  )
);

const IsAlive = () => {
  let alive = true;
  try { alive = serverWatch.isAlive("ROCK"); } catch (e) { /* do nothing */ }
  return { alive };
};

// Bind reactive data to component
const TemplateWithData = createContainer(IsAlive,
  ((props) => <Page {...props} />)
);

const Routes = [
  { path: "now", component: TemplateWithData },
];

export default {
  TemplateWithData,
  Routes,
};

export {
  PageWithoutData,
  IsAlive,
};
<|endoftext|>