<commit_before>import _ from 'lodash'

const calculalateX = x => {
	const midway = 89
	const diff = x - midway
	return midway - diff
}

const calculalateY = y => {
	const midway = 42.5
	const diff = y - midway
	return midway - diff
}

const calculateDistance = (x, y) => Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)).toFixed(2)

const getLatestDate = (rows) => {
	const sorted = rows.sort((a, b) => (+a.gameDate) - (+b.gameDate))
	const str = sorted[sorted.length - 1].gameDate
	const year = +str.substring(0, 4)
	const month = +str.substring(4, 6) - 1
	const day = +str.substring(6, 8)

	const d = new Date(year, month, day)
	const dateString = d.toDateString()
	const split = dateString.split(' ')
	const output = `${split[1]}. ${+split[2]}`
	return output
}

const clean = (data) => {
	// the row is column definitions, so must get season from second row
	const season = data[1].season
	return _.filter(data, d => d.season === season)
}

const createShotObj = (datum) => {
	// everything we (might) need
	const season = datum.season
	const gameDate = datum.gamedate
	const teamLocation = datum['team-location']
	const teamNickname = datum['team-nickname']
	const opponentLocation = datum['opponent-location']
	const opponentNickname = datum['opponent-nickname']
	const home = datum['home-away'] === 'home'
	const period = +datum.period
	const time = datum.time
	const player = datum.player
	const x = calculalateX(datum['player-x'])
	const y = calculalateY(datum['player-y'])
	const powerPlay = datum.strength.toLowerCase() !== 'even'
	const distance = +calculateDistance(x, y)
	const made = datum.event.toLowerCase().indexOf('shot') < 0

	return {
		season,
		gameDate,
		teamLocation,
		teamNickname,
		opponentLocation,
		opponentNickname,
		home,
		period,
		time,
		player,
		x,
		y,
		powerPlay,
		made,
		distance,
	}
}

const transform = (data) => {
	if (data.length) {
		const rows = clean(data).map(createShotObj)
		return { rows }
	}
	return { rows: [] }
}

const hed = ({ rows, filters }) => {
	if (rows.length) {
		// get season
		const seasonStr = rows[0].season.toString()

		// if more than one player, show team name
		const who = filters.player ? filters.player : filters.teamLocation
		const when = `${seasonStr.substring(0, 4)}-${seasonStr.substring(4, 6)}`

		return `${who} ${when}`
	}

	return ''
}

const subhed = ({ rows }) => {
	if (rows.length) {
		const date = getLatestDate(rows)
		return `All shots through ${date}`
	}

	return ''
}

const trimData = (data) => ({
	...data,
	rows: data.rows.map(row =>
		_.pick(row, [
			'gameDate',
			'x',
			'y',
			'made',
		])),
})

export default {
	transform,
	hed,
	subhed,
	trimData,
}
<commit_msg>hockey hed<commit_after>import _ from 'lodash'

const calculalateX = x => {
	const midway = 89
	const diff = x - midway
	return midway - diff
}

const calculalateY = y => {
	const midway = 42.5
	const diff = y - midway
	return midway - diff
}

const calculateDistance = (x, y) => Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)).toFixed(2)

const getLatestDate = (rows) => {
	const sorted = rows.sort((a, b) => (+a.gameDate) - (+b.gameDate))
	const str = sorted[sorted.length - 1].gameDate
	const year = +str.substring(0, 4)
	const month = +str.substring(4, 6) - 1
	const day = +str.substring(6, 8)

	const d = new Date(year, month, day)
	const dateString = d.toDateString()
	const split = dateString.split(' ')
	const output = `${split[1]}. ${+split[2]}`
	return output
}

const clean = (data) => {
	// the row is column definitions, so must get season from second row
	const season = data[1].season
	return _.filter(data, d => d.season === season)
}

const createShotObj = (datum) => {
	// everything we (might) need
	const season = datum.season
	const gameDate = datum.gamedate
	const teamLocation = datum['team-location']
	const teamNickname = datum['team-nickname']
	const opponentLocation = datum['opponent-location']
	const opponentNickname = datum['opponent-nickname']
	const home = datum['home-away'] === 'home'
	const period = +datum.period
	const time = datum.time
	const player = datum.player
	const x = calculalateX(datum['player-x'])
	const y = calculalateY(datum['player-y'])
	const powerPlay = datum.strength.toLowerCase() !== 'even'
	const distance = +calculateDistance(x, y)
	const made = datum.event.toLowerCase().indexOf('shot') < 0

	return {
		season,
		gameDate,
		teamLocation,
		teamNickname,
		opponentLocation,
		opponentNickname,
		home,
		period,
		time,
		player,
		x,
		y,
		powerPlay,
		made,
		distance,
	}
}

const transform = (data) => {
	if (data.length) {
		const rows = clean(data).map(createShotObj)
		return { rows }
	}
	return { rows: [] }
}

const hed = ({ rows, filters }) => {
	if (rows.length) {
		// get season
		const seasonStr = rows[0].season.toString()

		// if more than one player, show team name
		const who = filters.player ? filters.player : filters.teamNickname
		const when = `${seasonStr.substring(0, 4)}-${seasonStr.substring(4, 6)}`

		return `${who} ${when}`
	}

	return ''
}

const subhed = ({ rows }) => {
	if (rows.length) {
		const date = getLatestDate(rows)
		return `All shots through ${date}`
	}

	return ''
}

const trimData = (data) => ({
	...data,
	rows: data.rows.map(row =>
		_.pick(row, [
			'gameDate',
			'x',
			'y',
			'made',
		])),
})

export default {
	transform,
	hed,
	subhed,
	trimData,
}
<|endoftext|>


<commit_before>function _debug(msg){
    DEBUG && console.log(msg);
}

function callBotAPI(newmsg,callback) {
   //YQL for cross region json ajax
    var yql_url = 'https://query.yahooapis.com/v1/public/yql';
    var url = 'http://www.tuling123.com/openapi/api?key=abab9d3783d6e367e71e56c721e3165a&info='+encodeURIComponent(newmsg);
    $.ajax({
        'url': yql_url,
        'data': {
              'q': 'SELECT * FROM json WHERE url="'+url+'"',
                   'format': 'json',
                   'jsonCompat': 'new',
        },
        'dataType': 'jsonp',
        'success': function(response) {
            ans = response.query.results.json.text;
            callback(ans,arguments[1]);
        },
        'error': function() {
            _debug("ajax error occured");
        }
    });
}

function sendmsg(ans,name) {
    _debug("bot resp with: " + ans);
    $('#textInput')[0].value=ans;
    $('.chatSend')[0].click();
    _debug("msg sent to: " + name);
    $(activechat).click();
}

function installbot(chats){
    //add bot notes
    for (i=0;i<chats.length;i++) {
        var bot = document.createElement('div');
        bot.className='bot';
        chats[i].appendChild(bot);
    }

    //listen on bot click event
    $(chats).children('.bot').click(function(e){
        e.stopPropagation();
        $(this).toggleClass('active');
        typeof(observer) !== 'undefined' && observer.disconnect();
    });

    //add drag drop event to bot div
    jQuery.event.props.push('dataTransfer');
    $(chats).on({
        dragenter: function(e) {
            $(this).addClass('over');
        },
        dragover: function(e) {
            e.preventDefault();
        },
        dragleave: function(e) {
            $(this).removeClass('over');
        },
        drop: function() {
            $(this).find('.bot').toggleClass('active');
        },
    });

    _debug("installed "+chats.length.toString()+" bots");
}

function buildbothome() {
    //add bot home
    $('#profile').append([
    '
',
        '',
        '',
            '
快把我拖到聊天列表上
',
            '
点击我可以 暂停/恢复
',
        '',
        '',
    '
'].join(''));

    //show tooltip for 10s
    $('img.botico').load(function(){
        $('#bot-wrapper span').show();
        setTimeout(function(){
            $('#bot-wrapper span').hide();
        },10*1000);
    });

    //clear tooltip on mousedown
    $('.bot-home').mousedown(function(){
        $('#bot-wrapper span').hide();
    });

    //listen on bot-home click event
    $('.bot-home').click(function(){
        $('.bot-home').toggleClass('paused');
    });

    //listen drag event
    jQuery.event.props.push('dataTransfer');
    $('.bot-home').on({
        dragstart: function() {
            $(this).css('opacity', '0.5');
            $('.chatListColumn *').css('pointer-events','none');
        },
        dragend: function() {
            $(this).css('opacity', '1');
            $('.chatListColumn *').css('pointer-events','');
            $('.chatListColumn').removeClass('over');
        },
    });
    _debug("bot home constructed");
}

function botinit(){
    $(document).ready(function(){
        //init bot-home
        buildbothome();
        //init bot divs
        var bots = $(['.chatListColumn',
            ':not([un="newsapp"])',
            ':not([un="filehelper"])',
            ':not([un="fmessage"])',
            ':not(".loadMoreConv")'].join(''))
        installbot(bots);
        _debug(bots.length.toString()+" bots initiated");
        //set chat list update listener
        setupdater();
    });
}

function botstart(interval){
    interval = typeof interval !== 'undefined' ? interval : 30;
    setInterval(chatbot,interval*1000);
    _debug("started with interval: "+interval.toString()+"s");
}

function botupdate(){
    uninstalled = $(['.chatListColumn',
            ':not([un="newsapp"])',
            ':not([un="filehelper"])',
            ':not([un="fmessage"])',
            ':not(".loadMoreConv")',
            ':not(:has(".bot"))'].join(''));
    uninstalled.length && installbot(uninstalled);
}

function setupdater() {
    updater = new MutationObserver(function(mutations) {
        botupdate();
    });
    updater.observe($('#conversationContainer')[0], { childList: true});
    _debug('updater set');
}

function chatbot() {
    //if paused
    if ( $('.bot-home').hasClass('paused') ) return;
    //if no bot-took-overed
    if ( $('.bot.active').length == 0 )  return;

    //record current chat
    activechat = $('.activeColumn');

    //reply in current chat window
    typeof(observer) !== 'undefined' && observer.disconnect();
    if ( $('.activeColumn:has(".bot.active")').length ) {
        observer = new MutationObserver(function(mutations) {
            var m = mutations.pop();
            console.log(m);
            var newnode = m.addedNodes[1];
            if (newnode.className == 'chatItem you') {
                var newmsg = $(newnode).find('pre').text();
                var name = $(activechat).find('.left.name').text();
                _debug("msg from: " + name);
                if (newmsg) {
                    _debug("msg content: " + newmsg);
                    callBotAPI(newmsg,sendmsg,name);
                } else {
                    _debug("no msg found");
                }
            }
        });
        observer.observe($('#chat_chatmsglist')[0], { childList: true});
    }

    //reply for red dotted item
    $('.unreadDot:visible, .unreadDotS:visible').each(function()
    {
        is_active = $(this).parent().find(".bot").hasClass("active");
        if (!is_active) return;
        var name = $(this).parent().find(".left.name").text();
        _debug("msg from: " + name);

        $(this).parent().click();
        //Wait till chat box loaded
        setTimeout(function(){
            newmsg = $("#chat_chatmsglist").children(".chatItem.you").last().find("pre").text();
            if (newmsg) {
                _debug("msg content: " + newmsg);
                callBotAPI(newmsg,sendmsg,name);
            } else {
                _debug("no msg found");
            }
        },500);
    });
}

DEBUG = true;
botinit();
botstart(runtimeGlobal.interval);
botupdate();
<commit_msg>for debug<commit_after>function _debug(msg){
    DEBUG && console.log(msg);
}

function callBotAPI(newmsg,callback,activechat) {
   //YQL for cross region json ajax
    var yql_url = 'https://query.yahooapis.com/v1/public/yql';
    var url = 'http://www.tuling123.com/openapi/api?key=abab9d3783d6e367e71e56c721e3165a&info='+encodeURIComponent(newmsg);
    $.ajax({
        'url': yql_url,
        'data': {
              'q': 'SELECT * FROM json WHERE url="'+url+'"',
                   'format': 'json',
                   'jsonCompat': 'new',
        },
        'dataType': 'jsonp',
        'success': function(response) {
            ans = response.query.results.json.text;
            callback(ans,activechat);
        },
        'error': function() {
            _debug("ajax error occured");
        }
    });
}

function sendmsg(ans,jumpback){
    _debug("bot resp with: " + ans);
    $('#textInput')[0].value=ans;
    $('.chatSend')[0].click();
    $(jumpback).click();
}

function installbot(chats){
    //add bot notes
    for (i=0;i<chats.length;i++) {
        var bot = document.createElement('div');
        bot.className='bot';
        chats[i].appendChild(bot);
    }

    //listen on bot click event
    $(chats).children('.bot').click(function(e){
        e.stopPropagation();
        $(this).toggleClass('active');
        typeof(observer) !== 'undefined' && observer.disconnect();
    });

    //add drag drop event to bot div
    jQuery.event.props.push('dataTransfer');
    $(chats).on({
        dragenter: function(e) {
            $(this).addClass('over');
        },
        dragover: function(e) {
            e.preventDefault();
        },
        dragleave: function(e) {
            $(this).removeClass('over');
        },
        drop: function() {
            $(this).find('.bot').toggleClass('active');
        },
    });

    _debug("installed "+chats.length.toString()+" bots");
}

function buildbothome() {
    //add bot home
    $('#profile').append([
    '
',
        '',
        '',
            '
快把我拖到聊天列表上
',
            '
点击我可以 暂停/恢复
',
        '',
        '',
    '
'].join(''));

    //show tooltip for 10s
    $('img.botico').load(function(){
        $('#bot-wrapper span').show();
        setTimeout(function(){
            $('#bot-wrapper span').hide();
        },10*1000);
    });

    //clear tooltip on mousedown
    $('.bot-home').mousedown(function(){
        $('#bot-wrapper span').hide();
    });

    //listen on bot-home click event
    $('.bot-home').click(function(){
        $('.bot-home').toggleClass('paused');
    });

    //listen drag event
    jQuery.event.props.push('dataTransfer');
    $('.bot-home').on({
        dragstart: function() {
            $(this).css('opacity', '0.5');
            $('.chatListColumn *').css('pointer-events','none');
        },
        dragend: function() {
            $(this).css('opacity', '1');
            $('.chatListColumn *').css('pointer-events','');
            $('.chatListColumn').removeClass('over');
        },
    });
    _debug("bot home constructed");
}

function botinit(){
    $(document).ready(function(){
        //init bot-home
        buildbothome();
        //init bot divs
        var bots = $(['.chatListColumn',
            ':not([un="newsapp"])',
            ':not([un="filehelper"])',
            ':not([un="fmessage"])',
            ':not(".loadMoreConv")'].join(''))
        installbot(bots);
        _debug(bots.length.toString()+" bots initiated");
        //set chat list update listener
        setupdater();
    });
}

function botstart(interval){
    interval = typeof interval !== 'undefined' ? interval : 30;
    setInterval(chatbot,interval*1000);
    _debug("started with interval: "+interval.toString()+"s");
}

function botupdate(){
    uninstalled = $(['.chatListColumn',
            ':not([un="newsapp"])',
            ':not([un="filehelper"])',
            ':not([un="fmessage"])',
            ':not(".loadMoreConv")',
            ':not(:has(".bot"))'].join(''));
    uninstalled.length && installbot(uninstalled);
}

function setupdater() {
    updater = new MutationObserver(function(mutations) {
        botupdate();
    });
    updater.observe($('#conversationContainer')[0], { childList: true});
    _debug('updater set');
}

function chatbot() {
    //if paused
    if ( $('.bot-home').hasClass('paused') ) return;
    //if no bot-took-overed
    if ( $('.bot.active').length == 0 )  return;

    //record current chat
    var activechat = $('.activeColumn');

    //reply in current chat window
    typeof(observer) !== 'undefined' && observer.disconnect();
    if ( $('.activeColumn:has(".bot.active")').length ) {
        observer = new MutationObserver(function(mutations) {
            var m = mutations.pop();
            console.log(m);
            var newnode = m.addedNodes[1];
            if (newnode.className == 'chatItem you') {
                var newmsg = $(newnode).find('pre').text();
                var name = $(activechat).find('.left.name').text();
                _debug("msg from: " + name);
                if (newmsg) {
                    _debug("msg content: " + newmsg);
                    callBotAPI(newmsg,sendmsg,activechat);
                } else {
                    _debug("no msg found");
                }
            }
        });
        observer.observe($('#chat_chatmsglist')[0], { childList: true});
    }

    //reply for red dotted item
    $('.unreadDot:visible, .unreadDotS:visible').each(function()
    {
        is_active = $(this).parent().find(".bot").hasClass("active");
        if (!is_active) return;
        var name = $(this).parent().find(".left.name").text();
        _debug("msg from: " + name);

        $(this).parent().click();
        //Wait till chat box loaded
        setTimeout(function(){
            newmsg = $("#chat_chatmsglist").children(".chatItem.you").last().find("pre").text();
            if (newmsg) {
                _debug("msg content: " + newmsg);
                callBotAPI(newmsg,sendmsg,activechat);
            } else {
                _debug("no msg found");
            }
        },500);
    });
}

DEBUG = true;
botinit();
botstart(runtimeGlobal.interval);
botupdate();
<|endoftext|>


<commit_before>'use strict';

var React = require('react/addons');
var ReactTransitionGroup = React.addons.TransitionGroup;

require('normalize.css');
require('../styles/main.css');

var GeoList = require('components/GeoList');
var GeoDetails = require('components/GeoDetails');
var GeoApp = React.createClass({
    render: function () {
        return (

 Картографический сервис

            <GeoList categories={[]}/>
            <GeoDetails />

        );
    }
});

module.exports = GeoApp;
<commit_msg>remove unused transition group<commit_after>'use strict';

var React = require('react/addons');

require('normalize.css');
require('../styles/main.css');

var GeoList = require('components/GeoList');
var GeoDetails = require('components/GeoDetails');
var GeoApp = React.createClass({
    render: function () {
        return (

 Картографический сервис

            <GeoList categories={[]}/>
            <GeoDetails />

        );
    }
});

module.exports = GeoApp;
<|endoftext|>


<commit_before><commit_msg>fall back to the ejs that is included in pirouette-toolchain<commit_after><|endoftext|>


<commit_before>/**
 * Created by epotignano on 19/02/16.
 */

import React, {PropTypes} from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Dropdown, Text, Icon, Menu, Item } from "react-semantify";
import { Link } from "react-router";
import UserAvatar  from './userAvatar';
import Firebase from 'firebase';
import { FireRef, UidRef } from '../constants/Commons';
import { logoutUser } from "../actions/AuthActions";

var uid = localStorage.getItem(UidRef);
function generateRightMenu(isAuthenticated, router, store) {
  if(isAuthenticated) {
    return(
    <UserAvatar className="item"/>

New audition

 {
          e.preventDefault();
          store.dispatch(logoutUser());
        }}>Logout

    )
  }

  return(
      <Link className="item" to="/access/login">ログイン</Link>
  )
}

class Header extends React.Component {
  componentDidMount() {
    $('.ui.dropdown.item').dropdown();
  }

  componentDidUpdate() {
    $('.ui.dropdown.item').dropdown('refresh')
  }
  render() {
    const instance = new Firebase(FireRef);
    const { router, store } = this.context;
    return (

            { generateRightMenu(instance.getAuth(), router, store) }

                <input className="prompt" type="text" placeholder="Search..."/>

    )
  }
}

Header.contextTypes = {
  router: PropTypes.any,
  store: React.PropTypes.any
};

var mapStateProps = function(state) {
  const { auth, user } = state;

  return  {
    auth,user
  }
};

var mapDispatchToProps = function(dispatch){
  console.log(dispatch);
  return {
    logout: function(){ dispatch(logoutUser()); }
  }
};

export default connect(mapStateProps, mapDispatchToProps)(Header);
<commit_msg>remove link to login in principal page, access to login /access/login<commit_after>/**
 * Created by epotignano on 19/02/16.
 */

import React, {PropTypes} from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Dropdown, Text, Icon, Menu, Item } from "react-semantify";
import { Link } from "react-router";
import UserAvatar  from './userAvatar';
import Firebase from 'firebase';
import { FireRef, UidRef } from '../constants/Commons';
import { logoutUser } from "../actions/AuthActions";

var uid = localStorage.getItem(UidRef);
function generateRightMenu(isAuthenticated, router, store) {
  if(isAuthenticated) {
    return(
    <UserAvatar className="item"/>

New audition

 {
          e.preventDefault();
          store.dispatch(logoutUser());
        }}>Logout

    )
  }

  return(
      <Link className="item" to="/access/login">ログイン</Link>
  )
}

class Header extends React.Component {
  componentDidMount() {
    $('.ui.dropdown.item').dropdown();
  }

  componentDidUpdate() {
    $('.ui.dropdown.item').dropdown('refresh')
  }
  render() {
    const instance = new Firebase(FireRef);
    const { router, store } = this.context;
    return (

            title of webpage

                <input className="prompt" type="text" placeholder="Search..."/>

    )
  }
}

Header.contextTypes = {
  router: PropTypes.any,
  store: React.PropTypes.any
};

var mapStateProps = function(state) {
  const { auth, user } = state;

  return  {
    auth,user
  }
};

var mapDispatchToProps = function(dispatch){
  console.log(dispatch);
  return {
    logout: function(){ dispatch(logoutUser()); }
  }
};

export default connect(mapStateProps, mapDispatchToProps)(Header);
<|endoftext|>


<commit_before>var frameCount = 0;
var currentValue = 0;

var randomArray;

var randomResultArray = new Array();

function init(){
}

function update(){

    randomArray = new Array();

    for(var i = 0 ; i < 1000; i ++){
        var random = GetRandom11();
        currentValue += random;
        randomArray.push(currentValue);
    }

    randomResultArray.push(currentValue);

    context.clearRect(0, 0, canvas.width, canvas.height);

    context.beginPath();

    context.lineWidth = 1;

    context.strokeStyle = "black";
    context.fillStyle = "black";

    for(var i = 0 ; i < randomArray.length && i < canvas.width ; i ++){
        context.rect(i + 50, canvas.height / 2 - randomArray[i], 1, 1);
    }

    context.textAlign = "start"
    context.fillStyle = "#369"
    context.font = "12px 微软雅黑";

    context.moveTo(0,canvas.height / 2);
    context.lineTo(canvas.width, canvas.height / 2);

    context.fillText(randomArray[randomArray.length - 1], 30, canvas.height / 2 - currentValue);
    context.fillText("Steps = " + frameCount, 30 , 30);

    context.fill();
    context.stroke();

    currentValue = 0;

    frameCount ++;
}

function GetRandom11(){
    if(Math.random() < 0.5){
        return -1;
    } else {
        return 1;
    }
}
<commit_msg>update<commit_after>var frameCount = 0;
var currentValue = 0;

var randomArray;

var randomResultArray;

function init(){
    randomResultArray = new Array();
    for(var i = -100 ; i < 100; i ++){
        randomResultArray[i] = 0;
    }
}

function update(){

    randomArray = new Array();

    for(var i = 0 ; i < 1000; i ++){
        var random = GetRandom11();
        currentValue += random;
        randomArray.push(currentValue);
    }

    if(currentValue > -100 && currentValue < 100){
        randomResultArray[currentValue] ++;
    }

    context.clearRect(0, 0, canvas.width, canvas.height);

    context.beginPath();

    context.lineWidth = 1;

    context.strokeStyle = "black";
    context.fillStyle = "black";

    for(var i = 0 ; i < randomArray.length && i < canvas.width ; i ++){
        context.rect(i + 50, canvas.height / 2 - randomArray[i], 1, 1);
    }

    for(var i = -100 ; i < 100 ; i ++){
        if(randomResultArray[i] > 0){
            context.moveTo(1000, i);
            context.lineTo(1000 + randomResultArray[i], i);
        }
    }

    context.textAlign = "start"
    context.fillStyle = "#369"
    context.font = "12px 微软雅黑";

    context.moveTo(0,canvas.height / 2);
    context.lineTo(canvas.width, canvas.height / 2);

    context.fillText(randomArray[randomArray.length - 1], 30, canvas.height / 2 - currentValue);
    context.fillText("Steps = " + frameCount, 30 , 30);

    context.fill();
    context.stroke();

    currentValue = 0;

    frameCount ++;
}

function GetRandom11(){
    if(Math.random() < 0.5){
        return -1;
    } else {
        return 1;
    }
}
<|endoftext|>


<commit_before>import React from 'react/addons';

import Board       from '../../models/board';
import BoardAction from '../../actions/board';

import Dialog           from '../../components/dialog';
import BackgroundSelect from '../../components/background-select';

/**
 *
 */
export default React.createClass({
    mixins: [ React.addons.PureRenderMixin, React.addons.LinkedStateMixin ],

    propTypes: {
        board: (props) => {
            if(!props.board instanceof Board) throw new Error();
        },
        onDismiss: React.PropTypes.func.isRequired
    },

    submit(event) {
        event.preventDefault();
        return this.props.onDismiss();
    },

    hide() {
        BoardAction.revokeAccessCode({ id: this.props.board.id });
    },

    share() {
        BoardAction.generateAccessCode({ id: this.props.board.id });
    },

    render() {
        let id   = this.props.board.id;
        let code = this.props.board.accessCode;

        let sharedURL = code !== null && code.length > 0
            ? location.host + '/boards/' + id + '/access/' + code + ''
            : '';

        let shareButtonClass = sharedURL.length > 0 ? 'neutral' : 'secondary';
        let shareButtonClick = sharedURL.length > 0 ? this.hide : this.share;

        let shareButton = (
            <button className={`btn-${shareButtonClass}`}
                    onClick={shareButtonClick}>
                { sharedURL.length > 0 ? 'Hide' : 'Share' }
            </button>
        );

        return (
            <Dialog className="dialog-edit-board"
                    onDismiss={this.props.onDismiss}>

                    Share board

                    Shared URL

                        <input name="board-share" placeholder="Shared URL"
                               readOnly={true} value={sharedURL} tabIndex={-1}/>
                        {shareButton}

                    <button className="btn-primary" onClick={this.submit}>
                        Done
                    </button>

            </Dialog>
        );
    }
});
<commit_msg>Change the word 'URL' to 'Link' in share board dialog, for the grandmas<commit_after>import React from 'react/addons';

import Board       from '../../models/board';
import BoardAction from '../../actions/board';

import Dialog           from '../../components/dialog';
import BackgroundSelect from '../../components/background-select';

/**
 *
 */
export default React.createClass({
    mixins: [ React.addons.PureRenderMixin, React.addons.LinkedStateMixin ],

    propTypes: {
        board: (props) => {
            if(!props.board instanceof Board) throw new Error();
        },
        onDismiss: React.PropTypes.func.isRequired
    },

    submit(event) {
        event.preventDefault();
        return this.props.onDismiss();
    },

    hide() {
        BoardAction.revokeAccessCode({ id: this.props.board.id });
    },

    share() {
        BoardAction.generateAccessCode({ id: this.props.board.id });
    },

    render() {
        let id   = this.props.board.id;
        let code = this.props.board.accessCode;

        let sharedURL = code !== null && code.length > 0
            ? location.host + '/boards/' + id + '/access/' + code + ''
            : '';

        let shareButtonClass = sharedURL.length > 0 ? 'neutral' : 'secondary';
        let shareButtonClick = sharedURL.length > 0 ? this.hide : this.share;

        let shareButton = (
            <button className={`btn-${shareButtonClass}`}
                    onClick={shareButtonClick}>
                { sharedURL.length > 0 ? 'Hide' : 'Share' }
            </button>
        );

        return (
            <Dialog className="dialog-edit-board"
                    onDismiss={this.props.onDismiss}>

                    Share board

                    Shared link

                        <input name="board-share" placeholder="Shared link"
                               readOnly={true} value={sharedURL} tabIndex={-1}/>
                        {shareButton}

                    <button className="btn-primary" onClick={this.submit}>
                        Done
                    </button>

            </Dialog>
        );
    }
});
<|endoftext|>


<commit_before>import { getDirection } from '../../intl/locale';

const reHtml = /[&<>]/g;
const htmlEntities = {
  '&': '&',
  '<': '<',
  '>': '>',
};

const observerConfig = {
  attributes: true,
  characterData: false,
  childList: true,
  subtree: true,
  attributeFilter: ['data-l10n-id', 'data-l10n-args', 'data-l10n-bundle']
};

export class LocalizationObserver {
  constructor() {
    this.localizations = new Map();
    this.rootsByLocalization = new WeakMap();
    this.localizationsByRoot = new WeakMap();
    this.observer = new MutationObserver(
      mutations => this.translateMutations(mutations)
    );
  }

  has(name) {
    return this.localizations.has(name);
  }

  get(name) {
    return this.localizations.get(name);
  }

  set(name, value) {
    return this.localizations.set(name, value);
  }

  *[Symbol.iterator]() {
    yield* this.localizations;
  }

  handleEvent() {
    return this.requestLanguages();
  }

  requestLanguages(requestedLangs) {
    const localizations = Array.from(this.localizations.values());
    return Promise.all(
      localizations.map(l10n => l10n.requestLanguages(requestedLangs))
    ).then(
      () => this.translateAllRoots()
    )
  }

  setAttributes(element, id, args) {
    element.setAttribute('data-l10n-id', id);
    if (args) {
      element.setAttribute('data-l10n-args', JSON.stringify(args));
    }
    return element;
  }

  getAttributes(element) {
    return {
      id: element.getAttribute('data-l10n-id'),
      args: JSON.parse(element.getAttribute('data-l10n-args'))
    };
  }

  observeRoot(root, l10n = this.get('main')) {
    this.localizationsByRoot.set(root, l10n);
    if (!this.rootsByLocalization.has(l10n)) {
      this.rootsByLocalization.set(l10n, new Set());
    }
    this.rootsByLocalization.get(l10n).add(root);
    this.observer.observe(root, observerConfig);
  }

  disconnectRoot(root) {
    this.pause();
    this.localizationsByRoot.delete(root);
    for (let [name, l10n] of this.localizations) {
      const roots = this.rootsByLocalization.get(l10n);
      if (roots && roots.has(root)) {
        roots.delete(root);
        if (roots.size === 0) {
          this.delete(name);
          this.rootsByLocalization.delete(l10n);
        }
      }
    }
    this.resume();
  }

  pause() {
    this.observer.disconnect();
  }

  resume() {
    for (let l10n of this.localizations.values()) {
      if (this.rootsByLocalization.has(l10n)) {
        for (let root of this.rootsByLocalization.get(l10n)) {
          this.observer.observe(root, observerConfig)
        }
      }
    }
  }

  translateAllRoots() {
    const localizations = Array.from(this.localizations.values());
    return Promise.all(
      localizations.map(
        l10n => this.translateRoots(l10n)
      )
    );
  }

  translateRoots(l10n) {
    if (!this.rootsByLocalization.has(l10n)) {
      return Promise.resolve();
    }

    const roots = Array.from(this.rootsByLocalization.get(l10n));
    return Promise.all(
      roots.map(root => this.translateRoot(root, l10n))
    );
  }

  translateRoot(root, l10n = this.localizationsByRoot.get(root)) {
    return l10n.interactive.then(bundles => {
      const langs = bundles.map(bundle => bundle.lang);

      function setLangs() {
        const wasLocalizedBefore = root.hasAttribute('langs');

        root.setAttribute('langs', langs.join(' '));
        root.setAttribute('lang', langs[0]);
        root.setAttribute('dir', getDirection(langs[0]));

        if (wasLocalizedBefore) {
          root.dispatchEvent(new CustomEvent('DOMRetranslated', {
            bubbles: false,
            cancelable: false,
          }));
        }
      }

      return this.translateRootContent(root).then(setLangs);
    });
  }

  translateMutations(mutations) {
    const targets = new Set();

    for (let mutation of mutations) {
      switch (mutation.type) {
        case 'attributes':
          targets.add(mutation.target);
          break;
        case 'childList':
          for (let addedNode of mutation.addedNodes) {
            if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
              if (addedNode.childElementCount) {
                this.getTranslatables(addedNode).forEach(
                  targets.add.bind(targets)
                );
              } else {
                if (addedNode.hasAttribute('data-l10n-id')) {
                  targets.add(addedNode);
                }
              }
            }
          }
          break;
      }
    }

    if (targets.size === 0) {
      return;
    }

    this.translateElements(Array.from(targets));
  }

  translateFragment(frag) {
    return this.translateElements(this.getTranslatables(frag));
  }

  translateElements(elements) {
    const elemsByL10n = this.groupElementsByLocalization(elements);
    return this.getElementsTranslation(elemsByL10n).then(
      translations => this.applyTranslations(elemsByL10n, translations)
    );
  }

  applyTranslations(elemsByL10n, translationsByL10n) {
    this.pause();
    for (let [l10n, elems] of elemsByL10n) {
      const translations = translationsByL10n.get(l10n);
      for (let i = 0; i < elems.length; i++) {
        l10n.overlayElement(elems[i], translations[i]);
      }
    }
    this.resume();
  }

  groupElementsByLocalization(elements) {
    return Array.from(elements).reduce(
      (seq, elem) => {
        const l10n = this.getLocalizationForElement(elem);
        const group = (seq.get(l10n) || []).concat(elem);
        return seq.set(l10n, group);
      }, new Map()
    );
  }

  getTranslatables(element) {
    const nodes = Array.from(element.querySelectorAll('[data-l10n-id]'));

    if (typeof element.hasAttribute === 'function' &&
        element.hasAttribute('data-l10n-id')) {
      nodes.push(element);
    }

    return nodes;
  }

  getLocalizationForElement(elem) {
    return this.get(elem.getAttribute('data-l10n-bundle') || 'main');
  }

  getKeysForElements(elems) {
    return elems.map(elem => {
      const id = elem.getAttribute('data-l10n-id');
      const args = elem.getAttribute('data-l10n-args');

      return args ?
        [id, JSON.parse(args.replace(reHtml, match => htmlEntities[match]))] :
        id;
    });
  }

  getElementsTranslation(elemsByL10n) {
    return Promise.all(
      Array.from(elemsByL10n).map(
        ([l10n, elems]) => l10n.formatEntities(this.getKeysForElements(elems))
      )
    ).then(
      translationsList => Array.from(elemsByL10n.keys()).reduce(
        (seq, cur, idx) => seq.set(cur, translationsList[idx]),
        new Map()
      )
    );
  }

}
<commit_msg>Remove unused bundles from this.localizations<commit_after>import { getDirection } from '../../intl/locale';

const reHtml = /[&<>]/g;
const htmlEntities = {
  '&': '&',
  '<': '<',
  '>': '>',
};

const observerConfig = {
  attributes: true,
  characterData: false,
  childList: true,
  subtree: true,
  attributeFilter: ['data-l10n-id', 'data-l10n-args', 'data-l10n-bundle']
};

export class LocalizationObserver {
  constructor() {
    this.localizations = new Map();
    this.rootsByLocalization = new WeakMap();
    this.localizationsByRoot = new WeakMap();
    this.observer = new MutationObserver(
      mutations => this.translateMutations(mutations)
    );
  }

  has(name) {
    return this.localizations.has(name);
  }

  get(name) {
    return this.localizations.get(name);
  }

  set(name, value) {
    return this.localizations.set(name, value);
  }

  *[Symbol.iterator]() {
    yield* this.localizations;
  }

  handleEvent() {
    return this.requestLanguages();
  }

  requestLanguages(requestedLangs) {
    const localizations = Array.from(this.localizations.values());
    return Promise.all(
      localizations.map(l10n => l10n.requestLanguages(requestedLangs))
    ).then(
      () => this.translateAllRoots()
    )
  }

  setAttributes(element, id, args) {
    element.setAttribute('data-l10n-id', id);
    if (args) {
      element.setAttribute('data-l10n-args', JSON.stringify(args));
    }
    return element;
  }

  getAttributes(element) {
    return {
      id: element.getAttribute('data-l10n-id'),
      args: JSON.parse(element.getAttribute('data-l10n-args'))
    };
  }

  observeRoot(root, l10n = this.get('main')) {
    this.localizationsByRoot.set(root, l10n);
    if (!this.rootsByLocalization.has(l10n)) {
      this.rootsByLocalization.set(l10n, new Set());
    }
    this.rootsByLocalization.get(l10n).add(root);
    this.observer.observe(root, observerConfig);
  }

  disconnectRoot(root) {
    this.pause();
    this.localizationsByRoot.delete(root);
    for (let [name, l10n] of this.localizations) {
      const roots = this.rootsByLocalization.get(l10n);
      if (roots && roots.has(root)) {
        roots.delete(root);
        if (roots.size === 0) {
          this.localizations.delete(name);
          this.rootsByLocalization.delete(l10n);
        }
      }
    }
    this.resume();
  }

  pause() {
    this.observer.disconnect();
  }

  resume() {
    for (let l10n of this.localizations.values()) {
      if (this.rootsByLocalization.has(l10n)) {
        for (let root of this.rootsByLocalization.get(l10n)) {
          this.observer.observe(root, observerConfig)
        }
      }
    }
  }

  translateAllRoots() {
    const localizations = Array.from(this.localizations.values());
    return Promise.all(
      localizations.map(
        l10n => this.translateRoots(l10n)
      )
    );
  }

  translateRoots(l10n) {
    if (!this.rootsByLocalization.has(l10n)) {
      return Promise.resolve();
    }

    const roots = Array.from(this.rootsByLocalization.get(l10n));
    return Promise.all(
      roots.map(root => this.translateRoot(root, l10n))
    );
  }

  translateRoot(root, l10n = this.localizationsByRoot.get(root)) {
    return l10n.interactive.then(bundles => {
      const langs = bundles.map(bundle => bundle.lang);

      function setLangs() {
        const wasLocalizedBefore = root.hasAttribute('langs');

        root.setAttribute('langs', langs.join(' '));
        root.setAttribute('lang', langs[0]);
        root.setAttribute('dir', getDirection(langs[0]));

        if (wasLocalizedBefore) {
          root.dispatchEvent(new CustomEvent('DOMRetranslated', {
            bubbles: false,
            cancelable: false,
          }));
        }
      }

      return this.translateRootContent(root).then(setLangs);
    });
  }

  translateMutations(mutations) {
    const targets = new Set();

    for (let mutation of mutations) {
      switch (mutation.type) {
        case 'attributes':
          targets.add(mutation.target);
          break;
        case 'childList':
          for (let addedNode of mutation.addedNodes) {
            if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
              if (addedNode.childElementCount) {
                this.getTranslatables(addedNode).forEach(
                  targets.add.bind(targets)
                );
              } else {
                if (addedNode.hasAttribute('data-l10n-id')) {
                  targets.add(addedNode);
                }
              }
            }
          }
          break;
      }
    }

    if (targets.size === 0) {
      return;
    }

    this.translateElements(Array.from(targets));
  }

  translateFragment(frag) {
    return this.translateElements(this.getTranslatables(frag));
  }

  translateElements(elements) {
    const elemsByL10n = this.groupElementsByLocalization(elements);
    return this.getElementsTranslation(elemsByL10n).then(
      translations => this.applyTranslations(elemsByL10n, translations)
    );
  }

  applyTranslations(elemsByL10n, translationsByL10n) {
    this.pause();
    for (let [l10n, elems] of elemsByL10n) {
      const translations = translationsByL10n.get(l10n);
      for (let i = 0; i < elems.length; i++) {
        l10n.overlayElement(elems[i], translations[i]);
      }
    }
    this.resume();
  }

  groupElementsByLocalization(elements) {
    return Array.from(elements).reduce(
      (seq, elem) => {
        const l10n = this.getLocalizationForElement(elem);
        const group = (seq.get(l10n) || []).concat(elem);
        return seq.set(l10n, group);
      }, new Map()
    );
  }

  getTranslatables(element) {
    const nodes = Array.from(element.querySelectorAll('[data-l10n-id]'));

    if (typeof element.hasAttribute === 'function' &&
        element.hasAttribute('data-l10n-id')) {
      nodes.push(element);
    }

    return nodes;
  }

  getLocalizationForElement(elem) {
    return this.get(elem.getAttribute('data-l10n-bundle') || 'main');
  }

  getKeysForElements(elems) {
    return elems.map(elem => {
      const id = elem.getAttribute('data-l10n-id');
      const args = elem.getAttribute('data-l10n-args');

      return args ?
        [id, JSON.parse(args.replace(reHtml, match => htmlEntities[match]))] :
        id;
    });
  }

  getElementsTranslation(elemsByL10n) {
    return Promise.all(
      Array.from(elemsByL10n).map(
        ([l10n, elems]) => l10n.formatEntities(this.getKeysForElements(elems))
      )
    ).then(
      translationsList => Array.from(elemsByL10n.keys()).reduce(
        (seq, cur, idx) => seq.set(cur, translationsList[idx]),
        new Map()
      )
    );
  }

}
<|endoftext|>


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

import './Player.css';

import Select from './Select.js';
import AnnoText from './AnnoText.js';

import { getLastChunkAtTime } from '../util/chunk';

class VideoWrapper extends Component {
  constructor(props) {
    super(props);
    this.videoElem = null;
  }

  seek(t) {
    if (this.videoElem) {
      this.videoElem.currentTime = t;
    }
  }

  seekRelative(dt) {
    if (this.videoElem) {
      const nt = this.videoElem.currentTime + dt;
      this.videoElem.currentTime = nt >= 0 ? nt : 0;
    }
  }

  play() {
    this.videoElem.play();
  }

  pause() {
    this.videoElem.pause();
  }

  togglePause() {
    if (this.videoElem) {
      if (this.videoElem.paused) {
        this.videoElem.play();
      } else {
        this.videoElem.pause();
      }
    }
  }

  render() {
    const { videoURL, initialTime, onTimeUpdate, onPlaying, onPause, onEnded, onSeeking } = this.props;
    return (
      <video src={videoURL} controls onTimeUpdate={e => { onTimeUpdate(e.target.currentTime); }} onPlaying={onPlaying} onPause={onPause} onEnded={onEnded} onSeeking={onSeeking} ref={(el) => { this.videoElem = el; }} onLoadedMetadata={e => { e.target.currentTime = initialTime ? initialTime : 0; }} />
    );
  }
}

class PlayControls extends Component {
  componentDidMount() {
    document.body.addEventListener('keydown', this.handleKeyDown);
  }

  componentWillUnmount() {
    document.body.removeEventListener('keydown', this.handleKeyDown);
  }

  handleKeyDown = (e) => {
    // Only process event if the target is the body,
    // to avoid messing with typing into input elements, etc.
    // Should we do this instead? e.target.tagName.toUpperCase() === 'INPUT'
    if (e.target !== document.body) {
      return;
    }

    const { onBack, onReplay, onTogglePause, onContinue, onToggleRuby, onToggleHelp } = this.props;

    if (!e.repeat) {
      switch (e.keyCode) {
        case 37: // left arrow
          onBack();
          break;

        case 38: // up arrow
          onReplay();
          break;

        case 32: // space
          onTogglePause();
          e.preventDefault();
          break;

        case 40: // down arrow
          onContinue();
          e.preventDefault();
          break;

        case 70: // F key
          onToggleRuby();
          e.preventDefault();
          break;

        case 72: // H key
          onToggleHelp();
          e.preventDefault();
          break;

        default:
          // ignore
          break;
      }
    }
  }

  render() {
    return null;
    /*
    const { onBack, onReplay, onTogglePause, onContinue } = this.props;
    return (
      <form className="PlayControls">
        <button type="button" onClick={onBack}>Jump Back [A]</button>
        <button type="button" onClick={onReplay}>Replay [R]</button>
        <button type="button" onClick={onTogglePause}>Play/Pause [Space]</button>
        <button type="button" onClick={onContinue}>Continue [Enter]</button>
      </form>
    );
    */
  }
}

const MODE_TITLES = {
  manual: 'Manual',
  listen: 'Listening Test',
  read: 'Reading Test',
};

// Player
export default class Player extends Component {
  constructor(props) {
    super(props);
    this.videoMediaComponent = undefined;
    this.state = {
      textViewPosition: props.video.playbackPosition,
      subtitleMode: props.preferences.subtitleMode, // we just initialize from preference
      autoPaused: false, // are we paused (or have requested pause) for listen/read test?
      subtitleState: null,
    };
    this.videoTime = null;
    this.videoIsPlaying = false;
  }

  componentDidMount() {
    this.props.onNeedSubtitles();

    this.savePlaybackPositionTimer = window.setInterval(this.savePlaybackPosition, 1000);
  }

  componentWillUnmount() {
    if (this.savePlaybackPositionTimer) {
      window.clearInterval(this.savePlaybackPositionTimer);
    }
  }

  componentDidUpdate(prevProps) {
    // Compare object identity since it's immutable
    if (this.props.video.subtitleTracks !== prevProps.video.subtitleTracks) {
      this.props.onNeedSubtitles();
    }
  }

  savePlaybackPosition = () => {
    if ((this.videoTime !== null) && (this.videoTime !== undefined)) {
      this.props.onUpdatePlaybackPosition(this.videoTime);
    }
  };

  handleVideoTimeUpdate = (time) => {
    this.videoTime = time;

    if (this.state.autoPaused) {
      // We're either paused or in the process of pausing for question,
      //  so should ignore this time update.
      return;
    }

    const { video } = this.props;

    let updateTextPosition = true;

    // Determine if we need to auto-pause
    // Is the video playing? Don't want to mis-trigger pause upon seeking
    if (this.videoIsPlaying) {
      // Is there at least one text track?
      if (this.state.subtitleMode === 'manual') {
      } else if (this.state.subtitleMode === 'listen') {
        if (video.subtitleTracks.size >= 1) {
          const firstSubtitleTrack = video.subtitleTracks.first();

          // Look up chunk (if any) before this time change
          const currentChunk = getLastChunkAtTime(firstSubtitleTrack.chunkSet, this.state.textViewPosition);

          if (currentChunk) {
            // Are we passing the time that would trigger a pause?
            const PAUSE_DELAY = 0.3;
            const triggerTime = currentChunk.position.end - PAUSE_DELAY;

            if ((this.state.textViewPosition < triggerTime) && (time >= triggerTime)) {
              updateTextPosition = false;
              this.videoMediaComponent.pause();
              this.setState({
                autoPaused: true,
                subtitleState: {
                  textRevelation: 0,
                }
              });
            }
          }
        }
      } else if (this.state.subtitleMode === 'read') {
        if (video.subtitleTracks.size >= 1) {
          const firstSubtitleTrack = video.subtitleTracks.first();

          // TODO: A better way to do this would be to find the next sub-start even after the current time,
          // and then subtract the pause delay from that. If we are crossing that trigger time, then
          // do the pause (so we don't overshoot).

          // Look up chunk (if any) before this time change
          const currentChunk = getLastChunkAtTime(firstSubtitleTrack.chunkSet, this.state.textViewPosition);
          const newChunk = getLastChunkAtTime(firstSubtitleTrack.chunkSet, time);

          if ((currentChunk !== newChunk) && newChunk) {
            updateTextPosition = false;
            this.videoMediaComponent.pause();
            this.setState({
              autoPaused: true,
              textViewPosition: time,
            });
          }
        }
      } else {
        throw new Error('internal error');
      }
    }

    if (updateTextPosition) {
      this.setState({textViewPosition: time});
    }
  };

  releaseAutoPause = () => {
    this.setState({
      autoPaused: false,
      // Resync displayed text with video time, since they may have gotten
      //  very slightly out of sync if we were paused for question
      textViewPosition: this.videoTime,
    });
  }

  handleVideoPlaying = () => {
    this.videoIsPlaying = true;
  };

  handleVideoPause = () => {
    this.videoIsPlaying = false;
  };

  handleVideoEnded = () => {
    this.videoIsPlaying = false;
  };

  handleVideoSeeking = () => {
    this.videoIsPlaying = false;
    this.releaseAutoPause();
  };

  handleSetSubtitleMode = (mode) => {
    switch (mode) {
      case 'manual':
        this.setState({
          subtitleMode: mode,
          autoPaused: false,
          subtitleState: null,
        });
        break;

      case 'listen':
        this.setState({
          subtitleMode: mode,
          autoPaused: false,
          subtitleState: null,
        });
        break;

      case 'read':
        this.setState({
          subtitleMode: mode,
          autoPaused: false,
          subtitleState: null,
        });
        break;

      default:
        throw new Error('internal error');
    }
    this.props.onSetPreference('subtitleMode', mode);
  }

  handleBack = () => {
    if (this.videoMediaComponent) {
      this.videoMediaComponent.seekRelative(-3.0);
    }
  };

  handleReplay = () => {
    if (this.videoMediaComponent) {
      const firstSubtitleTrack = this.props.video.subtitleTracks.first();
      const currentChunk = getLastChunkAtTime(firstSubtitleTrack.chunkSet, this.state.textViewPosition);

      if (currentChunk) {
        this.videoMediaComponent.seek(currentChunk.position.begin);
        this.videoMediaComponent.play();
      }
    }
  };

  handleTogglePause = () => {
    this.releaseAutoPause();
    if (this.videoMediaComponent) {
      this.videoMediaComponent.togglePause();
    }
  };

  handleContinue = () => {
    switch (this.state.subtitleMode) {
      case 'manual':
        // ignore
        break;

      case 'listen':
        if (this.state.autoPaused) {
          const maxRevelation = this.props.video.subtitleTracks.size;
          const currentRevelation = this.state.subtitleState.textRevelation;

          if (currentRevelation > maxRevelation) {
            throw new Error('internal error');
          } else if (currentRevelation === maxRevelation) {
            // Continue playing video
            this.videoMediaComponent.play();
            this.releaseAutoPause();
            this.setState({
              subtitleState: null,
            });
          } else {
            // Increment state subtitleState.textRevelation
            this.setState(s => ({ subtitleState: { textRevelation: s.subtitleState.textRevelation + 1 }}));
          }
        }
        break;

      case 'read':
        if (this.state.autoPaused) {
          this.videoMediaComponent.play();
          this.releaseAutoPause();
        }
        break;

      default:
        throw new Error('internal error');
    }
  };

  handleToggleRuby = () => {
    const { preferences, onSetPreference } = this.props;
    onSetPreference('showRuby', !preferences.showRuby);
  };

  handleToggleHelp = () => {
    const { preferences, onSetPreference } = this.props;
    onSetPreference('showHelp', !preferences.showHelp);
  };

  handleExit = () => {
    this.savePlaybackPosition();
    this.props.onExit();
  }

  render() {
    const { video } = this.props;

    const REVEAL_MESSAGE = '(press enter to reveal)';
    const HIDDEN_MESSAGE = '(hidden)';
    const LISTEN_MESSAGE = '(listen)';

    return (

            <VideoWrapper videoURL={video.videoURL} initialTime={video.playbackPosition} onTimeUpdate={this.handleVideoTimeUpdate} onPlaying={this.handleVideoPlaying} onPause={this.handleVideoPause} onEnded={this.handleVideoEnded} onSeeking={this.handleVideoSeeking} ref={(c) => { this.videoMediaComponent = c; }} />

              {video.subtitleTracks.valueSeq().map((subTrack, subTrackIdx) => {
                const chunk = subTrack.chunkSet ? getLastChunkAtTime(subTrack.chunkSet, this.state.textViewPosition) : null;

                if (chunk) {
                  return (

                        {(() => {
                          let message; // if set, a message to display instead of subtitle

                          switch (this.state.subtitleMode) {
                            case 'manual':
                              // don't change
                              break;

                            case 'listen':
                              if (this.state.autoPaused) {
                                if (subTrackIdx === this.state.subtitleState.textRevelation) {
                                  message = REVEAL_MESSAGE;
                                } else if (subTrackIdx > this.state.subtitleState.textRevelation) {
                                  message = HIDDEN_MESSAGE;
                                }
                              } else {
                                message = LISTEN_MESSAGE;
                              }
                              break;

                            case 'read':
                              // nothing
                              break;

                            default:
                              throw new Error('internal error');
                          }

                          return (

                              {message ? (

{message}

                              ) : null}

                                <AnnoText key={chunk.uid} annoText={chunk.annoText} language={subTrack.language} showRuby={this.props.preferences.showRuby} />

                          );
                        })()}

                  );
                } else {
                  return null;
                }
              })}

          <PlayControls onBack={this.handleBack} onReplay={this.handleReplay} onTogglePause={this.handleTogglePause} onContinue={this.handleContinue} onToggleRuby={this.handleToggleRuby} onToggleHelp={this.handleToggleHelp} />

        <button className="Player-big-button Player-exit-button" onClick={this.handleExit}>↩</button>

          Subtitle Mode:
          <Select options={Object.entries(MODE_TITLES).map(([k, v]) => ({value: k, label: v}))} onChange={this.handleSetSubtitleMode} value={this.state.subtitleMode} />
          <button onClick={e => { e.preventDefault(); this.handleToggleHelp(); }}>Toggle Help</button>

Keyboard Controls

Replay Sub:	↑

Reveal Sub /
Continue:	↓

Previous Sub:	←

Next Sub:	→

Pause/Unpause:	space

Toggle Furigana:	F

Toggle Help:	H

Mode: {MODE_TITLES[this.state.subtitleMode]}

            {(() => {
              switch (this.state.subtitleMode) {
                case 'manual':
                  return (

Manually toggle the display of each subtitle track using the number keys (e.g. 1 for the first track). To be honest, this mode is pretty boring and you should try the others.

                  );

                case 'listen':
                  return (

Subtitles are initially hidden. At the end of each subtitle, the video will pause automatically. Could you hear what was said? Press ↑ to replay, if necessary. Then press the ↓ key to reveal the subs, and check if you heard correctly. Then press ↓ to unpause the video.

                  );

                case 'read':
                  return (

Read etc

                  );

                default:
                  throw new Error('internal error');
              }
            })()}

    );
  }
}
<commit_msg>Redo sub display logic to make it more robust<commit_after>import React, { Component } from 'react';

import './Player.css';

import Select from './Select.js';
import AnnoText from './AnnoText.js';

import { getLastChunkAtTime } from '../util/chunk';

class VideoWrapper extends Component {
  constructor(props) {
    super(props);
    this.videoElem = null;
  }

  seek(t) {
    if (this.videoElem) {
      this.videoElem.currentTime = t;
    }
  }

  seekRelative(dt) {
    if (this.videoElem) {
      const nt = this.videoElem.currentTime + dt;
      this.videoElem.currentTime = nt >= 0 ? nt : 0;
    }
  }

  play() {
    this.videoElem.play();
  }

  pause() {
    this.videoElem.pause();
  }

  togglePause() {
    if (this.videoElem) {
      if (this.videoElem.paused) {
        this.videoElem.play();
      } else {
        this.videoElem.pause();
      }
    }
  }

  render() {
    const { videoURL, initialTime, onTimeUpdate, onPlaying, onPause, onEnded, onSeeking } = this.props;
    return (
      <video src={videoURL} controls onTimeUpdate={e => { onTimeUpdate(e.target.currentTime); }} onPlaying={onPlaying} onPause={onPause} onEnded={onEnded} onSeeking={onSeeking} ref={(el) => { this.videoElem = el; }} onLoadedMetadata={e => { e.target.currentTime = initialTime ? initialTime : 0; }} />
    );
  }
}

class PlayControls extends Component {
  componentDidMount() {
    document.body.addEventListener('keydown', this.handleKeyDown);
  }

  componentWillUnmount() {
    document.body.removeEventListener('keydown', this.handleKeyDown);
  }

  handleKeyDown = (e) => {
    // Only process event if the target is the body,
    // to avoid messing with typing into input elements, etc.
    // Should we do this instead? e.target.tagName.toUpperCase() === 'INPUT'
    if (e.target !== document.body) {
      return;
    }

    const { onBack, onReplay, onTogglePause, onContinue, onToggleRuby, onToggleHelp } = this.props;

    if (!e.repeat) {
      switch (e.keyCode) {
        case 37: // left arrow
          onBack();
          break;

        case 38: // up arrow
          onReplay();
          break;

        case 32: // space
          onTogglePause();
          e.preventDefault();
          break;

        case 40: // down arrow
          onContinue();
          e.preventDefault();
          break;

        case 70: // F key
          onToggleRuby();
          e.preventDefault();
          break;

        case 72: // H key
          onToggleHelp();
          e.preventDefault();
          break;

        default:
          // ignore
          break;
      }
    }
  }

  render() {
    return null;
    /*
    const { onBack, onReplay, onTogglePause, onContinue } = this.props;
    return (
      <form className="PlayControls">
        <button type="button" onClick={onBack}>Jump Back [A]</button>
        <button type="button" onClick={onReplay}>Replay [R]</button>
        <button type="button" onClick={onTogglePause}>Play/Pause [Space]</button>
        <button type="button" onClick={onContinue}>Continue [Enter]</button>
      </form>
    );
    */
  }
}

const MODE_TITLES = {
  manual: 'Manual',
  listen: 'Listening Test',
  read: 'Reading Test',
};

// Player
export default class Player extends Component {
  constructor(props) {
    super(props);
    this.videoMediaComponent = undefined;

    const subtitleMode = props.preferences.subtitleMode;
    const subtitleState = this.initialSubtitleState(subtitleMode);
    const displayedSubTime = props.video.playbackPosition;
    this.state = {
      subtitleMode, // we just initialize from preference
      subtitleState,
      displayedSubTime,
      displayedSubs: this.getSubsToDisplay(displayedSubTime, subtitleMode, subtitleState),
    };

    this.videoTime = null;
    this.videoIsPlaying = false;
    this.subsFrozen = false;
  }

  componentDidMount() {
    this.props.onNeedSubtitles();

    this.savePlaybackPositionTimer = window.setInterval(this.savePlaybackPosition, 1000);
  }

  componentWillUnmount() {
    if (this.savePlaybackPositionTimer) {
      window.clearInterval(this.savePlaybackPositionTimer);
    }
  }

  componentDidUpdate(prevProps) {
    // Compare object identity since it's immutable
    if (this.props.video.subtitleTracks !== prevProps.video.subtitleTracks) {
      this.props.onNeedSubtitles();
    }
  }

  initialSubtitleState = (mode) => {
    if (mode === 'listen') {
      return { tracksRevealed: 0};
    } else {
      return null;
    }
  };

  getSubsToDisplay = (time, subtitleMode, subtitleState) => {
    const { video } = this.props;

    const result = video.subtitleTracks.valueSeq().toArray().map((subTrack, subTrackIdx) => {
      const chunk = subTrack.chunkSet ? getLastChunkAtTime(subTrack.chunkSet, time) : null;

      return {
        subTrack,
        chunk,
      };
    });

    return result;
  };

  savePlaybackPosition = () => {
    if ((this.videoTime !== null) && (this.videoTime !== undefined)) {
      this.props.onUpdatePlaybackPosition(this.videoTime);
    }
  };

  handleVideoTimeUpdate = (time) => {
    // console.log('time update', time, 'subsFrozen', this.subsFrozen);

    if (this.subsFrozen) {
      // If subs are frozen, we skip all the auto-pause logic and subtitle time updating
      return;
    }

    const { video } = this.props;

    const newDisplayedSubTime = time;
    let newDisplayedSubs = this.getSubsToDisplay(newDisplayedSubTime, this.state.subtitleMode, this.state.subtitleState);
    let updateSubs = true;

    // Determine if we need to auto-pause
    // Is the video playing? Don't want to mis-trigger pause upon seeking
    if (this.videoIsPlaying) {
      // Is there at least one text track?
      if (this.state.subtitleMode === 'manual') {
      } else if (this.state.subtitleMode === 'listen') {
        if (video.subtitleTracks.size >= 1) {
          const currentChunk = this.state.displayedSubs[0].chunk;
          if (currentChunk) {
            // Are we passing the time that would trigger a pause?
            const PAUSE_DELAY = 0.3;
            const triggerTime = currentChunk.position.end - PAUSE_DELAY;

            if ((this.videoTime < triggerTime) && (time >= triggerTime)) {
              this.subsFrozen = true;
              updateSubs = false;
              this.videoMediaComponent.pause();
            }
          }
        }
      } else if (this.state.subtitleMode === 'read') {
        if (video.subtitleTracks.size >= 1) {
          // TODO: A better way to do this would be to find the next sub-start even after the current time,
          // and then subtract the pause delay from that. If we are crossing that trigger time, then
          // do the pause (so we don't overshoot). We would also need to fast-forward the displayedSubTime
          // to the start of the next chunk.

          // Look up chunk (if any) before this time change
          const currentChunk = this.state.displayedSubs[0].chunk;
          const newChunk = newDisplayedSubs[0].chunk;

          if ((currentChunk !== newChunk) && newChunk) {
            this.subsFrozen = true;
            this.videoMediaComponent.pause();
          }
        }
      } else {
        throw new Error('internal error');
      }
    }

    this.videoTime = time;

    if (updateSubs) {
      let newSubtitleState = this.state.subtitleState;

      // Determine if we need to reset revelation in listen mode (whether or not video is playing)
      if (this.state.subtitleMode === 'listen') {
        if (video.subtitleTracks.size >= 1) {
          if (newDisplayedSubs[0].chunk !== this.state.displayedSubs[0].chunk) {
            // Reset subtitle track revelation
            newSubtitleState = {tracksRevealed: 0};
          }
        }
      }

      this.setState({
        displayedSubTime: newDisplayedSubTime,
        displayedSubs: newDisplayedSubs,
        subtitleState: newSubtitleState,
      });
    }
  };

  unfreezeSubs = () => {
    this.subsFrozen = false;
  };

  handleVideoPlaying = () => {
    this.videoIsPlaying = true;
  };

  handleVideoPause = () => {
    this.videoIsPlaying = false;
  };

  handleVideoEnded = () => {
    this.videoIsPlaying = false;
  };

  handleVideoSeeking = () => {
    this.videoIsPlaying = false;
    this.unfreezeSubs();
  };

  handleSetSubtitleMode = (newMode) => {
    this.setState(s => {
      const newSubtitleState = this.initialSubtitleState(newMode);
      return {
        subtitleMode: newMode,
        subtitleState: newSubtitleState,
        displayedSubTime: s.displayedSubTime,
        displayedSubs: this.getSubsToDisplay(s.displayedSubTime, newMode, newSubtitleState),
      };
    });
    this.props.onSetPreference('subtitleMode', newMode);
  }

  handleBack = () => {
    if (this.videoMediaComponent) {
      this.videoMediaComponent.seekRelative(-3.0);
    }
  };

  handleReplay = () => {
    if (this.videoMediaComponent) {
      const currentChunk = (this.state.displayedSubs.length > 0) ? this.state.displayedSubs[0].chunk : null;

      if (currentChunk) {
        this.videoMediaComponent.seek(currentChunk.position.begin);
        this.videoMediaComponent.play();
      }
    }
  };

  handleTogglePause = () => {
    this.unfreezeSubs();
    if (this.videoMediaComponent) {
      this.videoMediaComponent.togglePause();
    }
  };

  handleContinue = () => {
    switch (this.state.subtitleMode) {
      case 'manual':
        // ignore
        break;

      case 'listen':
        const maxRevelation = this.props.video.subtitleTracks.size;
        const currentRevelation = this.state.subtitleState.tracksRevealed;

        if (currentRevelation > maxRevelation) {
          throw new Error('internal error');
        } else if (currentRevelation === maxRevelation) {
          // Continue playing video
          this.videoMediaComponent.play();
          this.unfreezeSubs();
        } else {
          // Reveal one more subtitle track
          this.setState(s => {
            const newSubtitleState = {tracksRevealed: s.subtitleState.tracksRevealed + 1};
            return {
              ...s,
              subtitleState: newSubtitleState,
              displayedSubs: this.getSubsToDisplay(s.displayedSubTime, s.subtitleMode, newSubtitleState),
            };
          });
        }
        break;

      case 'read':
        // This appears safe to do even if the video is already playing
        this.videoMediaComponent.play();
        this.unfreezeSubs();
        break;

      default:
        throw new Error('internal error');
    }
  };

  handleToggleRuby = () => {
    const { preferences, onSetPreference } = this.props;
    onSetPreference('showRuby', !preferences.showRuby);
  };

  handleToggleHelp = () => {
    const { preferences, onSetPreference } = this.props;
    onSetPreference('showHelp', !preferences.showHelp);
  };

  handleExit = () => {
    this.savePlaybackPosition();
    this.props.onExit();
  }

  render() {
    const { video } = this.props;

    return (

            <VideoWrapper videoURL={video.videoURL} initialTime={video.playbackPosition} onTimeUpdate={this.handleVideoTimeUpdate} onPlaying={this.handleVideoPlaying} onPause={this.handleVideoPause} onEnded={this.handleVideoEnded} onSeeking={this.handleVideoSeeking} ref={(c) => { this.videoMediaComponent = c; }} />

              {this.state.displayedSubs.map(({ subTrack, chunk }, subTrackIdx) => {
                let hidden = false;

                if ((this.state.subtitleMode === 'listen') && (subTrackIdx >= this.state.subtitleState.tracksRevealed)) {
                  hidden = true;
                }

                return chunk ? (

                        {hidden ? (

(press ↓ to reveal)

                        ) : null}

                          <AnnoText key={chunk.uid} annoText={chunk.annoText} language={subTrack.language} showRuby={this.props.preferences.showRuby} />

                ) : null;
              })}

          <PlayControls onBack={this.handleBack} onReplay={this.handleReplay} onTogglePause={this.handleTogglePause} onContinue={this.handleContinue} onToggleRuby={this.handleToggleRuby} onToggleHelp={this.handleToggleHelp} />

        <button className="Player-big-button Player-exit-button" onClick={this.handleExit}>↩</button>

          Subtitle Mode:
          <Select options={Object.entries(MODE_TITLES).map(([k, v]) => ({value: k, label: v}))} onChange={this.handleSetSubtitleMode} value={this.state.subtitleMode} />
          <button onClick={e => { e.preventDefault(); this.handleToggleHelp(); }}>Toggle Help</button>

Keyboard Controls

Replay Sub:	↑

Reveal Sub /
Continue:	↓

Previous Sub:	←

Next Sub:	→

Pause/Unpause:	space

Toggle Furigana:	F

Toggle Help:	H

Mode: {MODE_TITLES[this.state.subtitleMode]}

            {(() => {
              switch (this.state.subtitleMode) {
                case 'manual':
                  return (

Manually toggle the display of each subtitle track using the number keys (e.g. 1 for the first track). To be honest, this mode is pretty boring and you should try the others.

                  );

                case 'listen':
                  return (

Subtitles are initially hidden. At the end of each subtitle, the video will pause automatically. Could you hear what was said? Press ↑ to replay, if necessary. Then press the ↓ key to reveal the subs, and check if you heard correctly. Then press ↓ to unpause the video.

                  );

                case 'read':
                  return (

Read etc

                  );

                default:
                  throw new Error('internal error');
              }
            })()}

    );
  }
}
<|endoftext|>


<commit_before>'use strict';

/* eslint no-implicit-coercion: "off", max-len: "off" */

module.exports = function($scope, $routeParams) {
  var moment = require('moment');

  // Enable the pills (tabs) functionality
  $('.nav-pills a').click(function(e) {
    e.preventDefault();
    $(this).tab('show');
  });

  $scope.getDateString = function(date) {
    if (!date) {
      return 'Present';
    }
    return moment(date).format('MMM YYYY');
  };

  $scope.getDuration = function(startDate, endDate) {
    var end;
    if (endDate) {
      end = moment(endDate);
    } else {
      end = moment();
    }
    var duration = moment.duration(end.diff(startDate));
    var years = duration.years();
    var months = duration.months();
    var result = '';
    if (years) {
      result = years + ' years';
    }
    if (months) {
      if (result) {
        result += ' ';
      }
      result += months + ' months';
    }
    return result;
  };

  $scope.jobs = [
    {
      title: 'Team Lead, Software Engineer',
      company: 'Wayfair',
      startDate: '2013/08/01',
      endDate: null,
      description: "" +
"
As the tech lead and manager of a team of software engineers, I am helping Wayfair transform into a global company. I am responsible for building a flexible, extensive localization framework that is used by hundreds of engineers. Additionally, I work closely with business stakeholders to identify and prioritize my team’s engineering work and ensure we are furthering business initiatives. Since March 2016 I have served as the functional manager for my team.
" +
"
Projects include:
" +
"
" +
"
Building an automated process for the translation of customer-facing data into different languages.
" +
"
Integrating with a third party, Smartling, using their RESTful API to send and receive data for manual translation.
" +
"
Developing internal and customer-facing web apps using Wayfair’s Tungsten.js and Backbone.
" +
"
Writing dynamic SQL used for the code generation of localized database tables and functions.
" +
"
Creating generic PHP classes that are used for all operations against the localized tables, including asynchronous actions that need to be triggered when any data is modified, such as logging and synchronization.
" +
"
Creating a system for tracking the translation status of data, including automatic flagging for re-translation.
" +
"
" +
"
Other tasks include: Mentoring new hires and interns, reviewing others’ code, interviewing candidates, giving employee performance reviews, and participating in Wayfair’s Emerging Manager training program
" +
"
Primary technologies: PHP, C#.NET, Javascript (Backbone, jQuery), MS SQL, WCF, Git
"
    },
    {
      title: 'Senior Software Engineer',
      company: 'Blackbaud',
      startDate: '2010/05/01',
      endDate: '2013/08/01',
      description: "" +
"
I worked on feature development for our CRM applications on the web and Windows. I worked on all layers of the tech stack on a geographically dispersed agile team managing both new and production software.
" +
"
Projects included:
" +
"
" +
"
Building bidirectional data synchronization between .NET and Java applications when Blackbaud acquired a competitor, Convio. This work included creating a WCF web service to communicate with a message queueing service bus, UIs for review of data, and deployment components.
" +
"
Developing web services that powered Blackbaud’s Android and iOS apps.
" +
"
Adding new features to Blackbaud’s web and desktop CRM applications, including fuzzy search capability, data de-duplication, a generic query builder user interface, and dashboards.
" +
"
" +
"
Primary technologies: C# and VB.NET, WCF, Javascript (Ext JS, jQuery), MS SQL, TFS
"
    },
    {
      title: 'Software Quality Engineer',
      company: 'Blackbaud',
      startDate: '2007/06/01',
      endDate: '2010/05/01',
      description: "I developed an automated software UI testing suite tied to continuous integration builds, testing both web and Windows applications. I became responsible for leading and mentoring three other engineers, and participated in planning and prioritizing projects for the team. In my spare time I familiarized myself with Blackbaud’s frameworks and completed software development tasks that enabled me to move into the Software Engineer role."
    },
    {
      title: 'Information Management Leadership Program Intern',
      company: 'General Electric',
      startDate: '2006/06/01',
      endDate: '2006/08/01',
      description: "This internship was focused on IT project management, delivering on a database migration project involving independent contractors."
    },
    {
      title: 'Web Development Intern',
      company: 'WebQuix',
      startDate: '2006/01/01',
      endDate: '2006/05/01',
      description: "I built websites using HTML, CSS, JavaScript and PHP as part of a software startup catering to professional photographers."
    }
  ];

  $scope.education = [
    {
      degree: 'M.S. in Computer Information Systems',
      school: 'Boston University',
      endDate: '2016/05/01',
      description: "" +
"
This program was a mix of software development courses and IT-specific management courses, which I pursued while working full-time for Wayfair. In addition to the challenge of time management it provided, this program served me well as a study in higher-level project planning and system analysis, as well as an introduction to AngularJS and CSS3, and a refresher on database design, HTML5 and Java.
" +
"
Notable courses:
" +
"
" +
"
IT Strategy and Management
" +
"
Project Management
" +
"
Enterprise Architecture
" +
"
System Analysis
" +
"
Web Application Development with AngularJS
" +
"
Database Design
" +
"
"
    },
    {
      degree: 'B.S. in Computer Information Systems',
      school: 'Clemson University',
      endDate: '2007/05/01',
      description: "This program was made up primarily of computer science courses, with just a splash of business and management."
    }
  ];
};
<commit_msg>Round date months up<commit_after>'use strict';

/* eslint no-implicit-coercion: "off", max-len: "off" */

module.exports = function($scope, $routeParams) {
  var moment = require('moment');

  // Enable the pills (tabs) functionality
  $('.nav-pills a').click(function(e) {
    e.preventDefault();
    $(this).tab('show');
  });

  $scope.getDateString = function(date) {
    if (!date) {
      return 'Present';
    }
    return moment(date).format('MMM YYYY');
  };

  $scope.getDuration = function(startDate, endDate) {
    var end;
    if (endDate) {
      end = moment(endDate);
    } else {
      end = moment();
    }
    var duration = moment.duration(end.diff(startDate));
    var years = duration.years();
    var months = duration.months() + 1;
    var result = '';
    if (years) {
      result = years + ' years';
    }
    if (months) {
      if (result) {
        result += ' ';
      }
      result += months + ' months';
    }
    return result;
  };

  $scope.jobs = [
    {
      title: 'Team Lead, Software Engineer',
      company: 'Wayfair',
      startDate: '2013/08/01',
      endDate: null,
      description: "" +
"
As the tech lead and manager of a team of software engineers, I am helping Wayfair transform into a global company. I am responsible for building a flexible, extensive localization framework that is used by hundreds of engineers. Additionally, I work closely with business stakeholders to identify and prioritize my team’s engineering work and ensure we are furthering business initiatives. Since March 2016 I have served as the functional manager for my team.
" +
"
Projects include:
" +
"
" +
"
Building an automated process for the translation of customer-facing data into different languages.
" +
"
Integrating with a third party, Smartling, using their RESTful API to send and receive data for manual translation.
" +
"
Developing internal and customer-facing web apps using Wayfair’s Tungsten.js and Backbone.
" +
"
Writing dynamic SQL used for the code generation of localized database tables and functions.
" +
"
Creating generic PHP classes that are used for all operations against the localized tables, including asynchronous actions that need to be triggered when any data is modified, such as logging and synchronization.
" +
"
Creating a system for tracking the translation status of data, including automatic flagging for re-translation.
" +
"
" +
"
Other tasks include: Mentoring new hires and interns, reviewing others’ code, interviewing candidates, giving employee performance reviews, and participating in Wayfair’s Emerging Manager training program
" +
"
Primary technologies: PHP, C#.NET, Javascript (Backbone, jQuery), MS SQL, WCF, Git
"
    },
    {
      title: 'Senior Software Engineer',
      company: 'Blackbaud',
      startDate: '2010/05/01',
      endDate: '2013/08/01',
      description: "" +
"
I worked on feature development for our CRM applications on the web and Windows. I worked on all layers of the tech stack on a geographically dispersed agile team managing both new and production software.
" +
"
Projects included:
" +
"
" +
"
Building bidirectional data synchronization between .NET and Java applications when Blackbaud acquired a competitor, Convio. This work included creating a WCF web service to communicate with a message queueing service bus, UIs for review of data, and deployment components.
" +
"
Developing web services that powered Blackbaud’s Android and iOS apps.
" +
"
Adding new features to Blackbaud’s web and desktop CRM applications, including fuzzy search capability, data de-duplication, a generic query builder user interface, and dashboards.
" +
"
" +
"
Primary technologies: C# and VB.NET, WCF, Javascript (Ext JS, jQuery), MS SQL, TFS
"
    },
    {
      title: 'Software Quality Engineer',
      company: 'Blackbaud',
      startDate: '2007/06/01',
      endDate: '2010/05/01',
      description: "I developed an automated software UI testing suite tied to continuous integration builds, testing both web and Windows applications. I became responsible for leading and mentoring three other engineers, and participated in planning and prioritizing projects for the team. In my spare time I familiarized myself with Blackbaud’s frameworks and completed software development tasks that enabled me to move into the Software Engineer role."
    },
    {
      title: 'Information Management Leadership Program Intern',
      company: 'General Electric',
      startDate: '2006/06/01',
      endDate: '2006/08/31',
      description: "This internship was focused on IT project management, delivering on a database migration project involving independent contractors."
    },
    {
      title: 'Web Development Intern',
      company: 'WebQuix',
      startDate: '2006/01/01',
      endDate: '2006/05/31',
      description: "I built websites using HTML, CSS, JavaScript and PHP as part of a software startup catering to professional photographers."
    }
  ];

  $scope.education = [
    {
      degree: 'M.S. in Computer Information Systems',
      school: 'Boston University',
      endDate: '2016/05/01',
      description: "" +
"
This program was a mix of software development courses and IT-specific management courses, which I pursued while working full-time for Wayfair. In addition to the challenge of time management it provided, this program served me well as a study in higher-level project planning and system analysis, as well as an introduction to AngularJS and CSS3, and a refresher on database design, HTML5 and Java.
" +
"
Notable courses:
" +
"
" +
"
IT Strategy and Management
" +
"
Project Management
" +
"
Enterprise Architecture
" +
"
System Analysis
" +
"
Web Application Development with AngularJS
" +
"
Database Design
" +
"
"
    },
    {
      degree: 'B.S. in Computer Information Systems',
      school: 'Clemson University',
      endDate: '2007/05/01',
      description: "This program was made up primarily of computer science courses, with just a splash of business and management."
    }
  ];
};
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';

import * as FmPropTypes from 'fm3/propTypes';
import { toastsRemove, toastsStopTimeout, toastsRestartTimeout } from 'fm3/actions/toastsActions';
import Toast from 'fm3/components/Toast';
import injectL10n from 'fm3/l10nInjector';

import 'fm3/styles/toasts.scss';

function Toasts({ toasts, onAction, onTimeoutStop, onTimeoutRestart, t }) {
  return (

      {toasts.map(({ message, messageKey, messageParams, id, actions, style }) => (
        <Toast
          key={id}
          id={id}
          message={typeof message === 'string' ? t(messageKey, messageParams, message) : message}
          style={style}
          onAction={onAction}
          actions={actions.map(action => ({ ...action, name: action.name || t(action.nameKey) }))}
          onTimeoutStop={onTimeoutStop}
          onTimeoutRestart={onTimeoutRestart}
        />
      ))}

  );
}

Toasts.propTypes = {
  onAction: PropTypes.func.isRequired,
  onTimeoutStop: PropTypes.func.isRequired,
  onTimeoutRestart: PropTypes.func.isRequired,
  toasts: PropTypes.arrayOf(FmPropTypes.toast.isRequired).isRequired,
  t: PropTypes.func.isRequired,
};

export default compose(
  injectL10n(),
  connect(
    state => ({
      toasts: state.toasts.toasts,
    }),
    dispatch => ({
      onAction(id, action) {
        // TODO use some action flag to indicate that we want the action to close the toast
        dispatch(toastsRemove(id));

        if (action) {
          if (Array.isArray(action)) {
            action.forEach(a => dispatch(a));
          } else {
            dispatch(action);
          }
        }
      },
      onTimeoutStop(id) {
        dispatch(toastsStopTimeout(id));
      },
      onTimeoutRestart(id) {
        dispatch(toastsRestartTimeout(id));
      },
    }),
  ),
)(Toasts);
<commit_msg>fixed toast<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';

import * as FmPropTypes from 'fm3/propTypes';
import { toastsRemove, toastsStopTimeout, toastsRestartTimeout } from 'fm3/actions/toastsActions';
import Toast from 'fm3/components/Toast';
import injectL10n from 'fm3/l10nInjector';

import 'fm3/styles/toasts.scss';

function Toasts({ toasts, onAction, onTimeoutStop, onTimeoutRestart, t }) {
  return (

      {toasts.map(({ message, messageKey, messageParams, id, actions, style }) => (
        <Toast
          key={id}
          id={id}
          message={typeof message === 'string' ? message : t(messageKey, messageParams, message)}
          style={style}
          onAction={onAction}
          actions={actions.map(action => ({ ...action, name: action.name || t(action.nameKey) }))}
          onTimeoutStop={onTimeoutStop}
          onTimeoutRestart={onTimeoutRestart}
        />
      ))}

  );
}

Toasts.propTypes = {
  onAction: PropTypes.func.isRequired,
  onTimeoutStop: PropTypes.func.isRequired,
  onTimeoutRestart: PropTypes.func.isRequired,
  toasts: PropTypes.arrayOf(FmPropTypes.toast.isRequired).isRequired,
  t: PropTypes.func.isRequired,
};

export default compose(
  injectL10n(),
  connect(
    state => ({
      toasts: state.toasts.toasts,
    }),
    dispatch => ({
      onAction(id, action) {
        // TODO use some action flag to indicate that we want the action to close the toast
        dispatch(toastsRemove(id));

        if (action) {
          if (Array.isArray(action)) {
            action.forEach(a => dispatch(a));
          } else {
            dispatch(action);
          }
        }
      },
      onTimeoutStop(id) {
        dispatch(toastsStopTimeout(id));
      },
      onTimeoutRestart(id) {
        dispatch(toastsRestartTimeout(id));
      },
    }),
  ),
)(Toasts);
<|endoftext|>


<commit_before>import React from 'react'
import { connect } from 'react-redux'
import AppBar from 'material-ui/AppBar'
import Drawer from 'material-ui/Drawer'
import List from 'material-ui/List/List'
import ListItem from 'material-ui/List/ListItem'
import { openLink } from 'assets/utils/tool'
import Account from 'material-ui/svg-icons/action/account-circle'
import Out from 'material-ui/svg-icons/maps/directions-run'
import { browserHistory } from 'react-router'
import { fetchWithToken, apiURL } from 'assets/utils/request'
import { logout } from 'modules/app_bar'

import './appbar.less'

class AppBarFixed extends React.Component {
  constructor (props) {
    super(props)
    this.state = {open: false}
  }

  titleCSS = {
    textAlign: 'center'
  }

  handleToggle = () => this.setState({open: !this.state.open})
  handleClose = () => this.setState({open: false})

  _gotoLogin = () => {
    browserHistory.push('/login')
    this.handleClose()
  }

  _logout = () => {
    const {logout} = this.props

    fetchWithToken(apiURL.Logout)
      .then(data => data.json())
      .then(response => {
        if (response.error_code == 0) {
          localStorage.removeItem('Token')
          // 通知store 用户已经登出
          logout()
          browserHistory.replace('/')
          this.handleClose()
        } else {
          alert(response.detail)
        }
      })
      .catch(err => console.log('登出问题:' + err))
  }

  render () {
    const {isLogin, appTitle} = this.props

    return (

          <AppBar
            title={appTitle} titleStyle={this.titleCSS}
            iconClassNameRight="muidocs-icon-navigation-expand-more"
            onLeftIconButtonTouchTap={this.handleToggle}
          />

        <Drawer open={this.state.open}
                onRequestChange={(open) => this.setState({open})}
                docked={false}
                width={280}>

          {isLogin ?

            <Out className="login-icon"/>

 :

            <Account className="login-icon"/>

}

          <List className="about-me">

Author:

            <ListItem primaryText='Damon' secondaryText='https://github.com/chaoming56'
                      onTouchTap={() => openLink('https://github.com/chaoming56')}/>
            <ListItem primaryText='Ross' secondaryText='https://github.com/DevRoss'
                      onTouchTap={() => openLink('https://github.com/DevRoss')}/>
          </List>
        </Drawer>

    )
  }
}

const mapStateToProps = ({appBar}) => ({
  ...appBar
})

const mapDispatchToProps = (dispatch) => ({
  logout: () => dispatch(logout())
})

export default connect(mapStateToProps, mapDispatchToProps)(AppBarFixed)
<commit_msg>完成V2.0 (用户体系开发)<commit_after>import React from 'react'
import { connect } from 'react-redux'
import AppBar from 'material-ui/AppBar'
import Drawer from 'material-ui/Drawer'
import List from 'material-ui/List/List'
import ListItem from 'material-ui/List/ListItem'
import { openLink } from 'assets/utils/tool'
import Account from 'material-ui/svg-icons/action/account-circle'
import IconButton from 'material-ui/IconButton';
import Home from 'material-ui/svg-icons/action/home'
import Out from 'material-ui/svg-icons/maps/directions-run'
import { browserHistory } from 'react-router'
import { fetchWithToken, apiURL } from 'assets/utils/request'
import { logout } from 'modules/app_bar'

import './appbar.less'

class AppBarFixed extends React.Component {
  constructor (props) {
    super(props)
    this.state = {open: false}
  }

  titleCSS = {
    textAlign: 'center'
  }

  handleToggle = () => this.setState({open: !this.state.open})
  handleClose = () => this.setState({open: false})

  _goHome = () => browserHistory.replace('/')

  _gotoLogin = () => {
    browserHistory.push('/login')
    this.handleClose()
  }

  _logout = () => {
    const {logout} = this.props

    fetchWithToken(apiURL.Logout)
      .then(data => data.json())
      .then(response => {
        if (response.error_code == 0) {
          localStorage.removeItem('Token')
          // 通知store 用户已经登出
          logout()
          browserHistory.replace('/')
          this.handleClose()
        } else {
          alert(response.detail)
        }
      })
      .catch(err => console.log('登出问题:' + err))
  }

  render () {
    const {isLogin, appTitle} = this.props

    return (

          <AppBar
            title={appTitle} titleStyle={this.titleCSS}
            onLeftIconButtonTouchTap={this.handleToggle}
            onRightIconButtonTouchTap={this._goHome}
            iconElementRight={<IconButton><Home/></IconButton>}

          />

        <Drawer open={this.state.open}
                onRequestChange={(open) => this.setState({open})}
                docked={false}
                width={280}>

          {isLogin ?

            <Out className="login-icon"/>

 :

            <Account className="login-icon"/>

}

          <List className="about-me">

Author:

            <ListItem primaryText='Damon' secondaryText='https://github.com/chaoming56'
                      onTouchTap={() => openLink('https://github.com/chaoming56')}/>
            <ListItem primaryText='Ross' secondaryText='https://github.com/DevRoss'
                      onTouchTap={() => openLink('https://github.com/DevRoss')}/>
          </List>
        </Drawer>

    )
  }
}

const mapStateToProps = ({appBar}) => ({
  ...appBar
})

const mapDispatchToProps = (dispatch) => ({
  logout: () => dispatch(logout())
})

export default connect(mapStateToProps, mapDispatchToProps)(AppBarFixed)
<|endoftext|>


<commit_before>import _ from 'lodash';
import React, { Component } from 'react';
import CodeMirror from 'react-codemirror';
import 'codemirror/mode/jsx/jsx';
import * as actions from 'actions';
import { connect } from 'react-redux';

class Editor extends Component {
  onCodeChange(code) {
    this.props.updateCode(code);
  }

  render() {
    return (

        <CodeMirror
          value={this.props.code}
          onChange={this.onCodeChange.bind(this)}
          options={{ mode: 'javascript', lineNumbers: true, tabSize: 2 }} />

    );
  }
}

function mapStateToProps({code}) {
  return { code };
}

export default connect(mapStateToProps, actions)(Editor);
<commit_msg>js -> jsx formatting<commit_after>import _ from 'lodash';
import React, { Component } from 'react';
import CodeMirror from 'react-codemirror';
import 'codemirror/mode/jsx/jsx';
import * as actions from 'actions';
import { connect } from 'react-redux';

class Editor extends Component {
  onCodeChange(code) {
    this.props.updateCode(code);
  }

  render() {
    return (

        <CodeMirror
          value={this.props.code}
          onChange={this.onCodeChange.bind(this)}
          options={{ mode: 'jsx', lineNumbers: true, tabSize: 2 }} />

    );
  }
}

function mapStateToProps({code}) {
  return { code };
}

export default connect(mapStateToProps, actions)(Editor);
<|endoftext|>