<commit_before>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// TODO: move to package

/**
 * Generic Google-like query-language parser generator.
 *
 * key:value                  key contains "value"
 * key=value                  key exactly matches "value"
 * key:value1,value2          key contains "value1" OR "value2"
 * key:(value1|value2)        "
 * key1:value key2:value      key1 contains value AND key2 contains "value"
 * key1:value AND key2:value  "
 * key1:value and key2:value  "
 * key1:value OR key2:value   key1 contains value OR key2 contains "value"
 * key1:value or key2:value   "
 * key:(-value)               key does not contain "value"
 * (expr)                     groups expression
 * -expr                      not expression, ie. -pri:1
 * NOT expr                   not expression, ie. NOT pri:1
 * has:key                    key has a value
 * is:key                     key is a boolean TRUE value
 * key>value                  key is greater than value
 * key-after:value            "
 * key<value                  key is less than value
 * key-before:value           "
 * date:YY/MM/DD              date specified
 * date:today                 date of today
 * date-after:today-7         date newer than 7 days ago
 * date:d1..d2                date within range d1 to d2, inclusive
 * key:me                     key is the current user
 *
 * Date formats:
 * YYYY
 * YYYY-MM
 * YYYY-MM-DD
 * YYYY-MM-DDTHH
 * YYYY-MM-DDTHH:MM
 */
var QueryParserFactory = function(model, opt_enableKeyword) {
  var g = {
    __proto__: grammar,

    START: sym('query'),

    query: sym('or'),

    or: repeat(sym('and'), alt(literal_ic(' OR '), literal(' | ')), 1),

    and: repeat(
      sym('expr'),
      alt(literal_ic('AND '), not(alt(literal_ic(' OR'), literal(' |')), ' ')),
      1),

    expr: alt(
      sym('paren'),
      sym('negate'),
      sym('has'),
      sym('is'),
      sym('equals'),
      sym('before'),
      sym('after'),
      sym('id'),
      sym('keyword')
    ),

    paren: seq1(1, '(', sym('query'), ')'),

    negate: alt(
      seq('-', sym('expr')),
      seq('NOT ', sym('expr'))
    ),

    id: sym('number'),

    has: seq(literal_ic('has:'), sym('fieldname')),

    is: seq(literal_ic('is:'), sym('fieldname')),

    equals: seq(sym('fieldname'), alt(':', '='), sym('valueList')),

    // TODO: merge with 'equals'
    before: seq(sym('fieldname'), alt('<', '<=', '-before:'), sym('value')),

    // TODO: merge with 'equals'
    after: seq(sym('fieldname'), alt('>', '>=', '-after:'), sym('value')),

    value: alt(
      sym('me'),
      sym('date'),
      sym('string'),
      sym('number')),

    compoundValue: alt(
      sym('negateValue'),
      sym('orValue'),
      sym('andValue')),

    negateValue: seq('(', alt('-', literal_ic('not ')), sym('value'), ')'),

    orValue: seq(
      '(',
      repeat(sym('value'), alt('|', literal_ic(' or '), ' | '), 1),
      ')'),

    andValue: seq(
      '(',
      repeat(sym('value'), alt(literal_ic(' and '), ' '), 1),
      ')'),

    valueList: alt(sym('compoundValue'), repeat(sym('value'), ',', 1)),

    keyword: (function() {
      var keyword_ = sym('keyword_');
      return function(ps) {
        return opt_enableKeyword && this.parse(keyword_, ps);
      }
    })(),

    keyword_: str(plus(notChar(' '))),

    me: seq(literal_ic('me'), lookahead(not(sym('char')))),

    date: alt(
      sym('range date'),
      sym('literal date'),
      sym('relative date')),

    'range date': seq(sym('literal date'), '..', sym('literal date')),

    'literal date': alt(
      // YYYY-MM-DDTHH:MM
      seq(sym('number'), '-', sym('number'), '-', sym('number'), 'T',
          sym('number'), ':', sym('number')),
      // YYYY-MM-DDTHH
      seq(sym('number'), '-', sym('number'), '-', sym('number'), 'T',
          sym('number')),
      // YYYY-MM-DD
      seq(sym('number'), '-', sym('number'), '-', sym('number')),
      // YYYY-MM
      seq(sym('number'), '-', sym('number')),
      // YY/MM/DD
      seq(sym('number'), '/', sym('number'), '/', sym('number'))),

    'relative date': seq(literal_ic('today'), optional(seq('-', sym('number')))),

    string: alt(
      sym('word'),
      sym('quoted string')),

    'quoted string': str(seq1(1, '"', repeat(alt(literal('\\"', '"'), notChar('"'))), '"')),

    word: str(plus(sym('char'))),

    char: alt(range('a','z'), range('A', 'Z'), range('0', '9'), '-', '^', '_', '@', '%', '.'),

    number: str(plus(range('0', '9')))
  };

  var fields = [];

  var properties = model.getRuntimeProperties()
  for ( var i = 0 ; i < properties.length ; i++ ) {
    var prop = properties[i];
    fields.push(literal_ic(prop.name, prop));

    for ( var j = 0 ; j < prop.aliases.length ; j++ )
      if ( prop.aliases[j] ) fields.push(literal_ic(prop.aliases[j], prop));

    if ( prop.shortName ) fields.push(literal_ic(prop.shortName, prop));
  }

  fields.sort(function(a, b) {
    var d = a.length - b.length;

    if ( d !== 0 ) return d;

    if ( a == b ) return 0;

    return a < b ? 1 : -1;
  });

  g.fieldname = alt.apply(null, fields);

  g.addActions({
    id: function(v) { return EQ(model.ID, v); },

    or: function(v) { return OR.apply(OR, v); },

    and: function(v) { return AND.apply(AND, v); },

    negate: function(v) { return NOT(v[1]); },

    number: function(v) { return parseInt(v); },

    me: function() { return this.ME || this.X.ME || ""; },

    has: function(v) { return NEQ(v[1], ''); },

    is: function(v) { return EQ(v[1], TRUE); },

    before: function(v) {
      // If the value (v[2]) is a Date range, we take the appropriate end.
      if ( Array.isArray(v[2]) && v[2][0] instanceof Date ) {
        v[2] = v[1] === '<=' ? v[2][1] : v[2][0];
      }
      return (v[1] === '<=' ? LTE : LT) (v[0], v[2]);
    },

    after: function(v) {
      // If the value (v[2]) is a Date range, we take the appropriate end.
      if ( Array.isArray(v[2]) && v[2][0] instanceof Date ) {
        v[2] = v[1] === '>=' ? v[2][0] : v[2][1];
      }
      return (v[1] === '>=' ? GTE : GT) (v[0], v[2]);
    },

    equals: function(v) {
      // v[2], the values, is an array, which might have an 'and', 'or', or
      // 'negated' property on it. The default is 'or'. The partial evaluator
      // will simplify if these are needlessly complex.

      var prop    = v[0];
      var values  = v[2];
      var isInt   = IntProperty.isInstance(prop);
      var isNum   = isInt || FloatProperty.isInstance(prop);
      var isDateField = DateProperty.isInstance(prop) || DateTimeProperty.isInstance(prop);
      var isDateRange = Array.isArray(values[0]) && values[0][0] instanceof Date;

      if ( isDateField || isDateRange ) {
        if ( ! isDateRange ) {
          // Convert the number, a single year, into a date. Fortunately, years
          // are easy to add.
          var start = new Date(0); // Jan 1 1970 at midnight UTC.
          var end   = new Date(0);
          start.setUTCFullYear(values[0]);
          end.setUTCFullYear(+values[0] + 1);
          values = [[start, end]];
        }
        var q = AND(GTE(prop, values[0][0]), LT(prop, values[0][1]));
        return q;
      }

      if ( isNum ) {
        for ( var i = 0 ; i < values.length ; i++ )
          values[i] = isInt ? parseInt(values[i]) : parseFloat(values[i]);
      }

      var expr = ( v[1] === '=' || isNum ) ?
        IN(v[0], values) :
        ContainedInICExpr.create({arg1: compile_(prop), arg2: values}) ;
      if ( values.negated ) {
        return NOT(expr);
      } else if ( values.and ) {
        return AndExpr.create({
          args: values.map(function(x) {
            return expr.model_.create({ arg1: expr.arg1, arg2: [x] });
          })
        });
      } else {
        return expr;
      }
    },

    keyword: function(v) { return KEYWORD(v); },

    negateValue: function(v) {
      v.negated = true;
      return v;
    },

    orValue: function(v) {
      v = v[1];
      v.or = true;
      return v;
    },

    andValue: function(v) {
      v = v[1];
      v.and = true;
      return v;
    },

    // All dates are actually treated as ranges. These are arrays of Date
    // objects: [start, end]. The start is inclusive and the end exclusive.
    // Using these objects, both ranges (date:2014, date:2014-05..2014-06) and
    // open-ended ranges (date>2014-01-01) can be computed higher up.

    // Date formats:
    // YYYY-MM-DDTHH:MM, YYYY-MM-DDTHH, YYYY-MM-DD, YYYY-MM, YY/MM/DD, YYYY
    'literal date': function(v) {
      var start, end, interval;

      start = new Date();
      end = new Date();
      var ops = ['FullYear', 'Month', 'Date', 'Hours', 'Minutes', 'Seconds'];
      var defaults = [0, 1, 1, 0, 0, 0];
      for (var i = 0; i < ops.length; i++) {
        var x = i*2 > v.length ? defaults[i] : v[i*2];
        // Adjust for months being 0-based.
        start['setUTC' + ops[i]](x - (i == 1 ? 1 : 0));
        end['setUTC' + ops[i]](x - (i == 1 ? 1 : 0));
      }

      // Start and end are currently clones of each other.
      // Bump the last portion of the date and set it in end.
      var last = Math.floor(v.length / 2);
      var op = 'UTC' + ops[last];
      end['set' + op](end['get' + op]() + 1);

      return [start, end];
    },

    'relative date': function(v) {
      var d = new Date();
      if ( v[1] ) d.setDate(d.getDate() - v[1][1]);
      return d;
    },

    'range date': function(v) {
      // This gives two dates, and we combined them, the range is from the start
      // of the first date to the end of the second.
      return [v[0][0], v[2][1]];
    }
  });

  return g;
};
<commit_msg>QueryParser '=' now uses EQ_IC.<commit_after>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// TODO: move to package

/**
 * Generic Google-like query-language parser generator.
 *
 * key:value                  key contains "value"
 * key=value                  key exactly matches "value"
 * key:value1,value2          key contains "value1" OR "value2"
 * key:(value1|value2)        "
 * key1:value key2:value      key1 contains value AND key2 contains "value"
 * key1:value AND key2:value  "
 * key1:value and key2:value  "
 * key1:value OR key2:value   key1 contains value OR key2 contains "value"
 * key1:value or key2:value   "
 * key:(-value)               key does not contain "value"
 * (expr)                     groups expression
 * -expr                      not expression, ie. -pri:1
 * NOT expr                   not expression, ie. NOT pri:1
 * has:key                    key has a value
 * is:key                     key is a boolean TRUE value
 * key>value                  key is greater than value
 * key-after:value            "
 * key<value                  key is less than value
 * key-before:value           "
 * date:YY/MM/DD              date specified
 * date:today                 date of today
 * date-after:today-7         date newer than 7 days ago
 * date:d1..d2                date within range d1 to d2, inclusive
 * key:me                     key is the current user
 *
 * Date formats:
 * YYYY
 * YYYY-MM
 * YYYY-MM-DD
 * YYYY-MM-DDTHH
 * YYYY-MM-DDTHH:MM
 */
var QueryParserFactory = function(model, opt_enableKeyword) {
  var g = {
    __proto__: grammar,

    START: sym('query'),

    query: sym('or'),

    or: repeat(sym('and'), alt(literal_ic(' OR '), literal(' | ')), 1),

    and: repeat(
      sym('expr'),
      alt(literal_ic('AND '), not(alt(literal_ic(' OR'), literal(' |')), ' ')),
      1),

    expr: alt(
      sym('paren'),
      sym('negate'),
      sym('has'),
      sym('is'),
      sym('equals'),
      sym('before'),
      sym('after'),
      sym('id'),
      sym('keyword')
    ),

    paren: seq1(1, '(', sym('query'), ')'),

    negate: alt(
      seq('-', sym('expr')),
      seq('NOT ', sym('expr'))
    ),

    id: sym('number'),

    has: seq(literal_ic('has:'), sym('fieldname')),

    is: seq(literal_ic('is:'), sym('fieldname')),

    equals: seq(sym('fieldname'), alt(':', '='), sym('valueList')),

    // TODO: merge with 'equals'
    before: seq(sym('fieldname'), alt('<', '<=', '-before:'), sym('value')),

    // TODO: merge with 'equals'
    after: seq(sym('fieldname'), alt('>', '>=', '-after:'), sym('value')),

    value: alt(
      sym('me'),
      sym('date'),
      sym('string'),
      sym('number')),

    compoundValue: alt(
      sym('negateValue'),
      sym('orValue'),
      sym('andValue')),

    negateValue: seq('(', alt('-', literal_ic('not ')), sym('value'), ')'),

    orValue: seq(
      '(',
      repeat(sym('value'), alt('|', literal_ic(' or '), ' | '), 1),
      ')'),

    andValue: seq(
      '(',
      repeat(sym('value'), alt(literal_ic(' and '), ' '), 1),
      ')'),

    valueList: alt(sym('compoundValue'), repeat(sym('value'), ',', 1)),

    keyword: (function() {
      var keyword_ = sym('keyword_');
      return function(ps) {
        return opt_enableKeyword && this.parse(keyword_, ps);
      }
    })(),

    keyword_: str(plus(notChar(' '))),

    me: seq(literal_ic('me'), lookahead(not(sym('char')))),

    date: alt(
      sym('range date'),
      sym('literal date'),
      sym('relative date')),

    'range date': seq(sym('literal date'), '..', sym('literal date')),

    'literal date': alt(
      // YYYY-MM-DDTHH:MM
      seq(sym('number'), '-', sym('number'), '-', sym('number'), 'T',
          sym('number'), ':', sym('number')),
      // YYYY-MM-DDTHH
      seq(sym('number'), '-', sym('number'), '-', sym('number'), 'T',
          sym('number')),
      // YYYY-MM-DD
      seq(sym('number'), '-', sym('number'), '-', sym('number')),
      // YYYY-MM
      seq(sym('number'), '-', sym('number')),
      // YY/MM/DD
      seq(sym('number'), '/', sym('number'), '/', sym('number'))),

    'relative date': seq(literal_ic('today'), optional(seq('-', sym('number')))),

    string: alt(
      sym('word'),
      sym('quoted string')),

    'quoted string': str(seq1(1, '"', repeat(alt(literal('\\"', '"'), notChar('"'))), '"')),

    word: str(plus(sym('char'))),

    char: alt(range('a','z'), range('A', 'Z'), range('0', '9'), '-', '^', '_', '@', '%', '.'),

    number: str(plus(range('0', '9')))
  };

  var fields = [];

  var properties = model.getRuntimeProperties()
  for ( var i = 0 ; i < properties.length ; i++ ) {
    var prop = properties[i];
    fields.push(literal_ic(prop.name, prop));

    for ( var j = 0 ; j < prop.aliases.length ; j++ )
      if ( prop.aliases[j] ) fields.push(literal_ic(prop.aliases[j], prop));

    if ( prop.shortName ) fields.push(literal_ic(prop.shortName, prop));
  }

  fields.sort(function(a, b) {
    var d = a.length - b.length;

    if ( d !== 0 ) return d;

    if ( a == b ) return 0;

    return a < b ? 1 : -1;
  });

  g.fieldname = alt.apply(null, fields);

  g.addActions({
    id: function(v) { return EQ(model.ID, v); },

    or: function(v) { return OR.apply(OR, v); },

    and: function(v) { return AND.apply(AND, v); },

    negate: function(v) { return NOT(v[1]); },

    number: function(v) { return parseInt(v); },

    me: function() { return this.ME || this.X.ME || ""; },

    has: function(v) { return NEQ(v[1], ''); },

    is: function(v) { return EQ(v[1], TRUE); },

    before: function(v) {
      // If the value (v[2]) is a Date range, we take the appropriate end.
      if ( Array.isArray(v[2]) && v[2][0] instanceof Date ) {
        v[2] = v[1] === '<=' ? v[2][1] : v[2][0];
      }
      return (v[1] === '<=' ? LTE : LT) (v[0], v[2]);
    },

    after: function(v) {
      // If the value (v[2]) is a Date range, we take the appropriate end.
      if ( Array.isArray(v[2]) && v[2][0] instanceof Date ) {
        v[2] = v[1] === '>=' ? v[2][0] : v[2][1];
      }
      return (v[1] === '>=' ? GTE : GT) (v[0], v[2]);
    },

    equals: function(v) {
      // v[2], the values, is an array, which might have an 'and', 'or', or
      // 'negated' property on it. The default is 'or'. The partial evaluator
      // will simplify if these are needlessly complex.

      var prop    = v[0];
      var values  = v[2];
      var isInt   = IntProperty.isInstance(prop);
      var isNum   = isInt || FloatProperty.isInstance(prop);
      var isDateField = DateProperty.isInstance(prop) || DateTimeProperty.isInstance(prop);
      var isDateRange = Array.isArray(values[0]) && values[0][0] instanceof Date;

      if ( isDateField || isDateRange ) {
        if ( ! isDateRange ) {
          // Convert the number, a single year, into a date. Fortunately, years
          // are easy to add.
          var start = new Date(0); // Jan 1 1970 at midnight UTC.
          var end   = new Date(0);
          start.setUTCFullYear(values[0]);
          end.setUTCFullYear(+values[0] + 1);
          values = [[start, end]];
        }
        var q = AND(GTE(prop, values[0][0]), LT(prop, values[0][1]));
        return q;
      }

      var expr;

      if ( isNum ) {
        for ( var i = 0 ; i < values.length ; i++ )
          values[i] = isInt ? parseInt(values[i]) : parseFloat(values[i]);

        expr = IN(v[0], values);
      } else {
        expr = ( v[1] === '=' ) ?
          IN_IC(v[0], values) :
          ContainedInICExpr.create({arg1: compile_(prop), arg2: values}) ;
      }

      if ( values.negated ) {
        return NOT(expr);
      } else if ( values.and ) {
        return AndExpr.create({
          args: values.map(function(x) {
            return expr.model_.create({ arg1: expr.arg1, arg2: [x] });
          })
        });
      } else {
        return expr;
      }
    },

    keyword: function(v) { return KEYWORD(v); },

    negateValue: function(v) {
      v.negated = true;
      return v;
    },

    orValue: function(v) {
      v = v[1];
      v.or = true;
      return v;
    },

    andValue: function(v) {
      v = v[1];
      v.and = true;
      return v;
    },

    // All dates are actually treated as ranges. These are arrays of Date
    // objects: [start, end]. The start is inclusive and the end exclusive.
    // Using these objects, both ranges (date:2014, date:2014-05..2014-06) and
    // open-ended ranges (date>2014-01-01) can be computed higher up.

    // Date formats:
    // YYYY-MM-DDTHH:MM, YYYY-MM-DDTHH, YYYY-MM-DD, YYYY-MM, YY/MM/DD, YYYY
    'literal date': function(v) {
      var start, end, interval;

      start = new Date();
      end = new Date();
      var ops = ['FullYear', 'Month', 'Date', 'Hours', 'Minutes', 'Seconds'];
      var defaults = [0, 1, 1, 0, 0, 0];
      for (var i = 0; i < ops.length; i++) {
        var x = i*2 > v.length ? defaults[i] : v[i*2];
        // Adjust for months being 0-based.
        start['setUTC' + ops[i]](x - (i == 1 ? 1 : 0));
        end['setUTC' + ops[i]](x - (i == 1 ? 1 : 0));
      }

      // Start and end are currently clones of each other.
      // Bump the last portion of the date and set it in end.
      var last = Math.floor(v.length / 2);
      var op = 'UTC' + ops[last];
      end['set' + op](end['get' + op]() + 1);

      return [start, end];
    },

    'relative date': function(v) {
      var d = new Date();
      if ( v[1] ) d.setDate(d.getDate() - v[1][1]);
      return d;
    },

    'range date': function(v) {
      // This gives two dates, and we combined them, the range is from the start
      // of the first date to the end of the second.
      return [v[0][0], v[2][1]];
    }
  });

  return g;
};
<|endoftext|>


<commit_before>'use strict';

var d3Time = require('d3-time');

module.exports = function getUpdateObject(axisLayout, buttonLayout) {
    var axName = axisLayout._name;
    var update = {};

    if(buttonLayout.step === 'all') {
        update[axName + '.autorange'] = true;
    } else {
        var xrange = getXRange(axisLayout, buttonLayout);

        update[axName + '.range[0]'] = xrange[0];
        update[axName + '.range[1]'] = xrange[1];
    }

    return update;
};

function getXRange(axisLayout, buttonLayout) {
    var currentRange = axisLayout.range;
    var base = new Date(axisLayout.r2l(currentRange[1]));
    var step = buttonLayout.step;

    var utcStep = d3Time['utc' +
        // Capitalized and pluralized step e.g. month -> Months
        (step.charAt(0).toUpperCase() + step.slice(1))
    ];

    var count = buttonLayout.count;
    var range0;

    switch(buttonLayout.stepmode) {
        case 'backward':
            range0 = axisLayout.l2r(+utcStep.offset(base, -count));
            break;

        case 'todate':
            var base2 = utcStep.offset(base, -count);

            range0 = axisLayout.l2r(+utcStep.ceil(base2));
            break;
    }

    var range1 = currentRange[1];

    return [range0, range1];
}
<commit_msg>correct comment<commit_after>'use strict';

var d3Time = require('d3-time');

module.exports = function getUpdateObject(axisLayout, buttonLayout) {
    var axName = axisLayout._name;
    var update = {};

    if(buttonLayout.step === 'all') {
        update[axName + '.autorange'] = true;
    } else {
        var xrange = getXRange(axisLayout, buttonLayout);

        update[axName + '.range[0]'] = xrange[0];
        update[axName + '.range[1]'] = xrange[1];
    }

    return update;
};

function getXRange(axisLayout, buttonLayout) {
    var currentRange = axisLayout.range;
    var base = new Date(axisLayout.r2l(currentRange[1]));
    var step = buttonLayout.step;

    var utcStep = d3Time['utc' +
        // Capitalized step e.g. month -> Month
        (step.charAt(0).toUpperCase() + step.slice(1))
    ];

    var count = buttonLayout.count;
    var range0;

    switch(buttonLayout.stepmode) {
        case 'backward':
            range0 = axisLayout.l2r(+utcStep.offset(base, -count));
            break;

        case 'todate':
            var base2 = utcStep.offset(base, -count);

            range0 = axisLayout.l2r(+utcStep.ceil(base2));
            break;
    }

    var range1 = currentRange[1];

    return [range0, range1];
}
<|endoftext|>


<commit_before>
let _ = require('lodash')
let Promise = require('bluebird')
let moment = require('moment')
let { log, getTarget, loadConfig, to } = require('../../helpers')
let ignoreParser = require('gitignore-parser')
let path = require('path')
let fs = require('fs')
Promise.promisifyAll(fs)
let requestify = require('../../requestify')
let chokidar = require('chokidar')
let AwaitLock = require('await-lock')

module.exports = async function (argv) {
  let ignore = null
  let config = await loadConfig()
  let target = await getTarget(config, argv)
  let lock = new AwaitLock()

  try {
    let ignoreFile = await fs.readFileAsync('.quickshot-ignore', 'utf8')
    ignore = ignoreParser.compile(ignoreFile)
  } catch (err) {}

  let watcher = chokidar.watch('./theme/', {
    ignored: /[/\\]\./,
    persistent: true,
    ignoreInitial: true,
    usePolling: true,
    interval: 300,
    binaryInterval: 300,
    cwd: process.cwd()
  })

  watcher.on('all', async function (event, filePath) {
    await lock.acquireAsync()
    try {
      let pathParts = filePath.split(path.sep)
      let trimmedParts = _.drop(pathParts, (_.lastIndexOf(pathParts, 'theme') + 1))
      let key = trimmedParts.join(path.sep)

      if (!filePath.match(/^theme/)) { return }
      if (filePath.match(/^\..*$/)) { return }
      if (filePath.match(/[()]/)) {
        log(`Filename may not contain parentheses, please rename - "${filePath}"`, 'red')
        return
      }

      if (ignore && ignore.denies(filePath)) {
        log(`IGNORING: ${filePath}`, 'yellow')
        return
      }

      if (['add', 'change'].includes(event)) {
        let data = await fs.readFileAsync(filePath)
        await requestify(target, {
          method: 'put',
          url: `/themes/${target.theme_id}/assets.json`,
          body: {
            asset: {
              key: key.split(path.sep).join('/'),
              attachment: data.toString('base64')
            }
          }
        })

        log(`Added/Updated ${filePath}`, 'green')
      } else if (event === 'unlink') {
        await requestify(target, {
          method: 'delete',
          url: `/themes/${target.theme_id}/assets.json?asset[key]=${key.split(path.sep).join('/')}`
        })

        log(`Deleted ${filePath}`, 'green')
      }
    } catch (err) {
      log(err, 'red')
    }
    await lock.release()
  })

  if (argv.sync === true) {
    log('Two-Way sync is enabled!', 'yellow')

    let checkShopify = async function () {
      await lock.acquireAsync()
      try {
        let res = await requestify(target, {
          method: 'get',
          url: `/themes/${target.theme_id}/assets.json`
        })

        Promise.map(res.assets, async function (asset) {
          let stat = await to(fs.statAsync(path.join('theme', asset.key)))
          let fileExists = true

          if (stat.isError && stat.code === 'ENOENT') {
            fileExists = false
          }

          let key = asset.key
          let localMtime = moment(stat.mtime).toDate()
          let remoteMtime = moment(asset.updated_at).toDate()
          let localSize = stat.size
          let remoteSize = asset.size

          if (fileExists) {
            if (localSize === remoteSize) {
              return
            }

            if (localMtime > remoteMtime) {
              return
            }
          }

          let data = await requestify(target, {
            method: 'get',
            url: `/themes/${target.theme_id}/assets.json`,
            qs: {
              'asset[key]': key,
              theme_id: target.theme_id
            }
          })

          data = data.asset
          let rawData = null

          if (data.attachment) {
            rawData = Buffer.from(data.attachment, 'base64')
          } else if (data.value) {
            rawData = Buffer.from(data.value, 'utf8')
          }

          await fs.mkdirAsync(path.join(process.cwd(), 'theme', path.dirname(data.key)), { recursive: true })
          await fs.writeFileAsync(path.join(process.cwd(), 'theme', data.key), rawData)

          log(`Downloaded ${key}`, 'green')
        }, { concurrency: 1 })
      } catch (err) {
        log(err, 'red')
      }
      await lock.release()
      setTimeout(checkShopify, 3000)
    }

    setTimeout(checkShopify, 100)
  }

  log('watching theme...', 'green')
}
<commit_msg>prevent null text<commit_after>
let _ = require('lodash')
let Promise = require('bluebird')
let moment = require('moment')
let { log, getTarget, loadConfig, to } = require('../../helpers')
let ignoreParser = require('gitignore-parser')
let path = require('path')
let fs = require('fs')
Promise.promisifyAll(fs)
let requestify = require('../../requestify')
let chokidar = require('chokidar')
let AwaitLock = require('await-lock')

module.exports = async function (argv) {
  let ignore = null
  let config = await loadConfig()
  let target = await getTarget(config, argv)
  let lock = new AwaitLock()

  try {
    let ignoreFile = await fs.readFileAsync('.quickshot-ignore', 'utf8')
    ignore = ignoreParser.compile(ignoreFile)
  } catch (err) {}

  let watcher = chokidar.watch('./theme/', {
    ignored: /[/\\]\./,
    persistent: true,
    ignoreInitial: true,
    usePolling: true,
    interval: 300,
    binaryInterval: 300,
    cwd: process.cwd()
  })

  watcher.on('all', async function (event, filePath) {
    await lock.acquireAsync()
    try {
      let pathParts = filePath.split(path.sep)
      let trimmedParts = _.drop(pathParts, (_.lastIndexOf(pathParts, 'theme') + 1))
      let key = trimmedParts.join(path.sep)

      if (!filePath.match(/^theme/)) { return }
      if (filePath.match(/^\..*$/)) { return }
      if (filePath.match(/[()]/)) {
        log(`Filename may not contain parentheses, please rename - "${filePath}"`, 'red')
        return
      }

      if (ignore && ignore.denies(filePath)) {
        log(`IGNORING: ${filePath}`, 'yellow')
        return
      }

      if (['add', 'change'].includes(event)) {
        let data = await fs.readFileAsync(filePath)
        data = data.toString('base64')

        if (data === 'null') {
          data = ''
        }

        await requestify(target, {
          method: 'put',
          url: `/themes/${target.theme_id}/assets.json`,
          body: {
            asset: {
              key: key.split(path.sep).join('/'),
              attachment: data
            }
          }
        })

        log(`Added/Updated ${filePath}`, 'green')
      } else if (event === 'unlink') {
        await requestify(target, {
          method: 'delete',
          url: `/themes/${target.theme_id}/assets.json?asset[key]=${key.split(path.sep).join('/')}`
        })

        log(`Deleted ${filePath}`, 'green')
      }
    } catch (err) {
      log(err, 'red')
    }
    await lock.release()
  })

  if (argv.sync === true) {
    log('Two-Way sync is enabled!', 'yellow')

    let checkShopify = async function () {
      await lock.acquireAsync()
      try {
        let res = await requestify(target, {
          method: 'get',
          url: `/themes/${target.theme_id}/assets.json`
        })

        Promise.map(res.assets, async function (asset) {
          let stat = await to(fs.statAsync(path.join('theme', asset.key)))
          let fileExists = true

          if (stat.isError && stat.code === 'ENOENT') {
            fileExists = false
          }

          let key = asset.key
          let localMtime = moment(stat.mtime).toDate()
          let remoteMtime = moment(asset.updated_at).toDate()
          let localSize = stat.size
          let remoteSize = asset.size

          if (fileExists) {
            if (localSize === remoteSize) {
              return
            }

            if (localMtime > remoteMtime) {
              return
            }
          }

          let data = await requestify(target, {
            method: 'get',
            url: `/themes/${target.theme_id}/assets.json`,
            qs: {
              'asset[key]': key,
              theme_id: target.theme_id
            }
          })

          data = data.asset
          let rawData = null

          if (data.attachment) {
            rawData = Buffer.from(data.attachment, 'base64')
          } else if (data.value) {
            rawData = Buffer.from(data.value, 'utf8')
          }

          await fs.mkdirAsync(path.join(process.cwd(), 'theme', path.dirname(data.key)), { recursive: true })
          await fs.writeFileAsync(path.join(process.cwd(), 'theme', data.key), rawData)

          log(`Downloaded ${key}`, 'green')
        }, { concurrency: 1 })
      } catch (err) {
        log(err, 'red')
      }
      await lock.release()
      setTimeout(checkShopify, 3000)
    }

    setTimeout(checkShopify, 100)
  }

  log('watching theme...', 'green')
}
<|endoftext|>


<commit_before>import {checkRendering} from '../../lib/utils.js';
import generateRanges from './generate-ranges.js';

const usage = `Usage:
var customPriceRanges = connectToggle(function render(params, isFirstRendering) {
  // params = {
  //   items,
  //   refine,
  //   instantSearchInstance,
  //   widgetParams,
  // }
});
search.addWidget(
  customPriceRanges({
    attributeName,
  })
);
Full documentation available at https://community.algolia.com/instantsearch.js/connectors/connectPriceRanges.html
`;

/**
 * @typedef {Object} CustomPriceRangesWidgetOptions
 * @property {string} attributeName Name of the attribute for faceting
 */

/**
 * @typedef {Object} PriceRangesRenderingOptions
 * @property {Object[]} items the prices ranges to display
 * @property {function({from: ?number, to: ?number})} refine select or unselect a price range and trigger a search
 * @property {Object} widgetParams all original options forwarded to rendering
 * @property {InstantSearch} instantSearchInstance the instance of instantsearch on which the widget is attached
 */

 /**
  * Connects a rendering function with the price ranges business logic.
  * @type {Connector}
  * @param {function(PriceRangesRenderingOptions, boolean)} renderFn function that renders the price ranges widget
  * @return {function(CustomPriceRangesWidgetOptions)} a widget factory for price ranges widget
  */
export default function connectPriceRanges(renderFn) {
  checkRendering(renderFn, usage);

  return (widgetParams = {}) => {
    const {attributeName} = widgetParams;

    if (!attributeName) {
      throw new Error(usage);
    }

    return {
      getConfiguration() {
        return {facets: [attributeName]};
      },

      _generateRanges(results) {
        const stats = results.getFacetStats(attributeName);
        return generateRanges(stats);
      },

      _extractRefinedRange(helper) {
        const refinements = helper.getRefinements(attributeName);
        let from;
        let to;

        if (refinements.length === 0) {
          return [];
        }

        refinements.forEach(v => {
          if (v.operator.indexOf('>') !== -1) {
            from = Math.floor(v.value[0]);
          } else if (v.operator.indexOf('<') !== -1) {
            to = Math.ceil(v.value[0]);
          }
        });
        return [{from, to, isRefined: true}];
      },

      _refine(helper, {from, to}) {
        const facetValues = this._extractRefinedRange(helper);

        helper.clearRefinements(attributeName);
        if (facetValues.length === 0 || facetValues[0].from !== from || facetValues[0].to !== to) {
          if (typeof from !== 'undefined') {
            helper.addNumericRefinement(attributeName, '>=', Math.floor(from));
          }
          if (typeof to !== 'undefined') {
            helper.addNumericRefinement(attributeName, '<=', Math.ceil(to));
          }
        }

        helper.search();
      },

      init({helper, instantSearchInstance}) {
        this._refine = this._refine.bind(this, helper);

        renderFn({
          instantSearchInstance,
          items: [],
          refine: this._refine,
          widgetParams,
        }, true);
      },

      render({results, helper, state, createURL, instantSearchInstance}) {
        let facetValues;

        if (results && results.hits && results.hits.length > 0) {
          facetValues = this._extractRefinedRange(helper);

          if (facetValues.length === 0) {
            facetValues = this._generateRanges(results);
          }
        } else {
          facetValues = [];
        }

        facetValues.map(facetValue => {
          let newState = state.clearRefinements(attributeName);
          if (!facetValue.isRefined) {
            if (facetValue.from !== undefined) {
              newState = newState.addNumericRefinement(attributeName, '>=', Math.floor(facetValue.from));
            }
            if (facetValue.to !== undefined) {
              newState = newState.addNumericRefinement(attributeName, '<=', Math.ceil(facetValue.to));
            }
          }
          facetValue.url = createURL(newState);
          return facetValue;
        });

        renderFn({
          items: facetValues,
          refine: this._refine,
          widgetParams,
          instantSearchInstance,
        }, false);
      },
    };
  };
}
<commit_msg>doc: connectPriceRanges<commit_after>import {checkRendering} from '../../lib/utils.js';
import generateRanges from './generate-ranges.js';

const usage = `Usage:
var customPriceRanges = connectToggle(function render(params, isFirstRendering) {
  // params = {
  //   items,
  //   refine,
  //   instantSearchInstance,
  //   widgetParams,
  // }
});
search.addWidget(
  customPriceRanges({
    attributeName,
  })
);
Full documentation available at https://community.algolia.com/instantsearch.js/connectors/connectPriceRanges.html
`;

/**
 * @typedef {Object} PriceRangesItem
 * @property {number} [from] Low bound of the price range.
 * @property {number} [to] High bound of the price range.
 */

/**
 * @typedef {Object} CustomPriceRangesWidgetOptions
 * @property {string} attributeName Name of the attribute for faceting.
 */

/**
 * @typedef {Object} PriceRangesRenderingOptions
 * @property {PriceRangesItem[]} items The prices ranges to display.
 * @property {function(PriceRangesItem)} refine Select or unselect a price range and trigger a search.
 * @property {Object} widgetParams All original `CustomPriceRangesWidgetOptions` forwarded to the `renderFn`.
 * @property {InstantSearch} instantSearchInstance Instance of instantsearch on which the widget is attached.
 */

/**
 * **PriceRanges** connector provides the logic to build a custom widget that will give the user the ability to refine results by price ranges.
 * @type {Connector}
 * @param {function(PriceRangesRenderingOptions, boolean)} renderFn Rendering function for the custom **PriceRanges** widget.
 * @return {function(CustomPriceRangesWidgetOptions)} Re-usable widget factory for a custom **PriceRanges** widget.
 * @example
 * var $ = window.$;
 * var instantsearch = window.instantsearch;
 *
 * function getLabel(item) {
 *   var from = item.from;
 *   var to = item.to;
 *
 *   if (to === undefined) return '≥ $' + from;
 *   if (from === undefined) return '≤ $' + to;
 *   return '$' + from + ' - $' + to;
 * }
 *
 * // custom `renderFn` to render the custom ClearAll widget
 * function renderFn(PriceRangesRenderingOptions, isFirstRendering) {
 *   if (isFirstRendering) {
 *     PriceRangesRenderingOptions.widgetParams.containerNode.html('

');
 *   }
 *
 *   PriceRangesRenderingOptions.widgetParams.containerNode
 *     .find('ul > li')
 *     .each(function() { $(this).off('click'); });
 *
 *   var list = PriceRangesRenderingOptions.items.map(function(item) {
 *     return '
' + getLabel(item) + '
';
 *   });
 *
 *   PriceRangesRenderingOptions.widgetParams.containerNode
 *     .find('ul')
 *     .html(list);
 *
 *   PriceRangesRenderingOptions.widgetParams.containerNode
 *     .find('li')
 *     .each(function(index) {
 *       $(this).on('click', function(event) {
 *         event.stopPropagation();
 *         event.preventDefault();
 *
 *         PriceRangesRenderingOptions.refine(items[index]);
 *       });
 *     });
 * }
 *
 * // connect `renderFn` to PriceRanges logic
 * var customPriceRanges = instantsearch.connectors.connectPriceRanges(renderFn);
 *
 * // mount widget on the page
 * search.addWidget(
 *   customPriceRanges({
 *     containerNode: $('#custom-price-ranges-container'),
 *     attributeName: 'price',
 *   })
 * );
 */
export default function connectPriceRanges(renderFn) {
  checkRendering(renderFn, usage);

  return (widgetParams = {}) => {
    const {attributeName} = widgetParams;

    if (!attributeName) {
      throw new Error(usage);
    }

    return {
      getConfiguration() {
        return {facets: [attributeName]};
      },

      _generateRanges(results) {
        const stats = results.getFacetStats(attributeName);
        return generateRanges(stats);
      },

      _extractRefinedRange(helper) {
        const refinements = helper.getRefinements(attributeName);
        let from;
        let to;

        if (refinements.length === 0) {
          return [];
        }

        refinements.forEach(v => {
          if (v.operator.indexOf('>') !== -1) {
            from = Math.floor(v.value[0]);
          } else if (v.operator.indexOf('<') !== -1) {
            to = Math.ceil(v.value[0]);
          }
        });
        return [{from, to, isRefined: true}];
      },

      _refine(helper, {from, to}) {
        const facetValues = this._extractRefinedRange(helper);

        helper.clearRefinements(attributeName);
        if (facetValues.length === 0 || facetValues[0].from !== from || facetValues[0].to !== to) {
          if (typeof from !== 'undefined') {
            helper.addNumericRefinement(attributeName, '>=', Math.floor(from));
          }
          if (typeof to !== 'undefined') {
            helper.addNumericRefinement(attributeName, '<=', Math.ceil(to));
          }
        }

        helper.search();
      },

      init({helper, instantSearchInstance}) {
        this._refine = this._refine.bind(this, helper);

        renderFn({
          instantSearchInstance,
          items: [],
          refine: this._refine,
          widgetParams,
        }, true);
      },

      render({results, helper, state, createURL, instantSearchInstance}) {
        let facetValues;

        if (results && results.hits && results.hits.length > 0) {
          facetValues = this._extractRefinedRange(helper);

          if (facetValues.length === 0) {
            facetValues = this._generateRanges(results);
          }
        } else {
          facetValues = [];
        }

        facetValues.map(facetValue => {
          let newState = state.clearRefinements(attributeName);
          if (!facetValue.isRefined) {
            if (facetValue.from !== undefined) {
              newState = newState.addNumericRefinement(attributeName, '>=', Math.floor(facetValue.from));
            }
            if (facetValue.to !== undefined) {
              newState = newState.addNumericRefinement(attributeName, '<=', Math.ceil(facetValue.to));
            }
          }
          facetValue.url = createURL(newState);
          return facetValue;
        });

        renderFn({
          items: facetValues,
          refine: this._refine,
          widgetParams,
          instantSearchInstance,
        }, false);
      },
    };
  };
}
<|endoftext|>


<commit_before>(function () {
'use strict';
var module = angular.module('fim.base');

function getCSS(clazz, property) {
  var p = document.createElement("span");
  p.setAttribute("class", clazz);
  p.setAttribute("style", "display:none");
  document.body.appendChild(p);
  var result;
  if (Array.isArray(property)) {
    result = [];
    for (var i=0; i<property.length; i++) {
      result.push(window.getComputedStyle(p).getPropertyValue(property[i]));
    }
  }
  else {
    result = window.getComputedStyle(p).getPropertyValue(property);
  }
  p.parentNode.removeChild(p);
  return result;
}

function generateSpeechBubbleBootstrapCSS() {
  var fromMe = getCSS('btn btn-primary', ['background-color', 'color']);
  var fromThem = getCSS('alert alert-info', ['background-color', 'color']);
  //var fromThem = getCSS('jumbotron', ['background-color', 'color']);
  var background = window.getComputedStyle(document.body).getPropertyValue('background-color');
  var styleElement = document.getElementById('speech-bubble-css');
  if (!styleElement) {
    styleElement = document.createElement('style');
    styleElement.setAttribute('id', 'speech-bubble-css');
    styleElement.setAttribute('type', 'text/css');
    document.head.appendChild(styleElement);
  }
  var html = [
    '.chat .fromMe { color:',fromMe[1],' !important; background:',fromMe[0],' !important; }\n',
    '.chat .fromMe:before { border-right:20px solid ',fromMe[0],' !important;}\n',
    '.chat .fromMe:after { background:',background,' !important;}\n',
    '.chat .fromThem { color:',fromThem[1],' !important; background:',fromThem[0],' !important;}\n',
    '.chat .fromThem:before { border-left:20px solid ',fromThem[0],' !important;}\n',
    '.chat .fromThem:after { background:',background,' !important;}\n',
  ].join('');
  if (styleElement.innerHTML != html) {
    styleElement.innerHTML = html;
  }
}

module.config(function($routeProvider) {
  $routeProvider
    .when('/messenger/:id_rs?', {
      templateUrl: 'partials/messenger.html',
      controller: 'MessengerController'
    });
});

module.controller('MessengerController', function($location, $q, $scope, modals, $rootScope,
  $routeParams, nxt, plugins, GossipChatMessagesProvider, Gossip, Emoji,
  KeyService, $timeout, settings, publicKeyService, GossipChatListProvider) {

  /* might not have been started */
  Gossip.onActivated();
  $scope.gossipUI = Gossip.ui;

  $scope.id_rs   = $routeParams.id_rs;
  $scope.message = {
    text: '',
    html: '',
    name: '',
    recipient: '',
    recipientPublicKey: '',
    recipientValid: false
  };
  $scope.emoji   = { groups: Emoji.groups };
  $scope.ui      = {};
  $scope.ui.emojiCollapse = true;
  $scope.ui.editRecipient = false;
  $scope.ui.chat          = null;
  $scope.ui.sendOffline   = false;
  $scope.ui.isTyping      = false;

  var typing_timeout = null;

  /* have to login first */
  if (!$rootScope.currentAccount) {
    $rootScope.destURL = $location.url();
    $location.path('/login-to');
    return;
  }

  var api = nxt.get($rootScope.currentAccount.id_rs);
  if (!api) {
    $location.path('/login-to');
    return;
  }

  $scope.feeCost = api.engine.feeCost + ' ' + api.engine.symbol;

  generateSpeechBubbleBootstrapCSS();
  settings.resolve('themes.default.theme', function () {
    $timeout(function () {
      generateSpeechBubbleBootstrapCSS();
    });
  });

  $scope.chatListProvider = new GossipChatListProvider(api, $scope, $rootScope.currentAccount.id_rs);
  var promise = $scope.chatListProvider.reload();

  /* enable the add contact editor */
  if ($routeParams.id_rs == 'new') {
    $scope.ui.editRecipient = true;
  }
  /* empty url - load contacts and select first one or go to add contact */
  else if (!$scope.id_rs) {
    promise.then(
      function () {
        var chat = $scope.chatListProvider.entities[0];
        if (chat) {
          $location.path('/messenger/'+chat.otherRS);
        }
        else {
          $location.path('/messenger/new');
        }
      }
    );
  }
  /* contact id provided */
  else {
    promise.then(
      function () {
        /* look up the chat in the provider */
        var provider = $scope.chatListProvider;
        for (var i=0; i<provider.entities.length; i++) {
          if (provider.entities[i].otherRS == $scope.id_rs) {
            $scope.$evalAsync(function () {
              var chat = $scope.ui.chat = provider.entities[i];
              /* sends a ping if user is offline */
              if (chat.provider && !chat.provider.online) {
                Gossip.ping(chat.otherRS);
              }
              /* create the messages provider */
              $scope.chatMessagesProvider = new GossipChatMessagesProvider(api, $scope, 8, $rootScope.currentAccount.id_rs, $scope.id_rs);
              $scope.chatMessagesProvider.reload().then(
                function () {
                  $scope.$evalAsync(function () {
                    $scope.contactName = $scope.chatMessagesProvider.accountTwoName;
                  });
                }
              );
            });
            return;
          }
        }
        /* if it aint there - assume it's an account we want to add */
        $scope.$evalAsync(function () {
          $scope.message.recipient = $scope.id_rs;
          $scope.ui.editRecipient  = true;
          $scope.accountChanged();
        });
      }
    );
  }

  var unregister = Gossip.addListener(
                      Gossip.IS_TYPING_TOPIC,
                      $scope.id_rs,
                      $rootScope.currentAccount.id_rs,
  function (gossip) {
    $scope.$evalAsync(
      function () {
        $scope.ui.isTyping = true;
        $timeout(function () {
          $scope.ui.isTyping = false;
        }, 10*1000);
      }
    );
  });
  $scope.$on('$destroy', unregister);

  $scope.reload = function () {
    if ($scope.chatListProvider) {
      $scope.chatListProvider.reload();
    }
    if ($scope.chatMessagesProvider) {
      $scope.chatMessagesProvider.reload();
    }
  }

  $scope.messageChanged = function () {
    /* notifies that the user is typing a message */
    if (!typing_timeout) {
      typing_timeout = $timeout(function () { typing_timeout = null }, 15*1000, false);
      Gossip.sendGossip($scope.id_rs, "typing", Gossip.IS_TYPING_TOPIC);
    }
    $scope.message.html = Emoji.emojifi($scope.message.text);
  }

  $scope.accountChanged = function () {
    $scope.$evalAsync(function () {
      $scope.message.recipientValid = false;
      $scope.message.recipientPublicKey = '';
      $scope.message.name = '';

      var id_rs = $scope.message.recipient;
      if (id_rs && id_rs.trim().length > 0) {
        var address = api.createAddress();
        if (address.set(id_rs)) {
          $scope.message.recipientValid = true;

          /* see if we can look up the name and public key */
          var arg = {account:id_rs, requestType:'getAccount'};
          api.engine.socket().callAPIFunction(arg).then(function (data) {
            if (data.publicKey || data.accountName) {
              $scope.$evalAsync(function () {
                if (data.publicKey) {
                  $scope.message.recipientPublicKey = data.publicKey;
                  publicKeyService.set(id_rs, data.publicKey);
                }
                $scope.message.name = data.accountName;
              });
            }
          });
        }
      }
    });
  }

  $scope.insertEmoji = function (name) {
    $scope.$evalAsync(function () {
      $scope.message.text += ':'+Emoji.toBase32(name)+':';
      $scope.message.html = Emoji.emojifi($scope.message.text);
    });
  }

  /* Handler for the New Message button.
   * Based on message type (gossip or blockchain) this action has a different effect */
  $scope.newMessage = function () {
    $scope.ui.editRecipient = true;
    $scope.slide.offCanvas  = false;
    $scope.chatMessagesProvider = null;
  }

  /* Handler for the Send Message button.
   * Based on message type (gossip or blockchain) this action has a different effect */
  $scope.sendDirectMessage = function () {
    Gossip.message($scope.id_rs, $scope.message.text).then(
      function () {
        $scope.$evalAsync(function () {
          $scope.ui.emojiCollapse  = true;
          $scope.message.text = '';
          $scope.message.html = '';
          $scope.message.recipient = '';
          $scope.message.recipientPublicKey = '';
          $scope.message.send = true;
          $timeout(function () { $scope.message.send = false }, 5000);
        });
        // TODO - update the chat in GossipChatListProvider last timestamp!
        // $timeout(function () { $scope.chatListProvider.reload() }, 3000);
        $timeout.cancel(typing_timeout);
        typing_timeout = null;
      }
    );
  };

  $scope.addContact = function (id_rs) {

    /* dont add duplicates */
    var provider = $scope.chatListProvider;
    for (var i=0; i<provider.entities.length; i++) {
      if (provider.entities[i].otherRS == id_rs) {
        $location.path('/messenger/'+id_rs);
        return;
      }
    }

    /* store the chat in the db - then go to its page */
    Gossip.getChatService().add(id_rs).then(
      function (chat) {
        $location.path('/messenger/'+id_rs);
      }
    );
  }

  $scope.removeContact = function (id_rs) {
    plugins.get('alerts').confirm({
      title: 'Remove contact',
      message: 'Do you want to remove this contact? Removal cannot be undone.'
    }).then(
      function (value) {
        if (value) {
          Gossip.removeContact(id_rs).then(
            function () {
              /* deleted the active account - navigate away */
              if ($scope.id_rs == id_rs) {
                $location.path('/messenger');
              }
            }
          );
        }
      }
    );
  }

  $scope.goOnlineAgain = function () {
    Gossip.setEnabled();
  }
});
})();<commit_msg>Re-enable sending blockchain messages in chat<commit_after>(function () {
'use strict';
var module = angular.module('fim.base');

function getCSS(clazz, property) {
  var p = document.createElement("span");
  p.setAttribute("class", clazz);
  p.setAttribute("style", "display:none");
  document.body.appendChild(p);
  var result;
  if (Array.isArray(property)) {
    result = [];
    for (var i=0; i<property.length; i++) {
      result.push(window.getComputedStyle(p).getPropertyValue(property[i]));
    }
  }
  else {
    result = window.getComputedStyle(p).getPropertyValue(property);
  }
  p.parentNode.removeChild(p);
  return result;
}

function generateSpeechBubbleBootstrapCSS() {
  var fromMe = getCSS('btn btn-primary', ['background-color', 'color']);
  var fromThem = getCSS('alert alert-info', ['background-color', 'color']);
  //var fromThem = getCSS('jumbotron', ['background-color', 'color']);
  var background = window.getComputedStyle(document.body).getPropertyValue('background-color');
  var styleElement = document.getElementById('speech-bubble-css');
  if (!styleElement) {
    styleElement = document.createElement('style');
    styleElement.setAttribute('id', 'speech-bubble-css');
    styleElement.setAttribute('type', 'text/css');
    document.head.appendChild(styleElement);
  }
  var html = [
    '.chat .fromMe { color:',fromMe[1],' !important; background:',fromMe[0],' !important; }\n',
    '.chat .fromMe:before { border-right:20px solid ',fromMe[0],' !important;}\n',
    '.chat .fromMe:after { background:',background,' !important;}\n',
    '.chat .fromThem { color:',fromThem[1],' !important; background:',fromThem[0],' !important;}\n',
    '.chat .fromThem:before { border-left:20px solid ',fromThem[0],' !important;}\n',
    '.chat .fromThem:after { background:',background,' !important;}\n',
  ].join('');
  if (styleElement.innerHTML != html) {
    styleElement.innerHTML = html;
  }
}

module.config(function($routeProvider) {
  $routeProvider
    .when('/messenger/:id_rs?', {
      templateUrl: 'partials/messenger.html',
      controller: 'MessengerController'
    });
});

module.controller('MessengerController', function($location, $q, $scope, modals, $rootScope,
  $routeParams, nxt, plugins, GossipChatMessagesProvider, Gossip, Emoji,
  KeyService, $timeout, settings, publicKeyService, GossipChatListProvider) {

  /* might not have been started */
  Gossip.onActivated();
  $scope.gossipUI = Gossip.ui;

  $scope.id_rs   = $routeParams.id_rs;
  $scope.message = {
    text: '',
    html: '',
    name: '',
    recipient: '',
    recipientPublicKey: '',
    recipientValid: false
  };
  $scope.emoji   = { groups: Emoji.groups };
  $scope.ui      = {};
  $scope.ui.emojiCollapse = true;
  $scope.ui.editRecipient = false;
  $scope.ui.chat          = null;
  $scope.ui.sendOffline   = false;
  $scope.ui.isTyping      = false;

  var typing_timeout = null;

  /* have to login first */
  if (!$rootScope.currentAccount) {
    $rootScope.destURL = $location.url();
    $location.path('/login-to');
    return;
  }

  var api = nxt.get($rootScope.currentAccount.id_rs);
  if (!api) {
    $location.path('/login-to');
    return;
  }

  $scope.feeCost = api.engine.feeCost + ' ' + api.engine.symbol;

  generateSpeechBubbleBootstrapCSS();
  settings.resolve('themes.default.theme', function () {
    $timeout(function () {
      generateSpeechBubbleBootstrapCSS();
    });
  });

  $scope.chatListProvider = new GossipChatListProvider(api, $scope, $rootScope.currentAccount.id_rs);
  var promise = $scope.chatListProvider.reload();

  /* enable the add contact editor */
  if ($routeParams.id_rs == 'new') {
    $scope.ui.editRecipient = true;
  }
  /* empty url - load contacts and select first one or go to add contact */
  else if (!$scope.id_rs) {
    promise.then(
      function () {
        var chat = $scope.chatListProvider.entities[0];
        if (chat) {
          $location.path('/messenger/'+chat.otherRS);
        }
        else {
          $location.path('/messenger/new');
        }
      }
    );
  }
  /* contact id provided */
  else {
    promise.then(
      function () {
        /* look up the chat in the provider */
        var provider = $scope.chatListProvider;
        for (var i=0; i<provider.entities.length; i++) {
          if (provider.entities[i].otherRS == $scope.id_rs) {
            $scope.$evalAsync(function () {
              var chat = $scope.ui.chat = provider.entities[i];
              /* sends a ping if user is offline */
              if (chat.provider && !chat.provider.online) {
                Gossip.ping(chat.otherRS);
              }
              /* create the messages provider */
              $scope.chatMessagesProvider = new GossipChatMessagesProvider(api, $scope, 8, $rootScope.currentAccount.id_rs, $scope.id_rs);
              $scope.chatMessagesProvider.reload().then(
                function () {
                  $scope.$evalAsync(function () {
                    $scope.contactName = $scope.chatMessagesProvider.accountTwoName;
                  });
                }
              );
            });
            return;
          }
        }
        /* if it aint there - assume it's an account we want to add */
        $scope.$evalAsync(function () {
          $scope.message.recipient = $scope.id_rs;
          $scope.ui.editRecipient  = true;
          $scope.accountChanged();
        });
      }
    );
  }

  var unregister = Gossip.addListener(
                      Gossip.IS_TYPING_TOPIC,
                      $scope.id_rs,
                      $rootScope.currentAccount.id_rs,
  function (gossip) {
    $scope.$evalAsync(
      function () {
        $scope.ui.isTyping = true;
        $timeout(function () {
          $scope.ui.isTyping = false;
        }, 10*1000);
      }
    );
  });
  $scope.$on('$destroy', unregister);

  $scope.reload = function () {
    if ($scope.chatListProvider) {
      $scope.chatListProvider.reload();
    }
    if ($scope.chatMessagesProvider) {
      $scope.chatMessagesProvider.reload();
    }
  }

  $scope.messageChanged = function () {
    /* notifies that the user is typing a message */
    if ($scope.ui.sendOffline) {
      if (!typing_timeout) {
        typing_timeout = $timeout(function () { typing_timeout = null }, 15*1000, false);
        Gossip.sendGossip($scope.id_rs, "typing", Gossip.IS_TYPING_TOPIC);
      }
    }
    $scope.message.html = Emoji.emojifi($scope.message.text);
  }

  $scope.accountChanged = function () {
    $scope.$evalAsync(function () {
      $scope.message.recipientValid = false;
      $scope.message.recipientPublicKey = '';
      $scope.message.name = '';

      var id_rs = $scope.message.recipient;
      if (id_rs && id_rs.trim().length > 0) {
        var address = api.createAddress();
        if (address.set(id_rs)) {
          $scope.message.recipientValid = true;

          /* see if we can look up the name and public key */
          var arg = {account:id_rs, requestType:'getAccount'};
          api.engine.socket().callAPIFunction(arg).then(function (data) {
            if (data.publicKey || data.accountName) {
              $scope.$evalAsync(function () {
                if (data.publicKey) {
                  $scope.message.recipientPublicKey = data.publicKey;
                  publicKeyService.set(id_rs, data.publicKey);
                }
                $scope.message.name = data.accountName;
              });
            }
          });
        }
      }
    });
  }

  $scope.insertEmoji = function (name) {
    $scope.$evalAsync(function () {
      $scope.message.text += ':'+Emoji.toBase32(name)+':';
      $scope.message.html = Emoji.emojifi($scope.message.text);
    });
  }

  /* Handler for the New Message button.
   * Based on message type (gossip or blockchain) this action has a different effect */
  $scope.newMessage = function () {
    $scope.ui.editRecipient = true;
    $scope.slide.offCanvas  = false;
    $scope.chatMessagesProvider = null;
  }

  /* Handler for the Send Message button.
   * Based on message type (gossip or blockchain) this action has a different effect */
  $scope.sendDirectMessage = function () {
    if ($scope.ui.sendOffline) {
      plugins.get('transaction').get('sendMessage').execute($rootScope.currentAccount.id_rs, {
        recipient: $scope.id_rs,
        message: $scope.message.text,
        autoSubmit: true,
      }).then(function (items) {
        $scope.$evalAsync(function () {
          if (items != null) {
            $scope.ui.emojiCollapse  = true;
            $scope.message.text = '';
            $scope.message.html = '';
            $scope.message.recipient = '';
            $scope.message.recipientPublicKey = '';
            $scope.message.send = true;
            $timeout(function () { $scope.message.send = false }, 5000);
          }
        });
      });
    }
    else {
      Gossip.message($scope.id_rs, $scope.message.text).then(
        function () {
          $scope.$evalAsync(function () {
            $scope.ui.emojiCollapse  = true;
            $scope.message.text = '';
            $scope.message.html = '';
            $scope.message.recipient = '';
            $scope.message.recipientPublicKey = '';
            $scope.message.send = true;
            $timeout(function () { $scope.message.send = false }, 5000);
          });
          // TODO - update the chat in GossipChatListProvider last timestamp!
          // $timeout(function () { $scope.chatListProvider.reload() }, 3000);
          $timeout.cancel(typing_timeout);
          typing_timeout = null;
        }
      );
    }
  };

  $scope.addContact = function (id_rs) {

    /* dont add duplicates */
    var provider = $scope.chatListProvider;
    for (var i=0; i<provider.entities.length; i++) {
      if (provider.entities[i].otherRS == id_rs) {
        $location.path('/messenger/'+id_rs);
        return;
      }
    }

    /* store the chat in the db - then go to its page */
    Gossip.getChatService().add(id_rs).then(
      function (chat) {
        $location.path('/messenger/'+id_rs);
      }
    );
  }

  $scope.removeContact = function (id_rs) {
    plugins.get('alerts').confirm({
      title: 'Remove contact',
      message: 'Do you want to remove this contact? Removal cannot be undone.'
    }).then(
      function (value) {
        if (value) {
          Gossip.removeContact(id_rs).then(
            function () {
              /* deleted the active account - navigate away */
              if ($scope.id_rs == id_rs) {
                $location.path('/messenger');
              }
            }
          );
        }
      }
    );
  }

  $scope.goOnlineAgain = function () {
    Gossip.setEnabled();
  }
});
})();<|endoftext|>


<commit_before>import makeApiUrl from './makeApiUrl';

const WCL_API_KEY = process.env.REACT_APP_WCL_API_KEY;
if (!WCL_API_KEY && process.env.NODE_ENV !== 'production') {
  throw new Error('Invalid API key. You need to enter your own API key by creating a new file in the root repo called `.env.local` with the contents: `WCL_API_KEY=INSERT_YOUR_API_KEY_HERE`. After saving this file, you need to restart `npm start`.');
}

// Since the WCL API has a fairly strict request cap, we have implemented a proxy that caches responses.  This proxy provides the same functionality as WCL.
export default function makeWclApiUrl(endpoint, queryParams = {}) {
  queryParams.api_key = WCL_API_KEY;
  return makeApiUrl(`v1/${endpoint}`, queryParams);
}
<commit_msg>Fix error in tests<commit_after>import makeApiUrl from './makeApiUrl';

const WCL_API_KEY = process.env.REACT_APP_WCL_API_KEY;
if (!WCL_API_KEY && process.env.NODE_ENV === 'development') {
  throw new Error('Invalid API key. You need to enter your own API key by creating a new file in the root repo called `.env.local` with the contents: `WCL_API_KEY=INSERT_YOUR_API_KEY_HERE`. After saving this file, you need to restart `npm start`.');
}

// Since the WCL API has a fairly strict request cap, we have implemented a proxy that caches responses.  This proxy provides the same functionality as WCL.
export default function makeWclApiUrl(endpoint, queryParams = {}) {
  queryParams.api_key = WCL_API_KEY;
  return makeApiUrl(`v1/${endpoint}`, queryParams);
}
<|endoftext|>


<commit_before>import * as THREE from 'three';
import { getSize, pad, runBenchmark } from './utils.js';
import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from '../src/index.js';

THREE.Mesh.prototype.raycast = acceleratedRaycast;
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;

const geometry = new THREE.TorusBufferGeometry( 5, 5, 700, 300 );
const mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial() );
const raycaster = new THREE.Raycaster();
raycaster.ray.origin.set( 0, 0, - 10 );
raycaster.ray.direction.set( 0, 0, 1 );

runBenchmark(

	'Compute Bounds Tree',
	() => {

		geometry.computeBoundsTree();
		geometry.boundsTree = null;

	},
	3000,
	50

);

geometry.boundsTree = null;
raycaster.firstHitOnly = false;
runBenchmark(

	'Default Raycast',
	() => mesh.raycast( raycaster, [] ),
	3000

);

geometry.computeBoundsTree();
raycaster.firstHitOnly = false;
runBenchmark(

	'BVH Raycast',
	() => mesh.raycast( raycaster, [] ),
	3000

);

geometry.computeBoundsTree();
raycaster.firstHitOnly = true;
runBenchmark(

	'First Hit Raycast',
	() => mesh.raycast( raycaster, [] ),
	3000

);

console.log( '' );

geometry.computeBoundsTree();

// NOTE: There is a reference to the index in the bounds tree so the
// index will get rolled up with the tree. Subtract it out here.
// If the reference is removed then this will no longer be valid.
const indexSize = getSize( geometry.boundsTree.index );
console.log( `${ pad( 'Index Memory Usage', 25 ) }: ${ indexSize / 1000 } kb` );

const bvhSize = getSize( geometry.boundsTree ) - indexSize;
console.log( `${ pad( 'BVH Memory Usage', 25 ) }: ${ bvhSize / 1000 } kb` );
<commit_msg>Clean up benchmark<commit_after>import * as THREE from 'three';
import { getSize, pad, runBenchmark } from './utils.js';
import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from '../src/index.js';

THREE.Mesh.prototype.raycast = acceleratedRaycast;
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;

const geometry = new THREE.TorusBufferGeometry( 5, 5, 700, 300 );
const mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial() );
const raycaster = new THREE.Raycaster();
raycaster.ray.origin.set( 0, 0, - 10 );
raycaster.ray.direction.set( 0, 0, 1 );

runBenchmark(

	'Compute Bounds Tree',
	() => {

		geometry.computeBoundsTree();
		geometry.boundsTree = null;

	},
	3000,
	50

);

geometry.boundsTree = null;
raycaster.firstHitOnly = false;
runBenchmark(

	'Default Raycast',
	() => mesh.raycast( raycaster, [] ),
	3000

);

geometry.computeBoundsTree();
raycaster.firstHitOnly = false;
runBenchmark(

	'BVH Raycast',
	() => mesh.raycast( raycaster, [] ),
	3000

);

geometry.computeBoundsTree();
raycaster.firstHitOnly = true;
runBenchmark(

	'First Hit Raycast',
	() => mesh.raycast( raycaster, [] ),
	3000

);

console.log( '' );

geometry.computeBoundsTree();

const bvhSize = getSize( geometry.boundsTree );
console.log( `${ pad( 'BVH Memory Usage', 25 ) }: ${ bvhSize / 1000 } kb` );
<|endoftext|>


<commit_before>(() => {
  'use strict'

  const body = document.body
  const cssStyles = {
    container: {
      'height': '100%',
      'width': '100%',
      'position': 'absolute',
      'top': '0',
      'left': '0',
      'background': '#000',
      'text-align': 'center',
      'z-index': '100000'
    },
    status: {
      'width': '80px',
      'position': 'absolute',
      'top': '0',
      'right': '0',
      'padding': '6px 0',
      'color': '#fff',
      'background': '#000',
      'text-align': 'center',
      'z-index': '1000000'
    },
    slide: {
      'height': '100%',
      'width': '100%',
      'display': 'none',
      'background-position': 'center center',
      'background-repeat': 'no-repeat',
      'background-size': 'contain'
    }
  }

  const closest = (el) => el && (el.id.length > 0 ? el : closest(el.parentNode))

  const addCssStyles = (cssStyles) => {
    const style = document.createElement('style')
    style.appendChild(document.createTextNode(''))    // WebKit hack
    document.head.appendChild(style)

    Object.keys(cssStyles).forEach((el) => {
      style.sheet.insertRule(`.fs-gallery-${el}
        ${JSON.stringify(cssStyles[el])
              .replace(/\"/gi,'')
              .replace(/\,/gi,';')}`, 0)
    })
  }

  const renderDiv = (className, container) => {
    const el = document.createElement('div')
    el.classList.add(`fs-gallery-${className}`)

    if (container) container.appendChild(el)

    return el
  }

  const renderSlides = (className, container, images) => {
    const slides = []
    let slide

    images.forEach((image, i) => {
      image.style.opacity = 0
      slide = renderDiv(className, container)
      slide.style.backgroundImage = 'url(' + image.src + ')',
      slide.appendChild(image)
      slides[i] = slide
    })

    slides[0].style.display = 'block'   // show first image

    return slides
  }

  class Gallery {
    constructor(images) {
      this._container = renderDiv('container')
      this._status    = renderDiv('status', this._container)
      this._slides    = renderSlides('slide', this._container, images)
      this._index     = 0
      this._updateStatus(this._index)   // set initial status

      this.showNextSlide = this._showNextSlide.bind(this)
      this.showPrevSlide = this._showPrevSlide.bind(this)
    }

    get html() {
      return this._container
    }

    _updateStatus(index) {
      this._status.innerText = parseInt(index + 1, 10) + ' / ' + this._slides.length
    }

    _showNextSlide() {
      if (this._index < this._slides.length - 1) {
        this._slides[this._index].style.display   = 'none'
        this._slides[++this._index].style.display = 'block'
        this._updateStatus(this._index)
      }
    }

    _showPrevSlide() {
      if (this._index > 0) {
        this._slides[this._index].style.display   = 'none'
        this._slides[--this._index].style.display = 'block'
        this._updateStatus(this._index)
      }
    }
  }

  function generateGallery (e) {
    if (e.target.tagName === 'IMG' && e.target !== e.currentTarget) {
      const images = document.querySelectorAll('#' + closest(e.target.parentNode).id + ' img')
      const gallery = new Gallery(images)

      document.documentElement.style.height = '100%'
      body.style.height  = '100%'
      body.style.padding = '0'
      body.innerHTML = ''     // reset page content
      body.appendChild(gallery.html)

      addCssStyles(cssStyles)

      document.removeEventListener('click', generateGallery)

      document.addEventListener('click', gallery.showNextSlide, false)
      document.addEventListener('keypress', (e) => {
        const key = e.which
        const keys = {
          '0'   : gallery.showNextSlide,  // spacebar
          '32'  : gallery.showNextSlide,  // spacebar
          '108' : gallery.showNextSlide,  // 'l' key
          '107' : gallery.showPrevSlide   // 'k' key
        }

        if (typeof keys[key] !== 'function') return
        else keys[key]()
      }, false)
    }

    e.preventDefault()
    e.stopPropagation()
  }

  function activateClickListener () {
    if (!document.processed) {
      document.processed = true      // allow gallery to be processed just once
      document.addEventListener('click', generateGallery, false)
    }
  }

  document.processed = document.processed || false

  activateClickListener()
})()
<commit_msg>Increased usage of ES6 features<commit_after>(() => {
  'use strict'

  const body = document.body
  const cssStyles = {
    container: {
      'height': '100%',
      'width': '100%',
      'position': 'absolute',
      'top': '0',
      'left': '0',
      'background': '#000',
      'text-align': 'center',
      'z-index': '100000'
    },
    status: {
      'width': '80px',
      'position': 'absolute',
      'top': '0',
      'right': '0',
      'padding': '6px 0',
      'color': '#fff',
      'background': '#000',
      'text-align': 'center',
      'z-index': '1000000'
    },
    slide: {
      'height': '100%',
      'width': '100%',
      'display': 'none',
      'background-position': 'center center',
      'background-repeat': 'no-repeat',
      'background-size': 'contain'
    }
  }

  const closest = (el) => el && (el.id.length > 0 ? el : closest(el.parentNode))

  const addCssStyles = (cssStyles) => {
    const style = document.createElement('style')
    style.appendChild(document.createTextNode(''))    // WebKit hack
    document.head.appendChild(style)

    Object.keys(cssStyles).forEach((el) => {
      style.sheet.insertRule(`.fs-gallery-${el}
        ${JSON.stringify(cssStyles[el])
              .replace(/\"/gi,'')
              .replace(/\,/gi,';')}`, 0)
    })
  }

  const renderDiv = (className, container) => {
    const el = document.createElement('div')
    el.classList.add(`fs-gallery-${className}`)

    if (container) container.appendChild(el)

    return el
  }

  const renderSlides = (className, container, images) => {
    const slides = []
    let slide

    images.forEach((image, i) => {
      image.style.opacity = 0
      slide = renderDiv(className, container)
      slide.style.backgroundImage = 'url(' + image.src + ')',
      slide.appendChild(image)
      slides[i] = slide
    })

    slides[0].style.display = 'block'   // show first image

    return slides
  }

  class Gallery {
    constructor(images) {
      this._container = renderDiv('container')
      this._status    = renderDiv('status', this._container)
      this._slides    = renderSlides('slide', this._container, images)
      this._index     = 0
      this._updateStatus(this._index)   // set initial status

      this.showNextSlide = this._showNextSlide.bind(this)
      this.showPrevSlide = this._showPrevSlide.bind(this)
    }

    get html() {
      return this._container
    }

    _updateStatus(index) {
      this._status.innerText = parseInt(index + 1, 10) + ' / ' + this._slides.length
    }

    _showNextSlide() {
      if (this._index < this._slides.length - 1) {
        this._slides[this._index].style.display   = 'none'
        this._slides[++this._index].style.display = 'block'
        this._updateStatus(this._index)
      }
    }

    _showPrevSlide() {
      if (this._index > 0) {
        this._slides[this._index].style.display   = 'none'
        this._slides[--this._index].style.display = 'block'
        this._updateStatus(this._index)
      }
    }
  }

  function generateGallery (e) {
    if (e.target.tagName === 'IMG' && e.target !== e.currentTarget) {
      const images = document.querySelectorAll(`#${closest(e.target.parentNode).id} img`)
      const gallery = new Gallery(images)

      document.documentElement.style.height = '100%'
      Object.assign(body.style, { height: '100%', padding: 0 })
      body.innerHTML = ''     // reset page content
      body.appendChild(gallery.html)

      addCssStyles(cssStyles)

      document.removeEventListener('click', generateGallery)

      document.addEventListener('click', gallery.showNextSlide, false)
      document.addEventListener('keypress', ({which}) => {
        const keys = {
          '0'   : gallery.showNextSlide,  // spacebar
          '32'  : gallery.showNextSlide,  // spacebar
          '108' : gallery.showNextSlide,  // 'l' key
          '107' : gallery.showPrevSlide   // 'k' key
        }

        if (typeof keys[which] !== 'function') return
        else keys[which]()
      }, false)
    }

    e.preventDefault()
    e.stopPropagation()
  }

  function activateClickListener () {
    if (!document.processed) {
      document.processed = true      // allow gallery to be processed just once
      document.addEventListener('click', generateGallery, false)
    }
  }

  document.processed = document.processed || false

  activateClickListener()
})()
<|endoftext|>


<commit_before>"use strict";

angular.module('IssueTrackingSystem.controllers.user', [])
    .controller('User', ['$scope', '$location', 'authentication', '$sessionStorage',
        function ($scope, $location, authentication, $sessionStorage) {

            $scope.loginUserInSystem = function (user) {
                authentication.loginUser(user)
                    .then(function (loggedUser) {
                        $sessionStorage.isAuthenticated = true;
                        $sessionStorage.access_token = loggedUser.data.access_token;
                        $sessionStorage.token_type = loggedUser.data.token_type;
                        $sessionStorage.username = loggedUser.data.userName;
                        $location.path('/login');
                    }, function (error) {
                        console.error(error);
                    });
            };

            $scope.registerUserInSystem = function (user) {
                authentication.registerUser(user)
                    .then(function (registeredUser) {
                        $scope.loginUserInSystem(registeredUser);
                    }, function (error) {
                        console.error(error);
                    });
            };

            $scope.logoutUser = function () {
                authentication.logout();
                $location.path('/logout');
            };
        }]);<commit_msg>use debug mode<commit_after>"use strict";

angular.module('IssueTrackingSystem.controllers.user', [])
    .controller('User', ['$scope', 'debug', '$location', 'authentication', '$sessionStorage', 'users',
        function user($scope, debug, $location, authentication, $sessionStorage, users) {

            $scope.loginUserInSystem = function (user) {
                authentication.loginUser(user)
                    .then(function (loggedUser) {
                        $sessionStorage.isAuthenticated = true;
                        $sessionStorage.access_token = loggedUser.data.access_token;
                        $sessionStorage.token_type = loggedUser.data.token_type;
                        $sessionStorage.username = loggedUser.data.userName;
                        //if debug mode
                        debug ? console.log(loggedUser.data) : '';
                        $location.path('/login');
                    }, function (error) {
                        console.error(error);
                    });
            };

            $scope.registerUserInSystem = function (user) {
                authentication.registerUser(user)
                    .then(function (registeredUser) {
                        $scope.loginUserInSystem(registeredUser);
                    }, function (error) {
                        console.error(error);
                    });
            };

            $scope.logoutUser = function () {
                authentication.logout();
                $location.path('/logout');
            };
        }]);<|endoftext|>


<commit_before>/* @flow */
import React, { PropTypes, Component } from 'react';
import { Button, Input, Row, Col } from 'react-bootstrap';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import { generateValidation } from 'redux-form-validation';
import TextInput from 'components/FormFields/TextInput';
import FormErrorMessages from 'components/FormFields/FormErrorMessages';
import validations from './ProfileEditForm.validations';
import { reduxForm } from 'redux-form';
import { messages } from './ProfileEditForm.i18n';
import styles from './ProfileEditForm.scss';
import { autobind } from 'core-decorators';

const MALE: string = 'male';
const FEMALE: string = 'female';

class ProfileEditForm extends React.Component {
  static propTypes = {
    fields: PropTypes.object.isRequired,
    intl: intlShape.isRequired,
    values: PropTypes.object.isRequired,
    pristine: PropTypes.bool.isRequired,
    invalid: PropTypes.bool.isRequired,
    resetForm: PropTypes.func.isRequired,
    user: PropTypes.object.isRequired,
    handleUpdate: PropTypes.func.isRequired,
  };

  @autobind
  onUpdateClick() {
    this.props.handleUpdate(Object.assign({}, this.props.user, this.props.values));
  }

  @autobind
  onResetClick() {
    this.props.resetForm();
  }

  isUpdateButtonDisabled(): boolean {
    return this.props.pristine || this.props.invalid;
  }

  isResetButtonDisabled(): boolean {
    return this.props.pristine;
  }

  render(): Component {
    const { fields: { givenName, familyName, nickname, email, emailVerified, gender, locale, notes } } = this.props;
    const { formatMessage } = this.props.intl;
    // in a real app, the locales would be populated via service call:
    const locales: string[] = ['en-GB', 'en-AU', 'es-ES', 'es-CR', 'es-NI'];

    return (

        <form onSubmit={this.onUpdateClick}>
          <Row>
            <Col sm={2}>

            </Col>
            <Col sm={5}>
              <TextInput field={givenName} placeholder={formatMessage(messages.givenName.placeholder)}>
                <FormErrorMessages field={givenName} />
              </TextInput>
              <TextInput field={familyName} placeholder={formatMessage(messages.familyName.placeholder)}>
                <FormErrorMessages field={familyName} />
              </TextInput>
              <TextInput field={nickname} placeholder={formatMessage(messages.nickname.placeholder)} />
              <TextInput field={email} type="email" placeholder={formatMessage(messages.email.placeholder)}>
                <FormErrorMessages field={email} />
              </TextInput>
              <Input type="checkbox" label={formatMessage(messages.emailVerified.label)} {...emailVerified} />
            </Col>
            <Col sm={5}>
              <Input>
                {
                  // Support for radio buttons in react-bootstrap is currently lame.
                  // It's worth watching this though:
                  // https://github.com/react-bootstrap/react-bootstrap/pull/962
                }

                  <input type="radio" className="radio" {...gender} value={MALE} checked={gender.value === MALE}/>
                  {formatMessage(messages.gender.male.label)}

                  <input type="radio" className="radio" {...gender} value={FEMALE} checked={gender.value === FEMALE}/>
                  {formatMessage(messages.gender.female.label)}

              </Input>
              <Input type="select" label={formatMessage(messages.locale.label)} {...locale}>
                {locales.map(_locale => <option key={_locale} value={_locale}>{_locale}</option>)}
              </Input>
              <Input type="textarea" label={formatMessage(messages.notes.label)} {...notes} />
            </Col>
          </Row>
          <Row>
            <Col sm={2} />
            <Col sm={10}>
              <Button
                bsStyle="primary"
                onClick={this.onUpdateClick}
                disabled={this.isUpdateButtonDisabled()}
              >
                <FormattedMessage {...messages.save.label} />
              </Button>
              {
                // Need to include the preceding non-breaking space, because when React renders the HTML,
                // there is no gap in the markup between the buttons - which results in the buttons being
                // flush up against each other.
              }
              <Button
                bsStyle="default"
                onClick={this.onResetClick}
                disabled={this.isResetButtonDisabled()}
              >
                <FormattedMessage {...messages.reset.label} />
              </Button>
            </Col>
          </Row>
        </form>

    );
  }
}

const reduxFormConfig: Object = {
  form: 'editProfile',
  ...generateValidation(validations),
};

const mapStateToProps = (state, props) => ({ initialValues: props.user });

export default reduxForm(reduxFormConfig, mapStateToProps)(injectIntl(ProfileEditForm));
<commit_msg>Change `gender` to HorizontalRadioGroup<commit_after>/* @flow */
import React, { PropTypes, Component } from 'react';
import { Button, Input, Row, Col } from 'react-bootstrap';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import { generateValidation } from 'redux-form-validation';
import TextInput from 'components/FormFields/TextInput';
import HorizontalRadioGroup from 'components/FormFields/HorizontalRadioGroup';
import FormErrorMessages from 'components/FormFields/FormErrorMessages';
import validations from './ProfileEditForm.validations';
import { reduxForm } from 'redux-form';
import { messages } from './ProfileEditForm.i18n';
import styles from './ProfileEditForm.scss';
import { autobind } from 'core-decorators';

const MALE: string = 'male';
const FEMALE: string = 'female';

class ProfileEditForm extends React.Component {
  static propTypes = {
    fields: PropTypes.object.isRequired,
    intl: intlShape.isRequired,
    values: PropTypes.object.isRequired,
    pristine: PropTypes.bool.isRequired,
    invalid: PropTypes.bool.isRequired,
    resetForm: PropTypes.func.isRequired,
    user: PropTypes.object.isRequired,
    handleUpdate: PropTypes.func.isRequired,
  };

  @autobind
  onUpdateClick() {
    this.props.handleUpdate(Object.assign({}, this.props.user, this.props.values));
  }

  @autobind
  onResetClick() {
    this.props.resetForm();
  }

  isUpdateButtonDisabled(): boolean {
    return this.props.pristine || this.props.invalid;
  }

  isResetButtonDisabled(): boolean {
    return this.props.pristine;
  }

  render(): Component {
    const { fields: { givenName, familyName, nickname, email, emailVerified, gender, locale, notes } } = this.props;
    const { formatMessage } = this.props.intl;
    const genderValues = [
      {
        label: formatMessage(messages.gender.male.label),
        value: MALE,
      },
      {
        label: formatMessage(messages.gender.female.label),
        value: FEMALE,
      },
    ];

    // in a real app, the locales would be populated via service call:
    const locales: string[] = ['en-GB', 'en-AU', 'es-ES', 'es-CR', 'es-NI'];

    return (

        <form onSubmit={this.onUpdateClick}>
          <Row>
            <Col sm={2}>

            </Col>
            <Col sm={5}>
              <TextInput field={givenName} placeholder={formatMessage(messages.givenName.placeholder)}>
                <FormErrorMessages field={givenName} />
              </TextInput>
              <TextInput field={familyName} placeholder={formatMessage(messages.familyName.placeholder)}>
                <FormErrorMessages field={familyName} />
              </TextInput>
              <TextInput field={nickname} placeholder={formatMessage(messages.nickname.placeholder)} />
              <TextInput field={email} type="email" placeholder={formatMessage(messages.email.placeholder)}>
                <FormErrorMessages field={email} />
              </TextInput>
              <Input type="checkbox" label={formatMessage(messages.emailVerified.label)} {...emailVerified} />
            </Col>
            <Col sm={5}>
              <HorizontalRadioGroup field={gender} values={genderValues} />
              <Input type="select" label={formatMessage(messages.locale.label)} {...locale}>
                {locales.map(_locale => <option key={_locale} value={_locale}>{_locale}</option>)}
              </Input>
              <Input type="textarea" label={formatMessage(messages.notes.label)} {...notes} />
            </Col>
          </Row>
          <Row>
            <Col sm={2} />
            <Col sm={10}>
              <Button
                bsStyle="primary"
                onClick={this.onUpdateClick}
                disabled={this.isUpdateButtonDisabled()}
              >
                <FormattedMessage {...messages.save.label} />
              </Button>
              {
                // Need to include the preceding non-breaking space, because when React renders the HTML,
                // there is no gap in the markup between the buttons - which results in the buttons being
                // flush up against each other.
              }
              <Button
                bsStyle="default"
                onClick={this.onResetClick}
                disabled={this.isResetButtonDisabled()}
              >
                <FormattedMessage {...messages.reset.label} />
              </Button>
            </Col>
          </Row>
        </form>

    );
  }
}

const reduxFormConfig: Object = {
  form: 'editProfile',
  ...generateValidation(validations),
};

const mapStateToProps = (state, props) => ({ initialValues: props.user });

export default reduxForm(reduxFormConfig, mapStateToProps)(injectIntl(ProfileEditForm));
<|endoftext|>