<commit_before>/**
 *	@author bh-lay
 *	@github https://github.com/bh-lay/uploader
 *  @updata 2014-5-11 0:16
 * Function depend on Jquery!
 *
 */
window.util = window.util || {};

(function(exports){
	var staticID = 0;
	var upCnt_tpl = ['

'].join('');
	var up_tpl = ['
',
		'',
		'<form method="post" action="{action}" enctype="multipart/form-data" name="uploader" target="uploader{ID}">',
			//请选择图片
			'<input name="{fileinputname}" type="file" multiple="multiple" class="uploader_btn" title="\u8BF7\u9009\u62E9\u56FE\u7247"/>',
			'{data}',
		'</form>',
	'
'].join('');

	var input_tpl = '<input type="hidden" name="{name}" value="{value}" />';

	//创建工作环境
	var global_cnt = $(upCnt_tpl);
	$(function(){
		$('body').append(global_cnt);
	});
	var private_isSupportTouch = "ontouchend" in document ? true : false;

	//iframe加载事件监听方法
	function iframe_load(iframe,callback){
		if (iframe.attachEvent){
			iframe.attachEvent("onload", function(){
				callback&&callback.call(iframe.contentDocument);
			});
		}else{
			iframe.onload = function(){
				callback&&callback.call(iframe.contentDocument);
			};
		}
	}
	//创建新的html
	function buildDom(){
		var this_up = this;

		//组装data对象对应的input
		var data_html = '';
		var data = this.data;
		for(var i in data){
			data_html += input_tpl.replace(/{(\w*)}/g,function(result,key){
				if(key == 'name'){
					return i;
				}else if(key == 'value'){
					return data[i]
				}
			});
		}
		//初始化上传配置
		var html = up_tpl.replace(/{(\w*)}/g,function(result,key){
			if(key == 'ID'){
				return this_up.ID
			}else if(key == 'action'){
				return this_up.action
			}else if(key == 'fileinputname'){
				return this_up.fileinputname
			}else if(key == "data"){
				return data_html;
			}
		});
		return $(html);
	}
	/**
	 * 获取文件信息
	 */
	function getFilesFromInput(input){
		var returns = [];

		if(input.value.length > 0){
			//有文件选中
			if(input.files){
				//可多选
				for(var i=0,total=input.files.length;i<total;i++){
					returns.push({
						'name' : input.files[i]['name'],
						'size' : input.files[i]['size']
					})
				}
			}else{
				//不支持多选
				returns.push({
					'name' : input.value.split(/\\/).pop()
				});
			}
		}
		return returns;
	}
	//格式化JSON
	function parseJSON(input){
		//去除部分浏览器自动添加的标签
		input = input.replace(/<[^>]+>|<\/[^>]+>/g,'');
		var output = {};
		//尝试去解析
		try{
			output = eval('(' + input + ')');
		}catch(e){
			output = 'fail';
		}
		return output;
	}
	/**
	 * 单次上传类
	 */
	function SingleUp(param){
		var this_up = this;
		this.ID = ++staticID;
		this.action = param.action;
		this.data = param['data'] || {};

		//状态 等待上传|上传中
		//    wait | uploading
		this.status = 'wait';
		this.responseParser = param['responseParser'] || null;
		this.fileinputname = param['fileinputname'] || 'photos';
		//事件集
		this.beforeUpload = param.beforeUpload || null;
		this.onStartUpload = param.onStartUpload || null;
		this.onFail = param.onFail || null;
		this.onSuccess = param.onSuccess || null;

		this.dom = buildDom.call(this);

		global_cnt.append(this.dom);

		/**
		 * 这个定时器是为了处理IE7浏览器异步创建iframe，导致无法立即得到iframe的BUG
		 * 如果你有好的办法，请  mail:bh_lay@126.com
		 */
		setTimeout(function(){
			this_up.init();
		},100);
	}
	//销毁自己
	SingleUp.prototype['destory'] = function(){
		if(this.status == 'wait'){
			this.dom.remove();
		}else{
			this.dom.hide();
		}
	};
	//处理response
	SingleUp.prototype['handleResponse'] = function(responseTXT){
		var ID = this.ID;
		this.status = 'wait';
		//是否已格式化数据方法
		if(this.responseParser){
			var jsonData = parseJSON(responseTXT);
			if(jsonData == 'fail'){
				//服务器数据异常！
				this.onFail && this.onFail(ID,'\u670D\u52A1\u5668\u6570\u636E\u5F02\u5E38\uFF01');
			}else{
				//数据交由开发者格式化
				var parserData = this.responseParser(jsonData);

				if(typeof(parserData) != 'object'){
					this.onFail && this.onFail(ID);
				}else{
					if(parserData['files'] && parserData['files']['length'] > 0){
						this.onSuccess && this.onSuccess(ID,parserData['files'],parserData['extra']);
					}else{
						this.onFail && this.onFail(ID,parserData['extra']);
					}
				}
			}
		}else{
			this.onFail && this.onFail(ID,'you need define method : responseParser! ');
		}
		//销毁单次上传模块
		this.destory();
	}
	//初始化单次上传模块
	SingleUp.prototype['init'] = function(){
		var this_up = this;

		var uploaderDom = this.dom;
		var iframe = this.dom.find('iframe')[0];
		var ID = this.ID;
		//事件处理
		uploaderDom.on('change','.uploader_btn',function(){
			var input = this;
			var can_start = true;
			//检测是否已选择文件
			var files = getFilesFromInput(input);
			if(files.length > 0){
				//触发上传前事件
				if(this_up.beforeUpload){
					can_start = this_up.beforeUpload(ID,files);
				}else{
					can_start = true;
				}
				if(can_start){
					uploaderDom.hide();
					//开始上传文件
					$(this).parents('form').submit();
					this_up.status = 'uploading';
					//触发上传事件
					this_up.onStartUpload && this_up.onStartUpload(ID,files);
				}else{
					//重置html
					input.outerHTML = input.outerHTML;
				}
			}
		});
		iframe_load(iframe,function(){
			var responseTXT = this.body.innerHTML;
			this_up.handleResponse(responseTXT);
		});
	};

	/**
	 * 为上传按钮绑定事件
	 */
	function bindEventsForUploader(){
		var this_up = this;
		//创建一个上传组建
		function workStart(){
			var btn = $(this);
			this_up.createSingleUp(btn);
		}
		if(this.dom[0].addEventListener && private_isSupportTouch){
			//移动端使用touch
			this.dom.each(function(){
				var DOM = $(this)[0];
				DOM.addEventListener('touchstart',workStart);
				DOM.addEventListener('MSPointerDown',workStart);
				DOM.addEventListener('pointerdown',workStart);
			});
		}else{
			//PC为按钮绑定悬停事件
			this.dom.mouseenter(workStart)
		}

		this.dom.mouseleave(function(){
			//注销上传组建
			this_up.activeSet && this_up.activeSet.destory();
		}).on('click',function(){
			//代理click事件至上传组建
			this_up.activeSet.dom.find('.uploader_btn').trigger('click');
		});
	}
	/**
	 * @method uploader 上传类
	 * @param {Object} param 主参数
	 *
	 */

	function uploader(param){
		var this_up = this;
		var param = param || {};
		//事件堆
		this._events = {};
		this.action = param['action'] || null;
		this.data = param['data'] || {};
		//绑定上传方法的DOM
		this.dom = param['dom'];
		this.fileinputname = param['fileinputname'] || 'photos';
		//是否可上传
		this.can_upload = true;
		this.responseParser = param['responseParser'] || null;

		//当前激活状态的上传组建
		this.activeSet = null;
		setTimeout(function(){
			bindEventsForUploader.call(this_up);
		},200);
	}
	uploader.prototype = {
		'on' : function (eventName,callback){
			//事件堆无该事件，创建一个事件堆
			if(!this._events[eventName]){
				this._events[eventName] = [];
			}
			this._events[eventName].push(callback);
			return this;
		},
		'emit' : function (eventName,args){
			//事件堆无该事件，结束运行
			if(!this._events[eventName]){
				return
			}
			args = args || null;
			for(var i=0,total=this._events[eventName].length;i<total;i++){
				this._events[eventName][i].apply(this.event_global || this , args);
			}
		},
		'createSingleUp' : function(){
			var this_up = this;
			this.activeSet = new SingleUp({
				'action' : this.action,
				'data' : this.data,
				'responseParser' : this.responseParser,
				'beforeUpload' : function(ID,files){
					this_up.emit('beforeUpload',[ID,files]);
					if(this_up.can_upload){
						return true;
					}else{
						return false;
					}
				},
				'onStartUpload' : function(ID,files){
					this_up.emit('startUpload',[ID,files]);
				},
				'onSuccess' : function(ID,files,extra){
					this_up.emit('success',[ID,files,extra]);
				},
				'onFail' : function(ID,extra){
					this_up.emit('error',[ID,extra]);
				}
			});
		}
	};
	exports.uploader = uploader;
})(window.util);

//提供CommonJS规范的接口
window.define && define(function(require,exports,module){
	//对外接口
	return window.util.uploader;
});<commit_msg>尝试解决Mac下，无法上传文件的bug<commit_after>/**
 *	@author bh-lay
 *	@github https://github.com/bh-lay/uploader
 *  @updata 2015-5-16 10:13
 * Function depend on Jquery!
 *
 */
window.util = window.util || {};

(function(exports){
	var staticID = 0;
	var upCnt_tpl = '

';
	var up_tpl = ['
',
		'',
		'<form method="post" action="{action}" enctype="multipart/form-data" name="uploader" target="uploader{ID}">',
			//请选择图片
			'<input name="{fileinputname}" type="file" multiple="multiple" class="uploader_btn" title="\u8BF7\u9009\u62E9\u56FE\u7247"/>',
			'{data}',
		'</form>',
	'
'].join('');

	var input_tpl = '<input type="hidden" name="{name}" value="{value}" />';

	//创建工作环境
	var global_cnt = $(upCnt_tpl);
	$(function(){
		$('body').append(global_cnt);
	});
	var private_isSupportTouch = "ontouchend" in document ? true : false;

	//iframe加载事件监听方法
	function iframe_load(iframe,callback){
		if (iframe.attachEvent){
			iframe.attachEvent("onload", function(){
				callback&&callback.call(iframe.contentDocument);
			});
		}else{
			iframe.onload = function(){
				callback&&callback.call(iframe.contentDocument);
			};
		}
	}
	//创建新的html
	function buildDom(){
		var this_up = this;

		//组装data对象对应的input
		var data_html = '';
		var data = this.data;
		for(var i in data){
			data_html += input_tpl.replace(/{(\w*)}/g,function(result,key){
				if(key == 'name'){
					return i;
				}else if(key == 'value'){
					return data[i]
				}
			});
		}
		//初始化上传配置
		var html = up_tpl.replace(/{(\w*)}/g,function(result,key){
			if(key == 'ID'){
				return this_up.ID
			}else if(key == 'action'){
				return this_up.action
			}else if(key == 'fileinputname'){
				return this_up.fileinputname
			}else if(key == "data"){
				return data_html;
			}
		});
		return $(html);
	}
	/**
	 * 获取文件信息
	 */
	function getFilesFromInput(input){
		var returns = [];

		if(input.value.length > 0){
			//有文件选中
			if(input.files){
				//可多选
				for(var i=0,total=input.files.length;i<total;i++){
					returns.push({
						name: input.files[i]['name'],
						size: input.files[i]['size']
					})
				}
			}else{
				//不支持多选
				returns.push({
					name: input.value.split(/\\/).pop()
				});
			}
		}
		return returns;
	}
	//格式化JSON
	function parseJSON(input){
		//去除部分浏览器自动添加的标签
		input = input.replace(/<[^>]+>|<\/[^>]+>/g,'');
		var output = {};
		//尝试去解析
		try{
			output = eval('(' + input + ')');
		}catch(e){
			output = 'fail';
		}
		return output;
	}
	/**
	 * 单次上传类
	 */
	function SingleUp(param){
		var this_up = this;
		this.ID = ++staticID;
		this.action = param.action;
		this.data = param['data'] || {};

		//状态 等待上传|上传中
		//    wait | uploading
		this.status = 'wait';
		this.responseParser = param['responseParser'] || null;
		this.fileinputname = param['fileinputname'] || 'photos';
		//事件集
		this.beforeUpload = param.beforeUpload || null;
		this.onStartUpload = param.onStartUpload || null;
		this.onFail = param.onFail || null;
		this.onSuccess = param.onSuccess || null;

		this.dom = buildDom.call(this);

		global_cnt.append(this.dom);

		/**
		 * 这个定时器是为了处理IE7浏览器异步创建iframe，导致无法立即得到iframe的BUG
		 * 如果你有好的办法，请  mail:bh_lay@126.com
		 */
		setTimeout(function(){
			this_up.init();
		},100);
	}
	//销毁自己
	SingleUp.prototype['destory'] = function(){
		if(this.status == 'wait'){
			this.dom.remove();
		}else{
			this.dom.hide();
		}
	};
	//处理response
	SingleUp.prototype['handleResponse'] = function(responseTXT){
		var ID = this.ID;
		this.status = 'wait';
		//是否已格式化数据方法
		if(this.responseParser){
			var jsonData = parseJSON(responseTXT);
			if(jsonData == 'fail'){
				//服务器数据异常！
				this.onFail && this.onFail(ID,'\u670D\u52A1\u5668\u6570\u636E\u5F02\u5E38\uFF01');
			}else{
				//数据交由开发者格式化
				var parserData = this.responseParser(jsonData);

				if(typeof(parserData) != 'object'){
					this.onFail && this.onFail(ID);
				}else{
					if(parserData['files'] && parserData['files']['length'] > 0){
						this.onSuccess && this.onSuccess(ID,parserData['files'],parserData['extra']);
					}else{
						this.onFail && this.onFail(ID,parserData['extra']);
					}
				}
			}
		}else{
			this.onFail && this.onFail(ID,'you need define method : responseParser! ');
		}
		//销毁单次上传模块
		this.destory();
	}
	//初始化单次上传模块
	SingleUp.prototype['init'] = function(){
		var this_up = this;

		var uploaderDom = this.dom;
		var iframe = this.dom.find('iframe')[0];
		var ID = this.ID;
		//事件处理
		uploaderDom.on('change','.uploader_btn',function(){
			var input = this;
			var can_start = true;
			//检测是否已选择文件
			var files = getFilesFromInput(input);
			if(files.length > 0){
				//触发上传前事件
				if(this_up.beforeUpload){
					can_start = this_up.beforeUpload(ID,files);
				}else{
					can_start = true;
				}
				if(can_start){
					uploaderDom.hide();
					//开始上传文件
					$(this).parents('form').submit();
					this_up.status = 'uploading';
					//触发上传事件
					this_up.onStartUpload && this_up.onStartUpload(ID,files);
				}else{
					//重置html
					input.outerHTML = input.outerHTML;
				}
			}
		});
		iframe_load(iframe,function(){
			var responseTXT = this.body.innerHTML;
			this_up.handleResponse(responseTXT);
		});
	};

	/**
	 * 为上传按钮绑定事件
	 */
	function bindEventsForUploader(){
		var this_up = this;
		//创建一个上传组建
		function workStart(){
			var btn = $(this);
			this_up.createSingleUp(btn);
		}
		if(this.dom[0].addEventListener && private_isSupportTouch){
			//移动端使用touch
			this.dom.each(function(){
				var DOM = $(this)[0];
				DOM.addEventListener('touchstart',workStart);
				DOM.addEventListener('MSPointerDown',workStart);
				DOM.addEventListener('pointerdown',workStart);
			});
		}else{
			//PC为按钮绑定悬停事件
			this.dom.mouseenter(workStart)
		}

		this.dom.on('click',function(){
			//代理click事件至上传组建
			this_up.activeSet.dom.find('.uploader_btn').trigger('click');
		});
	}
	/**
	 * @method uploader 上传类
	 * @param {Object} param 主参数
	 *
	 */

	function uploader(param){
		var this_up = this;
		var param = param || {};
		//事件堆
		this._events = {};
		this.action = param['action'] || null;
		this.data = param['data'] || {};
		//绑定上传方法的DOM
		this.dom = param['dom'];
		this.fileinputname = param['fileinputname'] || 'photos';
		//是否可上传
		this.can_upload = true;
		this.responseParser = param['responseParser'] || null;

		//当前激活状态的上传组建
		this.activeSet = null;
		setTimeout(function(){
			bindEventsForUploader.call(this_up);
		},200);
	}
	uploader.prototype = {
		on: function (eventName,callback){
			//事件堆无该事件，创建一个事件堆
			if(!this._events[eventName]){
				this._events[eventName] = [];
			}
			this._events[eventName].push(callback);
			return this;
		},
		emit: function (eventName,args){
			//事件堆无该事件，结束运行
			if(!this._events[eventName]){
				return
			}
			args = args || null;
			for(var i=0,total=this._events[eventName].length;i<total;i++){
				this._events[eventName][i].apply(this.event_global || this , args);
			}
		},
		createSingleUp: function(){
			var this_up = this;

			//注销上一个上传组建
			this.activeSet && this.activeSet.destory();
			this.activeSet = new SingleUp({
				action: this.action,
				data: this.data,
				responseParser: this.responseParser,
				beforeUpload: function(ID,files){
					this_up.emit('beforeUpload',[ID,files]);
					if(this_up.can_upload){
						return true;
					}else{
						return false;
					}
				},
				onStartUpload: function(ID,files){
					this_up.emit('startUpload',[ID,files]);
				},
				onSuccess: function(ID,files,extra){
					this_up.emit('success',[ID,files,extra]);
				},
				onFail: function(ID,extra){
					this_up.emit('error',[ID,extra]);
				}
			});
		}
	};
	exports.uploader = uploader;
})(window.util);

//提供CommonJS规范的接口
window.define && define(function(require,exports,module){
	//对外接口
	return window.util.uploader;
});<|endoftext|>


<commit_before>import Typography from 'typography'
import ms from 'modularscale'
import { TABLET_MEDIA_QUERY } from 'typography-breakpoint-constants'

const options = {
  baseFontSize: '18px',
  baseLineHeight: '27px',
  googleFonts: [
    {
      name: 'Alegreya Sans',
      styles: [
        '300',
        '300i',
        '400',
      ],
    },
    {
      name: 'Alegreya SC',
      styles: [
        '400',
      ],
    },
    {
      name: 'Alegreya',
      styles: [
        '400',
      ],
    },
  ],
  headerFontFamily: [
    'Alegreya',
    'Georgia',
    'serif',
  ],
  bodyFontFamily: ['Alegreya Sans', 'sans-serif'],
  bodyWeight: 300,
  headerWeight: 400,
  boldWeight: 400,
  headerGray: 10,
  bodyGray: 10,
  overrideStyles: ({ adjustFontSizeTo, rhythm }, options) => ({
    body: {
      background: '#fffdf8',
    },
    '.title-display': {
      ...adjustFontSizeTo(`${ms(8/5, 'octave') * options.baseFontSize.slice(0, -2)}px`),
      fontFamily: '"Alegreya SC",sans-serif',
    },
    [TABLET_MEDIA_QUERY]: {
      'h1.title-display': {
        ...adjustFontSizeTo(`${ms(8/5, 'golden') * options.baseFontSize.slice(0, -2)}px`),
      },
    },
    h4: {
      lineHeight: 1.5,
    },
    a: {
      color: '#9E5757',
      fontWeight: 600,
      textDecoration: 'none',
    },
    'a:hover': {
      textDecoration: 'underline',
    },
    blockquote: {
      fontStyle: 'italic',
    },
    'p > img': {
      marginBottom: 0,
    },
  }),
}
const typography = new Typography(options)

// Hot reload typography in development.
if (process.env.NODE_ENV !== 'production') {
  typography.injectStyles()
}

export default typography
<commit_msg>Change blockquote styles to plainer style allowing for italics within quote<commit_after>import Typography from 'typography'
import ms from 'modularscale'
import { TABLET_MEDIA_QUERY } from 'typography-breakpoint-constants'

const options = {
  baseFontSize: '18px',
  baseLineHeight: '27px',
  googleFonts: [
    {
      name: 'Alegreya Sans',
      styles: [
        '300',
        '300i',
        '400',
      ],
    },
    {
      name: 'Alegreya SC',
      styles: [
        '400',
      ],
    },
    {
      name: 'Alegreya',
      styles: [
        '400',
      ],
    },
  ],
  headerFontFamily: [
    'Alegreya',
    'Georgia',
    'serif',
  ],
  bodyFontFamily: ['Alegreya Sans', 'sans-serif'],
  bodyWeight: 300,
  headerWeight: 400,
  boldWeight: 400,
  headerGray: 10,
  bodyGray: 10,
  overrideStyles: ({ adjustFontSizeTo, rhythm }, options) => ({
    body: {
      background: '#fffdf8',
    },
    '.title-display': {
      ...adjustFontSizeTo(`${ms(8/5, 'octave') * options.baseFontSize.slice(0, -2)}px`),
      fontFamily: '"Alegreya SC",sans-serif',
    },
    [TABLET_MEDIA_QUERY]: {
      'h1.title-display': {
        ...adjustFontSizeTo(`${ms(8/5, 'golden') * options.baseFontSize.slice(0, -2)}px`),
      },
    },
    h4: {
      lineHeight: 1.5,
    },
    a: {
      color: '#9E5757',
      fontWeight: 600,
      textDecoration: 'none',
    },
    'a:hover': {
      textDecoration: 'underline',
    },
    blockquote: {
      marginLeft: rhythm(3),
      marginRight: 0,
      marginTop: rhythm(1.5),
      marginBottom: rhythm(1.5),
    },
    'p > img': {
      marginBottom: 0,
    },
  }),
}
const typography = new Typography(options)

// Hot reload typography in development.
if (process.env.NODE_ENV !== 'production') {
  typography.injectStyles()
}

export default typography
<|endoftext|>


<commit_before>import _ from 'lodash'
import React, { Component } from 'react'
import FontAwesome from 'react-fontawesome'

import {
  Panel,
  Button,
  ButtonGroup,
  FormGroup,
  Radio,
  DropdownButton,
  FormControl,
  MenuItem,
} from 'react-bootstrap'

import { PTyp } from '../../ptyp'
import { enumFromTo } from '../../utils'

const improvementToText = level =>
  level === 0 ? '★=0' :
  level === 10 ? '★+max' :
  `★+${level}`

const mkState = () => ({
  masterId: 68,
  level: 0,
  count: 1,
})

class NewEquipmentPanel extends Component {
  static propTypes = {
    style: PTyp.object,
  }

  static defaultProps = {
    style: {},
  }

  constructor(props) {
    super(props)
    this.state = mkState()
  }

  handleChangeItem = e => {
    const masterId = parseInt(e.target.value,10)
    this.setState({masterId})
  }

  handleCountChange = e => {
    const count = parseInt(e.target.value,10)
    if (_.isInteger(count) && count > 0) {
      this.setState({count})
    }
  }

  handleSelectLevel = level =>
    this.setState({level})

  render() {
    const {style} = this.props
    const {level, count, masterId} = this.state
    return (
      <Panel
        className="dlc-lab-control-panel"
        style={{
          ...style,
          marginBottom: 5,
          padding: 5,
        }}
      >
        <FormGroup>
          {
            [
              ['大発動艇', 68],
              ['特大発動艇', 193],
              ['大発動艇(八九式中戦車&陸戦隊)', 166],
              ['特型内火艇', 167],
            ].map(([name, id]) => (
              <Radio
                key={id}
                checked={masterId === id}
                name="equip-name"
                value={id}
                onChange={this.handleChangeItem}
                inline>
                {name}
              </Radio>
            ))
          }
        </FormGroup>

            <ButtonGroup justified>
              <DropdownButton
                onSelect={this.handleSelectLevel}
                id="dlc-lab-new-item-level"
                title={improvementToText(level)}>
                {
                  enumFromTo(0,10).map(x => (
                    <MenuItem key={x} eventKey={x}>
                      {improvementToText(x)}
                    </MenuItem>
                  ))
                }
              </DropdownButton>
            </ButtonGroup>

          <FormControl
            value={count}
            onChange={this.handleCountChange}
            style={{marginRight: 5, width: '40%'}}
            type="number" />
          <Button style={{minWidth: '3em', flex: 1}}>
            <FontAwesome name="plus" />
          </Button>

      </Panel>
    )
  }
}

export { NewEquipmentPanel }
<commit_msg>add new items into redux<commit_after>import _ from 'lodash'
import React, { Component } from 'react'
import FontAwesome from 'react-fontawesome'
import { connect } from 'react-redux'

import {
  Panel,
  Button,
  ButtonGroup,
  FormGroup,
  Radio,
  DropdownButton,
  FormControl,
  MenuItem,
} from 'react-bootstrap'

import { PTyp } from '../../ptyp'
import { enumFromTo, modifyObject } from '../../utils'
import { mapDispatchToProps } from '../../store/reducer/ui/dlc-lab'

const improvementToText = level =>
  level === 0 ? '★=0' :
  level === 10 ? '★+max' :
  `★+${level}`

const mkState = () => ({
  masterId: 68,
  level: 0,
  count: 1,
})

class NewEquipmentPanelImpl extends Component {
  static propTypes = {
    modifyDlcLabUI: PTyp.func.isRequired,
    style: PTyp.object,
  }

  static defaultProps = {
    style: {},
  }

  constructor(props) {
    super(props)
    this.state = mkState()
  }

  handleChangeItem = e => {
    const masterId = parseInt(e.target.value,10)
    this.setState({masterId})
  }

  handleCountChange = e => {
    const count = parseInt(e.target.value,10)
    if (_.isInteger(count) && count > 0) {
      this.setState({count})
    }
  }

  handleSelectLevel = level =>
    this.setState({level})

  handleAddToEquipments = () => {
    const {modifyDlcLabUI} = this.props
    const {masterId, level, count} = this.state
    modifyDlcLabUI(
      modifyObject(
        'equipments',
        modifyObject(
          masterId,
          equipmentOrUndef => {
            const equipment = equipmentOrUndef || {}
            return modifyObject(
              level,
              curCountOrUndef => {
                const curCount = curCountOrUndef || 0
                return curCount + count
              }
            )(equipment)
          })
      ))
    this.setState(mkState())
  }

  render() {
    const {style} = this.props
    const {level, count, masterId} = this.state
    return (
      <Panel
        className="dlc-lab-control-panel"
        style={{
          ...style,
          marginBottom: 5,
          padding: 5,
        }}
      >
        <FormGroup>
          {
            [
              ['大発動艇', 68],
              ['特大発動艇', 193],
              ['大発動艇(八九式中戦車&陸戦隊)', 166],
              ['特型内火艇', 167],
            ].map(([name, id]) => (
              <Radio
                key={id}
                checked={masterId === id}
                name="equip-name"
                value={id}
                onChange={this.handleChangeItem}
                inline>
                {name}
              </Radio>
            ))
          }
        </FormGroup>

            <ButtonGroup justified>
              <DropdownButton
                onSelect={this.handleSelectLevel}
                id="dlc-lab-new-item-level"
                title={improvementToText(level)}>
                {
                  enumFromTo(0,10).map(x => (
                    <MenuItem key={x} eventKey={x}>
                      {improvementToText(x)}
                    </MenuItem>
                  ))
                }
              </DropdownButton>
            </ButtonGroup>

          <FormControl
            value={count}
            onChange={this.handleCountChange}
            style={{marginRight: 5, width: '40%'}}
            type="number" />
          <Button
            onClick={this.handleAddToEquipments}
            style={{minWidth: '3em', flex: 1}}>
            <FontAwesome name="plus" />
          </Button>

      </Panel>
    )
  }
}

const NewEquipmentPanel = connect(
  null,
  mapDispatchToProps)(NewEquipmentPanelImpl)

export { NewEquipmentPanel }
<|endoftext|>


<commit_before>export { default } from '../../../../../../../../lib/eln-plugin/0.25.1/eln-plugin';
<commit_msg>chore: update elnPlugin to 0.26.0<commit_after>export { default } from '../../../../../../../../lib/eln-plugin/0.26.0/eln-plugin';
<|endoftext|>


<commit_before>//collect.js gets gets pages and runs them through the data pipeline

//  Output data structure:
///    this page's URL                  url        str
///    links from this page             mapsTo     []
///    pages linking to this one        mapsFrom   []
///    metaData from wiki's data page   metaData   {}
///    if object should not be scraped  noscrape   binary (either has attr or does not have it)

// Will need to ID 'influencers' - things that bear an outsize influence on all events of time, such as WWI, and catalogue them
// appropriately / define them as stopping points to keep thesis centered around Dada

// RUN NOTES:
// Wikipedia appears to block IP at some point (unclear what the cutoff is)

var request = require('request');
var fs = require('fs');
var async = require('async');

//local modules
var dataScrape = require('./s-wikiData');
var mF = require('./s-mapsFrom');
var mT = require('./s-mainPage');

var pagesIn = JSON.parse(fs.readFileSync('pages.json'));
var pages = [];

var endLoop = 1250;

var exceptions = [
  'wikisource.org',
  '.jpg',
  '.png',
  '.gif',
  'Book:'
]

var fullSkip = [
  'wikipedia', // no foriegn pages (not in scope)
  'wiktionary.org', // no wiktionary pages
  'wikiquote', // no wikiquote pages
  '_talk:', // no talk pages
  'Draft:'
]

 // Empty Array that is filled with already-scraped items
 // to avoid potentially scraping the same page twice
var dups = [];

// check for exceptions
function skip(link, exc) {
  for (var c in exc) {
    if (link.includes(exc[c])) {
      return true;
      // console.log('skipping');
      break;
    }
  }
}

function getRando(min, max) {
  return Math.floor(Math.random() * (max - min) + min);
}

//store the last batch saved
var lastBatch = 0;

function writeDataFile(counter) {
  counter++;
  if (counter>=endLoop || counter%250==0) { //if last loop or if counter is divisible by 250, write the file
    fs.writeFile('data/mapsFrom-r2-batch'+lastBatch+'.json', JSON.stringify(pages), function(err) {
        if (err) {throw err;}
        console.log('file written');
        lastBatch++;
        // clear pages to make room in memory
        pages = [];
    });
  }
}

//recursive loop for setTimeout
var i = 1000;

function crawler () {           //  create a loop function
   setTimeout(function () {    //  call a 3s setTimeout when the loop is called

        // check for already scraped urls
        if(!skip(pagesIn.mapsFrom[i], dups)) {

          var page = new Object;
          page.root = pagesIn.url; // will need to modify this for next batch?
          page.url = pagesIn.mapsFrom[i]; //url //'Dada';
          //track already scraped pages
          dups.push(page.url);

          //this scraper only handles english and is not equipped for non-english pages
          var url = 'https://en.wikipedia.org/wiki/'+page.url;
          var mapsFromURL = 'https://en.wikipedia.org/w/index.php?title=Special:WhatLinksHere/'+page.url+'&limit=3000';

          //check for fullSkip (pages not scraping) pages
          if (!skip(page.url, fullSkip)) {

          // collect links pointing at this page
          request(mapsFromURL, function(err, resp, body) {
            if (err) {throw err;}
              console.log('scraping mapsFrom '+mapsFromURL);
              page.mapsFrom = mF.scrape(body);
             //  console.log(page.mapsFrom);

             if (!skip(page.url, exceptions)) {

              // collect links pointing outwards from the page
              request(url, function(err, resp, body) {
                 if (err) {throw err;}
                  console.log('scraping mainPage '+url);
                  var s = mT.scrape(body, url);
                 //  console.log(s);
                  page.mapsTo = s.mapsTo;
                  page.wikiData = s.wikiData;
                  page.image = s.image;
                  page.title = s.title;

                 // collect wikiData (categorical info)
                 request(page.wikiData, function(err, resp, body) {
                    if (err) {throw err;}
                     console.log('scraping wikiData '+page.wikiData);
                     page.metaData = dataScrape.scrape(body);
                     pages.push(page);
                     writeDataFile(i);

                 }); // end wikiData request

              }); // end mainPage request

            } else {
              // some urls lead to direct sources, like images and manifesto, that do not have 'mainPage' or 'wikiData' items
              // so just push the page as is
              pages.push(page);
              writeDataFile(i);
            }

          });

        } else {
          page.noScrape = 1;
          pages.push(page);
          console.log('---------------------NO SCRAPE---------------------');
          writeDataFile(i);
        } // end fullSkip conditional

      } // end dups conditional

    //update count and check for end case
    i++;
    if (i < endLoop) { //pagesIn.mapsTo.length-1
         console.log(i+' of '+ endLoop);
         crawler();
    }

    //set delay between 1200 and 3600 milliseconds per request
  }, getRando(1200,3600))

}
//
crawler();
<commit_msg>batching update<commit_after>//collect.js gets gets pages and runs them through the data pipeline

//  Output data structure:
///    this page's URL                  url        str
///    links from this page             mapsTo     []
///    pages linking to this one        mapsFrom   []
///    metaData from wiki's data page   metaData   {}
///    if object should not be scraped  noscrape   binary (either has attr or does not have it)

// Will need to ID 'influencers' - things that bear an outsize influence on all events of time, such as WWI, and catalogue them
// appropriately / define them as stopping points to keep thesis centered around Dada

// RUN NOTES:
// Wikipedia appears to block IP at some point (unclear what the cutoff is)

var request = require('request');
var fs = require('fs');
var async = require('async');

//local modules
var dataScrape = require('./s-wikiData');
var mF = require('./s-mapsFrom');
var mT = require('./s-mainPage');

var pagesIn = JSON.parse(fs.readFileSync('pages.json'));
var pages = [];

var endLoop = 1250;

var exceptions = [
  'wikisource.org',
  '.jpg',
  '.png',
  '.gif',
  'Book:'
]

var fullSkip = [
  'wikipedia', // no foriegn pages (not in scope)
  'wiktionary.org', // no wiktionary pages
  'wikiquote', // no wikiquote pages
  '_talk:', // no talk pages
  'Draft:'
]

 // Empty Array that is filled with already-scraped items
 // to avoid potentially scraping the same page twice
var dups = [];

// check for exceptions
function skip(link, exc) {
  for (var c in exc) {
    if (link.includes(exc[c])) {
      return true;
      // console.log('skipping');
      break;
    }
  }
}

function getRando(min, max) {
  return Math.floor(Math.random() * (max - min) + min);
}

//store the last batch saved
var lastBatch = 1;

function writeDataFile(counter) {
  counter++;
  if (counter>=endLoop || counter%250==0) { //if last loop or if counter is divisible by 250, write the file
    fs.writeFile('data/mapsFrom-r2-batch'+lastBatch+'.json', JSON.stringify(pages), function(err) {
        if (err) {throw err;}
        console.log('file written');
        lastBatch++;
        // clear pages to make room in memory
        pages = [];
    });
  }
}

//recursive loop for setTimeout
var i = 1000;

function crawler () {           //  create a loop function
   setTimeout(function () {    //  call a 3s setTimeout when the loop is called

        // check for already scraped urls
        if(!skip(pagesIn.mapsFrom[i], dups)) {

          var page = new Object;
          page.root = pagesIn.url; // will need to modify this for next batch?
          page.url = pagesIn.mapsFrom[i]; //url //'Dada';
          //track already scraped pages
          dups.push(page.url);

          //this scraper only handles english and is not equipped for non-english pages
          var url = 'https://en.wikipedia.org/wiki/'+page.url;
          var mapsFromURL = 'https://en.wikipedia.org/w/index.php?title=Special:WhatLinksHere/'+page.url+'&limit=3000';

          //check for fullSkip (pages not scraping) pages
          if (!skip(page.url, fullSkip)) {

          // collect links pointing at this page
          request(mapsFromURL, function(err, resp, body) {
            if (err) {throw err;}
              console.log('scraping mapsFrom '+mapsFromURL);
              page.mapsFrom = mF.scrape(body);
             //  console.log(page.mapsFrom);

             if (!skip(page.url, exceptions)) {

              // collect links pointing outwards from the page
              request(url, function(err, resp, body) {
                 if (err) {throw err;}
                  console.log('scraping mainPage '+url);
                  var s = mT.scrape(body, url);
                 //  console.log(s);
                  page.mapsTo = s.mapsTo;
                  page.wikiData = s.wikiData;
                  page.image = s.image;
                  page.title = s.title;

                 // collect wikiData (categorical info)
                 request(page.wikiData, function(err, resp, body) {
                    if (err) {throw err;}
                     console.log('scraping wikiData '+page.wikiData);
                     page.metaData = dataScrape.scrape(body);
                     pages.push(page);
                     writeDataFile(i);

                 }); // end wikiData request

              }); // end mainPage request

            } else {
              // some urls lead to direct sources, like images and manifesto, that do not have 'mainPage' or 'wikiData' items
              // so just push the page as is
              pages.push(page);
              writeDataFile(i);
            }

          });

        } else {
          page.noScrape = 1;
          pages.push(page);
          console.log('---------------------NO SCRAPE---------------------');
          writeDataFile(i);
        } // end fullSkip conditional

      } // end dups conditional

    //update count and check for end case
    i++;
    if (i < endLoop) { //pagesIn.mapsTo.length-1
         console.log(i+' of '+ endLoop);
         crawler();
    }

    //set delay between 1200 and 3600 milliseconds per request
  }, getRando(1200,3600))

}
//
crawler();
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import { View, TouchableWithoutFeedback, Text, TextInput, StyleSheet } from 'react-native';

import Cell from './Cell';
import { dataTableColors } from '../../globalStyles/index';
import { getAdjustedStyle } from './utilities';

/**
 * Renders a cell that on press or focus contains a TextInput for
 * editing values.
 *
 * @param {string|number} value The value to render in cell
 * @param {string|number} rowKey Unique key associated to row cell is in
 * @param {string|number} columnKey Unique key associated to column cell is in
 * @param {bool} disabled If `true` will render a plain Cell element with no interaction
 * @param {bool} isFocused If `false` will TouchableOpacity that dispatches a focusAction
 *                         when pressed. When `true` will render a TextInput with focus
 * @param {func} editAction Action creator for handling editing of this cell.
 *                          `(newValue, rowKey, columnKey) => {...}`
 * @param {func} focusAction Action creator for handling focusing of this cell.
 *                          `(rowKey, columnKey) => {...}`
 * @param {func} focusNextAction Action creator for handling focusing of this cell.
 *                          `(rowKey, columnKey) => {...}`
 * @param {func} dispatch Reducer dispatch callback for handling actions
 * @param {Object} touchableStyle Style object for the wrapping Touchable component
 * @param {Object} viewStyle Style object for the wrapping View component
 * @param {Object} textStyle Style object for the inner Text component
 * @param {Number}        width Optional flex property to inject into styles.
 * @param {Bool}          isLastCell Indicator for if this cell is the last
 *                                   in a row. Removing the borderRight if true.
 */
const EditableCell = React.memo(
  ({
    value,
    rowKey,
    columnKey,
    disabled,
    isFocused,
    editAction,
    focusAction,
    focusNextAction,
    dispatch,
    touchableStyle,
    viewStyle,
    textInputStyle,
    textStyle,
    textViewStyle,
    isLastCell,
    width,
  }) => {
    const onEdit = newValue => dispatch(editAction(newValue, rowKey, columnKey));

    const focusCell = () => dispatch(focusAction(rowKey, columnKey));
    const focusNextCell = () => dispatch(focusNextAction(rowKey, columnKey));

    console.log(`- EditableCell: ${value}`);

    const internalViewStyle = getAdjustedStyle(viewStyle, width, isLastCell);
    const internalTextStyle = getAdjustedStyle(textStyle, width);

    // Render a plain Cell if disabled.
    if (disabled) {
      return <Cell viewStyle={internalViewStyle} textStyle={internalTextStyle} value={value} />;
    }

    // Too many TextInputs causes React Native to crash, so only
    // truly mount the TextInput when the Cell is focused.
    // Use TouchableWithoutFeedback because we want the appearance and
    // feedback to resemble a TextInput regardless of focus.
    if (!isFocused) {
      return (
        <TouchableWithoutFeedback style={touchableStyle} onPress={focusCell}>
          <View style={internalViewStyle}>
            <View style={textViewStyle}>
              <Text style={internalTextStyle}>{value}</Text>
            </View>
          </View>
        </TouchableWithoutFeedback>
      );
    }

    // Render a Cell with a textInput.
    return (
      <View style={internalViewStyle}>
        <TextInput
          style={textInputStyle}
          value={String(value)}
          onChangeText={onEdit}
          autoFocus={isFocused}
          onSubmitEditing={focusNextCell}
          underlineColorAndroid={dataTableColors.editableCellUnderline}
        />
      </View>
    );
  }
);

const defaultStyles = StyleSheet.create({
  viewStyle: {
    flex: 1,
    justifyContent: 'center',
  },
  textStyle: {
    flex: 1,
    justifyContent: 'center',
  },
  touchableStyle: {},
  textInputStyle: {},
  textViewStyle: {},
});

EditableCell.propTypes = {
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
  columnKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
  disabled: PropTypes.bool,
  isFocused: PropTypes.bool,
  editAction: PropTypes.func.isRequired,
  focusAction: PropTypes.func.isRequired,
  focusNextAction: PropTypes.func.isRequired,
  dispatch: PropTypes.func.isRequired,
  touchableStyle: PropTypes.object,
  viewStyle: PropTypes.object,
  textStyle: PropTypes.object,
  width: PropTypes.number,
  textInputStyle: PropTypes.object,
  textViewStyle: PropTypes.object,
  isLastCell: PropTypes.bool,
};

EditableCell.defaultProps = {
  value: '',
  disabled: false,
  isFocused: false,
  width: 0,
  touchableStyle: defaultStyles.touchableStyle,
  viewStyle: defaultStyles.viewStyle,
  textStyle: defaultStyles.textStyle,
  textInputStyle: defaultStyles.textInputStyle,
  textViewStyle: defaultStyles.textInputStyle,
  isLastCell: false,
};

export default EditableCell;
<commit_msg>Add removal of defaultStyles from EditableCell<commit_after>/* eslint-disable react/forbid-prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import { View, TouchableWithoutFeedback, Text, TextInput } from 'react-native';

import Cell from './Cell';
import { dataTableColors } from '../../globalStyles/index';
import { getAdjustedStyle } from './utilities';

/**
 * Renders a cell that on press or focus contains a TextInput for
 * editing values.
 *
 * @param {string|number} value The value to render in cell
 * @param {string|number} rowKey Unique key associated to row cell is in
 * @param {string|number} columnKey Unique key associated to column cell is in
 * @param {bool} disabled If `true` will render a plain Cell element with no interaction
 * @param {bool} isFocused If `false` will TouchableOpacity that dispatches a focusAction
 *                         when pressed. When `true` will render a TextInput with focus
 * @param {func} editAction Action creator for handling editing of this cell.
 *                          `(newValue, rowKey, columnKey) => {...}`
 * @param {func} focusAction Action creator for handling focusing of this cell.
 *                          `(rowKey, columnKey) => {...}`
 * @param {func} focusNextAction Action creator for handling focusing of this cell.
 *                          `(rowKey, columnKey) => {...}`
 * @param {func}  dispatch Reducer dispatch callback for handling actions
 * @param {Object}  touchableStyle Style object for the wrapping Touchable component
 * @param {Object}  viewStyle Style object for the wrapping View component
 * @param {Object}  textStyle Style object for the inner Text component
 * @param {Number}  width Optional flex property to inject into styles.
 * @param {Bool}  isLastCell Indicator for if this cell is the last
 *                                   in a row. Removing the borderRight if true.
 */
const EditableCell = React.memo(
  ({
    value,
    rowKey,
    columnKey,
    disabled,
    isFocused,
    editAction,
    focusAction,
    focusNextAction,
    dispatch,
    touchableStyle,
    viewStyle,
    textInputStyle,
    textStyle,
    textViewStyle,
    isLastCell,
    width,
  }) => {
    const onEdit = newValue => dispatch(editAction(newValue, rowKey, columnKey));

    const focusCell = () => dispatch(focusAction(rowKey, columnKey));
    const focusNextCell = () => dispatch(focusNextAction(rowKey, columnKey));

    console.log(`- EditableCell: ${value}`);

    const internalViewStyle = getAdjustedStyle(viewStyle, width, isLastCell);
    const internalTextStyle = getAdjustedStyle(textStyle, width);

    // Render a plain Cell if disabled.
    if (disabled) {
      return <Cell viewStyle={internalViewStyle} textStyle={internalTextStyle} value={value} />;
    }

    // Too many TextInputs causes React Native to crash, so only
    // truly mount the TextInput when the Cell is focused.
    // Use TouchableWithoutFeedback because we want the appearance and
    // feedback to resemble a TextInput regardless of focus.
    if (!isFocused) {
      return (
        <TouchableWithoutFeedback style={touchableStyle} onPress={focusCell}>
          <View style={internalViewStyle}>
            <View style={textViewStyle}>
              <Text style={internalTextStyle}>{value}</Text>
            </View>
          </View>
        </TouchableWithoutFeedback>
      );
    }

    // Render a Cell with a textInput.
    return (
      <View style={internalViewStyle}>
        <TextInput
          style={textInputStyle}
          value={String(value)}
          onChangeText={onEdit}
          autoFocus={isFocused}
          onSubmitEditing={focusNextCell}
          underlineColorAndroid={dataTableColors.editableCellUnderline}
        />
      </View>
    );
  }
);

EditableCell.propTypes = {
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
  columnKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
  disabled: PropTypes.bool,
  isFocused: PropTypes.bool,
  editAction: PropTypes.func.isRequired,
  focusAction: PropTypes.func.isRequired,
  focusNextAction: PropTypes.func.isRequired,
  dispatch: PropTypes.func.isRequired,
  touchableStyle: PropTypes.object,
  viewStyle: PropTypes.object,
  textStyle: PropTypes.object,
  width: PropTypes.number,
  textInputStyle: PropTypes.object,
  textViewStyle: PropTypes.object,
  isLastCell: PropTypes.bool,
};

EditableCell.defaultProps = {
  value: '',
  disabled: false,
  isFocused: false,
  width: 0,
  touchableStyle: {},
  viewStyle: {},
  textStyle: {},
  textInputStyle: {},
  textViewStyle: {},
  isLastCell: false,
};

export default EditableCell;
<|endoftext|>


<commit_before>// Thesis - Data Wrangling - To Do:

// • Need to gather countries and remove them in pre-processing
// • Add Dada set into final data via…? needs to be ranked - it has to come in early.
//  	- re-scrape Dada with updated crawler,
//      add that in p-reduce, remove that from the other stuff
//
// -> How does final dataset get made?
// • Continue scrape
//
//-----------------------------------------------------------------------------
//collect.js gets gets pages and runs them through the data pipeline

//  Output data structure:
///    this page's URL                  url        str
///    links from this page             mapsTo     []
///    pages linking to this one        mapsFrom   []
///    metaData from wiki's data page   metaData   {}
///    if object should not be scraped  noscrape   binary (either has attr or does not have it)

// Will need to ID 'influencers' - things that bear an outsize influence on all events of time, such as WWI, and catalogue them
// appropriately / define them as stopping points to keep thesis centered around Dada

// RUN NOTES:
// Wikipedia appears to block IP at some point (unclear what the cutoff is)

var request = require('request');
var fs = require('fs');
var async = require('async');

//local modules
var dataScrape = require('./s-wikiData');
var mF = require('./s-mapsFrom');
var mT = require('./s-mainPage');
var detect = require('./s-detect');

//data
var pagesIn = JSON.parse(fs.readFileSync('Dada-update0.json'));
var pages = [];

// test URLs - 'Francis_Picabia' - 'Ann%C3%A9es_folles' - 'Dada'

// pagesIn[pgI] counter
var pgI = 0;
// array of urls to scrape (this will be a parameter)
var urlArr = pagesIn[pgI].mapsFrom;
// loop start
var i = 0;
// loop endPoint
var endLoop = urlArr.length-1;

var exceptions = [
  'wikisource.org',
  '.jpg',
  '.png',
  '.gif',
  'Book:'
]

var fullSkip = [
  'wikipedia',      // no foriegn pages (not in scope)
  'wiktionary.org', // no wiktionary pages
  'wikiquote',      // no wikiquote pages
  '_talk:',         // no talk pages
  'Draft:'
]

 // Empty Array that is filled with already-scraped items
 // to avoid potentially scraping the same page twice
var dups = [];

// check for exceptions
function skip(link, exc) {
  var fact = false;
  for (var c in exc) {
    if (link.includes(exc[c])) {
      fact = true;
      break;
    }
  }
  return fact;
}

// check for matches
function skipMatch(link, exc) {
  var fact = false;
  for (var c in exc) {
    if (link===exc[c]) {
      fact = true;
      console.log('COLLECT CAUGHT DUP: '+link);
      break;
    }
  }
  return fact;
}

function getRando(min, max) {
  return Math.floor(Math.random() * (max - min) + min);
}

//initialize lastBatch
var lastBatch = 0;

function writeDataFile(counter) {
  counter++;
  if (counter>=endLoop || counter%250==0) { //if last loop or if counter is divisible by 250, write the file
    fs.writeFile('data/MapsFrom-update'+lastBatch+'-'+counter+'.json', JSON.stringify(pages), function(err) {
        if (err) {throw err;}
        console.log('file written');
        lastBatch=counter;
        pages = [];
    });
  }
}

function crawler () {           //  create a loop function
   setTimeout(function () {    //  call a 3s setTimeout when the loop is called

        // check for already scraped urls
        if(!skipMatch(pagesIn[pgI].mapsTo[i], dups)) {

          var page = new Object;
          page.distance = 1;
          page.root = pagesIn[pgI].url; // will need to modify this for next batch?
          page.url =  urlArr[i]; //testURL_1;
          //track already scraped pages
          dups.push(page.url);

          //this scraper only handles english and is not equipped for non-english pages
          var url = 'https://en.wikipedia.org/wiki/'+page.url;
          var mapsFromURL = 'https://en.wikipedia.org/w/index.php?title=Special:WhatLinksHere/'+page.url+'&limit=3000';

          //check for fullSkip (pages not scraping) pages
          if (!skip(page.url, fullSkip)) {

          // collect links pointing at this page
          request(mapsFromURL, function(err, resp, body) {
            if (err) {throw err;}
              console.log('scraping mapsFrom '+mapsFromURL);
              page.mapsFrom = mF.scrape(body);
             //  console.log(page.mapsFrom);
             if (!skip(page.url, exceptions)) {

              // collect links pointing outwards from the page
              request(url, function(err, resp, body) {
                 if (err) {throw err;}
                  console.log('scraping mainPage '+url);
                  var s = mT.scrape(body, url);
                 //  console.log(s);
                  page.mapsTo = s.mapsTo;
                  page.wikiData = s.wikiData;
                  page.image = s.image;
                  page.title = s.title;

                 if (page.wikiData!=undefined) {
                   // collect wikiData (categorical info)
                   request(page.wikiData, function(err, resp, body) {
                      if (err) {throw err;}
                       console.log('scraping wikiData '+page.wikiData);
                       page.metaData = dataScrape.scrape(body);
                       pages.push(page);
                       writeDataFile(i);

                   }); // end wikiData request
                 } else {
                   // case no wikiData
                   page.metaData = null;
                   pages.push(page);
                   writeDataFile(i);
                 }
              }); // end mainPage request

            } else {
              // some urls lead to direct sources, like images and manifesto, that do not have 'mainPage' or 'wikiData' items
              // so just push the page as is
              pages.push(page);
              writeDataFile(i);
            }

          });

        } else {
          page.noScrape = 1;
          pages.push(page);
          console.log('-NO SCRAPE-');
          writeDataFile(i);
        } // end fullSkip conditional

      } // end dups conditional

    //update count and check for end case
    i++;
    if (i < endLoop) { //pagesIn[pgI].mapsTo.length-1
         console.log(i+' of '+ endLoop);
         crawler();
    }

    //set delay between 1200 and 3600 milliseconds per request
  }, getRando(1200,3600))

}
//
crawler();
<commit_msg>crawler cleanup<commit_after>//collect.js gets gets pages and runs them through the data pipeline

//  Output schema:
///    this page's URL                  url        str
///---->    the next closest root to Dada    url        [] (handle case dup)
///    the page's title                 words      str
///    the page's image, if it has one  url        str
///    links from this page             mapsTo     []
///    pages linking to this one        mapsFrom   []
///    metaData from wiki's data page   metaData   {}

var request = require('request');
var fs = require('fs');
var async = require('async');

//local modules
var dataScrape = require('./s-wikiData');
var mF = require('./s-mapsFrom');
var mT = require('./s-mainPage');
var detect = require('./s-detect');

//data
var pagesIn = JSON.parse(fs.readFileSync('Dada-update0.json'));
var pages = [];

// test URLs - 'Francis_Picabia' - 'Ann%C3%A9es_folles' - 'Dada'

var pgI = 0,                        // pagesIn[pgI] counter
    urlArr = pagesIn[pgI].mapsFrom, // array of urls to scrape (this will be a parameter)
    i = 0,                          // loop start
    endLoop = urlArr.length-1,      // loop endPoint
    lastBatch = 0,                  //initialize lastBatch
    var dups = [];                  // track already scraped pages to avoid dups

var exceptions = [
  'wikisource.org',
  '.jpg',
  '.png',
  '.gif',
  'Book:'
]

var fullSkip = [
  'wikipedia',      // no foriegn pages (not in scope)
  'wiktionary.org', // no wiktionary pages
  'wikiquote',      // no wikiquote pages
  '_talk:',         // no talk pages
  'Draft:'
]

// check for exceptions
function skip(link, exc) {
  var fact = false;
  for (var c in exc) {
    if (link.includes(exc[c])) {
      fact = true;
      break;
    }
  }
  return fact;
}

// check for matches
function skipMatch(link, exc) {
  var fact = false;
  for (var c in exc) {
    if (link===exc[c]) {
      fact = true;
      console.log('COLLECT CAUGHT DUP: '+link);
      break;
    }
  }
  return fact;
}

function getRando(min, max) {
  return Math.floor(Math.random() * (max - min) + min);
}

function writeDataFile(counter) {
  counter++;
  if (counter>=endLoop || counter%250==0) { //if last loop or if counter is divisible by 250, write the file
    fs.writeFile('data/MapsFrom-update'+lastBatch+'-'+counter+'.json', JSON.stringify(pages), function(err) {
        if (err) {throw err;}
        console.log('file written');
        lastBatch=counter;
        pages = [];
    });
  }
}

function crawler () {           //  create a loop function
   setTimeout(function () {    //  call a 3s setTimeout when the loop is called

        // check for already scraped urls
        if(!skipMatch(pagesIn[pgI].mapsTo[i], dups)) {

          var page = new Object;
          page.distance = 1;
          page.root = pagesIn[pgI].url; // will need to modify this for next batch?
          page.url =  urlArr[i];        // testURL_1;
          dups.push(page.url);

          var url = 'https://en.wikipedia.org/wiki/'+page.url;
          var mapsFromURL = 'https://en.wikipedia.org/w/index.php?title=Special:WhatLinksHere/'+page.url+'&limit=3000';

          //check for fullSkip (pages not scraping) pages
          if (!skip(page.url, fullSkip)) {

          // collect links pointing at this page
          request(mapsFromURL, function(err, resp, body) {
            if (err) {throw err;}
              console.log('scraping mapsFrom '+mapsFromURL);
              page.mapsFrom = mF.scrape(body);
             if (!skip(page.url, exceptions)) {

              // collect links pointing outwards from the page
              request(url, function(err, resp, body) {
                 if (err) {throw err;}
                  console.log('scraping mainPage '+url);
                  var s = mT.scrape(body, url);
                  page.mapsTo = s.mapsTo;
                  page.wikiData = s.wikiData;
                  page.image = s.image;
                  page.title = s.title;

                 if (page.wikiData!=undefined) {
                   // collect wikiData (categorical info)
                   request(page.wikiData, function(err, resp, body) {
                      if (err) {throw err;}
                       console.log('scraping wikiData '+page.wikiData);
                       page.metaData = dataScrape.scrape(body);
                       pages.push(page);
                       writeDataFile(i);
                   });
                 } else {
                   // case no wikiData
                   page.metaData = null;
                   pages.push(page);
                   writeDataFile(i);
                 }
              }); // end mainPage request
            } else {
              // some page types don't have link info, push as-is
              pages.push(page);
              writeDataFile(i);
            }
          });
        } else {
          page.noScrape = 1;
          pages.push(page);
          console.log('-NO SCRAPE-');
          writeDataFile(i);
        } // end fullSkip conditional
      } // end dups conditional
    //update count and check for end case
    i++;
    if (i < endLoop) { //pagesIn[pgI].mapsTo.length-1
         console.log(i+' of '+ endLoop);
         crawler();
    }
  //set delay between 1200 and 3600 milliseconds per request
  }, getRando(1200,3600))
}

crawler();

// async loop
// moves through each array item
// updates filenames
// clears
<|endoftext|>


<commit_before>var _ = require('underscore');
var cdb = require('cartodb.js');
var Backbone = require('backbone');
var formatter = require('../../formatter');
var HistogramTitleView = require('./histogram-title-view');
var HistogramChartView = require('./chart');
var placeholder = require('./placeholder.tpl');
var template = require('./content.tpl');
var DropdownView = require('../dropdown/widget-dropdown-view');
var AnimateValues = require('../animate-values.js');
var animationTemplate = require('./animation-template.tpl');

/**
 * Widget content view for a histogram
 */
module.exports = cdb.core.View.extend({
  className: 'CDB-Widget-body',

  defaults: {
    chartHeight: 48 + 20 + 4
  },

  events: {
    'click .js-clear': '_resetWidget',
    'click .js-zoom': '_zoom'
  },

  initialize: function () {
    this._dataviewModel = this.model.dataviewModel;
    this._originalData = this._dataviewModel.getUnfilteredDataModel();
    this.filter = this._dataviewModel.filter;
    this.lockedByUser = false;
    this._initBinds();
  },

  _initViews: function () {
    this._initTitleView();

    var dropdown = new DropdownView({
      target: this.$('.js-actions'),
      container: this.$('.js-header')
    });

    dropdown.bind('click', function (action) {
      if (action === 'toggle') {
        this.model.set('collapsed', !this.model.get('collapsed'));
      }
    }, this);

    this.addView(dropdown);

    this._renderMiniChart();
    this._renderMainChart();
  },

  _initTitleView: function () {
    var titleView = new HistogramTitleView({
      widgetModel: this.model,
      dataviewModel: this._dataviewModel
    });

    this.$('.js-title').append(titleView.render().el);
    this.addView(titleView);
  },

  _initBinds: function () {
    this._originalData.once('change:data', this._onFirstLoad, this);
    this.model.bind('change:collapsed', this.render, this);
  },

  _onFirstLoad: function () {
    this.render();
    this._dataviewModel.bind('change:data', this._onHistogramDataChanged, this);
    this.add_related_model(this._dataviewModel);
    this._dataviewModel.fetch();
  },

  _isZoomed: function () {
    return this.model.get('zoomed');
  },

  _onHistogramDataChanged: function () {
    // When the histogram is zoomed, we don't need to rely
    // on the change url to update the histogram
    // TODO the widget should not know about the URL… could this state be got from the dataview model somehow?
    if (this._dataviewModel.changed.url && this._isZoomed()) {
      return;
    }

    // if the action was initiated by the user
    // don't replace the stored data
    if (this.lockedByUser) {
      this.lockedByUser = false;
    } else {
      if (!this._isZoomed()) {
        this.histogramChartView.showShadowBars();
        this.miniHistogramChartView.replaceData(this._dataviewModel.getData());
      }
      this.histogramChartView.replaceData(this._dataviewModel.getData());
    }

    if (this.unsettingRange) {
      this._unsetRange();
    } else {
      if (this._isZoomed() && !this.lockZoomedData) {
        this.lockZoomedData = true;
      }
    }

    this._updateStats();
  },

  render: function () {
    this.clearSubViews();
    var data = this._dataviewModel.getData();
    var originalData = this._originalData.getData();
    var isDataEmpty = !_.size(data) && !_.size(originalData);

    this.$el.html(
      template({
        title: this.model.get('title'),
        showStats: this.model.get('show_stats'),
        itemsCount: !isDataEmpty ? data.length : '-'
      })
    );

    if (isDataEmpty) {
      this._addPlaceholder();
      this._initTitleView();
    } else {
      this._setupBindings();
      this.$el.toggleClass('is-collapsed', !!this.model.get('collapsed'));
      this._initViews();
    }

    return this;
  },

  _unsetRange: function () {
    this.unsettingRange = false;
    this.histogramChartView.replaceData(this._dataviewModel.getData());
    this.model.set({ lo_index: null, hi_index: null });

    if (!this._isZoomed()) {
      this.histogramChartView.showShadowBars();
    }
  },

  _addPlaceholder: function () {
    this.$('.js-content').append(placeholder());
  },

  _renderMainChart: function () {
    this.histogramChartView = new HistogramChartView(({
      margin: { top: 4, right: 4, bottom: 4, left: 4 },
      hasHandles: true,
      hasAxisTip: true,
      width: this.canvasWidth,
      height: this.defaults.chartHeight,
      data: this._dataviewModel.getData(),
      originalData: this._originalData,
      displayShadowBars: true
    }));

    this.$('.js-content').append(this.histogramChartView.el);
    this.addView(this.histogramChartView);

    this.histogramChartView.bind('range_updated', this._onRangeUpdated, this);
    this.histogramChartView.bind('on_brush_end', this._onBrushEnd, this);
    this.histogramChartView.bind('hover', this._onValueHover, this);
    this.histogramChartView.render().show();

    this._updateStats();
  },

  _renderMiniChart: function () {
    this.miniHistogramChartView = new HistogramChartView(({
      className: 'CDB-Chart--mini',
      margin: { top: 0, right: 4, bottom: 4, left: 4 },
      height: 40,
      showOnWidthChange: false,
      data: this._dataviewModel.getData(),
      originalData: this._originalData
    }));

    this.addView(this.miniHistogramChartView);
    this.$('.js-content').append(this.miniHistogramChartView.el);
    this.miniHistogramChartView.bind('on_brush_end', this._onMiniRangeUpdated, this);
    this.miniHistogramChartView.render();
  },

  _setupBindings: function () {
    this._dataviewModel.bind('change:bins', this._onChangeBins, this);
    this.model.bind('change:zoomed', this._onChangeZoomed, this);
    this.model.bind('change:zoom_enabled', this._onChangeZoomEnabled, this);
    this.model.bind('change:filter_enabled', this._onChangeFilterEnabled, this);
    this.model.bind('change:total', this._onChangeTotal, this);
    this.model.bind('change:nulls', this._onChangeNulls, this);
    this.model.bind('change:max', this._onChangeMax, this);
    this.model.bind('change:min', this._onChangeMin, this);
    this.model.bind('change:avg', this._onChangeAvg, this);
  },

  _clearTooltip: function () {
    this.$('.js-tooltip').stop().hide();
  },

  _onValueHover: function (info) {
    var $tooltip = this.$('.js-tooltip');

    if (info && info.data) {
      var bottom = this.defaults.chartHeight + 3 - info.top;

      $tooltip.css({ bottom: bottom, left: info.left });
      $tooltip.text(info.data);
      $tooltip.css({ left: info.left - $tooltip.width() / 2 });
      $tooltip.fadeIn(70);
    } else {
      this._clearTooltip();
    }
  },

  _onMiniRangeUpdated: function (loBarIndex, hiBarIndex) {
    this.lockedByUser = false;
    this.lockZoomedData = false;

    this._clearTooltip();
    this.histogramChartView.removeSelection();

    var data = this._originalData.getData();

    if (loBarIndex >= 0 && loBarIndex < data.length && (hiBarIndex - 1) >= 0 && (hiBarIndex - 1) < data.length) {
      this.filter.setRange(
        data[loBarIndex].start,
        data[hiBarIndex - 1].end
      );
      this._updateStats();
    } else {
      console.error('Error accessing array bounds', loBarIndex, hiBarIndex, data);
    }
  },

  _onBrushEnd: function (loBarIndex, hiBarIndex) {
    var data = this._dataviewModel.getData();

    if (!data || !data.length) {
      return;
    }

    if (this._isZoomed()) {
      this.lockedByUser = true;
    }

    var properties = { filter_enabled: true, lo_index: loBarIndex, hi_index: hiBarIndex };

    if (!this.model.get('zoomed')) {
      properties.zoom_enabled = true;
    }

    this.model.set(properties);

    if (loBarIndex >= 0 && loBarIndex < data.length && (hiBarIndex - 1) >= 0 && (hiBarIndex - 1) < data.length) {
      this.filter.setRange(
        data[loBarIndex].start,
        data[hiBarIndex - 1].end
      );
      this._updateStats();
    } else {
      console.error('Error accessing array bounds', loBarIndex, hiBarIndex, data);
    }
  },

  _onRangeUpdated: function (loBarIndex, hiBarIndex) {
    var self = this;
    if (this.model.get('zoomed')) {
      this.model.set({ zoom_enabled: false, lo_index: loBarIndex, hi_index: hiBarIndex });
    } else {
      this.model.set({ lo_index: loBarIndex, hi_index: hiBarIndex });
    }

    var updateStats = _.debounce(function () { self._updateStats(); }, 400);
    updateStats();
  },

  _onChangeFilterEnabled: function () {
    this.$('.js-filter').toggleClass('is-hidden', !this.model.get('filter_enabled'));
  },

  _onChangeBins: function (mdl, bins) {
    this._originalData.setBins(bins);
    this.model.set({
      zoom_enabled: false,
      filter_enabled: false,
      lo_index: null,
      hi_index: null
    });
  },

  _onChangeZoomEnabled: function () {
    this.$('.js-zoom').toggleClass('is-hidden', !this.model.get('zoom_enabled'));
  },

  _changeHeaderValue: function (className, what, suffix) {
    if (this.model.get(what) === undefined) {
      this.$(className).text('0 ' + suffix);
      return;
    }

    this._addTitleForValue(className, what, suffix);

    var animator = new AnimateValues({
      el: this.$el
    });

    animator.animateValue(this.model, what, className, animationTemplate, {
      formatter: formatter.formatNumber,
      templateData: { suffix: ' ' + suffix }
    });
  },

  _onChangeNulls: function () {
    this._changeHeaderValue('.js-nulls', 'nulls', '');
  },

  _onChangeTotal: function () {
    this._changeHeaderValue('.js-val', 'total', 'SELECTED');
  },

  _onChangeMax: function () {
    this._changeHeaderValue('.js-max', 'max', '');
  },

  _onChangeMin: function () {
    this._changeHeaderValue('.js-min', 'min', '');
  },

  _onChangeAvg: function () {
    this._changeHeaderValue('.js-avg', 'avg', '');
  },

  _addTitleForValue: function (className, what, unit) {
    this.$(className).attr('title', this._formatNumberWithCommas(this.model.get(what).toFixed(2)) + ' ' + unit);
  },

  _formatNumberWithCommas: function (x) {
    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  },

  _updateStats: function () {
    var data = this._dataviewModel.getData();
    var nulls = this._dataviewModel.get('nulls');
    var min, max;

    if (data && data.length) {
      var loBarIndex = this.model.get('lo_index') || 0;
      var hiBarIndex = this.model.get('hi_index') || data.length;

      var sum = this._calcSum(data, loBarIndex, hiBarIndex);
      var avg = this._calcAvg(data, loBarIndex, hiBarIndex);

      if (loBarIndex >= 0 && loBarIndex < data.length) {
        min = data[loBarIndex].start;
      }

      if (hiBarIndex >= 0 && hiBarIndex - 1 < data.length) {
        max = data[hiBarIndex - 1].end;
      }

      this.model.set({ total: sum, nulls: nulls, min: min, max: max, avg: avg });
    }
  },

  _calcAvg: function (data, start, end) {
    var selectedData = data.slice(start, end);

    var total = this._calcSum(data, start, end, total);

    if (!total) {
      return 0;
    }

    var area = _.reduce(selectedData, function (memo, d) {
      return (d.avg && d.freq) ? (d.avg * d.freq) + memo : memo;
    }, 0);

    return area / total;
  },

  _calcSum: function (data, start, end) {
    return _.reduce(data.slice(start, end), function (memo, d) {
      return d.freq + memo;
    }, 0);
  },

  _onChangeZoomed: function () {
    if (this.model.get('zoomed')) {
      this._onZoomIn();
    } else {
      this._resetWidget();
    }
  },

  _showMiniRange: function () {
    var loBarIndex = this.model.get('lo_index');
    var hiBarIndex = this.model.get('hi_index');

    this.miniHistogramChartView.selectRange(loBarIndex, hiBarIndex);
    this.miniHistogramChartView.show();
  },

  _zoom: function () {
    this.model.set({ zoomed: true, zoom_enabled: false });
    this.histogramChartView.removeSelection();
  },

  _onZoomIn: function () {
    this.lockedByUser = false;
    this._showMiniRange();
    this.histogramChartView.setBounds();
    this._dataviewModel.enableFilter();
    this._dataviewModel.fetch();
  },

  _resetWidget: function () {
    this.lockedByUser = true;
    this.lockZoomedData = false;
    this.unsettingRange = true;
    this.model.set({
      zoomed: false,
      zoom_enabled: false,
      filter_enabled: false,
      lo_index: null,
      hi_index: null
    });
    this._dataviewModel.disableFilter();

    this.filter.unsetRange();

    this.histogramChartView.unsetBounds();
    this.miniHistogramChartView.hide();
  }
});
<commit_msg>Removed unnecessary variable lockZoomedData<commit_after>var _ = require('underscore');
var cdb = require('cartodb.js');
var Backbone = require('backbone');
var formatter = require('../../formatter');
var HistogramTitleView = require('./histogram-title-view');
var HistogramChartView = require('./chart');
var placeholder = require('./placeholder.tpl');
var template = require('./content.tpl');
var DropdownView = require('../dropdown/widget-dropdown-view');
var AnimateValues = require('../animate-values.js');
var animationTemplate = require('./animation-template.tpl');

/**
 * Widget content view for a histogram
 */
module.exports = cdb.core.View.extend({
  className: 'CDB-Widget-body',

  defaults: {
    chartHeight: 48 + 20 + 4
  },

  events: {
    'click .js-clear': '_resetWidget',
    'click .js-zoom': '_zoom'
  },

  initialize: function () {
    this._dataviewModel = this.model.dataviewModel;
    this._originalData = this._dataviewModel.getUnfilteredDataModel();
    this.filter = this._dataviewModel.filter;
    this.lockedByUser = false;
    this._initBinds();
  },

  _initViews: function () {
    this._initTitleView();

    var dropdown = new DropdownView({
      target: this.$('.js-actions'),
      container: this.$('.js-header')
    });

    dropdown.bind('click', function (action) {
      if (action === 'toggle') {
        this.model.set('collapsed', !this.model.get('collapsed'));
      }
    }, this);

    this.addView(dropdown);

    this._renderMiniChart();
    this._renderMainChart();
  },

  _initTitleView: function () {
    var titleView = new HistogramTitleView({
      widgetModel: this.model,
      dataviewModel: this._dataviewModel
    });

    this.$('.js-title').append(titleView.render().el);
    this.addView(titleView);
  },

  _initBinds: function () {
    this._originalData.once('change:data', this._onFirstLoad, this);
    this.model.bind('change:collapsed', this.render, this);
  },

  _onFirstLoad: function () {
    this.render();
    this._dataviewModel.bind('change:data', this._onHistogramDataChanged, this);
    this.add_related_model(this._dataviewModel);
    this._dataviewModel.fetch();
  },

  _isZoomed: function () {
    return this.model.get('zoomed');
  },

  _onHistogramDataChanged: function () {
    // When the histogram is zoomed, we don't need to rely
    // on the change url to update the histogram
    // TODO the widget should not know about the URL… could this state be got from the dataview model somehow?
    if (this._dataviewModel.changed.url && this._isZoomed()) {
      return;
    }

    // if the action was initiated by the user
    // don't replace the stored data
    if (this.lockedByUser) {
      this.lockedByUser = false;
    } else {
      if (!this._isZoomed()) {
        this.histogramChartView.showShadowBars();
        this.miniHistogramChartView.replaceData(this._dataviewModel.getData());
      }
      this.histogramChartView.replaceData(this._dataviewModel.getData());
    }

    if (this.unsettingRange) {
      this._unsetRange();
    }

    this._updateStats();
  },

  render: function () {
    this.clearSubViews();
    var data = this._dataviewModel.getData();
    var originalData = this._originalData.getData();
    var isDataEmpty = !_.size(data) && !_.size(originalData);

    this.$el.html(
      template({
        title: this.model.get('title'),
        showStats: this.model.get('show_stats'),
        itemsCount: !isDataEmpty ? data.length : '-'
      })
    );

    if (isDataEmpty) {
      this._addPlaceholder();
      this._initTitleView();
    } else {
      this._setupBindings();
      this.$el.toggleClass('is-collapsed', !!this.model.get('collapsed'));
      this._initViews();
    }

    return this;
  },

  _unsetRange: function () {
    this.unsettingRange = false;
    this.histogramChartView.replaceData(this._dataviewModel.getData());
    this.model.set({ lo_index: null, hi_index: null });

    if (!this._isZoomed()) {
      this.histogramChartView.showShadowBars();
    }
  },

  _addPlaceholder: function () {
    this.$('.js-content').append(placeholder());
  },

  _renderMainChart: function () {
    this.histogramChartView = new HistogramChartView(({
      margin: { top: 4, right: 4, bottom: 4, left: 4 },
      hasHandles: true,
      hasAxisTip: true,
      width: this.canvasWidth,
      height: this.defaults.chartHeight,
      data: this._dataviewModel.getData(),
      originalData: this._originalData,
      displayShadowBars: true
    }));

    this.$('.js-content').append(this.histogramChartView.el);
    this.addView(this.histogramChartView);

    this.histogramChartView.bind('range_updated', this._onRangeUpdated, this);
    this.histogramChartView.bind('on_brush_end', this._onBrushEnd, this);
    this.histogramChartView.bind('hover', this._onValueHover, this);
    this.histogramChartView.render().show();

    this._updateStats();
  },

  _renderMiniChart: function () {
    this.miniHistogramChartView = new HistogramChartView(({
      className: 'CDB-Chart--mini',
      margin: { top: 0, right: 4, bottom: 4, left: 4 },
      height: 40,
      showOnWidthChange: false,
      data: this._dataviewModel.getData(),
      originalData: this._originalData
    }));

    this.addView(this.miniHistogramChartView);
    this.$('.js-content').append(this.miniHistogramChartView.el);
    this.miniHistogramChartView.bind('on_brush_end', this._onMiniRangeUpdated, this);
    this.miniHistogramChartView.render();
  },

  _setupBindings: function () {
    this._dataviewModel.bind('change:bins', this._onChangeBins, this);
    this.model.bind('change:zoomed', this._onChangeZoomed, this);
    this.model.bind('change:zoom_enabled', this._onChangeZoomEnabled, this);
    this.model.bind('change:filter_enabled', this._onChangeFilterEnabled, this);
    this.model.bind('change:total', this._onChangeTotal, this);
    this.model.bind('change:nulls', this._onChangeNulls, this);
    this.model.bind('change:max', this._onChangeMax, this);
    this.model.bind('change:min', this._onChangeMin, this);
    this.model.bind('change:avg', this._onChangeAvg, this);
  },

  _clearTooltip: function () {
    this.$('.js-tooltip').stop().hide();
  },

  _onValueHover: function (info) {
    var $tooltip = this.$('.js-tooltip');

    if (info && info.data) {
      var bottom = this.defaults.chartHeight + 3 - info.top;

      $tooltip.css({ bottom: bottom, left: info.left });
      $tooltip.text(info.data);
      $tooltip.css({ left: info.left - $tooltip.width() / 2 });
      $tooltip.fadeIn(70);
    } else {
      this._clearTooltip();
    }
  },

  _onMiniRangeUpdated: function (loBarIndex, hiBarIndex) {
    this.lockedByUser = false;

    this._clearTooltip();
    this.histogramChartView.removeSelection();

    var data = this._originalData.getData();

    if (loBarIndex >= 0 && loBarIndex < data.length && (hiBarIndex - 1) >= 0 && (hiBarIndex - 1) < data.length) {
      this.filter.setRange(
        data[loBarIndex].start,
        data[hiBarIndex - 1].end
      );
      this._updateStats();
    } else {
      console.error('Error accessing array bounds', loBarIndex, hiBarIndex, data);
    }
  },

  _onBrushEnd: function (loBarIndex, hiBarIndex) {
    var data = this._dataviewModel.getData();

    if (!data || !data.length) {
      return;
    }

    if (this._isZoomed()) {
      this.lockedByUser = true;
    }

    var properties = { filter_enabled: true, lo_index: loBarIndex, hi_index: hiBarIndex };

    if (!this.model.get('zoomed')) {
      properties.zoom_enabled = true;
    }

    this.model.set(properties);

    if (loBarIndex >= 0 && loBarIndex < data.length && (hiBarIndex - 1) >= 0 && (hiBarIndex - 1) < data.length) {
      this.filter.setRange(
        data[loBarIndex].start,
        data[hiBarIndex - 1].end
      );
      this._updateStats();
    } else {
      console.error('Error accessing array bounds', loBarIndex, hiBarIndex, data);
    }
  },

  _onRangeUpdated: function (loBarIndex, hiBarIndex) {
    var self = this;
    if (this.model.get('zoomed')) {
      this.model.set({ zoom_enabled: false, lo_index: loBarIndex, hi_index: hiBarIndex });
    } else {
      this.model.set({ lo_index: loBarIndex, hi_index: hiBarIndex });
    }

    var updateStats = _.debounce(function () { self._updateStats(); }, 400);
    updateStats();
  },

  _onChangeFilterEnabled: function () {
    this.$('.js-filter').toggleClass('is-hidden', !this.model.get('filter_enabled'));
  },

  _onChangeBins: function (mdl, bins) {
    this._originalData.setBins(bins);
    this.model.set({
      zoom_enabled: false,
      filter_enabled: false,
      lo_index: null,
      hi_index: null
    });
  },

  _onChangeZoomEnabled: function () {
    this.$('.js-zoom').toggleClass('is-hidden', !this.model.get('zoom_enabled'));
  },

  _changeHeaderValue: function (className, what, suffix) {
    if (this.model.get(what) === undefined) {
      this.$(className).text('0 ' + suffix);
      return;
    }

    this._addTitleForValue(className, what, suffix);

    var animator = new AnimateValues({
      el: this.$el
    });

    animator.animateValue(this.model, what, className, animationTemplate, {
      formatter: formatter.formatNumber,
      templateData: { suffix: ' ' + suffix }
    });
  },

  _onChangeNulls: function () {
    this._changeHeaderValue('.js-nulls', 'nulls', '');
  },

  _onChangeTotal: function () {
    this._changeHeaderValue('.js-val', 'total', 'SELECTED');
  },

  _onChangeMax: function () {
    this._changeHeaderValue('.js-max', 'max', '');
  },

  _onChangeMin: function () {
    this._changeHeaderValue('.js-min', 'min', '');
  },

  _onChangeAvg: function () {
    this._changeHeaderValue('.js-avg', 'avg', '');
  },

  _addTitleForValue: function (className, what, unit) {
    this.$(className).attr('title', this._formatNumberWithCommas(this.model.get(what).toFixed(2)) + ' ' + unit);
  },

  _formatNumberWithCommas: function (x) {
    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  },

  _updateStats: function () {
    var data = this._dataviewModel.getData();
    var nulls = this._dataviewModel.get('nulls');
    var min, max;

    if (data && data.length) {
      var loBarIndex = this.model.get('lo_index') || 0;
      var hiBarIndex = this.model.get('hi_index') || data.length;

      var sum = this._calcSum(data, loBarIndex, hiBarIndex);
      var avg = this._calcAvg(data, loBarIndex, hiBarIndex);

      if (loBarIndex >= 0 && loBarIndex < data.length) {
        min = data[loBarIndex].start;
      }

      if (hiBarIndex >= 0 && hiBarIndex - 1 < data.length) {
        max = data[hiBarIndex - 1].end;
      }

      this.model.set({ total: sum, nulls: nulls, min: min, max: max, avg: avg });
    }
  },

  _calcAvg: function (data, start, end) {
    var selectedData = data.slice(start, end);

    var total = this._calcSum(data, start, end, total);

    if (!total) {
      return 0;
    }

    var area = _.reduce(selectedData, function (memo, d) {
      return (d.avg && d.freq) ? (d.avg * d.freq) + memo : memo;
    }, 0);

    return area / total;
  },

  _calcSum: function (data, start, end) {
    return _.reduce(data.slice(start, end), function (memo, d) {
      return d.freq + memo;
    }, 0);
  },

  _onChangeZoomed: function () {
    if (this.model.get('zoomed')) {
      this._onZoomIn();
    } else {
      this._resetWidget();
    }
  },

  _showMiniRange: function () {
    var loBarIndex = this.model.get('lo_index');
    var hiBarIndex = this.model.get('hi_index');

    this.miniHistogramChartView.selectRange(loBarIndex, hiBarIndex);
    this.miniHistogramChartView.show();
  },

  _zoom: function () {
    this.model.set({ zoomed: true, zoom_enabled: false });
    this.histogramChartView.removeSelection();
  },

  _onZoomIn: function () {
    this.lockedByUser = false;
    this._showMiniRange();
    this.histogramChartView.setBounds();
    this._dataviewModel.enableFilter();
    this._dataviewModel.fetch();
  },

  _resetWidget: function () {
    this.lockedByUser = true;
    this.unsettingRange = true;
    this.model.set({
      zoomed: false,
      zoom_enabled: false,
      filter_enabled: false,
      lo_index: null,
      hi_index: null
    });
    this._dataviewModel.disableFilter();

    this.filter.unsetRange();

    this.histogramChartView.unsetBounds();
    this.miniHistogramChartView.hide();
  }
});
<|endoftext|>


<commit_before>/*
 * jQuery UI @VERSION
 *
 * Copyright (c) 2008 Paul Bakaus (ui.jquery.com)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI
 *
 * $Id: ui.core.js 5587 2008-05-13 19:56:42Z scott.gonzalez $
 */
;(function($) {

$.ui = {
	plugin: {
		add: function(module, option, set) {
			var proto = $.ui[module].prototype;
			for(var i in set) {
				proto.plugins[i] = proto.plugins[i] || [];
				proto.plugins[i].push([option, set[i]]);
			}
		},
		call: function(instance, name, args) {
			var set = instance.plugins[name];
			if(!set) { return; }

			for (var i = 0; i < set.length; i++) {
				if (instance.options[set[i][0]]) {
					set[i][1].apply(instance.element, args);
				}
			}
		}
	},
	cssCache: {},
	css: function(name) {
		if ($.ui.cssCache[name]) { return $.ui.cssCache[name]; }
		var tmp = $('
').addClass(name).css({position:'absolute', top:'-5000px', left:'-5000px', display:'block'}).appendTo('body');

		//if (!$.browser.safari)
			//tmp.appendTo('body');

		//Opera and Safari set width and height to 0px instead of auto
		//Safari returns rgba(0,0,0,0) when bgcolor is not set
		$.ui.cssCache[name] = !!(
			(!(/auto|default/).test(tmp.css('cursor')) || (/^[1-9]/).test(tmp.css('height')) || (/^[1-9]/).test(tmp.css('width')) ||
			!(/none/).test(tmp.css('backgroundImage')) || !(/transparent|rgba\(0, 0, 0, 0\)/).test(tmp.css('backgroundColor')))
		);
		try { $('body').get(0).removeChild(tmp.get(0));	} catch(e){}
		return $.ui.cssCache[name];
	},
	disableSelection: function(e) {
		e.unselectable = "on";
		e.onselectstart = function() { return false; };
		if (e.style) { e.style.MozUserSelect = "none"; }
	},
	enableSelection: function(e) {
		e.unselectable = "off";
		e.onselectstart = function() { return true; };
		if (e.style) { e.style.MozUserSelect = ""; }
	},
	hasScroll: function(e, a) {
		var scroll = /top/.test(a||"top") ? 'scrollTop' : 'scrollLeft', has = false;
		if (e[scroll] > 0) return true; e[scroll] = 1;
		has = e[scroll] > 0 ? true : false; e[scroll] = 0;
		return has;
	}
};

/** jQuery core modifications and additions **/

var _remove = $.fn.remove;
$.fn.remove = function() {
	$("*", this).add(this).trigger("remove");
	return _remove.apply(this, arguments );
};

// $.widget is a factory to create jQuery plugins
// taking some boilerplate code out of the plugin code
// created by Scott Gonz�lez and J�rn Zaefferer
function getter(namespace, plugin, method) {
	var methods = $[namespace][plugin].getter || [];
	methods = (typeof methods == "string" ? methods.split(/,?\s+/) : methods);
	return ($.inArray(method, methods) != -1);
}

var widgetPrototype = {
	init: function() {},
	destroy: function() {
		this.element.removeData(this.widgetName);
	},

	getData: function(key) {
		return this.options[key];
	},
	setData: function(key, value) {
		this.options[key] = value;
	},

	enable: function() {
		this.setData('disabled', false);
	},
	disable: function() {
		this.setData('disabled', true);
	}
};

$.widget = function(name, prototype) {
	var namespace = name.split(".")[0];
	name = name.split(".")[1];
	// create plugin method
	$.fn[name] = function(options) {
		var isMethodCall = (typeof options == 'string'),
			args = Array.prototype.slice.call(arguments, 1);

		if (isMethodCall && getter(namespace, name, options)) {
			var instance = $.data(this[0], name);
			return (instance ? instance[options].apply(instance, args)
				: undefined);
		}

		return this.each(function() {
			var instance = $.data(this, name);
			if (!instance) {
				$.data(this, name, new $[namespace][name](this, options));
			} else if (isMethodCall) {
				instance[options].apply(instance, args);
			}
		});
	};

	// create widget constructor
	$[namespace][name] = function(element, options) {
		var self = this;

		this.widgetName = name;

		this.options = $.extend({}, $[namespace][name].defaults, options);
		this.element = $(element)
			.bind('setData.' + name, function(e, key, value) {
				return self.setData(key, value);
			})
			.bind('getData.' + name, function(e, key) {
				return self.getData(key);
			})
			.bind('remove', function() {
				return self.destroy();
			});
		this.init();
	};

	// add widget prototype
	$[namespace][name].prototype = $.extend({}, widgetPrototype, prototype);
};

/** Mouse Interaction Plugin **/

$.ui.mouse = {
	mouseInit: function() {
		var self = this;

		this.element.bind('mousedown.'+this.widgetName, function(e) {
			return self.mouseDown(e);
		});

		// Prevent text selection in IE
		if ($.browser.msie) {
			this._mouseUnselectable = this.element.attr('unselectable');
			this.element.attr('unselectable', 'on');
		}

		this.started = false;
	},

	// TODO: make sure destroying one instance of mouse doesn't mess with
	// other instances of mouse
	mouseDestroy: function() {
		this.element.unbind('.'+this.widgetName);

		// Restore text selection in IE
		($.browser.msie
			&& this.element.attr('unselectable', this._mouseUnselectable));
	},

	mouseDown: function(e) {
		// we may have missed mouseup (out of window)
		(this._mouseStarted && this.mouseUp(e));

		this._mouseDownEvent = e;

		var self = this,
			btnIsLeft = (e.which == 1),
			elIsCancel = ($(e.target).is(this.options.cancel));
		if (!btnIsLeft || elIsCancel) {
			return true;
		}

		this._mouseDelayMet = !this.options.delay;
		if (!this._mouseDelayMet) {
			this._mouseDelayTimer = setTimeout(function() {
				self._mouseDelayMet = true;
			}, this.options.delay);
		}

		if (this.mouseDistanceMet(e) && this.mouseDelayMet(e)) {
			this._mouseStarted = (this.mouseStart(e) !== false);
			if (!this._mouseStarted) { return false; }
		}

		// these delegates are required to keep context
		this._mouseMoveDelegate = function(e) {
			return self.mouseMove(e);
		};
		this._mouseUpDelegate = function(e) {
			return self.mouseUp(e);
		};
		$(document)
			.bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
			.bind('mouseup.'+this.widgetName, this._mouseUpDelegate);

		return false;
	},

	mouseMove: function(e) {
		// IE mouseup check - mouseup happened when mouse was out of window
		if ($.browser.msie && !e.button) {
			return this.mouseUp(e);
		}

		if (this._mouseStarted) {
			this.mouseDrag(e);
			return false;
		}

		if (this.mouseDistanceMet(e) && this.mouseDelayMet(e)) {
			this._mouseStarted =
				(this.mouseStart(this._mouseDownEvent, e) !== false);
			(this._mouseStarted || this.mouseUp(e));
		}

		return !this._mouseStarted;
	},

	mouseUp: function(e) {
		$(document)
			.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
			.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);

		if (this._mouseStarted) {
			this._mouseStarted = false;
			this.mouseStop(e);
		}

		return false;
	},

	mouseDistanceMet: function(e) {
		return (Math.max(
				Math.abs(this._mouseDownEvent.pageX - e.pageX),
				Math.abs(this._mouseDownEvent.pageY - e.pageY)
			) >= this.options.distance
		);
	},

	mouseDelayMet: function(e) {
		return this._mouseDelayMet;
	},

	// These are placeholder methods, to be overriden by extending plugin
	mouseStart: function(e) {},
	mouseDrag: function(e) {},
	mouseStop: function(e) {}
};

$.ui.mouse.defaults = {
	cancel: null,
	distance: 1,
	delay: 0
};

})(jQuery);<commit_msg>Mouse: invoke drag immediately on start if the start occurs during mousemove..<commit_after>/*
 * jQuery UI @VERSION
 *
 * Copyright (c) 2008 Paul Bakaus (ui.jquery.com)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI
 *
 * $Id: ui.core.js 5587 2008-05-13 19:56:42Z scott.gonzalez $
 */
;(function($) {

$.ui = {
	plugin: {
		add: function(module, option, set) {
			var proto = $.ui[module].prototype;
			for(var i in set) {
				proto.plugins[i] = proto.plugins[i] || [];
				proto.plugins[i].push([option, set[i]]);
			}
		},
		call: function(instance, name, args) {
			var set = instance.plugins[name];
			if(!set) { return; }

			for (var i = 0; i < set.length; i++) {
				if (instance.options[set[i][0]]) {
					set[i][1].apply(instance.element, args);
				}
			}
		}
	},
	cssCache: {},
	css: function(name) {
		if ($.ui.cssCache[name]) { return $.ui.cssCache[name]; }
		var tmp = $('
').addClass(name).css({position:'absolute', top:'-5000px', left:'-5000px', display:'block'}).appendTo('body');

		//if (!$.browser.safari)
			//tmp.appendTo('body');

		//Opera and Safari set width and height to 0px instead of auto
		//Safari returns rgba(0,0,0,0) when bgcolor is not set
		$.ui.cssCache[name] = !!(
			(!(/auto|default/).test(tmp.css('cursor')) || (/^[1-9]/).test(tmp.css('height')) || (/^[1-9]/).test(tmp.css('width')) ||
			!(/none/).test(tmp.css('backgroundImage')) || !(/transparent|rgba\(0, 0, 0, 0\)/).test(tmp.css('backgroundColor')))
		);
		try { $('body').get(0).removeChild(tmp.get(0));	} catch(e){}
		return $.ui.cssCache[name];
	},
	disableSelection: function(e) {
		e.unselectable = "on";
		e.onselectstart = function() { return false; };
		if (e.style) { e.style.MozUserSelect = "none"; }
	},
	enableSelection: function(e) {
		e.unselectable = "off";
		e.onselectstart = function() { return true; };
		if (e.style) { e.style.MozUserSelect = ""; }
	},
	hasScroll: function(e, a) {
		var scroll = /top/.test(a||"top") ? 'scrollTop' : 'scrollLeft', has = false;
		if (e[scroll] > 0) return true; e[scroll] = 1;
		has = e[scroll] > 0 ? true : false; e[scroll] = 0;
		return has;
	}
};

/** jQuery core modifications and additions **/

var _remove = $.fn.remove;
$.fn.remove = function() {
	$("*", this).add(this).trigger("remove");
	return _remove.apply(this, arguments );
};

// $.widget is a factory to create jQuery plugins
// taking some boilerplate code out of the plugin code
// created by Scott Gonz�lez and J�rn Zaefferer
function getter(namespace, plugin, method) {
	var methods = $[namespace][plugin].getter || [];
	methods = (typeof methods == "string" ? methods.split(/,?\s+/) : methods);
	return ($.inArray(method, methods) != -1);
}

var widgetPrototype = {
	init: function() {},
	destroy: function() {
		this.element.removeData(this.widgetName);
	},

	getData: function(key) {
		return this.options[key];
	},
	setData: function(key, value) {
		this.options[key] = value;
	},

	enable: function() {
		this.setData('disabled', false);
	},
	disable: function() {
		this.setData('disabled', true);
	}
};

$.widget = function(name, prototype) {
	var namespace = name.split(".")[0];
	name = name.split(".")[1];
	// create plugin method
	$.fn[name] = function(options) {
		var isMethodCall = (typeof options == 'string'),
			args = Array.prototype.slice.call(arguments, 1);

		if (isMethodCall && getter(namespace, name, options)) {
			var instance = $.data(this[0], name);
			return (instance ? instance[options].apply(instance, args)
				: undefined);
		}

		return this.each(function() {
			var instance = $.data(this, name);
			if (!instance) {
				$.data(this, name, new $[namespace][name](this, options));
			} else if (isMethodCall) {
				instance[options].apply(instance, args);
			}
		});
	};

	// create widget constructor
	$[namespace][name] = function(element, options) {
		var self = this;

		this.widgetName = name;

		this.options = $.extend({}, $[namespace][name].defaults, options);
		this.element = $(element)
			.bind('setData.' + name, function(e, key, value) {
				return self.setData(key, value);
			})
			.bind('getData.' + name, function(e, key) {
				return self.getData(key);
			})
			.bind('remove', function() {
				return self.destroy();
			});
		this.init();
	};

	// add widget prototype
	$[namespace][name].prototype = $.extend({}, widgetPrototype, prototype);
};

/** Mouse Interaction Plugin **/

$.ui.mouse = {
	mouseInit: function() {
		var self = this;

		this.element.bind('mousedown.'+this.widgetName, function(e) {
			return self.mouseDown(e);
		});

		// Prevent text selection in IE
		if ($.browser.msie) {
			this._mouseUnselectable = this.element.attr('unselectable');
			this.element.attr('unselectable', 'on');
		}

		this.started = false;
	},

	// TODO: make sure destroying one instance of mouse doesn't mess with
	// other instances of mouse
	mouseDestroy: function() {
		this.element.unbind('.'+this.widgetName);

		// Restore text selection in IE
		($.browser.msie
			&& this.element.attr('unselectable', this._mouseUnselectable));
	},

	mouseDown: function(e) {
		// we may have missed mouseup (out of window)
		(this._mouseStarted && this.mouseUp(e));

		this._mouseDownEvent = e;

		var self = this,
			btnIsLeft = (e.which == 1),
			elIsCancel = ($(e.target).is(this.options.cancel));
		if (!btnIsLeft || elIsCancel) {
			return true;
		}

		this._mouseDelayMet = !this.options.delay;
		if (!this._mouseDelayMet) {
			this._mouseDelayTimer = setTimeout(function() {
				self._mouseDelayMet = true;
			}, this.options.delay);
		}

		if (this.mouseDistanceMet(e) && this.mouseDelayMet(e)) {
			this._mouseStarted = (this.mouseStart(e) !== false);
			if (!this._mouseStarted) { return false; }
		}

		// these delegates are required to keep context
		this._mouseMoveDelegate = function(e) {
			return self.mouseMove(e);
		};
		this._mouseUpDelegate = function(e) {
			return self.mouseUp(e);
		};
		$(document)
			.bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
			.bind('mouseup.'+this.widgetName, this._mouseUpDelegate);

		return false;
	},

	mouseMove: function(e) {
		// IE mouseup check - mouseup happened when mouse was out of window
		if ($.browser.msie && !e.button) {
			return this.mouseUp(e);
		}

		if (this._mouseStarted) {
			this.mouseDrag(e);
			return false;
		}

		if (this.mouseDistanceMet(e) && this.mouseDelayMet(e)) {
			this._mouseStarted =
				(this.mouseStart(this._mouseDownEvent, e) !== false);
			(this._mouseStarted ? this.mouseDrag(e) : this.mouseUp(e));
		}

		return !this._mouseStarted;
	},

	mouseUp: function(e) {
		$(document)
			.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
			.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);

		if (this._mouseStarted) {
			this._mouseStarted = false;
			this.mouseStop(e);
		}

		return false;
	},

	mouseDistanceMet: function(e) {
		return (Math.max(
				Math.abs(this._mouseDownEvent.pageX - e.pageX),
				Math.abs(this._mouseDownEvent.pageY - e.pageY)
			) >= this.options.distance
		);
	},

	mouseDelayMet: function(e) {
		return this._mouseDelayMet;
	},

	// These are placeholder methods, to be overriden by extending plugin
	mouseStart: function(e) {},
	mouseDrag: function(e) {},
	mouseStop: function(e) {}
};

$.ui.mouse.defaults = {
	cancel: null,
	distance: 1,
	delay: 0
};

})(jQuery);<|endoftext|>