<commit_before>// Copyright 2013 Bowery Software, LLC
/**
 * @fileoverview Event builder.
 */

// Module Dependencies.
var assert = require('assert')
var Builder = require('./builder')

/**
 * @constructor
 */
function EventBuilder () {}

require('util').inherits(EventBuilder, Builder)

/**
 * Set event origin.
 * @param {string} collection
 * @param {string} key
 * @return {EventBuilder}
 */
EventBuilder.prototype.from = function (collection, key) {
  assert(collection && key, 'Collection and key required.')
  this.collection = collection
  this.key = key
  return this
}

/**
 * Set event type.
 * @param {string} type
 * @return {EventBuilder}
 */
EventBuilder.prototype.type = function (type) {
  assert(type, 'Type required.')
  this.type = type
  if (!this.write) return this._execute('get')
  return this
}

/**
 * Set time range start.
 * @param {number} time
 * @return {EventBuilder}
 */
EventBuilder.prototype.start = function (time) {
  assert(!this.write, 'Invalid operation.')
  assert(time && typeof time == 'number', 'Time required (number).')
  this.startTime = time
  return this
}

/**
 * Set time range end.
 * @param {number} time
 * @return {EventBuilder}
 */
EventBuilder.prototype.end = function (time) {
  assert(!this.write, 'Invalid operation.')
  assert(time && typeof time == 'number', 'Time required (number).')
  this.endTime = time
  return this
}

/**
 * Set event data.
 * @param {Object} data
 * @return {EventBuilder}
 */
EventBuilder.prototype.data = function (data) {
  assert(data, 'Data required.')
  this.data = data
  return this._execute('put')
}

/**
 * Execute event read/write.
 * @param {string} method
 * @return {Object}
 * @protected
 */
EventBuilder.prototype._execute = function (method) {
  var pathArgs = [this.collection, this.key, 'events', this.type]
  if (this.startTime) pathArgs.push(this.startTime)
  if (this.endTime) pathArgs.push(this.endTime)
  var url = this.getDelegate() && this.getDelegate().generateApiUrl(pathArgs)
  return this.getDelegate()['_' + method](url, this.data)
}

// Module Exports.
module.exports = EventBuilder
<commit_msg>support for creating event timestamps<commit_after>// Copyright 2013 Bowery Software, LLC
/**
 * @fileoverview Event builder.
 */

// Module Dependencies.
var assert = require('assert')
var Builder = require('./builder')

/**
 * @constructor
 */
function EventBuilder () {}

require('util').inherits(EventBuilder, Builder)

/**
 * Set event origin.
 * @param {string} collection
 * @param {string} key
 * @return {EventBuilder}
 */
EventBuilder.prototype.from = function (collection, key) {
  assert(collection && key, 'Collection and key required.')
  this.collection = collection
  this.key = key
  return this
}

/**
 * Set event type.
 * @param {string} type
 * @return {EventBuilder}
 */
EventBuilder.prototype.type = function (type) {
  assert(type, 'Type required.')
  this.type = type
  if (!this.write) return this._execute('get')
  return this
}

/**
 * Set time range start.
 * @param {number} time
 * @return {EventBuilder}
 */
EventBuilder.prototype.start = function (time) {
  assert(!this.write, 'Invalid operation.')
  assert(time && typeof time == 'number', 'Time required (number).')
  this.startTime = time
  return this
}

/**
 * Set time range end.
 * @param {number} time
 * @return {EventBuilder}
 */
EventBuilder.prototype.end = function (time) {
  assert(!this.write, 'Invalid operation.')
  assert(time && typeof time == 'number', 'Time required (number).')
  this.endTime = time
  return this
}

/**
 * Set event timestamp.
 * @param {Object} time
 * @return {EventBuilder}
 */
EventBuilder.prototype.time = function (time) {
  assert(this.write, 'Invalid operation.')
  assert(time && typeof time == 'number', 'Time required (number).')
  this.timestamp = time
  return this
}

/**
 * Set event data.
 * @param {Object} data
 * @return {EventBuilder}
 */
EventBuilder.prototype.data = function (data) {
  assert(data, 'Data required.')
  this.data = data
  return this._execute('put')
}

/**
 * Execute event read/write.
 * @param {string} method
 * @return {Object}
 * @protected
 */
EventBuilder.prototype._execute = function (method) {
  var query = {}, pathArgs = [this.collection, this.key, 'events', this.type]
  if (this.startTime) query['start'] = this.startTime
  if (this.endTime) query['end'] = this.endTime
  if (this.timestamp) query['timestamp'] = this.timestamp
  var url = this.getDelegate() && this.getDelegate().generateApiUrl(pathArgs, query)
  return this.getDelegate()['_' + method](url, this.data)
}

// Module Exports.
module.exports = EventBuilder
<|endoftext|>


<commit_before>/**
 * @file 获取代理相关的信息
 * @author zdying
 */

var url = require('url');

var execCommand = require('../commands/execCommand');
var replaceVar = require('../rewrite/replaceVar');
var utils = require('../helpers/utils');
var clone = utils.clone;
var parseCookie = utils.parseCookie;
var parseQueryString = utils.parseQueryString;

/**
 * 获取代理信息, 用于请求代理的地址
 * @param {Object}  request 请求对象
 * @param {Object}  hostsRules 解析后的hosts规则
 * @param {Array}   rewriteRules 解析后的rewrite规则
 * @private
 * @ignore
 * @returns {Object}
 */
module.exports = function getProxyInfo (request, hostsRules, rewriteRules) {
  var originUrl = request.url;
  var uri = url.parse(originUrl);
  var rewrite = !!rewriteRules && getRewriteRule(uri, rewriteRules);
  var host = !!hostsRules && hostsRules[uri.hostname];

  var hostname, port, path, proxyName, protocol;

  protocol = uri.protocol;

  // rewrite 优先级高于 hosts
  if (rewrite /* && rewrite.props.proxy */) {
    // 根据请求信息，设置内置变量
    setBuiltInVars(rewrite, request);
    // 再次替换变量的值
    replaceVar(rewrite.props, rewrite);
    replaceFuncVar(rewrite.commands, rewrite);

    var rewriteProps = rewrite.props;
    var proxy = rewriteProps.proxy || '';
    var isBaseRule = rewrite.isBaseRule;
    var alias = rewriteProps.alias;
    var proxyUrlObj = url.parse(proxy);
    var protocolReg = /^(\w+:\/\/)/;
    var newUrl, newUrlObj;

    // 如果代理地址中包含具体协议，删除原本url中的协议
    // 最终替换位代理地址的协议
    if (!alias && !isBaseRule && proxyUrlObj.protocol) {
      protocol = proxyUrlObj.protocol;
      originUrl = originUrl.replace(protocolReg, '');
    }

    // 将原本url中的部分替换为代理地址
    if (rewrite.source.indexOf('~') === 0) {
      newUrl = proxy;
    } else {
      // 普通地址字符串
      // 否则，把url中的source部分替换成proxy
      newUrl = originUrl.replace(rewrite.source, proxy);
    }

    // TODO 这里应该有个bug, props是共享的, 一个修改了,其他的也修改了
    var context = {
      request: request
      // props: rewrite.props
    };

    execCommand(rewrite, context, 'request');

    log.debug('newURL ==>', newUrl);
    log.debug('newURL ==>', alias);

    if (alias) {
      // 本地文件系统路径, 删除前面的协议部分
      newUrl = newUrl.replace(/^(\w+:\/\/)/, '').split('?')[0];
    } else {
      newUrlObj = url.parse(newUrl);

      hostname = newUrlObj.hostname || '';
      port = newUrlObj.port;
      path = newUrlObj.path;
    }

    proxyName = 'HiProxy';
  } else if (host) {
    // TODO 这里的协议，到底应该用什么?
    protocol = 'http:';
    hostname = host.split(':')[0];
    // port = (protocol === 'https:') ? 443 : Number(uri.port || host.split(':')[1]);
    port = host.split(':')[1] || uri.port || (protocol === 'https:' ? 443 : 80);
    path = uri.path;
    proxyName = 'HiProxy';
  } else {
    hostname = uri.hostname;
    port = uri.port;
    path = uri.path;
  }

  var proxyInfo = {
    proxy_options: {
      hostname: hostname,
      port: port,
      path: path,
      method: request.method,
      headers: request.headers,
      protocol: protocol
    },
    proxyPass: proxy,
    PROXY: proxyName,
    hosts_rule: host,
    rewrite_rule: rewrite,
    alias: alias,
    newUrl: newUrl
  };

  return proxyInfo;
};

/**
 * 根据url查找对应的rewrite规则
 * @param urlObj
 * @param rewriteRules
 * @param domainCache
 * @returns {*}
 */
function getRewriteRule (urlObj, rewriteRules) {
  var hostname = urlObj.hostname;
  var href = urlObj.href;
  var rewriteRule = null;
  var lastDeep = -1;

  var domains = rewriteRules[hostname];

  if (!domains || !Array.isArray(domains)) {
    return null;
  }

  domains.forEach(function (domain) {
    var rule = domain;
    var location = rule.location;
    var urlPath = urlObj.path;
    var loc = null;
    var currentDeep = 0;
    var locPath = '';

    for (var i = 0, len = location.length; i < len; i++) {
      loc = location[i];

      locPath = loc.path;

      log.debug('getRewriteRule - current location path =>', locPath.bold.green);

      if (locPath.indexOf('~') === 0) {
        /** 正则表达式 **/
        var reg = toRegExp(locPath, 'i');

        if (reg.test(href)) {
          currentDeep = reg.source.replace(/^\\?\/|\\?\/$/, '').split(/\\?\//).length;

          if (currentDeep > lastDeep) {
            rewriteRule = loc;
          }

          log.debug(
            'getRewriteRule -',
            'regexp match =>', locPath.match(reg),
            'deep =>', String(currentDeep).bold.green,
            'last deep =>', String(lastDeep).bold.green,
            'should replace last rule =>', String(currentDeep > lastDeep).bold.green
          );
        }
      } else if (urlPath.indexOf(locPath) === 0) {
        /** 非正则表达式 **/
        // 如果url中path以location中的path开头
        currentDeep = locPath.replace(/^\/|\/$/g, '').split('/').length;

        // 如果是'/', 长度设置为0
        if (currentDeep === 1 && locPath === '/') {
          currentDeep = 0;
        }

        log.debug('getRewriteRule -', 'get rewrite rule for url =>', urlObj.href.bold.green);
        log.debug(
          'getRewriteRule -',
          'current match location.path =>', locPath.bold.green,
          'deep =>', String(currentDeep).bold.green,
          'last deep =>', String(lastDeep).bold.green,
          'should replace last rule =>', String(currentDeep > lastDeep).bold.green
        );

        // 如果匹配的深度比上一次匹配的深度深（比上次匹配更精确）
        // 替换成新匹配到的规则
        if (currentDeep > lastDeep) {
          rewriteRule = loc;
          lastDeep = currentDeep;
        }
      }
    }
  });

  log.debug('getProxyInfo -', href, '==>', JSON.stringify(rewriteRule));

  // 不克隆parent，防止clone的时候循环引用
  if (rewriteRule) {
    var newRule = clone(rewriteRule, ['parent']);
    newRule.parent = rewriteRule.parent;
    return newRule;
  } else {
    return null;
  }
}

function toRegExp (str, flags) {
  str = str.replace(/^~\s*\/(.*)\/(\w*)/, '$1 O_o $2');

  var arr = str.split(' O_o ');

  return new RegExp(arr[0], flags === undefined ? arr[2] : flags);
}

// TODO 优化重复代码
function replaceFuncVar (funcs, source) {
  funcs.forEach(function (fun) {
    var params = fun.params;
    var name = fun.name;

    if (name === 'set') {
      // 如果是 set 命令, 不替换第一个参数
      fun.params = [params[0]].concat(replaceVar(fun.params.slice(1), source));
    } else {
      fun.params = replaceVar(fun.params, source);
    }

    // console.log('替换function参数:', name, fun.params);
  });

  return funcs;
}

/**
 * 设置内置变量
 *
 * @param {Object} rewrite 当前请求匹配的rewrite规则
 * @param {http.IncomingMessage} request 当前请求对象
 */
function setBuiltInVars (rewrite, request) {
  var props = rewrite.props;
  var urlObj = url.parse(request.url);
  var headers = request.headers;
  var cookies = parseCookie(headers.cookie);
  var queryObj = parseQueryString(urlObj.search);
  var tmpKey = '';

  var headerHost = headers.host || '';

  var vars = {
    $host: urlObj.host || headerHost,
    $hostname: urlObj.hostname || headerHost.split(':')[0],
    $search: urlObj.search || '',
    $query_string: urlObj.query || '',
    $server_port: urlObj.port || '80',
    $scheme: (urlObj.protocol || '').replace(':', ''),
    $request_uri: urlObj.href,
    $path: urlObj.path,
    $path_name: urlObj.pathname || '',
    $hash: urlObj.hash || '',
    $hash_value: (urlObj.hash || '').replace('#', ''),
    $uri: urlObj.href
  };

  // $cookie_name
  for (var cookie in cookies) {
    tmpKey = cookie.toLowerCase().replace(/-/, '_');
    vars['$cookie_' + tmpKey] = cookies[cookie] || '';
  }

  // $http_name
  // arbitrary request header field; the last part of a variable name is the field name
  // converted to lower case with dashes replaced by underscores
  for (var header in headers) {
    tmpKey = header.toLowerCase().replace(/-/, '_');
    vars['$http_' + tmpKey] = headers[header] || '';
  }

  // $arg_name query string value
  for (var arg in queryObj) {
    tmpKey = arg.toLowerCase().replace(/-/, '_');
    vars['$arg_' + tmpKey] = queryObj[arg] || '';
  }

  // RegExp group
  if (rewrite.source.indexOf('~') === 0) {
    // 正则表达式
    var sourceReg = toRegExp(rewrite.path, 'i');
    var urlMatch = request.url.match(sourceReg);

    if (Array.isArray(urlMatch)) {
      for (var i = 1; i < 9; i++) {
        vars['$' + i] = urlMatch[i] || '';
      }
    }
  }

  for (var key in vars) {
    props[key] = vars[key];
  }
}
<commit_msg>add built-in variable `$base_name` and `$dir_name`<commit_after>/**
 * @file 获取代理相关的信息
 * @author zdying
 */

var url = require('url');
var path = require('path');

var execCommand = require('../commands/execCommand');
var replaceVar = require('../rewrite/replaceVar');
var utils = require('../helpers/utils');
var clone = utils.clone;
var parseCookie = utils.parseCookie;
var parseQueryString = utils.parseQueryString;

/**
 * 获取代理信息, 用于请求代理的地址
 * @param {Object}  request 请求对象
 * @param {Object}  hostsRules 解析后的hosts规则
 * @param {Array}   rewriteRules 解析后的rewrite规则
 * @private
 * @ignore
 * @returns {Object}
 */
module.exports = function getProxyInfo (request, hostsRules, rewriteRules) {
  var originUrl = request.url;
  var uri = url.parse(originUrl);
  var rewrite = !!rewriteRules && getRewriteRule(uri, rewriteRules);
  var host = !!hostsRules && hostsRules[uri.hostname];

  var hostname, port, path, proxyName, protocol;

  protocol = uri.protocol;

  // rewrite 优先级高于 hosts
  if (rewrite /* && rewrite.props.proxy */) {
    // 根据请求信息，设置内置变量
    setBuiltInVars(rewrite, request);
    // 再次替换变量的值
    replaceVar(rewrite.props, rewrite);
    replaceFuncVar(rewrite.commands, rewrite);

    var rewriteProps = rewrite.props;
    var proxy = rewriteProps.proxy || '';
    var isBaseRule = rewrite.isBaseRule;
    var alias = rewriteProps.alias;
    var proxyUrlObj = url.parse(proxy);
    var protocolReg = /^(\w+:\/\/)/;
    var newUrl, newUrlObj;

    // 如果代理地址中包含具体协议，删除原本url中的协议
    // 最终替换位代理地址的协议
    if (!alias && !isBaseRule && proxyUrlObj.protocol) {
      protocol = proxyUrlObj.protocol;
      originUrl = originUrl.replace(protocolReg, '');
    }

    // 将原本url中的部分替换为代理地址
    if (rewrite.source.indexOf('~') === 0) {
      newUrl = proxy;
    } else {
      // 普通地址字符串
      // 否则，把url中的source部分替换成proxy
      newUrl = originUrl.replace(rewrite.source, proxy);
    }

    // TODO 这里应该有个bug, props是共享的, 一个修改了,其他的也修改了
    var context = {
      request: request
      // props: rewrite.props
    };

    execCommand(rewrite, context, 'request');

    log.debug('newURL ==>', newUrl);
    log.debug('newURL ==>', alias);

    if (alias) {
      // 本地文件系统路径, 删除前面的协议部分
      newUrl = newUrl.replace(/^(\w+:\/\/)/, '').split('?')[0];
    } else {
      newUrlObj = url.parse(newUrl);

      hostname = newUrlObj.hostname || '';
      port = newUrlObj.port;
      path = newUrlObj.path;
    }

    proxyName = 'HiProxy';
  } else if (host) {
    // TODO 这里的协议，到底应该用什么?
    protocol = 'http:';
    hostname = host.split(':')[0];
    // port = (protocol === 'https:') ? 443 : Number(uri.port || host.split(':')[1]);
    port = host.split(':')[1] || uri.port || (protocol === 'https:' ? 443 : 80);
    path = uri.path;
    proxyName = 'HiProxy';
  } else {
    hostname = uri.hostname;
    port = uri.port;
    path = uri.path;
  }

  var proxyInfo = {
    proxy_options: {
      hostname: hostname,
      port: port,
      path: path,
      method: request.method,
      headers: request.headers,
      protocol: protocol
    },
    proxyPass: proxy,
    PROXY: proxyName,
    hosts_rule: host,
    rewrite_rule: rewrite,
    alias: alias,
    newUrl: newUrl
  };

  return proxyInfo;
};

/**
 * 根据url查找对应的rewrite规则
 * @param urlObj
 * @param rewriteRules
 * @param domainCache
 * @returns {*}
 */
function getRewriteRule (urlObj, rewriteRules) {
  var hostname = urlObj.hostname;
  var href = urlObj.href;
  var rewriteRule = null;
  var lastDeep = -1;

  var domains = rewriteRules[hostname];

  if (!domains || !Array.isArray(domains)) {
    return null;
  }

  domains.forEach(function (domain) {
    var rule = domain;
    var location = rule.location;
    var urlPath = urlObj.path;
    var loc = null;
    var currentDeep = 0;
    var locPath = '';

    for (var i = 0, len = location.length; i < len; i++) {
      loc = location[i];

      locPath = loc.path;

      log.debug('getRewriteRule - current location path =>', locPath.bold.green);

      if (locPath.indexOf('~') === 0) {
        /** 正则表达式 **/
        var reg = toRegExp(locPath, 'i');

        if (reg.test(href)) {
          currentDeep = reg.source.replace(/^\\?\/|\\?\/$/, '').split(/\\?\//).length;

          if (currentDeep > lastDeep) {
            rewriteRule = loc;
          }

          log.debug(
            'getRewriteRule -',
            'regexp match =>', locPath.match(reg),
            'deep =>', String(currentDeep).bold.green,
            'last deep =>', String(lastDeep).bold.green,
            'should replace last rule =>', String(currentDeep > lastDeep).bold.green
          );
        }
      } else if (urlPath.indexOf(locPath) === 0) {
        /** 非正则表达式 **/
        // 如果url中path以location中的path开头
        currentDeep = locPath.replace(/^\/|\/$/g, '').split('/').length;

        // 如果是'/', 长度设置为0
        if (currentDeep === 1 && locPath === '/') {
          currentDeep = 0;
        }

        log.debug('getRewriteRule -', 'get rewrite rule for url =>', urlObj.href.bold.green);
        log.debug(
          'getRewriteRule -',
          'current match location.path =>', locPath.bold.green,
          'deep =>', String(currentDeep).bold.green,
          'last deep =>', String(lastDeep).bold.green,
          'should replace last rule =>', String(currentDeep > lastDeep).bold.green
        );

        // 如果匹配的深度比上一次匹配的深度深（比上次匹配更精确）
        // 替换成新匹配到的规则
        if (currentDeep > lastDeep) {
          rewriteRule = loc;
          lastDeep = currentDeep;
        }
      }
    }
  });

  log.debug('getProxyInfo -', href, '==>', JSON.stringify(rewriteRule));

  // 不克隆parent，防止clone的时候循环引用
  if (rewriteRule) {
    var newRule = clone(rewriteRule, ['parent']);
    newRule.parent = rewriteRule.parent;
    return newRule;
  } else {
    return null;
  }
}

function toRegExp (str, flags) {
  str = str.replace(/^~\s*\/(.*)\/(\w*)/, '$1 O_o $2');

  var arr = str.split(' O_o ');

  return new RegExp(arr[0], flags === undefined ? arr[2] : flags);
}

// TODO 优化重复代码
function replaceFuncVar (funcs, source) {
  funcs.forEach(function (fun) {
    var params = fun.params;
    var name = fun.name;

    if (name === 'set') {
      // 如果是 set 命令, 不替换第一个参数
      fun.params = [params[0]].concat(replaceVar(fun.params.slice(1), source));
    } else {
      fun.params = replaceVar(fun.params, source);
    }

    // console.log('替换function参数:', name, fun.params);
  });

  return funcs;
}

/**
 * 设置内置变量
 *
 * @param {Object} rewrite 当前请求匹配的rewrite规则
 * @param {http.IncomingMessage} request 当前请求对象
 */
function setBuiltInVars (rewrite, request) {
  var props = rewrite.props;
  var urlObj = url.parse(request.url);
  var headers = request.headers;
  var cookies = parseCookie(headers.cookie);
  var queryObj = parseQueryString(urlObj.search);
  var tmpKey = '';

  var headerHost = headers.host || '';

  var vars = {
    $host: urlObj.host || headerHost,
    $hostname: urlObj.hostname || headerHost.split(':')[0],
    $search: urlObj.search || '',
    $query_string: urlObj.query || '',
    $server_port: urlObj.port || '80',
    $scheme: (urlObj.protocol || '').replace(':', ''),
    $request_uri: urlObj.href,
    $path: urlObj.path,
    $path_name: urlObj.pathname || '',
    $base_name: path.basename(urlObj.path),
    $dir_name: path.dirname(urlObj.path),
    $hash: urlObj.hash || '',
    $hash_value: (urlObj.hash || '').replace('#', ''),
    $uri: urlObj.href
  };

  // $cookie_name
  for (var cookie in cookies) {
    tmpKey = cookie.toLowerCase().replace(/-/, '_');
    vars['$cookie_' + tmpKey] = cookies[cookie] || '';
  }

  // $http_name
  // arbitrary request header field; the last part of a variable name is the field name
  // converted to lower case with dashes replaced by underscores
  for (var header in headers) {
    tmpKey = header.toLowerCase().replace(/-/, '_');
    vars['$http_' + tmpKey] = headers[header] || '';
  }

  // $arg_name query string value
  for (var arg in queryObj) {
    tmpKey = arg.toLowerCase().replace(/-/, '_');
    vars['$arg_' + tmpKey] = queryObj[arg] || '';
  }

  // RegExp group
  if (rewrite.source.indexOf('~') === 0) {
    // 正则表达式
    var sourceReg = toRegExp(rewrite.path, 'i');
    var urlMatch = request.url.match(sourceReg);

    if (Array.isArray(urlMatch)) {
      for (var i = 1; i < 9; i++) {
        vars['$' + i] = urlMatch[i] || '';
      }
    }
  }

  for (var key in vars) {
    props[key] = vars[key];
  }
}
<|endoftext|>


<commit_before>// Defining Vegalite Encoding's schema

//

// TODO use vl.merge, vl.duplicate instead, move to /src as vl.schema
var _ = require("lodash");

var schema = {};

schema.marktype = {
  type: "string",
  enum: ["point", "bar", "line", "area", "circle", "square", "text"]
};

schema.aggr = {
  type: "string",
  enum: ["avg", "sum", "min", "max", "count"],
  supportedEnums: {
    Q: ["avg", "sum", "min", "max", "count"],
    O: ["count"],
    T: ["avg", "min", "max", "count"],
    "": ["count"],
  },
  supportedTypes: {"Q": true, "O": true, "T": true, "": true}
};

schema.timefns = ["month", "year", "day", "date", "hour", "minute", "second"];

schema.fn = {
  type: "string",
  enum: schema.timefns,
  supportedTypes: {"T": true}
}

//TODO(kanitw): add other type of function here

schema.scale_type = {
  type: "string",
  enum: ["linear", "log","pow", "sqrt", "quantile"],
  supportedTypes: {"Q": true}
};

schema.field = {
  type: "object",
  required: ["name", "type"],
  properties: {
    name: {
      type: "string"
    }
  }
};

var typicalField = _.chain(schema.field).cloneDeep().merge({
  type: "object",
  properties: {
    type: {
      type: "string",
      enum: ["O", "Q", "T"]
    },
    bin: {
      type: "boolean",
      supportedTypes: {"Q": true, "O": true}
    },
    aggr: schema.aggr,
    fn: schema.fn,
    scale: {
      type: "object",
      properties: {
        type: schema.scale_type,
        reverse: { type: "boolean", default: false },
        zero: {
          type: "boolean",
          description: "Include zero",
          default: false,
          supportedTypes: {"Q": true}
        }
      }
    }
  }
}).value();

var onlyOrdinalField = _.chain(schema.field).cloneDeep().merge({
  type: "object",
  properties: {
    type: {
      type: "string",
      enum: ["O"]
    },
    bin: {
      type: "boolean",
      supportedTypes: {"O": true}
    },
    aggr: {
      type: "string",
      enum: ["count"],
      supportedTypes: {"O": true}
    }
  }
}).value();

var x = _.cloneDeep(typicalField);
var y = _.cloneDeep(x);

var row = _.cloneDeep(onlyOrdinalField);
var col = _.cloneDeep(row);

var size = _.cloneDeep(typicalField);
var color = _.cloneDeep(typicalField);
var alpha = _.cloneDeep(typicalField);
var shape = _.cloneDeep(onlyOrdinalField)

var text = _.chain(typicalField).cloneDeep().merge({
  type: "object",
  properties: {
    text: {
      type: "object",
      properties: {
        weight: {
          type: "string",
          enum: ["normal", "bold"],
          default: "normal",
          supportedTypes: {"T": true}
        },
        size: {
          type: "integer",
          default: 10,
          minimum: 0,
          supportedTypes: {"T": true}
        },
        font: {
          type: "string",
          default: "Halvetica Neue",
          supportedTypes: {"T": true}
        }
      }
    }
  }
}).value();

var cfg = {
  type: "object",
  properties: {
    dataFormatType: {
      type: "string",
      enum: ["json", "csv"]
    },
    useVegaServer: {
      type: "boolean",
      default: false
    },
    dataUrl: {
      type: "string"
    },
    vegaServerTable: {
      type: "string"
    },
    vegaServerUrl: {
      type: "string",
      default: "http://localhost:3001"
    }
  }
}

exports.schema = schema;

exports.encoding = {
  $schema: "http://json-schema.org/draft-04/schema#",
  type: "object",
  required: ["marktype", "enc", "cfg"],
  properties: {
    marktype: schema.marktype,
    enc: {
      type: "object",
      properties: {
        x: x,
        y: y,
        row: row,
        col: col,
        size: size,
        color: color,
        alpha: alpha,
        shape: shape,
        text: text
      }
    },
    cfg: cfg
  }
};
<commit_msg>Schema includes axis.grid for x and y<commit_after>// Defining Vegalite Encoding's schema

//

// TODO use vl.merge, vl.duplicate instead, move to /src as vl.schema
var _ = require("lodash");

var schema = {};

schema.marktype = {
  type: "string",
  enum: ["point", "bar", "line", "area", "circle", "square", "text"]
};

schema.aggr = {
  type: "string",
  enum: ["avg", "sum", "min", "max", "count"],
  supportedEnums: {
    Q: ["avg", "sum", "min", "max", "count"],
    O: ["count"],
    T: ["avg", "min", "max", "count"],
    "": ["count"],
  },
  supportedTypes: {"Q": true, "O": true, "T": true, "": true}
};

schema.timefns = ["month", "year", "day", "date", "hour", "minute", "second"];

schema.fn = {
  type: "string",
  enum: schema.timefns,
  supportedTypes: {"T": true}
}

//TODO(kanitw): add other type of function here

schema.scale_type = {
  type: "string",
  enum: ["linear", "log","pow", "sqrt", "quantile"],
  supportedTypes: {"Q": true}
};

schema.field = {
  type: "object",
  required: ["name", "type"],
  properties: {
    name: {
      type: "string"
    }
  }
};

var typicalField = _.chain(schema.field).cloneDeep().merge({
  type: "object",
  properties: {
    type: {
      type: "string",
      enum: ["O", "Q", "T"]
    },
    bin: {
      type: "boolean",
      supportedTypes: {"Q": true, "O": true}
    },
    aggr: schema.aggr,
    fn: schema.fn,
    scale: {
      type: "object",
      properties: {
        type: schema.scale_type,
        reverse: { type: "boolean", default: false },
        zero: {
          type: "boolean",
          description: "Include zero",
          default: false,
          supportedTypes: {"Q": true}
        }
      }
    }
  }
}).value();

var onlyOrdinalField = _.chain(schema.field).cloneDeep().merge({
  type: "object",
  properties: {
    type: {
      type: "string",
      enum: ["O"]
    },
    bin: {
      type: "boolean",
      supportedTypes: {"O": true}
    },
    aggr: {
      type: "string",
      enum: ["count"],
      supportedTypes: {"O": true}
    }
  }
}).value();

var fieldWithAxis = _.chain(typicalField).cloneDeep().merge({
  properties: {
    axis: {
      type: "object",
      properties: {
        grid: { type: "boolean", default: false }
      }
    }
  }
}).value();

var x = _.cloneDeep(fieldWithAxis);
var y = _.cloneDeep(x);

var row = _.cloneDeep(onlyOrdinalField);
var col = _.cloneDeep(row);

var size = _.cloneDeep(typicalField);
var color = _.cloneDeep(typicalField);
var alpha = _.cloneDeep(typicalField);
var shape = _.cloneDeep(onlyOrdinalField)

var text = _.chain(typicalField).cloneDeep().merge({
  type: "object",
  properties: {
    text: {
      type: "object",
      properties: {
        weight: {
          type: "string",
          enum: ["normal", "bold"],
          default: "normal",
          supportedTypes: {"T": true}
        },
        size: {
          type: "integer",
          default: 10,
          minimum: 0,
          supportedTypes: {"T": true}
        },
        font: {
          type: "string",
          default: "Halvetica Neue",
          supportedTypes: {"T": true}
        }
      }
    }
  }
}).value();

var cfg = {
  type: "object",
  properties: {
    dataFormatType: {
      type: "string",
      enum: ["json", "csv"]
    },
    useVegaServer: {
      type: "boolean",
      default: false
    },
    dataUrl: {
      type: "string"
    },
    vegaServerTable: {
      type: "string"
    },
    vegaServerUrl: {
      type: "string",
      default: "http://localhost:3001"
    }
  }
}

exports.schema = schema;

exports.encoding = {
  $schema: "http://json-schema.org/draft-04/schema#",
  type: "object",
  required: ["marktype", "enc", "cfg"],
  properties: {
    marktype: schema.marktype,
    enc: {
      type: "object",
      properties: {
        x: x,
        y: y,
        row: row,
        col: col,
        size: size,
        color: color,
        alpha: alpha,
        shape: shape,
        text: text
      }
    },
    cfg: cfg
  }
};
<|endoftext|>


<commit_before>import PropTypes from 'prop-types'
import React from 'react'
import ReactDOM from 'react-dom'
import Select, {components} from 'react-select'
import {connect, select} from 'store'
import {msg} from 'translate'
import Icon from 'widget/icon'
import styles from './comboBox.css'

const mapStateToProps = () => ({
    appDimensions: select('dimensions')
})

class ComboBox extends React.Component {
    state = {}
    element = React.createRef()
    menuPortalTarget = React.createRef()

    render() {
        const {
            input,
            validate = 'onBlur',
            isClearable = true,
            isLoading,
            showChevron = true,
            showCurrentSelection = true,
            menuPlacement = 'bottom',
            controlClassName,
            menuClassName,
            onChange,
            onBlur,
            children,
            ...props
        } = this.props
        const components = {LoadingIndicator}
        if (!showChevron)
            components.DropdownIndicator = null
        if (children)
            components.SingleValue = createSingleValue(children)
        return (

                {ReactDOM.createPortal(

,
                    document.body
                )}
                <Select
                    {...props}
                    name={input.name}
                    value={showCurrentSelection ? this.state.selectedOption : null}
                    classNamePrefix='comboBox'
                    maxMenuHeight={this.state.maxMenuHeight}
                    menuPlacement={menuPlacement}
                    menuPortalTarget={this.menuPortalTarget.current}
                    isClearable={isClearable}
                    isLoading={!!isLoading}
                    loadingMessage={() => msg('widget.comboBox.loading')}
                    components={components}
                    onChange={(e) => {
                        input.handleChange({target: {value: e ? e.value : '', name: input.name}})
                        if (onChange)
                            onChange(e)
                        if (validate === 'onChange')
                            input.validate()
                    }}
                    onBlur={(e) => {
                        if (onBlur)
                            onBlur(e)
                        if (validate === 'onBlur')
                            input.validate()
                    }}
                />

        )
    }

    componentDidMount() {
        this.updateSelectedOption()
        this.updateMaxMenuHeight()
    }

    componentDidUpdate(prevProps) {
        const value = this.props.input.value
        if (prevProps.input.value !== value)
            this.updateSelectedOption()

        const appHeight = this.props.appDimensions.height
        if (this.element.current && prevProps.appDimensions.height !== appHeight)
            this.updateMaxMenuHeight()
    }

    updateMaxMenuHeight() {
        const appHeight = this.props.appDimensions.height
        const comboBoxBox = this.element.current.getBoundingClientRect()
        const availableSpace = (
            this.props.menuPlacement === 'top'
                ? comboBoxBox.top
                : appHeight - comboBoxBox.bottom
        ) - 10

        let maxMenuHeightProp = this.props.maxMenuHeight || '20rem'
        if (maxMenuHeightProp.toString().toLowerCase().endsWith('rem'))
            maxMenuHeightProp = parseFloat(maxMenuHeightProp) * parseFloat(getComputedStyle(document.documentElement).fontSize)
        const maxMenuHeight = Math.trunc(Math.min(availableSpace, maxMenuHeightProp))
        this.setState(prevState => ({...prevState, maxMenuHeight}))
    }

    updateSelectedOption() {
        const value = this.props.input.value
        let selectedOption = null

        const setSelected = (option) => {
            if (option.value === value)
                selectedOption = option
            return option.value === value
        }

        this.props.options.find(option =>
            option.options
                ? option.options.find(setSelected)
                : setSelected(option)
        )
        this.setState(prevState => ({...prevState, selectedOption}))

    }
}

ComboBox.propTypes = {
    input: PropTypes.object.isRequired,
    validate: PropTypes.oneOf(['onChange', 'onBlur']),
    ...Select.propTypes
}
export default connect(mapStateToProps)(ComboBox)

const LoadingIndicator = () => {
    return (
        <Icon name='spinner'/>
    )
}

const createSingleValue = (children) =>
    (props) => {
        return (
            <components.SingleValue {...props}>
                {children && children(props)}
            </components.SingleValue>
        )
    }<commit_msg>Updated selection option on options update.<commit_after>import PropTypes from 'prop-types'
import React from 'react'
import ReactDOM from 'react-dom'
import Select, {components} from 'react-select'
import {connect, select} from 'store'
import {msg} from 'translate'
import Icon from 'widget/icon'
import styles from './comboBox.css'

const mapStateToProps = () => ({
    appDimensions: select('dimensions')
})

class ComboBox extends React.Component {
    state = {}
    element = React.createRef()
    menuPortalTarget = React.createRef()

    render() {
        const {
            input,
            validate = 'onBlur',
            isClearable = true,
            isLoading,
            showChevron = true,
            showCurrentSelection = true,
            menuPlacement = 'bottom',
            controlClassName,
            menuClassName,
            onChange,
            onBlur,
            children,
            ...props
        } = this.props
        const components = {LoadingIndicator}
        if (!showChevron)
            components.DropdownIndicator = null
        if (children)
            components.SingleValue = createSingleValue(children)
        return (

                {ReactDOM.createPortal(

,
                    document.body
                )}
                <Select
                    {...props}
                    name={input.name}
                    value={showCurrentSelection ? this.state.selectedOption : null}
                    classNamePrefix='comboBox'
                    maxMenuHeight={this.state.maxMenuHeight}
                    menuPlacement={menuPlacement}
                    menuPortalTarget={this.menuPortalTarget.current}
                    isClearable={isClearable}
                    isLoading={!!isLoading}
                    loadingMessage={() => msg('widget.comboBox.loading')}
                    components={components}
                    onChange={(e) => {
                        input.handleChange({target: {value: e ? e.value : '', name: input.name}})
                        if (onChange)
                            onChange(e)
                        if (validate === 'onChange')
                            input.validate()
                    }}
                    onBlur={(e) => {
                        if (onBlur)
                            onBlur(e)
                        if (validate === 'onBlur')
                            input.validate()
                    }}
                />

        )
    }

    componentDidMount() {
        this.updateSelectedOption()
        this.updateMaxMenuHeight()
    }

    componentDidUpdate(prevProps) {
        const value = this.props.input.value
        if (prevProps.input.value !== value || prevProps.options !== this.props.options)
            this.updateSelectedOption()

        const appHeight = this.props.appDimensions.height
        if (this.element.current && prevProps.appDimensions.height !== appHeight)
            this.updateMaxMenuHeight()
    }

    updateMaxMenuHeight() {
        const appHeight = this.props.appDimensions.height
        const comboBoxBox = this.element.current.getBoundingClientRect()
        const availableSpace = (
            this.props.menuPlacement === 'top'
                ? comboBoxBox.top
                : appHeight - comboBoxBox.bottom
        ) - 10

        let maxMenuHeightProp = this.props.maxMenuHeight || '20rem'
        if (maxMenuHeightProp.toString().toLowerCase().endsWith('rem'))
            maxMenuHeightProp = parseFloat(maxMenuHeightProp) * parseFloat(getComputedStyle(document.documentElement).fontSize)
        const maxMenuHeight = Math.trunc(Math.min(availableSpace, maxMenuHeightProp))
        this.setState(prevState => ({...prevState, maxMenuHeight}))
    }

    updateSelectedOption() {
        const value = this.props.input.value
        let selectedOption = null

        const setSelected = (option) => {
            if (option.value === value)
                selectedOption = option
            return option.value === value
        }

        this.props.options.find(option =>
            option.options
                ? option.options.find(setSelected)
                : setSelected(option)
        )
        this.setState(prevState => ({...prevState, selectedOption}))

    }
}

ComboBox.propTypes = {
    input: PropTypes.object.isRequired,
    validate: PropTypes.oneOf(['onChange', 'onBlur']),
    ...Select.propTypes
}
export default connect(mapStateToProps)(ComboBox)

const LoadingIndicator = () => {
    return (
        <Icon name='spinner'/>
    )
}

const createSingleValue = (children) =>
    (props) => {
        return (
            <components.SingleValue {...props}>
                {children && children(props)}
            </components.SingleValue>
        )
    }<|endoftext|>


<commit_before>( function ( mw, $ ) {
	/*global moment:false */
	/**
	 * Notification option widget for echo popup.
	 *
	 * @class
	 * @extends OO.ui.OptionWidget
	 *
	 * @constructor
	 * @param {Object} [config] Configuration object
	 * @cfg {boolean} [markReadWhenSeen=false] This option is marked as read when it is viewed
	 * @cfg {jQuery} [$overlay] A jQuery element functioning as an overlay
	 *  for popups.
	 * @cfg {boolean} [bundle=false] This notification item is part of a bundle.
	 */
	mw.echo.ui.NotificationItemWidget = function MwEchoUiNotificationItemWidget( model, config ) {
		var i, secondaryUrls, urlObj, linkButton, $icon, isInsideMenu,
			$message = $( '<div>' ).addClass( 'mw-echo-ui-notificationItemWidget-content-message' ),
			widget = this;

		config = config || {};

		// Parent constructor
		mw.echo.ui.NotificationItemWidget.parent.call( this, $.extend( { data: model.getId() }, config ) );

		this.model = model;
		this.$overlay = config.$overlay || this.$element;
		this.bundle = !!config.bundle;

		this.$content = $( '<div>' ).addClass( 'mw-echo-ui-notificationItemWidget-content' );
		this.$actions = $( '<div>' )
			.addClass( 'mw-echo-ui-notificationItemWidget-content-actions' );

		// Mark unread
		this.markAsReadButton = new OO.ui.ButtonWidget( {
			icon: 'close',
			framed: false,
			classes: [ 'mw-echo-ui-notificationItemWidget-markAsReadButton' ]
		} );

		this.markReadWhenSeen = !!config.markReadWhenSeen;

		// Icon
		if ( this.model.getIconURL() ) {
			$icon = $( '<div>' )
				.addClass( 'mw-echo-ui-notificationItemWidget-icon' )
				.append( $( '<img>' ).attr( 'src', this.model.getIconURL() ) );
		}

		// Content
		$message.append(
			$( '<div>' )
				.addClass( 'mw-echo-ui-notificationItemWidget-content-message-header' )
				.append( this.model.getContentHeader() )
		);
		if ( !this.bundle && this.model.getContentBody() ) {
			$message.append(
				$( '<div>' )
					.addClass( 'mw-echo-ui-notificationItemWidget-content-message-body' )
					.append( this.model.getContentBody() )
			);
		}

		// Actions menu
		this.actionsButtonSelectWidget = new OO.ui.ButtonSelectWidget( {
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-buttons' ]
		} );

		// Popup menu
		this.menuPopupButtonWidget = new mw.echo.ui.ActionMenuPopupWidget( {
			framed: false,
			icon: 'ellipsis',
			$overlay: this.$overlay,
			menuWidth: 200,
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-menu' ]
		} );

		// Timestamp
		this.timestampWidget = new OO.ui.LabelWidget( {
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-timestamp' ],
			label: moment.utc( this.model.getTimestamp(), 'YYYYMMDDHHmmss' ).fromNow()
		} );

		// Build the actions line
		if ( this.bundle ) {
			// In a bundled item, the timestamp should go before the menu
			this.$actions.append(
				$( '<div>' )
					// We are wrapping the actions in a 'row' div so that the
					// internal pieces are also a table layout
					.addClass( 'mw-echo-ui-notificationItemWidget-content-actions-row' )
					.append(
						this.actionsButtonSelectWidget.$element,
						this.timestampWidget.$element,
						this.menuPopupButtonWidget.$element
					)
			);
		} else {
			this.$actions.append(
				this.actionsButtonSelectWidget.$element,
				this.menuPopupButtonWidget.$element,
				this.timestampWidget.$element
			);
		}

		// Actions
		secondaryUrls = this.model.getSecondaryUrls();
		for ( i = 0; i < secondaryUrls.length; i++ ) {
			urlObj = secondaryUrls[ i ];

			isInsideMenu = !this.bundle && urlObj.prioritized !== undefined;

			linkButton = new mw.echo.ui.MenuItemWidget( {
				icon: urlObj.icon || 'next',
				label: urlObj.label,
				tooltip: urlObj.tooltip,
				description: urlObj.description,
				url: urlObj.url,
				prioritized: isInsideMenu
			} );

			if ( isInsideMenu ) {
				this.actionsButtonSelectWidget.addItems( [ linkButton ] );
			} else {
				this.menuPopupButtonWidget.getMenu().addItems( [ linkButton ] );
			}
		}
		// Add a "mark as read" secondary action
		this.markAsReadSecondary = new OO.ui.ButtonOptionWidget( {
			icon: 'check',
			framed: false,
			data: 'markAsRead',
			label: mw.msg( 'echo-notification-markasread' ),
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-button' ]
		} );
		// Toggle 'mark as read' functionality
		this.toggleMarkAsReadButtons( !this.markReadWhenSeen && !this.model.isRead() );

		if ( this.bundle ) {
			// In a bundle, we have table layout, so the icon is
			// inserted into the content, and the 'mark as read'
			// button is not floating, and should be at the end
			this.$content.append(
				$icon,
				$message,
				this.$actions,
				this.markAsReadButton.$element
			);
			this.$element.append( this.$content );
		} else {
			this.$content.append(
				this.markAsReadButton.$element,
				$message,
				this.$actions
			);
			this.$element.append( $icon, this.$content );
		}

		this.$element
			.addClass( 'mw-echo-ui-notificationItemWidget mw-echo-ui-notificationItemWidget-' + this.model.getType() )
			.toggleClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen', !this.model.isSeen() && !this.bundle )
			.toggleClass( 'mw-echo-ui-notificationItemWidget-bundle', this.bundle );

		if ( this.model.getPrimaryUrl() ) {
			this.$element.contents()
				.wrapAll(
					// HACK: Wrap the entire item with a link that takes
					// the user to the primary url. This is not perfect,
					// but it makes the behavior native to the browser rather
					// than us listening to click events and opening new
					// windows.
					$( '<a>' )
						.addClass( 'mw-echo-ui-notificationItemWidget-linkWrapper' )
						.attr( 'href', this.model.getPrimaryUrl() )
						.on( 'click', function () {
							// Log notification click

							// TODO: In order to properly log a click of an item that
							// is part of a bundled cross-wiki notification, we will
							// need to add 'source' to the logging schema. Otherwise,
							// the logger will log item ID as if it is local, which
							// is wrong.
							mw.echo.logger.logInteraction(
								mw.echo.Logger.static.actions.notificationClick,
								mw.echo.Logger.static.context.popup,
								widget.getModel().getId(),
								widget.getModel().getCategory(),
								false,
								// Source of this notification if it is cross-wiki
								widget.bundle ? widget.getModel().getSource() : ''
							);
						} )
				);
		}

		// Events
		this.markAsReadButton.connect( this, { click: 'onMarkAsReadButtonClick' } );
		this.menuPopupButtonWidget.getMenu().connect( this, { choose: 'onPopupButtonWidgetChoose' } );
		this.model.connect( this, {
			seen: 'toggleSeen',
			read: 'toggleRead'
		} );

		// Initialize state
		this.toggleRead( this.model.isRead() );
		this.toggleSeen( this.model.isSeen() );

		// HACK: We have to remove the built-in label. When this
		// widget is switched to a standalone widget rather than
		// an OptionWidget we can get rid of this
		this.$label.detach();
	};

	/* Initialization */

	OO.inheritClass( mw.echo.ui.NotificationItemWidget, OO.ui.OptionWidget );

	/* Static properties */

	mw.echo.ui.NotificationItemWidget.static.pressable = false;
	mw.echo.ui.NotificationItemWidget.static.selectable = false;

	/* Events */

	/**
	 * @event markAsRead
	 *
	 * Mark this notification as read
	 */

	/* Methods */

	/**
	 * Respond to mark as read button click
	 */
	mw.echo.ui.NotificationItemWidget.prototype.onMarkAsReadButtonClick = function () {
		this.model.toggleRead( true );
	};

	/**
	 * Respond to selecting an item from the popup button widget
	 */
	mw.echo.ui.NotificationItemWidget.prototype.onPopupButtonWidgetChoose = function ( item ) {
		var action = item && item.getData();

		if ( action === 'markAsRead' ) {
			this.model.toggleRead( true );
		}
	};

	/**
	 * Toggle the visibility of the 'mark as read' buttons and update the visibility
	 * of the secondary menu accordingly.
	 *
	 * @param {boolean} [show] Show the 'mark as read' buttons
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleMarkAsReadButtons = function ( show ) {
		show = show !== undefined ? show : !this.model.isRead();

		this.markAsReadButton.toggle( show );
		this.markAsReadSecondary.toggle( show );

		if ( show ) {
			this.menuPopupButtonWidget.getMenu().addItems( [ this.markAsReadSecondary ] );
		} else {
			this.menuPopupButtonWidget.getMenu().removeItems( [ this.markAsReadSecondary ] );
		}
		this.menuPopupButtonWidget.toggle( !this.menuPopupButtonWidget.getMenu().isEmpty() );
	};

	/**
	 * Reset the status of the notification without touching its user-controlled status.
	 * For one, remove 'initiallyUnseen' which exists only for the animation to work.
	 * This is called when new notifications are added to the parent widget, having to
	 * reset the 'unseen' status from the old ones.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.reset = function () {
		this.$element.removeClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen' );
	};

	/**
	 * Toggle the read state of the widget
	 *
	 * @param {boolean} [read] The current read state. If not given, the state will
	 *  become the opposite of its current state.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleRead = function ( read ) {
		this.read = read !== undefined ? read : !this.read;

		this.$element.toggleClass( 'mw-echo-ui-notificationItemWidget-unread', !this.read );
		this.toggleMarkAsReadButtons( !this.read );
	};

	/**
	 * Toggle the seen state of the widget
	 *
	 * @param {boolean} [seen] The current seen state. If not given, the state will
	 *  become the opposite of its current state.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleSeen = function ( seen ) {
		this.seen = seen !== undefined ? seen : !this.seen;

		this.$element
			.toggleClass( 'mw-echo-ui-notificationItemWidget-unseen', !this.seen );
	};

	/**
	 * Get the notification link
	 *
	 * @return {string} Notification link
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getModel = function () {
		return this.model;
	};

	/**
	 * Get the notification link
	 *
	 * @return {string} Notification link
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getPrimaryUrl = function () {
		return this.model.getPrimaryUrl();
	};

	/**
	 * Disconnect events when widget is destroyed.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.destroy = function () {
		this.model.disconnect( this );
	};

} )( mediaWiki, jQuery );
<commit_msg>Bidi-isolate notification bodies<commit_after>( function ( mw, $ ) {
	/*global moment:false */
	/**
	 * Notification option widget for echo popup.
	 *
	 * @class
	 * @extends OO.ui.OptionWidget
	 *
	 * @constructor
	 * @param {Object} [config] Configuration object
	 * @cfg {boolean} [markReadWhenSeen=false] This option is marked as read when it is viewed
	 * @cfg {jQuery} [$overlay] A jQuery element functioning as an overlay
	 *  for popups.
	 * @cfg {boolean} [bundle=false] This notification item is part of a bundle.
	 */
	mw.echo.ui.NotificationItemWidget = function MwEchoUiNotificationItemWidget( model, config ) {
		var i, secondaryUrls, urlObj, linkButton, $icon, isInsideMenu,
			$message = $( '<div>' ).addClass( 'mw-echo-ui-notificationItemWidget-content-message' ),
			widget = this;

		config = config || {};

		// Parent constructor
		mw.echo.ui.NotificationItemWidget.parent.call( this, $.extend( { data: model.getId() }, config ) );

		this.model = model;
		this.$overlay = config.$overlay || this.$element;
		this.bundle = !!config.bundle;

		this.$content = $( '<div>' ).addClass( 'mw-echo-ui-notificationItemWidget-content' );
		this.$actions = $( '<div>' )
			.addClass( 'mw-echo-ui-notificationItemWidget-content-actions' );

		// Mark unread
		this.markAsReadButton = new OO.ui.ButtonWidget( {
			icon: 'close',
			framed: false,
			classes: [ 'mw-echo-ui-notificationItemWidget-markAsReadButton' ]
		} );

		this.markReadWhenSeen = !!config.markReadWhenSeen;

		// Icon
		if ( this.model.getIconURL() ) {
			$icon = $( '<div>' )
				.addClass( 'mw-echo-ui-notificationItemWidget-icon' )
				.append( $( '<img>' ).attr( 'src', this.model.getIconURL() ) );
		}

		// Content
		$message.append(
			$( '<div>' )
				.addClass( 'mw-echo-ui-notificationItemWidget-content-message-header' )
				.append( this.model.getContentHeader() )
		);
		if ( !this.bundle && this.model.getContentBody() ) {
			$message.append(
				$( '<div>' )
					.addClass( 'mw-echo-ui-notificationItemWidget-content-message-body' )
					.append( this.model.getContentBody() )
					// dir=auto has a similar effect to wrapping the content in <bdi>, but
					// makes text-overflow: ellipsis; behave less strangely
					.attr( 'dir', 'auto' )
			);
		}

		// Actions menu
		this.actionsButtonSelectWidget = new OO.ui.ButtonSelectWidget( {
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-buttons' ]
		} );

		// Popup menu
		this.menuPopupButtonWidget = new mw.echo.ui.ActionMenuPopupWidget( {
			framed: false,
			icon: 'ellipsis',
			$overlay: this.$overlay,
			menuWidth: 200,
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-menu' ]
		} );

		// Timestamp
		this.timestampWidget = new OO.ui.LabelWidget( {
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-timestamp' ],
			label: moment.utc( this.model.getTimestamp(), 'YYYYMMDDHHmmss' ).fromNow()
		} );

		// Build the actions line
		if ( this.bundle ) {
			// In a bundled item, the timestamp should go before the menu
			this.$actions.append(
				$( '<div>' )
					// We are wrapping the actions in a 'row' div so that the
					// internal pieces are also a table layout
					.addClass( 'mw-echo-ui-notificationItemWidget-content-actions-row' )
					.append(
						this.actionsButtonSelectWidget.$element,
						this.timestampWidget.$element,
						this.menuPopupButtonWidget.$element
					)
			);
		} else {
			this.$actions.append(
				this.actionsButtonSelectWidget.$element,
				this.menuPopupButtonWidget.$element,
				this.timestampWidget.$element
			);
		}

		// Actions
		secondaryUrls = this.model.getSecondaryUrls();
		for ( i = 0; i < secondaryUrls.length; i++ ) {
			urlObj = secondaryUrls[ i ];

			isInsideMenu = !this.bundle && urlObj.prioritized !== undefined;

			linkButton = new mw.echo.ui.MenuItemWidget( {
				icon: urlObj.icon || 'next',
				label: urlObj.label,
				tooltip: urlObj.tooltip,
				description: urlObj.description,
				url: urlObj.url,
				prioritized: isInsideMenu
			} );

			if ( isInsideMenu ) {
				this.actionsButtonSelectWidget.addItems( [ linkButton ] );
			} else {
				this.menuPopupButtonWidget.getMenu().addItems( [ linkButton ] );
			}
		}
		// Add a "mark as read" secondary action
		this.markAsReadSecondary = new OO.ui.ButtonOptionWidget( {
			icon: 'check',
			framed: false,
			data: 'markAsRead',
			label: mw.msg( 'echo-notification-markasread' ),
			classes: [ 'mw-echo-ui-notificationItemWidget-content-actions-button' ]
		} );
		// Toggle 'mark as read' functionality
		this.toggleMarkAsReadButtons( !this.markReadWhenSeen && !this.model.isRead() );

		if ( this.bundle ) {
			// In a bundle, we have table layout, so the icon is
			// inserted into the content, and the 'mark as read'
			// button is not floating, and should be at the end
			this.$content.append(
				$icon,
				$message,
				this.$actions,
				this.markAsReadButton.$element
			);
			this.$element.append( this.$content );
		} else {
			this.$content.append(
				this.markAsReadButton.$element,
				$message,
				this.$actions
			);
			this.$element.append( $icon, this.$content );
		}

		this.$element
			.addClass( 'mw-echo-ui-notificationItemWidget mw-echo-ui-notificationItemWidget-' + this.model.getType() )
			.toggleClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen', !this.model.isSeen() && !this.bundle )
			.toggleClass( 'mw-echo-ui-notificationItemWidget-bundle', this.bundle );

		if ( this.model.getPrimaryUrl() ) {
			this.$element.contents()
				.wrapAll(
					// HACK: Wrap the entire item with a link that takes
					// the user to the primary url. This is not perfect,
					// but it makes the behavior native to the browser rather
					// than us listening to click events and opening new
					// windows.
					$( '<a>' )
						.addClass( 'mw-echo-ui-notificationItemWidget-linkWrapper' )
						.attr( 'href', this.model.getPrimaryUrl() )
						.on( 'click', function () {
							// Log notification click

							// TODO: In order to properly log a click of an item that
							// is part of a bundled cross-wiki notification, we will
							// need to add 'source' to the logging schema. Otherwise,
							// the logger will log item ID as if it is local, which
							// is wrong.
							mw.echo.logger.logInteraction(
								mw.echo.Logger.static.actions.notificationClick,
								mw.echo.Logger.static.context.popup,
								widget.getModel().getId(),
								widget.getModel().getCategory(),
								false,
								// Source of this notification if it is cross-wiki
								widget.bundle ? widget.getModel().getSource() : ''
							);
						} )
				);
		}

		// Events
		this.markAsReadButton.connect( this, { click: 'onMarkAsReadButtonClick' } );
		this.menuPopupButtonWidget.getMenu().connect( this, { choose: 'onPopupButtonWidgetChoose' } );
		this.model.connect( this, {
			seen: 'toggleSeen',
			read: 'toggleRead'
		} );

		// Initialize state
		this.toggleRead( this.model.isRead() );
		this.toggleSeen( this.model.isSeen() );

		// HACK: We have to remove the built-in label. When this
		// widget is switched to a standalone widget rather than
		// an OptionWidget we can get rid of this
		this.$label.detach();
	};

	/* Initialization */

	OO.inheritClass( mw.echo.ui.NotificationItemWidget, OO.ui.OptionWidget );

	/* Static properties */

	mw.echo.ui.NotificationItemWidget.static.pressable = false;
	mw.echo.ui.NotificationItemWidget.static.selectable = false;

	/* Events */

	/**
	 * @event markAsRead
	 *
	 * Mark this notification as read
	 */

	/* Methods */

	/**
	 * Respond to mark as read button click
	 */
	mw.echo.ui.NotificationItemWidget.prototype.onMarkAsReadButtonClick = function () {
		this.model.toggleRead( true );
	};

	/**
	 * Respond to selecting an item from the popup button widget
	 */
	mw.echo.ui.NotificationItemWidget.prototype.onPopupButtonWidgetChoose = function ( item ) {
		var action = item && item.getData();

		if ( action === 'markAsRead' ) {
			this.model.toggleRead( true );
		}
	};

	/**
	 * Toggle the visibility of the 'mark as read' buttons and update the visibility
	 * of the secondary menu accordingly.
	 *
	 * @param {boolean} [show] Show the 'mark as read' buttons
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleMarkAsReadButtons = function ( show ) {
		show = show !== undefined ? show : !this.model.isRead();

		this.markAsReadButton.toggle( show );
		this.markAsReadSecondary.toggle( show );

		if ( show ) {
			this.menuPopupButtonWidget.getMenu().addItems( [ this.markAsReadSecondary ] );
		} else {
			this.menuPopupButtonWidget.getMenu().removeItems( [ this.markAsReadSecondary ] );
		}
		this.menuPopupButtonWidget.toggle( !this.menuPopupButtonWidget.getMenu().isEmpty() );
	};

	/**
	 * Reset the status of the notification without touching its user-controlled status.
	 * For one, remove 'initiallyUnseen' which exists only for the animation to work.
	 * This is called when new notifications are added to the parent widget, having to
	 * reset the 'unseen' status from the old ones.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.reset = function () {
		this.$element.removeClass( 'mw-echo-ui-notificationItemWidget-initiallyUnseen' );
	};

	/**
	 * Toggle the read state of the widget
	 *
	 * @param {boolean} [read] The current read state. If not given, the state will
	 *  become the opposite of its current state.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleRead = function ( read ) {
		this.read = read !== undefined ? read : !this.read;

		this.$element.toggleClass( 'mw-echo-ui-notificationItemWidget-unread', !this.read );
		this.toggleMarkAsReadButtons( !this.read );
	};

	/**
	 * Toggle the seen state of the widget
	 *
	 * @param {boolean} [seen] The current seen state. If not given, the state will
	 *  become the opposite of its current state.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.toggleSeen = function ( seen ) {
		this.seen = seen !== undefined ? seen : !this.seen;

		this.$element
			.toggleClass( 'mw-echo-ui-notificationItemWidget-unseen', !this.seen );
	};

	/**
	 * Get the notification link
	 *
	 * @return {string} Notification link
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getModel = function () {
		return this.model;
	};

	/**
	 * Get the notification link
	 *
	 * @return {string} Notification link
	 */
	mw.echo.ui.NotificationItemWidget.prototype.getPrimaryUrl = function () {
		return this.model.getPrimaryUrl();
	};

	/**
	 * Disconnect events when widget is destroyed.
	 */
	mw.echo.ui.NotificationItemWidget.prototype.destroy = function () {
		this.model.disconnect( this );
	};

} )( mediaWiki, jQuery );
<|endoftext|>


<commit_before>// var five = require("johnny-five");
// var board = new five.Board();

var poseMap = {
    '0': {
        pose: 'POSE_REST'
    }, //POSE_REST
    '1': {
        pose: 'POSE_FIST'
    }, //POSE_FIST
    '2': {
        pose: 'POSE_WAVE_IN'
    }, //POSE_WAVE_IN
    '3': {
        pose: 'POSE_WAVE_OUT'
    }, //POSE_WAVE_OUT
    '4': {
        pose: 'POSE_FINGERS_SPREAD'
    } //POSE_FINGERS_SPREAD
};

var WebSocketClient = require('websocket').client;

var client = new WebSocketClient();

client.on('connectFailed', function(error) {
    console.log('Connect Error: ' + error.toString());
});

client.on('connect', function(connection) {
    console.log('WebSocket Client Connected');

    connection.on('error', function(error) {
        console.log("Connection Error: " + error.toString());
    });

    connection.on('close', function() {
        console.log('Connection Closed');
    });

    connection.on('message', function(message) {
        if (message.type === 'utf8') {
            console.log("Received: '" + message.utf8Data + "'");
        } else {
            console.log('Message received: ', message);
        }
    });
});

client.connect('ws://localhost:8080/', 'echo-protocol', 'http://localhost:8080');

// board.on("ready", function() {
//   // Create an Led on pin 13
//   var led = new five.Led(13);
//   // Blink every half second
//   led.blink(500);
// });<commit_msg>servo driver functions<commit_after>var five = require("johnny-five");
var board = new five.Board();
var pivotServo, forwardServo, isReady = false;

var moveLeft = function() {
    if (forwardServo.isMoving) {
        stop(forwardServo);
    }
    // Move pivotServo left
};

var moveRight = function() {
    if (forwardServo.isMoving) {
        stop(forwardServo);
    }
    // Move pivotServo left
};

var moveForward = function() {
    if (pivotServo.isMoving) {
        stop(pivotServo);
    }
    // Move forwardServo forward
};

var moveBackward = function() {
    if (pivotServo.isMoving) {
        stop(pivotServo);
    }
    // Move forwardServo backward
};

var stop = function(servo) {
    if (servo) {
        servo.stop();
    } else {
        forwardServo.stop();
        pivotServo.stop();
    }
    setTimeout(function() {}, 500);
};

var strikeAPose = {
    '0': stop, //POSE_REST
    '1': moveForward, //POSE_FIST
    '2': moveLeft, //POSE_WAVE_IN
    '3': moveRight, //POSE_WAVE_OUT
    '4': moveBackward //POSE_FINGERS_SPREAD
};

var WebSocketClient = require('websocket').client;

var client = new WebSocketClient();

client.on('connectFailed', function(error) {
    console.log('Connect Error: ' + error.toString());
});

client.on('connect', function(connection) {
    console.log('WebSocket Client Connected');

    connection.on('error', function(error) {
        console.log("Connection Error: " + error.toString());
    });

    connection.on('close', function() {
        console.log('Connection Closed');
    });

    connection.on('message', function(message) {
        if (isReady && message.type === 'utf8') {
            console.log("Received: '" + message.utf8Data + "'");
            strikeAPose[message.utf8Data]();
        } else {
            console.log('Non UTF8 Message received: ', message);
        }
    });
});

board.on("ready", function() {
    pivotServo = new five.Servo() {
        pin: 10,
        range: [0, 180],
        startAt: 90
    };

    forwardServo = new five.Servo() {
        pin: 11,
        range: [0, 180],
        startAt: 90
    }

    isReady = true;
});

client.connect('ws://localhost:8080/', 'echo-protocol', 'http://localhost:8080');<|endoftext|>


<commit_before>// old comment, left for future reference because may re-implement
// Browserify entry point for the functions.js bundle (yay JavaScript!)
// global.js already contains jQuery, so in our config.js file, we are exposing it to other files like this one in the
// `require` array. Also in config.js, jquery is listed in `external` array for this bundle. This combination lets this
// file use the jquery module bundled with global.js, instead of including it twice!

var _ = require('underscore');
var Backbone = require('backbone');
var $ = require('jquery');
Backbone.$ = $;

$(document)
    .ajaxStart(function () {})
    .ajaxStop(function () {})
;

$(function()
{
    $(".hiButton").on( "click", function() {
      alert('hi');
    });

    //Overrides persistence storage with dummy function. This enables use of Model.destroy() without raising an error.
    Backbone.sync = function(method, model, success, error){
        success();
    };

    var Item = Backbone.Model.extend({
        defaults: {
            part1: 'hello',
            part2: 'world'
        }
    });

    var List = Backbone.Collection.extend({
        model: Item
    });

    //ItemViews now respond to two clickable actions for each Item: swap and delete.
    //Responsible for rendering each individual Item.
    var ItemView = Backbone.View.extend({
        tagName: 'li', //name of (orphan) root tag in this.el

        events: {
            'click span.swap': 'swap',
            'click span.delete': 'remove'
        },

        //initialize() now binds model change/removal to the corresponding handlers below.
        initialize: function(){
            _.bindAll(this, 'render', 'unrender', 'swap', 'remove'); //every function that uses 'this' as the current object should be in here

            this.model.bind('change', this.render);
            this.model.bind('remove', this.unrender);
        },

        //render() now includes two extra spans corresponding to the actions swap and delete.
        render: function(){
            $(this.el).html(
                '' +
                this.model.get('part1') + ' ' +
                this.model.get('part2') + '' +
                '     ' +
                '[swap] ' +
                '[delete]');
            return this; // for chainable calls, like .render().el
        },

        //unrender(): Makes Model remove itself from the DOM.
        unrender: function(){
            $(this.el).remove();
        },

        //swap() will interchange an Item's attributes. When the .set() model function is called, the event change will be triggered.
        swap: function(){
            var swapped = {
                part1: this.model.get('part2'),
                part2: this.model.get('part1')
            };
            this.model.set(swapped);
        },

        //remove(): We use the method destroy() to remove a model from its collection. Normally this would also delete the record from its persistent storage, but we have overridden that (see above).
        remove: function(){
            this.model.destroy();
        }
    });

    var ListView = Backbone.View.extend({
        el: $('.backbone'), // attaches `this.el` to an existing element.

        events: {
            'click button#add': 'addItem'
        },

        initialize: function(){
            // every function that uses 'this' as the current object should be in here
            _.bindAll(this, 'render', 'addItem', 'appendItem'); // fixes loss of context for 'this' within methods

            this.collection = new List();
            this.collection.bind('add', this.appendItem); //collection event binder

            this.counter = 0; //total nubmer of items added so far
            this.render(); // not all views are self-rendering. This one is.
        },

        render: function(){
            //Save reference to this so it can be accessed from within the scope of the callback below
            var self = this;
            $(this.el).append("<button id='add'>Add list item</button>");
            $(this.el).append("

");
            _(this.collection.models).each(function(item){ //in case collection is not empty
                self.appendItem(item);
            }, this);
        },

        //addItem() now deals solely with models/collections.
        addItem: function(){
            this.counter++;
            var item = new Item();
            item.set({
               part2: item.get('part2') + this.counter //modify item details
            });
            this.collection.add(item); // add item to collection; view is updated via event 'add'
        },

        //appendItem() is no longer responsible for rendering an individual Item. This is now delegated to the render() method of each ItemView instance.
        appendItem: function(item){
            var itemView = new ItemView({
                model: item
            });
            $('ul', this.el).append(itemView.render().el);
        }

    });

    var listView = new ListView();

});
<commit_msg>alert<commit_after>// old comment, left for future reference because may re-implement
// Browserify entry point for the functions.js bundle (yay JavaScript!)
// global.js already contains jQuery, so in our config.js file, we are exposing it to other files like this one in the
// `require` array. Also in config.js, jquery is listed in `external` array for this bundle. This combination lets this
// file use the jquery module bundled with global.js, instead of including it twice!

var _ = require('underscore');
var Backbone = require('backbone');
var $ = require('jquery');
Backbone.$ = $;

$(document)
    .ajaxStart(function () {})
    .ajaxStop(function () {})
;

$(function()
{
    $(".hiButton").on( "click", function() {
      alert('hi');
    });

    //Overrides persistence storage with dummy function. This enables use of Model.destroy() without raising an error.
    Backbone.sync = function(method, model, success, error){
        success();
    };

    var Item = Backbone.Model.extend({
        defaults: {
            part1: 'hello',
            part2: 'world'
        }
    });

    var List = Backbone.Collection.extend({
        model: Item
    });

    //ItemViews now respond to two clickable actions for each Item: swap and delete.
    //Responsible for rendering each individual Item.
    var ItemView = Backbone.View.extend({
        tagName: 'li', //name of (orphan) root tag in this.el

        events: {
            'click span.swap': 'swap',
            'click span.delete': 'remove',
            'click span.alert': 'alert'
        },

        //initialize() now binds model change/removal to the corresponding handlers below.
        initialize: function(){
            _.bindAll(this, 'render', 'unrender', 'swap', 'remove', 'alert'); //every function that uses 'this' as the current object should be in here

            this.model.bind('change', this.render);
            this.model.bind('remove', this.unrender);
        },

        //render() now includes two extra spans corresponding to the actions swap and delete.
        render: function(){
            $(this.el).html(
                '' +
                this.model.get('part1') + ' ' +
                this.model.get('part2') + '' +
                '     ' +
                '[swap] ' +
                '[delete] ' +
                '[alert]');
            return this; // for chainable calls, like .render().el
        },

        //unrender(): Makes Model remove itself from the DOM.
        unrender: function(){
            $(this.el).remove();
        },

        //swap() will interchange an Item's attributes. When the .set() model function is called, the event change will be triggered.
        swap: function(){
            var swapped = {
                part1: this.model.get('part2'),
                part2: this.model.get('part1')
            };
            this.model.set(swapped);
        },

        //remove(): We use the method destroy() to remove a model from its collection. Normally this would also delete the record from its persistent storage, but we have overridden that (see above).
        remove: function(){
            this.model.destroy();
        },

        alert: function(){
            //(this.model);
            alert(this.model.get('part1') + ' ' + this.model.get('part2'));
        }
    });

    var ListView = Backbone.View.extend({
        el: $('.backbone'), // attaches `this.el` to an existing element.

        events: {
            'click button#add': 'addItem'
        },

        initialize: function(){
            // every function that uses 'this' as the current object should be in here
            _.bindAll(this, 'render', 'addItem', 'appendItem'); // fixes loss of context for 'this' within methods

            this.collection = new List();
            this.collection.bind('add', this.appendItem); //collection event binder

            this.counter = 0; //total nubmer of items added so far
            this.render(); // not all views are self-rendering. This one is.
        },

        render: function(){
            //Save reference to this so it can be accessed from within the scope of the callback below
            var self = this;
            $(this.el).append("<button id='add'>Add list item</button>");
            $(this.el).append("

");
            _(this.collection.models).each(function(item){ //in case collection is not empty
                self.appendItem(item);
            }, this);
        },

        //addItem() now deals solely with models/collections.
        addItem: function(){
            this.counter++;
            var item = new Item();
            item.set({
               part2: item.get('part2') + this.counter //modify item details
            });
            this.collection.add(item); // add item to collection; view is updated via event 'add'
        },

        //appendItem() is no longer responsible for rendering an individual Item. This is now delegated to the render() method of each ItemView instance.
        appendItem: function(item){
            var itemView = new ItemView({
                model: item
            });
            $('ul', this.el).append(itemView.render().el);
        }

    });

    var listView = new ListView();

});
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react'
import { Link, browserHistory } from 'react-router'
import 'stylesheets/modules/showcase'
import 'stylesheets/utilities/clearfix'

class Showcase extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      dragXStart: null,
      dragXNext: null,
      direction: null,
      xOffset: -200 - (100*(this.props.params.number - 1)),
      showcaseIndex: ((this.props.params.number - 1) || 0),
      sliding: false,
      transition: '0.5s',
    };
    this.hideDrag = this.hideDrag.bind(this);
    this.drag = this.drag.bind(this);
    this.setDrag = this.setDrag.bind(this);
    this.setIndex = this.setIndex.bind(this);
    this.setFocus = this.setFocus.bind(this);
    this.incrementIndex = this.incrementIndex.bind(this);
    this.decrementIndex = this.decrementIndex.bind(this);
  }

  render() {
    const showcaseItem = this.props.params.piece ? this.props.images[this.props.params.piece]: this.props.images[30];
    const showcaseIndex = this.props.params.number-1;
     const slideshow = showcaseItem.slideshow;
     const slideShowLength = slideshow.length;
     const setIndex = this.setIndex;
     const setFocus = this.setFocus;
       return (

        {(((showcaseItem.type !== "Animation")&&(showcaseItem.type !== "Film")) || (slideshow[0].indexOf('gif')>-1)) && }
        {(((showcaseItem.type === "Animation")||(showcaseItem.type === "Film"))&& (slideshow[0].indexOf('gif')===-1)) && }
         {slideShowLength < 5 && slideShowLength > 1 &&

            {slideshow.map(function(item, index){
              return (
                <Link key={index} to={ {pathname: '/work/'+showcaseNumber+'/'+(index+1)} }>
                   setFocus(index)} className={(index === showcaseIndex) ? 'focus' : 'non-focus'} src={item} />
                </Link>
              )
            })
            }

 }
          {slideShowLength > 4 &&

{this.decrementIndex(showcaseIndex, slideShowLength)}}  className={'arrow left'}>

             this.setIndex((slideShowLength - 4), showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[slideShowLength - 4]} />
             this.setIndex((slideShowLength - 3), showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[slideShowLength - 3]} />
             this.setIndex((slideShowLength - 2), showcaseIndex, slideShowLength)} className={((slideShowLength - 2) === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[slideShowLength - 2]} />
             this.setIndex((slideShowLength - 1), showcaseIndex, slideShowLength)} className={((slideShowLength - 1) === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[slideShowLength - 1]} />

          {slideshow.map(function(item, sliderIndex){
             return (
                setIndex(sliderIndex, showcaseIndex, slideShowLength)} className={(sliderIndex === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={item} />
              );
             }
          )}
            this.setIndex(0, showcaseIndex, slideShowLength)} className={(0 === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[0]} />
            this.setIndex(1, showcaseIndex, slideShowLength)} className={(1 === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[1]} />
            this.setIndex(2, showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[2]} />
            this.setIndex(3, showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[3]} />

{this.incrementIndex(showcaseIndex, slideShowLength)}} className={'arrow right'}>

}

{showcaseItem.name}

{showcaseItem.year}

          {showcaseItem.description &&
{showcaseItem.description}
}
          {showcaseItem.client &&
{showcaseItem.client}
}

        )
  }

  componentDidUpdate() {
    if(this.props.showcaseItem !== false){
    document.getElementsByClassName('showcase-image')[0].addEventListener('dragstart', this.hideDrag);
    }
    return true;
   }

  componentWillUnmount() {
    document.getElementsByClassName('showcase-image')[0].removeEventListener('dragstart', this.hideDrag);
  }

  setFocus(newIndex) {
    this.setState({showcaseIndex: newIndex});
  }

  setIndex(newIndex, oldIndex, length) {
    console.log('settingindex', newIndex, oldIndex, length);
    if (!this.state.sliding){
      this.setState({sliding: true});
      if((newIndex - oldIndex) > 3){
        const tx = this.state.xOffset + ((oldIndex + (length - newIndex))*100);
        this.setState({xOffset: tx, showcaseIndex: newIndex});
        if (this.state.xOffset > -300){
          setTimeout(
            function(){
              this.setState({transition: '0s', xOffset: this.state.xOffset - (length * 100)});
            }.bind(this), 550);
          setTimeout(
            function(){
              this.setState({transition: '0.5s', sliding: false});
            }.bind(this), 590);
       }
      } else if ((oldIndex - newIndex) > 3) {
         const tx = this.state.xOffset - ((newIndex + (length - oldIndex))*100);
         this.setState({xOffset: tx, showcaseIndex: newIndex});
         if (this.state.xOffset < -200){
           setTimeout(
             function(){
               this.setState({transition: '0s', xOffset: this.state.xOffset + (length * 100)});
             }.bind(this), 550);
           setTimeout(
             function(){
               this.setState({transition: '0.5s', sliding: false});
             }.bind(this), 590);
         }
      } else {
        const tx = this.state.xOffset + ((oldIndex  - newIndex)*100);
        this.setState({xOffset: tx, showcaseIndex: newIndex, sliding: false});
      }
    }
    console.log('routing');
    setTimeout(function(){
      this.setState({showcaseIndex: newIndex});
      this.props.router.push('work/'+this.props.params.piece+'/'+(newIndex+1));
    }.bind(this),600);
  }

  incrementIndex(currentIndex, length) {
    const newIndex = (currentIndex === length - 1) ? 0 : (currentIndex + 1)
    const oldIndex = currentIndex;
    this.setIndex(newIndex, oldIndex, length);
  }

  decrementIndex(currentIndex, length) {
    const newIndex = (currentIndex  === 0) ? (length - 1) : (currentIndex - 1);
    const oldIndex = currentIndex;
    this.setIndex(newIndex, oldIndex, length);
  }

  setDrag(){
    this.setState({direction: null, dragXStart: null, dragXNext: null});
  }

  drag(event) {
    if(event.type === 'drag'){
    } else if (event.type === 'touch'){
    }
    if(this.state.dragXStart === null){
      if(event.type === 'drag'){
      this.setState({dragXStart: event.clientX});
      } else if (event.type === 'touchmove'){
        this.setState({dragXStart: event.touches[0].pageX});
      }
    } else if((this.state.dragXNext === null) && (this.state.dragXStart!==null)) {
      if (event.type === 'drag' && (this.state.dragXStart !== event.clientX)){
      this.setState({dragXNext: event.clientX}, function(){
        if(this.state.dragXStart < this.state.dragXNext){
          this.setState({direction: 'right'}, this.decrementIndex);
          } else if(this.state.dragXStart > this.state.dragXNext){
          this.setState({direction: 'left'}, this.incrementIndex);
        }
      });
      } else if (event.type === 'touchmove' && (this.state.dragXStart !== event.touches[0].clientX)){
      this.setState({dragXNext: event.touches[0].clientX}, function(){
        if(this.state.dragXStart < this.state.dragXNext){
          this.setState({direction: 'right'}, this.decrementIndex);
        } else if (this.state.dragXStart > this.state.dragXNext) {
          this.setState({direction: 'left'}, this.incrementIndex);
        }
        });
      }
    }
  }

  hideDrag(event) {
    if(this.props.showcaseItem !== false){
      event.dataTransfer.effectAllowed = 'none';
      const image = this.props.images[this.props.showcaseItem].slideshow[this.props.showcaseIndex]
      event.dataTransfer.setData("text/plain", event.target.id);
      var img = new Image();
      img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
      event.dataTransfer.setDragImage(img, 100, 100);
    }
  }

}

export default Showcase
<commit_msg>remove browserHistory import<commit_after>import React, {Component, PropTypes} from 'react'
import { Link } from 'react-router'
import 'stylesheets/modules/showcase'
import 'stylesheets/utilities/clearfix'

class Showcase extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      dragXStart: null,
      dragXNext: null,
      direction: null,
      xOffset: -200 - (100*(this.props.params.number - 1)),
      showcaseIndex: ((this.props.params.number - 1) || 0),
      sliding: false,
      transition: '0.5s',
    };
    this.hideDrag = this.hideDrag.bind(this);
    this.drag = this.drag.bind(this);
    this.setDrag = this.setDrag.bind(this);
    this.setIndex = this.setIndex.bind(this);
    this.setFocus = this.setFocus.bind(this);
    this.incrementIndex = this.incrementIndex.bind(this);
    this.decrementIndex = this.decrementIndex.bind(this);
  }

  render() {
    const showcaseItem = this.props.params.piece ? this.props.images[this.props.params.piece]: this.props.images[30];
    const showcaseIndex = this.props.params.number-1;
     const slideshow = showcaseItem.slideshow;
     const slideShowLength = slideshow.length;
     const setIndex = this.setIndex;
     const setFocus = this.setFocus;
       return (

        {(((showcaseItem.type !== "Animation")&&(showcaseItem.type !== "Film")) || (slideshow[0].indexOf('gif')>-1)) && }
        {(((showcaseItem.type === "Animation")||(showcaseItem.type === "Film"))&& (slideshow[0].indexOf('gif')===-1)) && }
         {slideShowLength < 5 && slideShowLength > 1 &&

            {slideshow.map(function(item, index){
              return (
                <Link key={index} to={ {pathname: '/work/'+showcaseNumber+'/'+(index+1)} }>
                   setFocus(index)} className={(index === showcaseIndex) ? 'focus' : 'non-focus'} src={item} />
                </Link>
              )
            })
            }

 }
          {slideShowLength > 4 &&

{this.decrementIndex(showcaseIndex, slideShowLength)}}  className={'arrow left'}>

             this.setIndex((slideShowLength - 4), showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[slideShowLength - 4]} />
             this.setIndex((slideShowLength - 3), showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[slideShowLength - 3]} />
             this.setIndex((slideShowLength - 2), showcaseIndex, slideShowLength)} className={((slideShowLength - 2) === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[slideShowLength - 2]} />
             this.setIndex((slideShowLength - 1), showcaseIndex, slideShowLength)} className={((slideShowLength - 1) === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[slideShowLength - 1]} />

          {slideshow.map(function(item, sliderIndex){
             return (
                setIndex(sliderIndex, showcaseIndex, slideShowLength)} className={(sliderIndex === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={item} />
              );
             }
          )}
            this.setIndex(0, showcaseIndex, slideShowLength)} className={(0 === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[0]} />
            this.setIndex(1, showcaseIndex, slideShowLength)} className={(1 === showcaseIndex) ? 'slider-item focus' : 'slider-item non-focus'} src={slideshow[1]} />
            this.setIndex(2, showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[2]} />
            this.setIndex(3, showcaseIndex, slideShowLength)} className={'slider-item non-focus'} src={slideshow[3]} />

{this.incrementIndex(showcaseIndex, slideShowLength)}} className={'arrow right'}>

}

{showcaseItem.name}

{showcaseItem.year}

          {showcaseItem.description &&
{showcaseItem.description}
}
          {showcaseItem.client &&
{showcaseItem.client}
}

        )
  }

  componentDidUpdate() {
    if(this.props.showcaseItem !== false){
    document.getElementsByClassName('showcase-image')[0].addEventListener('dragstart', this.hideDrag);
    }
    return true;
   }

  componentWillUnmount() {
    document.getElementsByClassName('showcase-image')[0].removeEventListener('dragstart', this.hideDrag);
  }

  setFocus(newIndex) {
    this.setState({showcaseIndex: newIndex});
  }

  setIndex(newIndex, oldIndex, length) {
    console.log('settingindex', newIndex, oldIndex, length);
    if (!this.state.sliding){
      this.setState({sliding: true});
      if((newIndex - oldIndex) > 3){
        const tx = this.state.xOffset + ((oldIndex + (length - newIndex))*100);
        this.setState({xOffset: tx, showcaseIndex: newIndex});
        if (this.state.xOffset > -300){
          setTimeout(
            function(){
              this.setState({transition: '0s', xOffset: this.state.xOffset - (length * 100)});
            }.bind(this), 550);
          setTimeout(
            function(){
              this.setState({transition: '0.5s', sliding: false});
            }.bind(this), 590);
       }
      } else if ((oldIndex - newIndex) > 3) {
         const tx = this.state.xOffset - ((newIndex + (length - oldIndex))*100);
         this.setState({xOffset: tx, showcaseIndex: newIndex});
         if (this.state.xOffset < -200){
           setTimeout(
             function(){
               this.setState({transition: '0s', xOffset: this.state.xOffset + (length * 100)});
             }.bind(this), 550);
           setTimeout(
             function(){
               this.setState({transition: '0.5s', sliding: false});
             }.bind(this), 590);
         }
      } else {
        const tx = this.state.xOffset + ((oldIndex  - newIndex)*100);
        this.setState({xOffset: tx, showcaseIndex: newIndex, sliding: false});
      }
    }
    console.log('routing');
    setTimeout(function(){
      this.setState({showcaseIndex: newIndex});
      this.props.router.push('work/'+this.props.params.piece+'/'+(newIndex+1));
    }.bind(this),600);
  }

  incrementIndex(currentIndex, length) {
    const newIndex = (currentIndex === length - 1) ? 0 : (currentIndex + 1)
    const oldIndex = currentIndex;
    this.setIndex(newIndex, oldIndex, length);
  }

  decrementIndex(currentIndex, length) {
    const newIndex = (currentIndex  === 0) ? (length - 1) : (currentIndex - 1);
    const oldIndex = currentIndex;
    this.setIndex(newIndex, oldIndex, length);
  }

  setDrag(){
    this.setState({direction: null, dragXStart: null, dragXNext: null});
  }

  drag(event) {
    if(event.type === 'drag'){
    } else if (event.type === 'touch'){
    }
    if(this.state.dragXStart === null){
      if(event.type === 'drag'){
      this.setState({dragXStart: event.clientX});
      } else if (event.type === 'touchmove'){
        this.setState({dragXStart: event.touches[0].pageX});
      }
    } else if((this.state.dragXNext === null) && (this.state.dragXStart!==null)) {
      if (event.type === 'drag' && (this.state.dragXStart !== event.clientX)){
      this.setState({dragXNext: event.clientX}, function(){
        if(this.state.dragXStart < this.state.dragXNext){
          this.setState({direction: 'right'}, this.decrementIndex);
          } else if(this.state.dragXStart > this.state.dragXNext){
          this.setState({direction: 'left'}, this.incrementIndex);
        }
      });
      } else if (event.type === 'touchmove' && (this.state.dragXStart !== event.touches[0].clientX)){
      this.setState({dragXNext: event.touches[0].clientX}, function(){
        if(this.state.dragXStart < this.state.dragXNext){
          this.setState({direction: 'right'}, this.decrementIndex);
        } else if (this.state.dragXStart > this.state.dragXNext) {
          this.setState({direction: 'left'}, this.incrementIndex);
        }
        });
      }
    }
  }

  hideDrag(event) {
    if(this.props.showcaseItem !== false){
      event.dataTransfer.effectAllowed = 'none';
      const image = this.props.images[this.props.showcaseItem].slideshow[this.props.showcaseIndex]
      event.dataTransfer.setData("text/plain", event.target.id);
      var img = new Image();
      img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
      event.dataTransfer.setDragImage(img, 100, 100);
    }
  }

}

export default Showcase
<|endoftext|>