<commit_before>import m from 'mithril';
import Stream from 'mithril/stream';
import _ from 'underscore';
import h, { VNode } from '../h';
import projectVM, { ProjectIntegration } from '../vms/project-vm';
import projectEditSaveBtn from '../c/project-edit-save-btn';
import railsErrorsVM from '../vms/rails-errors-vm';
import popNotification from '../c/pop-notification';

const GA = 'GA';
const PIXEL = 'PIXEL';

export class ProjectEditIntegrations {

    oninit(/** @type {VNode} */vnode) {

        /**
         * @param {string} name
         * @param {ProjectIntegration[]} integrations
         * @returns {ProjectIntegration}
         * */
        const findIntegrationByName = (name = '', integrations = []) => integrations.find(integration => integration.name === name);

        const GATracking = h.RedrawStream({ data: { id: '' }, name: GA });
        const FBPixelTracking = h.RedrawStream({ data: { id: '' }, name: PIXEL });

        const GATrackingID = h.RedrawStream('', id => {
            GATracking(_.extend(GATracking(), { data: { id }, name: GA }));
        });
        const FBPixelTrackingID = h.RedrawStream('', id => {
            FBPixelTracking(_.extend(FBPixelTracking(), { data: { id }, name: PIXEL }));
        });

        const loadingIntegrations = h.RedrawStream(true);
        const loading = h.RedrawStream(false);
        const error = h.RedrawStream(false);
        const showSuccess = h.RedrawStream(false);
        const showError = h.RedrawStream(false);
        const projectId = vnode.attrs.project_id;

        projectVM
            .getIntegrations(projectId)
            .then(data => {
                GATrackingID(GATracking(findIntegrationByName(GA, data)).data.id);
                FBPixelTrackingID(FBPixelTracking(findIntegrationByName(PIXEL, data)).data.id);
                loadingIntegrations(false);
            })
            .catch(error => {
                loadingIntegrations(false);
            });

        /**
         * @param {Stream<ProjectIntegration>} integration
         */
        async function requestForIntegration(integration) {
            const integrationData = integration();
            const shouldCreate = !!integrationData.id;

            const response = shouldCreate ?
                await projectVM.updateIntegration(projectId, integrationData)
            :
                await projectVM.createIntegration(projectId, integrationData);

            integrationData.id = response.integration_id;
            integration(integrationData);
        }

        async function save() {
            loading(true);
            try {
                showError(false);
                error(false);

                await requestForIntegration(GATracking);
                await requestForIntegration(FBPixelTracking);

                showSuccess(true);
            } catch(e) {
                error(true);
                showError(true);
            }
            loading(false);
        }

        vnode.state = {
            GATrackingID,
            FBPixelTrackingID,
            loading,
            save,
            error,
            showSuccess,
            showError,
            loadingIntegrations
        };
    }

    view({ state, attrs }) {

        const GATrackingID = state.GATrackingID;
        const FBPixelTrackingID = state.FBPixelTrackingID;
        const save = state.save;
        const error = state.error;
        const showSuccess = state.showSuccess;
        const showError = state.showError;
        const loadingIntegrations = state.loadingIntegrations;

        if (loadingIntegrations()) {
            return h.loader();
        } else {
            return m('#integrations', [
                (state.showSuccess() ? m(popNotification, {
                    message: window.I18n.t('shared.successful_update'),
                    toggleOpt: state.showSuccess
                }) : ''),
                (state.showError() ? m(popNotification, {
                    message: window.I18n.t('shared.failed_update'),
                    toggleOpt: state.showError,
                    error: true
                }) : ''),

                m('div.section',
                    m('div.w-container',
                        m('div.w-row', [
                            m('div.w-col.w-col-1'),
                            m('div.w-col.w-col-10',
                                m('div.w-form', [
                                    m('form', [
                                        m('div.u-marginbottom-20.card.card-terciary.medium.w-row', [
                                            m('div.w-col.w-col-5', [
                                                m('label.fontweight-semibold.fontsize-base', 'Google Analytics Tracking ID'),
                                                m('label.field-label.fontsize-smallest.fontcolor-secondary', [
                                                    'Envia informações dos visitantes de sua página para a sua conta do Google Analytics ',
                                                    m('a.alt-link[href="#"]', 'Saiba mais')
                                                ]),
                                                m('img[src="https://uploads-ssl.webflow.com/57ba58b4846cc19e60acdd5b/5c5cacc4a3136e8188581846_logo_lockup_analytics_icon_horizontal_black_2x.png"][width="146"][alt=""]')
                                            ]),
                                            m('div.w-col.w-col-7',
                                                m('div.w-row', [
                                                    m('div.text-field.prefix.no-hover.medium.prefix-permalink.w-col.w-col-2.w-col-tiny-2',
                                                        m('div.fontcolor-secondary.u-text-center.fontsize-smallest', 'UA-')
                                                    ),
                                                    m('div.w-col.w-col-10.w-col-tiny-10',
                                                        m(`input${error() ? '.error' : ''}.text-field.postfix.positive.medium.w-input[type="text"][placeholder="1234567-1"]`, {
                                                            value: GATrackingID(),
                                                            onkeyup: (/** @type {Event} */ event) => GATrackingID(event.target.value)
                                                        }),
                                                    )
                                                ])
                                            )
                                        ]),
                                        m('div.u-marginbottom-20.card.card-terciary.medium.w-row', [
                                            m('div.w-col.w-col-5', [
                                                m('label.fontweight-semibold.fontsize-base', 'Facebook Pixel'),
                                                    m('label.field-label.fontsize-smallest.fontcolor-secondary', [
                                                        'Envia informações dos visitantes de sua página para o seu Facebook Pixel ',
                                                        m('a.alt-link[href="#"]', 'Saiba mais')
                                                    ]),
                                                    m('img[src="https://uploads-ssl.webflow.com/57ba58b4846cc19e60acdd5b/5c5cad962b0f59780182c2b8_xfacebook-pixel-logotyp.png.pagespeed.ic.LYUwNiemhv.png"][width="146"][alt=""]')
                                            ]),
                                            m('div.w-col.w-col-7', [
                                                m(`input${error() ? '.error' : ''}.text-field.medium.w-input[type="text"][placeholder="123456789123456"]`, {
                                                    value: FBPixelTrackingID(),
                                                    onkeyup: (/** @type {Event} */ event) => FBPixelTrackingID(event.target.value.replace(/\D*/g, ''))
                                                }),
                                            ])
                                        ])
                                    ]),
                                ])
                            ),
                        ])
                    ),

                    m(projectEditSaveBtn, { loading: state.loading, onSubmit: save }),
                )
            ]);
        }
    }
}<commit_msg>added id to fields<commit_after>import m from 'mithril';
import Stream from 'mithril/stream';
import _ from 'underscore';
import h, { VNode } from '../h';
import projectVM, { ProjectIntegration } from '../vms/project-vm';
import projectEditSaveBtn from '../c/project-edit-save-btn';
import railsErrorsVM from '../vms/rails-errors-vm';
import popNotification from '../c/pop-notification';

const GA = 'GA';
const PIXEL = 'PIXEL';

export class ProjectEditIntegrations {

    oninit(/** @type {VNode} */vnode) {

        /**
         * @param {string} name
         * @param {ProjectIntegration[]} integrations
         * @returns {ProjectIntegration}
         * */
        const findIntegrationByName = (name = '', integrations = []) => integrations.find(integration => integration.name === name);

        const GATracking = h.RedrawStream({ data: { id: '' }, name: GA });
        const FBPixelTracking = h.RedrawStream({ data: { id: '' }, name: PIXEL });

        const GATrackingID = h.RedrawStream('', id => {
            GATracking(_.extend(GATracking(), { data: { id }, name: GA }));
        });
        const FBPixelTrackingID = h.RedrawStream('', id => {
            FBPixelTracking(_.extend(FBPixelTracking(), { data: { id }, name: PIXEL }));
        });

        const loadingIntegrations = h.RedrawStream(true);
        const loading = h.RedrawStream(false);
        const error = h.RedrawStream(false);
        const showSuccess = h.RedrawStream(false);
        const showError = h.RedrawStream(false);
        const projectId = vnode.attrs.project_id;

        projectVM
            .getIntegrations(projectId)
            .then(data => {
                GATrackingID(GATracking(findIntegrationByName(GA, data)).data.id);
                FBPixelTrackingID(FBPixelTracking(findIntegrationByName(PIXEL, data)).data.id);
                loadingIntegrations(false);
            })
            .catch(error => {
                loadingIntegrations(false);
            });

        /**
         * @param {Stream<ProjectIntegration>} integration
         */
        async function requestForIntegration(integration) {
            const integrationData = integration();
            const shouldCreate = !!integrationData.id;

            const response = shouldCreate ?
                await projectVM.updateIntegration(projectId, integrationData)
            :
                await projectVM.createIntegration(projectId, integrationData);

            integrationData.id = response.integration_id;
            integration(integrationData);
        }

        async function save() {
            loading(true);
            try {
                showError(false);
                error(false);

                await requestForIntegration(GATracking);
                await requestForIntegration(FBPixelTracking);

                showSuccess(true);
            } catch(e) {
                error(true);
                showError(true);
            }
            loading(false);
        }

        vnode.state = {
            GATrackingID,
            FBPixelTrackingID,
            loading,
            save,
            error,
            showSuccess,
            showError,
            loadingIntegrations
        };
    }

    view({ state, attrs }) {

        const GATrackingID = state.GATrackingID;
        const FBPixelTrackingID = state.FBPixelTrackingID;
        const save = state.save;
        const error = state.error;
        const showSuccess = state.showSuccess;
        const showError = state.showError;
        const loadingIntegrations = state.loadingIntegrations;

        if (loadingIntegrations()) {
            return h.loader();
        } else {
            return m('#integrations', [
                (state.showSuccess() ? m(popNotification, {
                    message: window.I18n.t('shared.successful_update'),
                    toggleOpt: state.showSuccess
                }) : ''),
                (state.showError() ? m(popNotification, {
                    message: window.I18n.t('shared.failed_update'),
                    toggleOpt: state.showError,
                    error: true
                }) : ''),

                m('div.section',
                    m('div.w-container',
                        m('div.w-row', [
                            m('div.w-col.w-col-1'),
                            m('div.w-col.w-col-10',
                                m('div.w-form', [
                                    m('form', [
                                        m('div.u-marginbottom-20.card.card-terciary.medium.w-row', [
                                            m('div.w-col.w-col-5', [
                                                m('label.fontweight-semibold.fontsize-base', 'Google Analytics Tracking ID'),
                                                m('label.field-label.fontsize-smallest.fontcolor-secondary', [
                                                    'Envia informações dos visitantes de sua página para a sua conta do Google Analytics ',
                                                    m('a.alt-link[href="#"]', 'Saiba mais')
                                                ]),
                                                m('img[src="https://uploads-ssl.webflow.com/57ba58b4846cc19e60acdd5b/5c5cacc4a3136e8188581846_logo_lockup_analytics_icon_horizontal_black_2x.png"][width="146"][alt=""]')
                                            ]),
                                            m('div.w-col.w-col-7',
                                                m('div.w-row', [
                                                    m('div.text-field.prefix.no-hover.medium.prefix-permalink.w-col.w-col-2.w-col-tiny-2',
                                                        m('div.fontcolor-secondary.u-text-center.fontsize-smallest', 'UA-')
                                                    ),
                                                    m('div.w-col.w-col-10.w-col-tiny-10',
                                                        m(`input${error() ? '.error' : ''}.text-field.postfix.positive.medium.w-input[type="text"][placeholder="1234567-1"][id="google-analytics-id"]`, {
                                                            value: GATrackingID(),
                                                            onkeyup: (/** @type {Event} */ event) => GATrackingID(event.target.value)
                                                        }),
                                                    )
                                                ])
                                            )
                                        ]),
                                        m('div.u-marginbottom-20.card.card-terciary.medium.w-row', [
                                            m('div.w-col.w-col-5', [
                                                m('label.fontweight-semibold.fontsize-base', 'Facebook Pixel'),
                                                    m('label.field-label.fontsize-smallest.fontcolor-secondary', [
                                                        'Envia informações dos visitantes de sua página para o seu Facebook Pixel ',
                                                        m('a.alt-link[href="#"]', 'Saiba mais')
                                                    ]),
                                                    m('img[src="https://uploads-ssl.webflow.com/57ba58b4846cc19e60acdd5b/5c5cad962b0f59780182c2b8_xfacebook-pixel-logotyp.png.pagespeed.ic.LYUwNiemhv.png"][width="146"][alt=""]')
                                            ]),
                                            m('div.w-col.w-col-7', [
                                                m(`input${error() ? '.error' : ''}.text-field.medium.w-input[type="text"][placeholder="123456789123456"][id="fb-pixel-id"]`, {
                                                    value: FBPixelTrackingID(),
                                                    onkeyup: (/** @type {Event} */ event) => FBPixelTrackingID(event.target.value.replace(/\D*/g, ''))
                                                }),
                                            ])
                                        ])
                                    ]),
                                ])
                            ),
                        ])
                    ),

                    m(projectEditSaveBtn, { loading: state.loading, onSubmit: save }),
                )
            ]);
        }
    }
}<|endoftext|>


<commit_before>/* global
        ace
*/
$(document).ready(function () {
    $("select").selectize();
    var md = window.markdownit({
        html: true,
        linkify: true,
        typographer: true
    });

    var editor = ace.edit("editor");
    var textarea = $("textarea[name='content']");
    var output = $(".output");

    editor.getSession().setUseWrapMode(true);
    editor.getSession().setMode("ace/mode/markdown");
    editor.getSession().on("change", function () {
        textarea.val(editor.getSession().getValue());
        output.html(md.render(editor.getSession().getValue()));
    });
});
<commit_msg>Implement Add New Tags<commit_after>/* global
        ace
*/
$(document).ready(function () {
    $("select[name='author']").selectize();
    $("select[name='tag']").selectize({
        create: true
    });
    var md = window.markdownit({
        html: true,
        linkify: true,
        typographer: true
    });

    var editor = ace.edit("editor");
    var textarea = $("textarea[name='content']");
    var output = $(".output");

    editor.getSession().setUseWrapMode(true);
    editor.getSession().setMode("ace/mode/markdown");
    editor.getSession().on("change", function () {
        textarea.val(editor.getSession().getValue());
        output.html(md.render(editor.getSession().getValue()));
    });
});
<|endoftext|>


<commit_before>import { not, sort } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { task, timeout } from 'ember-concurrency';

export default Component.extend({
  flashMessages: service(),
  router: service(),
  store: service(),
  isDisabled: not(
    'model.permissions.write',
  ),
  sortedEntriesProperties: [
    'nomen',
  ],
  sortedEntries: sort(
    'model.entries',
    'sortedEntriesProperties'
  ),
  searchGroup: task(function* (term){
    yield timeout(600);
    let kindOptions = {
      'Chorus': 32,
      'Quartet': 41,
    };
    let kindModel = this.get('model.kind');
    let kindInt = kindOptions[kindModel];
    let groups = yield this.get('store').query('group', {
        'nomen__icontains': term,
        'status__gt': 0,
        'page_size': 1000,
        'kind': kindInt,
      });
    return groups
  }),
  createEntryModal: false,
  createEntryModalError: false,
  saveEntry: task(function* (group){
    try {
      let entry = yield this.get('store').createRecord('entry', {
        group: group,
        session: this.get('model'),
        description: '',
        contestants: [],
        isEvaluation: true,
        isPrivate: false,
        competitor: null,
      }).save();
      yield entry.build({
        'by': this.get('currentUser.user.id'),
      });
      this.get('model').reload();
      this.set('createEntryModal', false);
      this.set('createEntryModalError', false);
      this.get('flashMessages').success("Created!");
      this.get('router').transitionTo('dashboard.session-manager.session.entries.entry', this.get('model'), entry.get('id'));
    } catch(e) {
      e.errors.forEach((e) => {
        this.set('createEntryModalError', true);
        this.get('flashMessages').danger(e.detail);
      })
    }
  }).drop(),
  actions: {
    cancelEntry(entry){
      entry.deleteRecord();
    },
  }
});

<commit_msg>Session flow verified<commit_after>import { not, sort } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { task, timeout } from 'ember-concurrency';

export default Component.extend({
  flashMessages: service(),
  router: service(),
  store: service(),
  isDisabled: not(
    'model.permissions.write',
  ),
  sortedEntriesProperties: [
    'nomen',
  ],
  sortedEntries: sort(
    'model.entries',
    'sortedEntriesProperties'
  ),
  searchGroup: task(function* (term){
    yield timeout(600);
    let kindOptions = {
      'Chorus': 32,
      'Quartet': 41,
    };
    let kindModel = this.get('model.kind');
    let kindInt = kindOptions[kindModel];
    let groups = yield this.get('store').query('group', {
        'nomen__icontains': term,
        'status__gt': 0,
        'page_size': 1000,
        'kind': kindInt,
      });
    return groups
  }),
  createEntryModal: false,
  createEntryModalError: false,
  saveEntry: task(function* (group){
    try {
      let entry = yield this.get('store').createRecord('entry', {
        group: group,
        session: this.get('model'),
        description: '',
        contestants: [],
        isEvaluation: true,
        isPrivate: false,
        competitor: null,
      }).save();
      yield entry.build({
        'by': this.get('currentUser.user.id'),
      });
      this.get('model').reload();
      this.get('model').hasMany('entries').reload();
      this.set('createEntryModal', false);
      this.set('createEntryModalError', false);
      this.get('flashMessages').success("Created!");
      this.get('router').transitionTo('dashboard.session-manager.session.entries.entry', this.get('model'), entry.get('id'));
    } catch(e) {
      e.errors.forEach((e) => {
        this.set('createEntryModalError', true);
        this.get('flashMessages').danger(e.detail);
      })
    }
  }).drop(),
  actions: {
    cancelEntry(entry){
      entry.deleteRecord();
    },
  }
});

<|endoftext|>


<commit_before>// It's about debouncing not throttling

'use strict';

var callable     = require('es5-ext/object/valid-callable')
  , nextTick     = require('next-tick')
  , validTimeout = require('./valid-timeout')

  , apply = Function.prototype.apply;

module.exports = function (fn/*, timeout*/) {
	var scheduled, run, context, args, delay, timeout = arguments[1], handle;
	callable(fn);
	if (timeout == null) {
		delay = nextTick;
	} else {
		timeout = validTimeout(timeout);
		delay = setTimeout;
	}
	run = function () {
		if (!scheduled) return; // IE8 tends to not clear immediate timeouts properly
		scheduled = false;
		handle = null;
		apply.call(fn, context, args);
		context = null;
		args = null;
	};
	return function () {
		if (scheduled) {
			if (handle == null) {
				// 'nextTick' based, no room for debounce
				return;
			}
			clearTimeout(handle);
		}
		scheduled = true;
		context = this;
		args = arguments;
		handle = delay(run, timeout);
	};
};
<commit_msg>docs: improve in code description<commit_after>// It's actually "debounce"

'use strict';

var callable     = require('es5-ext/object/valid-callable')
  , nextTick     = require('next-tick')
  , validTimeout = require('./valid-timeout')

  , apply = Function.prototype.apply;

module.exports = function (fn/*, timeout*/) {
	var scheduled, run, context, args, delay, timeout = arguments[1], handle;
	callable(fn);
	if (timeout == null) {
		delay = nextTick;
	} else {
		timeout = validTimeout(timeout);
		delay = setTimeout;
	}
	run = function () {
		if (!scheduled) return; // IE8 tends to not clear immediate timeouts properly
		scheduled = false;
		handle = null;
		apply.call(fn, context, args);
		context = null;
		args = null;
	};
	return function () {
		if (scheduled) {
			if (handle == null) {
				// 'nextTick' based, no room for debounce
				return;
			}
			clearTimeout(handle);
		}
		scheduled = true;
		context = this;
		args = arguments;
		handle = delay(run, timeout);
	};
};
<|endoftext|>


<commit_before>(function() {
  var oldI18nlookup = I18n.lookup;
  I18n.lookup = function(scope, options) {
    return oldI18nlookup.apply(this, ["js." + scope, options]);
  };

  // Default format for storage units
  var oldI18ntoHumanSize = I18n.toHumanSize;
  I18n.toHumanSize = function(number, options) {
    options = options || {};
    options.format = I18n.t("number.human.storage_units.format");
    return oldI18ntoHumanSize.apply(this, [number, options]);
  };

  if (Ember.EXTEND_PROTOTYPES) {
    String.prototype.i18n = function(options) {
      return I18n.t(String(this), options);
    };
  }
})();
<commit_msg>FIX: Activate account page JS error with I18n<commit_after>(function() {
  if (typeof I18n !== "undefined") {
    var oldI18nlookup = I18n.lookup;
    I18n.lookup = function(scope, options) {
      return oldI18nlookup.apply(this, ["js." + scope, options]);
    };

    // Default format for storage units
    var oldI18ntoHumanSize = I18n.toHumanSize;
    I18n.toHumanSize = function(number, options) {
      options = options || {};
      options.format = I18n.t("number.human.storage_units.format");
      return oldI18ntoHumanSize.apply(this, [number, options]);
    };

    if (Ember.EXTEND_PROTOTYPES) {
      String.prototype.i18n = function(options) {
        return I18n.t(String(this), options);
      };
    }
  }
})();
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import treeData from './../AnimationData/treeStructure';
import GitAnimation from './GitAnimation';
import StructureAnimation from './StructureAnimation';
var d3 = require('d3');
var ReactDOM = require('react-dom');
var _ = require('lodash');

export default class Animation extends Component {

  constructor(props) {
    super(props);
    this.state = {
      treeData: this.props.initialTreeData,
      structureAnimationVisible: this.props.initialStructureAnimationVisible
    }
  }

  componentDidMount(){
    var mountNode = ReactDOM.findDOMNode(this.refs.treeRender);
    // Render the tree usng d3 after first component mount
    renderTree(this.state.treeData, mountNode);
  }

  shouldComponentUpdate(nextProps, nextState){
    // console.log(_.isEqual(this.lastState,nextState));
    // console.log(this.lastState, nextState)
    if (_.isEqual(this.lastState,nextState)){
      return false
    } else {
      // Delegate rendering the tree to a d3 function on prop change
      renderTree(nextState.treeData, ReactDOM.findDOMNode(this.refs.treeRender));
      // Do not allow react to render the component on prop change
      return false;
    }
  }

  updateTree(newSchema){
    this.lastState = this.state
    this.setState({
      treeData: newSchema
    })
  }

  render() {
    ipcRenderer.on('direc-schema', (e,arg)=>{
      this.updateTree(arg);
    })

    var selectedAnimation;
    var gitStyle = {
      borderBottom: '1px solid black',
      padding: '1rem'
     };
    var structureStyle = {padding: '1rem'};

    if (this.state.structureAnimationVisible) {
      // What to show for structure Animation
      selectedAnimation = <StructureAnimation />;
      gitStyle.backgroundColor = 'transparent';
      structureStyle.backgroundColor = 'lightBlue';
    } else {
      // What to show for Git Animation
      selectedAnimation = <GitAnimation />;
      gitStyle.backgroundColor = 'lightBlue';
      structureStyle.backgroundColor = 'transparent';
    }

    return (

Git view

Directory view

          {selectedAnimation}
          <svg ref="treeRender"></svg>

    )
  }
}

var renderTree = function(treeData, svgDomNode) {

  var margin = {top: 0, right: 90, bottom: 0, left: 180},
  	width = 660 - margin.right - margin.left,
  	height = 200 - margin.top - margin.bottom;

    var i = 0,
      duration = 450,
      root;

    // Cleans up the SVG on re-render
    d3.select(svgDomNode).selectAll("*").remove();

    var tree = d3.layout.tree()
      .size([height, width]);

    var diagonal = d3.svg.diagonal()
      .projection(function(d) { return [d.y, d.x]; });

    var svg = d3.select(svgDomNode)
      .attr("width", width + margin.right + margin.left)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    root = treeData[0];
    root.x0 = height / 2;
    root.y0 = 0;

    update(root);

    function update(source) {

      // Compute the new tree layout.
      var nodes = tree.nodes(root).reverse(),
        links = tree.links(nodes);

      // Normalize for fixed-depth.
      nodes.forEach(function(d) { d.y = d.depth * 180; });

      // Update the nodes…
      var node = svg.selectAll("g.node")
        .data(nodes, function(d) { return d.id || (d.id = ++i); });

      // Enter any new nodes at the parent's previous position.
      var nodeEnter = node.enter().append("g")
        .attr("class", "node")
        .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
        .on("click", click);

      nodeEnter.append("circle")
        .attr("r", 1e-6)
        .style("fill", function(d) { return d._children ? "lightsteelblue" : d.level; });

      nodeEnter.append("text")
        .attr("x", function(d) { return d.children || d._children ? -20 : 20; })
        .attr("dy", ".35em")
        .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
        .text(function(d) { return d.name; })
        .style("fill-opacity", 1e-6);

      // Transition nodes to their new position.
      var nodeUpdate = node.transition()
        .duration(duration)
        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

      nodeUpdate.select("circle")
        .attr("r", function(d) { return d.value ? d.value : 5; })
        .style("fill", function(d) { return d._children ? "lightsteelblue" : d.level;; });

      nodeUpdate.select("text")
        .style("fill-opacity", 1);

      // Transition exiting nodes to the parent's new position.
      var nodeExit = node.exit().transition()
        .duration(duration)
        .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
        .remove();

      nodeExit.select("circle")
        .attr("r", 1e-6);

      nodeExit.select("text")
        .style("fill-opacity", 1e-6);

      // Update the links…
      var link = svg.selectAll("path.link")
        .data(links, function(d) { return d.target.id; });

      // Enter any new links at the parent's previous position.
      link.enter().insert("path", "g")
        .attr("class", "link")
        .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0};
        return diagonal({source: o, target: o});
        });

      // Transition links to their new position.
      link.transition()
        .duration(duration)
        .attr("d", diagonal);

      // Transition exiting nodes to the parent's new position.
      link.exit().transition()
        .duration(duration)
        .attr("d", function(d) {
        var o = {x: source.x, y: source.y};
        return diagonal({source: o, target: o});
        })
        .remove();

      // Stash the old positions for transition.
      nodes.forEach(function(d) {
      d.x0 = d.x;
      d.y0 = d.y;
      });
    }

    // Toggle children on click.
    function click(d) {
      if (d.children) {
      d._children = d.children;
      d.children = null;
      } else {
      d.children = d._children;
      d._children = null;
      }
      update(d);
    }
}

Animation.defaultProps = {
  initialTreeData: treeData,
  initialStructureAnimationVisible: true
}
<commit_msg>Animation toggle works if shouldComponentUpdate is commented out. About to move tree-rendering logic to GitAnimation.js<commit_after>import React, {Component} from 'react';
import treeData from './../AnimationData/treeStructure';
import GitAnimation from './GitAnimation';
import StructureAnimation from './StructureAnimation';
var d3 = require('d3');
var ReactDOM = require('react-dom');
var _ = require('lodash');

export default class Animation extends Component {

  constructor(props) {
    super(props);
    this.state = {
      treeData: this.props.initialTreeData,
      structureAnimationVisible: this.props.initialStructureAnimationVisible
    }
  }

  componentDidMount(){
    var mountNode = ReactDOM.findDOMNode(this.refs.treeRender);
    // Render the tree usng d3 after first component mount
    renderTree(this.state.treeData, mountNode);
  }

  shouldComponentUpdate(nextProps, nextState){
    // console.log(_.isEqual(this.lastState,nextState));
    // console.log(this.lastState, nextState)
    if (_.isEqual(this.lastState,nextState)){
      return false
    } else {
      // Delegate rendering the tree to a d3 function on prop change
      renderTree(nextState.treeData, ReactDOM.findDOMNode(this.refs.treeRender));
      // Do not allow react to render the component on prop change
      return false;
    }
  }

  updateTree(newSchema){
    this.lastState = this.state
    this.setState({
      treeData: newSchema
    })
  }

  showGit() {
    console.log('showing Git');
    this.setState({
      structureAnimationVisible: false
    })
  }

  showStructure() {
    console.log('showing Structure');
    this.setState({
      structureAnimationVisible: true
    })
  }

  render() {
    ipcRenderer.on('direc-schema', (e,arg)=>{
      this.updateTree(arg);
    })

    console.log('rendering');

    var selectedAnimation;
    var gitStyle = {
      borderBottom: '1px solid black',
      padding: '1rem'
     };
    var structureStyle = {padding: '1rem'};

    if (this.state.structureAnimationVisible) {
      // What to show for structure Animation
      selectedAnimation = <StructureAnimation />;
      gitStyle.backgroundColor = 'transparent';
      structureStyle.backgroundColor = 'lightBlue';
    } else {
      // What to show for Git Animation
      selectedAnimation = <GitAnimation />;
      gitStyle.backgroundColor = 'lightBlue';
      structureStyle.backgroundColor = 'transparent';
    }

    return (

Git view

Directory view

          {selectedAnimation}
          <svg ref="treeRender"></svg>

    )
  }
}

var renderTree = function(treeData, svgDomNode) {

  var margin = {top: 0, right: 90, bottom: 0, left: 180},
  	width = 660 - margin.right - margin.left,
  	height = 200 - margin.top - margin.bottom;

    var i = 0,
      duration = 450,
      root;

    // Cleans up the SVG on re-render
    d3.select(svgDomNode).selectAll("*").remove();

    var tree = d3.layout.tree()
      .size([height, width]);

    var diagonal = d3.svg.diagonal()
      .projection(function(d) { return [d.y, d.x]; });

    var svg = d3.select(svgDomNode)
      .attr("width", width + margin.right + margin.left)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    root = treeData[0];
    root.x0 = height / 2;
    root.y0 = 0;

    update(root);

    function update(source) {

      // Compute the new tree layout.
      var nodes = tree.nodes(root).reverse(),
        links = tree.links(nodes);

      // Normalize for fixed-depth.
      nodes.forEach(function(d) { d.y = d.depth * 180; });

      // Update the nodes…
      var node = svg.selectAll("g.node")
        .data(nodes, function(d) { return d.id || (d.id = ++i); });

      // Enter any new nodes at the parent's previous position.
      var nodeEnter = node.enter().append("g")
        .attr("class", "node")
        .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
        .on("click", click);

      nodeEnter.append("circle")
        .attr("r", 1e-6)
        .style("fill", function(d) { return d._children ? "lightsteelblue" : d.level; });

      nodeEnter.append("text")
        .attr("x", function(d) { return d.children || d._children ? -20 : 20; })
        .attr("dy", ".35em")
        .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
        .text(function(d) { return d.name; })
        .style("fill-opacity", 1e-6);

      // Transition nodes to their new position.
      var nodeUpdate = node.transition()
        .duration(duration)
        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

      nodeUpdate.select("circle")
        .attr("r", function(d) { return d.value ? d.value : 5; })
        .style("fill", function(d) { return d._children ? "lightsteelblue" : d.level;; });

      nodeUpdate.select("text")
        .style("fill-opacity", 1);

      // Transition exiting nodes to the parent's new position.
      var nodeExit = node.exit().transition()
        .duration(duration)
        .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
        .remove();

      nodeExit.select("circle")
        .attr("r", 1e-6);

      nodeExit.select("text")
        .style("fill-opacity", 1e-6);

      // Update the links…
      var link = svg.selectAll("path.link")
        .data(links, function(d) { return d.target.id; });

      // Enter any new links at the parent's previous position.
      link.enter().insert("path", "g")
        .attr("class", "link")
        .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0};
        return diagonal({source: o, target: o});
        });

      // Transition links to their new position.
      link.transition()
        .duration(duration)
        .attr("d", diagonal);

      // Transition exiting nodes to the parent's new position.
      link.exit().transition()
        .duration(duration)
        .attr("d", function(d) {
        var o = {x: source.x, y: source.y};
        return diagonal({source: o, target: o});
        })
        .remove();

      // Stash the old positions for transition.
      nodes.forEach(function(d) {
      d.x0 = d.x;
      d.y0 = d.y;
      });
    }

    // Toggle children on click.
    function click(d) {
      if (d.children) {
      d._children = d.children;
      d.children = null;
      } else {
      d.children = d._children;
      d._children = null;
      }
      update(d);
    }
}

Animation.defaultProps = {
  initialTreeData: treeData,
  initialStructureAnimationVisible: true
}
<|endoftext|>


<commit_before>(function (angular) {
    'use strict';

    angular
            .module('myApp.zen')
            .factory('ZenService', ZenService);

    ZenService.$inject = ['$http', '$q'];

    /**
     *
     * @returns {Object}
     * @constructor
     */
    function ZenService($http, $q, $scope) {
        var pathWords = '/components/zen/words/';
        var words = [];

//        var tenses = {
//            ACT: toACT = function (verb) {
//                if (verb.slice(-1) == 's' || verb.slice(-1) == 'y') {
//                    return verb + 'es';
//                } else {
//                    return verb + 's';
//                }
//            },
//            FUT: toFUT = function (verb) {
//                return ' will' + verb;
//            },
//            INF: toINF = function (verb) {
////                if verb.endswith("e"):
////		return (verb[:-1]+"ing")
////	elif verb.endswith("p"):
////		return (verb+"ping")
////	else:
////		return (verb+"ing")
//                return verb;
//            },
//            PAS: toPAS = function (verb) {
////f verb == "run":
////		verbsl.append("ran")
////	elif verb.endswith("e"):
////		verbsl.append(verb+"d")
////	elif verb.endswith("p"):
////		verbsl.append(verb+"ped")
////	else:
////		verbsl.append(verb+"ed")
//                return verb;
//            },
//        }

        /**
         * @TODO use constanst for types
         */

//        _initialize();

        return {
            words: words,
            getWords: getWords,
            getPhrase: getPhrase
        };

        /**
         *
         * @private
         */
        function getWords() {
            var promises = [];
            var types = [
                'adjective',
                'article',
                'conjs',
                'nouns',
                /**
                 * Präpositionen sind kleine Wörter (an, in, zu),
                 * die normalerweise vor einem Nomen stehen (manchmal auch vor einem Verb im Gerundium).
                 */
                'preposition',
                'subjective',
                'verbs'
            ];

            angular.forEach(types, function (type) {
                var promise = ftowl(type).then(function (response) {
                    var name = response.name;
                    words[name] = response.data;
                });
                promises.push(promise);
            });

            return $q.all(promises);
        }

        /**
         *
         * @param string word
         * @param string lastWord
         * @param string part
         * @returns string
         */
        function toWord(word, lastWord, part) {
            // wenn ein > dann folgt ein Komma!
            if (word.slice(0, 1) == '>') {
                return word.slice(1);
            }
            if (part == 'ARTICLE') {
                // TODO zB Frau"en"
            }
            return ' ' + word;
        }

        /**
         * Returns all available creatives.
         * @param int limit
         * @returns {Array}
         */
        function getPhrase(limit) {
            var ret = [];
            var part = "ARTICLE"; // Das erste Wort startet mit einem Artikel
            var phrase = "";
            var nextWord, lastWord;
            var ccount = 0; // counts conjunctions
            var subj = true; // 1 if subject, 2 if object is next

            var lim = 0;
            while (true) {
                if (part == "CONJ") {
                    ccount++;
                    if (ccount == limit) {
                        return formatPhrase(phrase)
                    }
                }

                nextWord = getRandomWord(part);
                phrase += toWord(nextWord, lastWord, part);
                lastWord = nextWord;
                var ret = getNextPart(part, subj);
                if (ret) {
                    part = ret[0];
                    subj = ret[1];
                }

                if (part == "END") {
                    //# fix 'a' vs 'an'
                    return formatPhrase(phrase);
                }

                /**
                 * TODO
                 * remove this case
                 */
                lim++;
                if (lim > 100) {
                    console.log('endless loop?!');
                    return formatPhrase(phrase);
                    break;
                }
            }

            console.log(phrase);
        }

        /**
         *
         * @param string filename
         * @returns promise
         */
        function ftowl(filename) {
            return $http.get(pathWords + filename + '.txt')
                    .then(function (result) {
                        return {'name': filename, 'data': result.data.trim().split('\n')};
                    }, function (error) {
                        return {'error': error}
                    });
        }

        /**
         * Random word in array
         *
         * @param array words
         * @returns {zen_service_L1.ZenService.choice.a}
         */
        function choice(words) {
            var key = Math.floor(Math.random() * words.length);
            return words[key]; // return random
        }

        /**
         * find the next Word by random
         *
         * @param {type} part
         * @returns {zen_service_L1.ZenService.choice.a}
         */
        function getRandomWord(part) {
            var data = {
                "SUBJECTIVE": words.nouns,
                "OBJ": words.nouns,
                "ADJECTIVE": words.adjective,
                "VERB": words.verbs,
                "ARTICLE": words.article,
                "PREPOSITION": words.preposition,
                "CONJ": words.conjs,
                "END": [""],
            };
            return choice(data[part]);
        }

        /**
         * Ermittelt das nächste Wort welches auf das vorherige folgen soll
         *
         * @param string part
         * @param boolean subj
         * @returns {Array}
         */
        function getNextPart(part, subj) {
            //# markov thing to map parts of speech together
            // Was soll als nächstes folgen?!
            // Die Summe der Gewichtungen muss zusammen 1 ergeben.
            var l = {
                "SUBJECTIVE": [["VERB", 1.0]],
                "OBJ": [
                    ["PREPOSITION", 0.3],
                    ["CONJ", 0.3],
                    ["END", 0.1]
                ],
                "ADJECTIVE": [
                    ["ADJECTIVE", 0.3],
                    ["SUBJECTIVE", 0.7 * subj],
                    ["OBJ", 0.7 * !subj]
                ],
                "VERB": [
                    ["PREPOSITION", 0.5],
                    ["ARTICLE", 0.5]
                ],
                "ARTICLE": [
                    ["ADJECTIVE", 0.6],
                    ["SUBJECTIVE", 0.4 * subj],
                    ["OBJ", 0.4 * !subj]
                ],
                "PREPOSITION": [["ARTICLE", 1.0]],
                "CONJ": [["ARTICLE", 1.0]],
            }[part];

            // Der Wert darf nie direkt 1 sein!
            var c = (Math.random() * (1 - 0.2) + 0.1).toFixed(1);
//            var c = 0.13;
            console.log(c);
            var e = [["None", 1.0]];

            while (c > 0.0) {
                if (l.length < 1) {
                    break;
                }
                e = l.pop();
                c -= e[1];
            }

            if (e[0] == "VERB" || e[0] == "CONJ") {
                subj = !subj;
            }

            return [e[0], subj];
        }

        /**
         * First letter to uppercase + point at the end.
         *
         * @param string phrase
         * @returns string
         */
        function formatPhrase(phrase) {
            return phrase.charAt(1).toUpperCase() + phrase.slice(2) + '.';
        }
    }

})(window.angular);
<commit_msg>remove first directory seperator<commit_after>(function (angular) {
    'use strict';

    angular
            .module('myApp.zen')
            .factory('ZenService', ZenService);

    ZenService.$inject = ['$http', '$q'];

    /**
     *
     * @returns {Object}
     * @constructor
     */
    function ZenService($http, $q, $scope) {
        var pathWords = 'components/zen/words/';
        var words = [];

//        var tenses = {
//            ACT: toACT = function (verb) {
//                if (verb.slice(-1) == 's' || verb.slice(-1) == 'y') {
//                    return verb + 'es';
//                } else {
//                    return verb + 's';
//                }
//            },
//            FUT: toFUT = function (verb) {
//                return ' will' + verb;
//            },
//            INF: toINF = function (verb) {
////                if verb.endswith("e"):
////		return (verb[:-1]+"ing")
////	elif verb.endswith("p"):
////		return (verb+"ping")
////	else:
////		return (verb+"ing")
//                return verb;
//            },
//            PAS: toPAS = function (verb) {
////f verb == "run":
////		verbsl.append("ran")
////	elif verb.endswith("e"):
////		verbsl.append(verb+"d")
////	elif verb.endswith("p"):
////		verbsl.append(verb+"ped")
////	else:
////		verbsl.append(verb+"ed")
//                return verb;
//            },
//        }

        /**
         * @TODO use constanst for types
         */

//        _initialize();

        return {
            words: words,
            getWords: getWords,
            getPhrase: getPhrase
        };

        /**
         *
         * @private
         */
        function getWords() {
            var promises = [];
            var types = [
                'adjective',
                'article',
                'conjs',
                'nouns',
                /**
                 * Präpositionen sind kleine Wörter (an, in, zu),
                 * die normalerweise vor einem Nomen stehen (manchmal auch vor einem Verb im Gerundium).
                 */
                'preposition',
                'subjective',
                'verbs'
            ];

            angular.forEach(types, function (type) {
                var promise = ftowl(type).then(function (response) {
                    var name = response.name;
                    words[name] = response.data;
                });
                promises.push(promise);
            });

            return $q.all(promises);
        }

        /**
         *
         * @param string word
         * @param string lastWord
         * @param string part
         * @returns string
         */
        function toWord(word, lastWord, part) {
            // wenn ein > dann folgt ein Komma!
            if (word.slice(0, 1) == '>') {
                return word.slice(1);
            }
            if (part == 'ARTICLE') {
                // TODO zB Frau"en"
            }
            return ' ' + word;
        }

        /**
         * Returns all available creatives.
         * @param int limit
         * @returns {Array}
         */
        function getPhrase(limit) {
            var ret = [];
            var part = "ARTICLE"; // Das erste Wort startet mit einem Artikel
            var phrase = "";
            var nextWord, lastWord;
            var ccount = 0; // counts conjunctions
            var subj = true; // 1 if subject, 2 if object is next

            var lim = 0;
            while (true) {
                if (part == "CONJ") {
                    ccount++;
                    if (ccount == limit) {
                        return formatPhrase(phrase)
                    }
                }

                nextWord = getRandomWord(part);
                phrase += toWord(nextWord, lastWord, part);
                lastWord = nextWord;
                var ret = getNextPart(part, subj);
                if (ret) {
                    part = ret[0];
                    subj = ret[1];
                }

                if (part == "END") {
                    //# fix 'a' vs 'an'
                    return formatPhrase(phrase);
                }

                /**
                 * TODO
                 * remove this case
                 */
                lim++;
                if (lim > 100) {
                    console.log('endless loop?!');
                    return formatPhrase(phrase);
                    break;
                }
            }

            console.log(phrase);
        }

        /**
         *
         * @param string filename
         * @returns promise
         */
        function ftowl(filename) {
            return $http.get(pathWords + filename + '.txt')
                    .then(function (result) {
                        return {'name': filename, 'data': result.data.trim().split('\n')};
                    }, function (error) {
                        return {'error': error}
                    });
        }

        /**
         * Random word in array
         *
         * @param array words
         * @returns {zen_service_L1.ZenService.choice.a}
         */
        function choice(words) {
            var key = Math.floor(Math.random() * words.length);
            return words[key]; // return random
        }

        /**
         * find the next Word by random
         *
         * @param {type} part
         * @returns {zen_service_L1.ZenService.choice.a}
         */
        function getRandomWord(part) {
            var data = {
                "SUBJECTIVE": words.nouns,
                "OBJ": words.nouns,
                "ADJECTIVE": words.adjective,
                "VERB": words.verbs,
                "ARTICLE": words.article,
                "PREPOSITION": words.preposition,
                "CONJ": words.conjs,
                "END": [""],
            };
            return choice(data[part]);
        }

        /**
         * Ermittelt das nächste Wort welches auf das vorherige folgen soll
         *
         * @param string part
         * @param boolean subj
         * @returns {Array}
         */
        function getNextPart(part, subj) {
            //# markov thing to map parts of speech together
            // Was soll als nächstes folgen?!
            // Die Summe der Gewichtungen muss zusammen 1 ergeben.
            var l = {
                "SUBJECTIVE": [["VERB", 1.0]],
                "OBJ": [
                    ["PREPOSITION", 0.3],
                    ["CONJ", 0.3],
                    ["END", 0.1]
                ],
                "ADJECTIVE": [
                    ["ADJECTIVE", 0.3],
                    ["SUBJECTIVE", 0.7 * subj],
                    ["OBJ", 0.7 * !subj]
                ],
                "VERB": [
                    ["PREPOSITION", 0.5],
                    ["ARTICLE", 0.5]
                ],
                "ARTICLE": [
                    ["ADJECTIVE", 0.6],
                    ["SUBJECTIVE", 0.4 * subj],
                    ["OBJ", 0.4 * !subj]
                ],
                "PREPOSITION": [["ARTICLE", 1.0]],
                "CONJ": [["ARTICLE", 1.0]],
            }[part];

            // Der Wert darf nie direkt 1 sein!
            var c = (Math.random() * (1 - 0.2) + 0.1).toFixed(1);
//            var c = 0.13;
            console.log(c);
            var e = [["None", 1.0]];

            while (c > 0.0) {
                if (l.length < 1) {
                    break;
                }
                e = l.pop();
                c -= e[1];
            }

            if (e[0] == "VERB" || e[0] == "CONJ") {
                subj = !subj;
            }

            return [e[0], subj];
        }

        /**
         * First letter to uppercase + point at the end.
         *
         * @param string phrase
         * @returns string
         */
        function formatPhrase(phrase) {
            return phrase.charAt(1).toUpperCase() + phrase.slice(2) + '.';
        }
    }

})(window.angular);
<|endoftext|>


<commit_before>/**
 * @file 警告
 * @author errorrik(errorrik@gmail.com)
 */

var prefix = '[SAN WARNING] ';

/**
 * 警告
 *
 * @param {Object} message 警告信息
 */
function warn(message) {

    /* eslint-disable no-console */
    if (typeof console === 'object' && console.warn) {
        console.warn(prefix + message);
    }
    else {
        // 防止警告中断调用堆栈
        setTimeout(function () {
            throw new Error(prefix + message);
        });
    }
    /* eslint-enable no-console */
}

exports = module.exports = warn;
<commit_msg>opti warn<commit_after>/**
 * @file 开发时的警告提示
 * @author errorrik(errorrik@gmail.com)
 */

// #[begin] error
/**
 * 开发时的警告提示
 *
 * @param {Object} message 警告信息
 */
function warn(message) {
    message = '[SAN WARNING] ' + message;

    /* eslint-disable no-console */
    if (typeof console === 'object' && console.warn) {
        console.warn(message);
    }
    else {
        // 防止警告中断调用堆栈
        setTimeout(function () {
            throw new Error(message);
        }, 0);
    }
    /* eslint-enable no-console */
}
// #[end]

exports = module.exports = warn;
<|endoftext|>


<commit_before>// @flow

import React, { Component } from 'react';
import classNames from 'classnames';
import { SHAPES, COLORS } from '../constants/tetromino';
import { attachPointerDownEvent } from '../utils/events';
import { getUser, getPlayingUsers } from '../reducers/game';
import Tetromino from './Tetromino';
import Button from './Button';

import type { Game } from '../types';

export type Props = {
  game: Game,
  userId: number,
  showMenuButton: boolean,
  onMenu: Function
};

export default class GamePanel extends Component<Props> {
  /**
   * The game panel contains:
   * - The next Tetromino to be inserted
   * - The score and lines cleared
   * - Start or pause/resume controls
   */
  renderGameButton() {
    const { game, userId, showMenuButton, onMenu } = this.props;
    const { activeUserId } = game;

    // Screens take over controls after the menu button has been pressed
    if (!showMenuButton) {
      return;
    }

    const curUser = getUser(game, userId);
    const playingUsers = getPlayingUsers(game);

    // Allow user to open menu (and thus become a watcher) when it's not their
    // turn or when the user is solo
    if (playingUsers.length === 1 || (curUser && activeUserId !== curUser.id)) {
      return <Button {...attachPointerDownEvent(onMenu)}>Menu</Button>;
    }

    return <Button disabled>Menu</Button>;
  }

  render() {
    const { game, userId } = this.props;

    return (

Flatris

Score

{game.score}

Lines Cleared

{game.lines}

Next Shape

          <Tetromino
            key={game.nextTetromino}
            color={COLORS[game.nextTetromino]}
            grid={SHAPES[game.nextTetromino]}
          />

          {game.users.map(user => {
            const classes = classNames('user', {
              'user-active': user.id === game.activeUserId,
              'user-watching': user.status === 'WATCHING'
            });

            return (

                {userId === user.id && You: }
                User #{user.id}

            );
          })}

{this.renderGameButton()}

    );
  }

  getNextTetrominoClass() {
    const { nextTetromino } = this.props.game;

    // We use this extra class to position tetrominoes differently from CSS
    // based on their type
    return `next-tetromino next-tetromino-${nextTetromino}`;
  }
}
<commit_msg>Redesign game panel and make more room for users<commit_after>// @flow

import React, { Component } from 'react';
import classNames from 'classnames';
import { SHAPES, COLORS } from '../constants/tetromino';
import { attachPointerDownEvent } from '../utils/events';
import { getUser, getPlayingUsers } from '../reducers/game';
import Tetromino from './Tetromino';
import Button from './Button';

import type { Game } from '../types';

export type Props = {
  game: Game,
  userId: number,
  showMenuButton: boolean,
  onMenu: Function
};

export default class GamePanel extends Component<Props> {
  /**
   * The game panel contains:
   * - The next Tetromino to be inserted
   * - The score and lines cleared
   * - Start or pause/resume controls
   */
  renderGameButton() {
    const { game, userId, showMenuButton, onMenu } = this.props;
    const { activeUserId } = game;

    // Screens take over controls after the menu button has been pressed
    if (!showMenuButton) {
      return;
    }

    const curUser = getUser(game, userId);
    const playingUsers = getPlayingUsers(game);

    // Allow user to open menu (and thus become a watcher) when it's not their
    // turn or when the user is solo
    if (playingUsers.length === 1 || (curUser && activeUserId !== curUser.id)) {
      return <Button {...attachPointerDownEvent(onMenu)}>Menu</Button>;
    }

    return <Button disabled>Menu</Button>;
  }

  render() {
    const { game, userId } = this.props;
    const { maxPlayers } = game;
    const playingUsers = getPlayingUsers(game);

    return (

            World Tetris

Score

{game.score}

Lines

{game.lines}

Next

          <Tetromino
            key={game.nextTetromino}
            color={COLORS[game.nextTetromino]}
            grid={SHAPES[game.nextTetromino]}
          />

          Players
          {game.status !== 'PENDING' &&
            ` ${playingUsers.length} / ${maxPlayers}`}

          {game.users.map(user => {
            const classes = classNames('user', {
              'user-active': user.id === game.activeUserId,
              'user-watching': user.status === 'WATCHING'
            });

            return (

                {userId === user.id && You: }
                User #{user.id}

            );
          })}

{this.renderGameButton()}

    );
  }

  getNextTetrominoClass() {
    const { nextTetromino } = this.props.game;

    // We use this extra class to position tetrominoes differently from CSS
    // based on their type
    return `next-tetromino next-tetromino-${nextTetromino}`;
  }
}
<|endoftext|>


<commit_before>/**
 * 优化 typeof 获取未知对象类型
 * @param  {mixed} mixed
 * @return {string}       Number|String|Object|Array|Function
 */
export const typeOf = mixed =>
  Object.prototype.toString.apply(mixed).match(/\[object (\w+)\]/)[1];

/**
 * 对象数据扩充
 * @param  {Object} obj 目标对象
 * @param  {object} ext 扩充对象
 * @return {object}
 */
export const assign = (obj = {}, ext = {}) => {
  for (let k in ext) {
    if (ext.hasOwnProperty(k)) {
      obj[k] = ext[k];
    }
  }
  return obj;
};

/**
 * object => serialize
 * @param  {Object} obj [description]
 * @return {[type]}     [description]
 */
export const serialize = (obj = {}) =>
  Object.keys(obj).map(k =>
    `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`
  ).join('&');

/**
 * hash search 转对象
 * ?a=a&b=b => {a:"a",b:"b"}
 * @param  {string} hash
 * @return {obj}
 */
export const search2obj = (hash = "") => {
  let ret = {},
    seg = decodeURIComponent(hash).replace(/^\?/, '').split('&'),
    len = seg.length,
    i = 0,
    s;
  for (; i < len; i++) {
    if (!seg[i]) {
      continue;
    }
    s = seg[i].split('=');
    ret[s[0]] = s[1];
  }
  return ret;
};

/**
 * xmlhttp 请求
 * @param  {string} url     请求地址
 * @param  {object} options 设置
 * @return {callback}
 */
export const xhr = (url, options = {}) => {
  let opts = assign({
      method: "GET",
      data: {},
      headers: {},
      cache: false,
      type: "json",
      done() {},
      fail() {},
      progress() {},
      complete() {}
    }, options),
    xhr, progress = 0,
    send_data = [],
    has_q = url.split("/").pop().search(/\?/) !== -1;
  // 是否有缓存
  if (!opts.cache) {
    url += (has_q ? "&" : "?") + "_=" + Math.random();
    has_q = true;
  }
  // 整理发送数据
  if(serialize(opts.data) !== ""){
    send_data.push(serialize(opts.data));
  }
  // 如果是put /post 则用formdata
  if (/^put$|^post$/i.test(opts.method)) {
    opts.headers["Content-type"] = "application/x-www-form-urlencoded";
  } else if(send_data.length > 0) {
    url += (has_q ? "&" : "?") + send_data;
  }
  xhr = new XMLHttpRequest();
  xhr.open(opts.method, url, true);
  for (let k in opts.headers) {
    xhr.setRequestHeader(k, opts.headers[k]);
  }
  // 如果支持进度条
  xhr.upload.onprogress = xhr.onprogress = (e) => {
    if (e.lengthComputable) {
      progress = Math.round(e.loaded * 100 / e.total);
      opts.progress.call(e.target, progress);
    }
  };
  xhr.addEventListener('load', (e) => {
    let res;
    if (e.target.status === 200 || e.target.status === 304) {
      res = e.target.responseText;
      if (opts.type === "json") {
        res = JSON.parse(res);
      }
      opts.done.call(e.target, res);
    }
    else{
      opts.fail.call(e.target, e.target.status);
    }
  }, false);
  xhr.addEventListener('error', () => {
    opts.fail();
  }, false);
  xhr.addEventListener('loadend', () => {
    opts.complete();
  }, false);
  // done().fail().progress()
  xhr.done = fn => {
    opts.done = fn;
    return xhr;
  }
  xhr.fail = fn => {
    opts.fail = fn;
    return xhr;
  }
  xhr.progress = fn => {
    opts.progress = fn;
    return xhr;
  }
  xhr.complete = fn => {
    opts.complete = fn;
    return xhr;
  }
  xhr.send(send_data);
  return xhr;
};

/**
 * 清除字符串中指定的标签
 * @param  {string} tag 标签名称
 * @param  {string} str 字符串
 * @return {string}
 */
export const tagRemove = (tag, str) => {
  return str.replace(new RegExp(`<${tag}(.|\\s)*?\\/${tag}>`, "g"), "");
};

/**
 * 解析字符串中的标签内容
 * @param  {string} tag 标签名称
 * @param  {string} 解析字符串
 * @return {string}
 */
export const tagContent = function(tag, str) {
  let re = new RegExp(`<${tag}\\b[^>]*>([\\s\\S]*?)<\\/${tag}>`, "gm"),
    text = "";
  for (let match = re.exec(str); match; match = re.exec(str)) {
    text += match[1];
  }
  return text;
};

export const cookie = {
  /**
   * 设置 cookie
   * @param  {string} name  项
   * @param  {String} value 值
   * @param  {Object} opts  扩展配置
   * @return {null}
   */
  set(name, value = "", options) {
    let data = {},
      cookies = [],
      opts = assign({
        path: "/",
        domain: ""
      }, options);
    if (!name ||
      /^(?:expires|max\-age|path|domain|secure)$/i.test(name)) {
      throw new Error("Cookie name is required and do not use cookie reserved keywords.");
    }
    data[encodeURIComponent(name)] = encodeURIComponent(value);
    if (opts["expires"]) {
      throw new Error("Please use max-age to replace expires");
    }
    for (let opt in opts) {
      data[opt] = opts[opt];
    }
    for (let key in data) {
      cookies.push(`${key}=${data[key]}`);
    }
    cookies = cookies.join("; ");
    document.cookie = `${cookies};`;
  },

  /**
   * 删除cookie某项
   * @param  {string} name
   * @return {null}
   */
  remove(name) {
    this.set(name, "", {
      "max-age": 0
    });
  },

  /**
   * 获取cookie某项
   * @param  {String} key [description]
   * @return {[type]}      [description]
   */
  get(key = "") {
    for (let _cookie of document.cookie.split("; ")) {
      let [name, value] = _cookie.split("=");
      if (key !== "" && key === name) {
        return decodeURIComponent(value);
      }
    }
  }

};

/**
 * 动态加载文件,建议私有化
 * @param  {String} type   加载远程文件类型 [script,link,img]
 * @param  {String} url    地址
 * @param  {Object} opts   附加配置
 * @return {Element}
 */
export const loadFile = (type = "script", url, options) => {
  let el = document.createElement(type),
    src = {
      script: "src",
      link: "href",
      img: "src"
    },
    opts = assign({
      position: "head",
      attrs: {},
      success() {},
      error() {}
    }, options);

  if (!src.hasOwnProperty(type)) {
    throw new Error(`File type:${type} is not support dynamic load.`);
  }
  if (typeof url === "undefined") {
    throw new Error("Load file url is required.");
  }
  // 扩展属性
  if (Object.keys(opts.attrs).length) {
    for (let _attr in opts.attrs) {
      el[_attr] = opts.attrs[_attr];
    }
  }
  el[src[type]] = url;
  if (type === "link") {
    el.rel = "stylesheet";
  }
  el.addEventListener("load", opts.success, false);
  el.addEventListener("error", opts.error, false);
  document[opts.position].appendChild(el);
  return el;
};

/**
 * 删除动态载入的文件标签，loadFile失败后可用，建议私有化
 * @param  {String} type [description]
 * @param  {String} rel  [description]
 * @return {null}      [description]
 */
export const removeFile = (type = "script", position = "head", rel) => {
  let i = 0,
    tags = document[position].getElementsByTagName(type);
  for (; i < tags.length; i++) {
    if (tags[i].rel === rel)
      tags[i].parentNode.removeChild(tags[i]);
  }
};

/**
 * 生成 hash
 * @param  {string} s
 * @return {hash}
 */
export const hashCode = s => {
  return s.split("").reduce((a, b) => {
    a = ((a << 5) - a) + b.charCodeAt(0);
    return a & a;
  }, 0);
};

/**
 * 在浏览器关闭之前缓存ajax获取的json数据
 * @param  {String} url           [description]
 * @param  {Object} options [description]
 * @return {Object}               [description]
 */
export const cacheJSON = (url, options={}) => {
  const name = `_hc_json_${url.split("/").pop().split(".")[0]}`;
  let callback = assign({
    force: false, // true的时候强制ajax获取
    done(){}, fail(){}
  }, options);
  if ("localStorage" in window && cookie.get(`${name}`) && !callback.force) {
    setTimeout(() => {
      callback.done.call(null, JSON.parse(window.localStorage.getItem(name)));
    }, 0);
  }
  else{
    xhr(url).done(res => {
      // 关闭浏览器失效，保证下次浏览获取新的资源列表
      cookie.set(name, "y");
      window.localStorage &&
      window.localStorage.setItem(name, JSON.stringify(res));
      callback.done.call(null, res);
    })
    .fail(status => {
      callback.fail.call(null, status);
    });
  }
  // 支持 done().fail()
  callback.done = fn => {
    callback.done = fn;
    return callback;
  };
  callback.fail = fn => {
    callback.fail = fn;
    return callback;
  };
  return callback;
};
<commit_msg>兼容ie9<commit_after>/**
 * 优化 typeof 获取未知对象类型
 * @param  {mixed} mixed
 * @return {string}       Number|String|Object|Array|Function
 */
export const typeOf = mixed =>
  Object.prototype.toString.apply(mixed).match(/\[object (\w+)\]/)[1];

/**
 * 对象数据扩充
 * @param  {Object} obj 目标对象
 * @param  {object} ext 扩充对象
 * @return {object}
 */
export const assign = (obj = {}, ext = {}) => {
  for (let k in ext) {
    if (ext.hasOwnProperty(k)) {
      obj[k] = ext[k];
    }
  }
  return obj;
};

/**
 * object => serialize
 * @param  {Object} obj [description]
 * @return {[type]}     [description]
 */
export const serialize = (obj = {}) =>
  Object.keys(obj).map(k =>
    `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`
  ).join('&');

/**
 * hash search 转对象
 * ?a=a&b=b => {a:"a",b:"b"}
 * @param  {string} hash
 * @return {obj}
 */
export const search2obj = (hash = "") => {
  let ret = {},
    seg = decodeURIComponent(hash).replace(/^\?/, '').split('&'),
    len = seg.length,
    i = 0,
    s;
  for (; i < len; i++) {
    if (!seg[i]) {
      continue;
    }
    s = seg[i].split('=');
    ret[s[0]] = s[1];
  }
  return ret;
};

/**
 * xmlhttp 请求
 * @param  {string} url     请求地址
 * @param  {object} options 设置
 * @return {callback}
 */
export const xhr = (url, options = {}) => {
  let opts = assign({
      method: "GET",
      data: {},
      headers: {},
      cache: false,
      type: "json",
      done() {},
      fail() {},
      progress() {},
      complete() {}
    }, options),
    xhr, progress = 0,
    send_data = [],
    has_q = url.split("/").pop().search(/\?/) !== -1;
  // 是否有缓存
  if (!opts.cache) {
    url += (has_q ? "&" : "?") + "_=" + Math.random();
    has_q = true;
  }
  // 整理发送数据
  if(serialize(opts.data) !== ""){
    send_data.push(serialize(opts.data));
  }
  // 如果是put /post 则用formdata
  if (/^put$|^post$/i.test(opts.method)) {
    opts.headers["Content-type"] = "application/x-www-form-urlencoded";
  } else if(send_data.length > 0) {
    url += (has_q ? "&" : "?") + send_data;
  }
  xhr = new XMLHttpRequest();
  xhr.open(opts.method, url, true);
  for (let k in opts.headers) {
    xhr.setRequestHeader(k, opts.headers[k]);
  }
  // 如果支持进度条
  let progressFn = (e) => {
    if (e.lengthComputable) {
      progress = Math.round(e.loaded * 100 / e.total);
      opts.progress.call(e.target, progress);
    }
  };
  if(xhr.upload){
    xhr.upload.addEventListener('progress', progressFn, false);
  }
  xhr.addEventListener('progress', progressFn, false);
  xhr.addEventListener('load', (e) => {
    let res;
    if (e.target.status === 200 || e.target.status === 304) {
      res = e.target.responseText;
      if (opts.type === "json") {
        res = JSON.parse(res);
      }
      opts.done.call(e.target, res);
    }
    else{
      opts.fail.call(e.target, e.target.status);
    }
  }, false);
  xhr.addEventListener('error', () => {
    opts.fail();
  }, false);
  xhr.addEventListener('loadend', () => {
    opts.complete();
  }, false);
  // done().fail().progress()
  xhr.done = fn => {
    opts.done = fn;
    return xhr;
  }
  xhr.fail = fn => {
    opts.fail = fn;
    return xhr;
  }
  xhr.progress = fn => {
    opts.progress = fn;
    return xhr;
  }
  xhr.complete = fn => {
    opts.complete = fn;
    return xhr;
  }
  xhr.send(send_data);
  return xhr;
};

/**
 * 清除字符串中指定的标签
 * @param  {string} tag 标签名称
 * @param  {string} str 字符串
 * @return {string}
 */
export const tagRemove = (tag, str) => {
  return str.replace(new RegExp(`<${tag}(.|\\s)*?\\/${tag}>`, "g"), "");
};

/**
 * 解析字符串中的标签内容
 * @param  {string} tag 标签名称
 * @param  {string} 解析字符串
 * @return {string}
 */
export const tagContent = function(tag, str) {
  let re = new RegExp(`<${tag}\\b[^>]*>([\\s\\S]*?)<\\/${tag}>`, "gm"),
    text = "";
  for (let match = re.exec(str); match; match = re.exec(str)) {
    text += match[1];
  }
  return text;
};

export const cookie = {
  /**
   * 设置 cookie
   * @param  {string} name  项
   * @param  {String} value 值
   * @param  {Object} opts  扩展配置
   * @return {null}
   */
  set(name, value = "", options) {
    let data = {},
      cookies = [],
      opts = assign({
        path: "/",
        domain: ""
      }, options);
    if (!name ||
      /^(?:expires|max\-age|path|domain|secure)$/i.test(name)) {
      throw new Error("Cookie name is required and do not use cookie reserved keywords.");
    }
    data[encodeURIComponent(name)] = encodeURIComponent(value);
    if (opts["expires"]) {
      throw new Error("Please use max-age to replace expires");
    }
    for (let opt in opts) {
      data[opt] = opts[opt];
    }
    for (let key in data) {
      cookies.push(`${key}=${data[key]}`);
    }
    cookies = cookies.join("; ");
    document.cookie = `${cookies};`;
  },

  /**
   * 删除cookie某项
   * @param  {string} name
   * @return {null}
   */
  remove(name) {
    this.set(name, "", {
      "max-age": 0
    });
  },

  /**
   * 获取cookie某项
   * @param  {String} key [description]
   * @return {[type]}      [description]
   */
  get(key = "") {
    for (let _cookie of document.cookie.split("; ")) {
      let [name, value] = _cookie.split("=");
      if (key !== "" && key === name) {
        return decodeURIComponent(value);
      }
    }
  }

};

/**
 * 动态加载文件,建议私有化
 * @param  {String} type   加载远程文件类型 [script,link,img]
 * @param  {String} url    地址
 * @param  {Object} opts   附加配置
 * @return {Element}
 */
export const loadFile = (type = "script", url, options) => {
  let el = document.createElement(type),
    src = {
      script: "src",
      link: "href",
      img: "src"
    },
    opts = assign({
      position: "head",
      attrs: {},
      success() {},
      error() {}
    }, options);

  if (!src.hasOwnProperty(type)) {
    throw new Error(`File type:${type} is not support dynamic load.`);
  }
  if (typeof url === "undefined") {
    throw new Error("Load file url is required.");
  }
  // 扩展属性
  if (Object.keys(opts.attrs).length) {
    for (let _attr in opts.attrs) {
      el[_attr] = opts.attrs[_attr];
    }
  }
  el[src[type]] = url;
  if (type === "link") {
    el.rel = "stylesheet";
  }
  el.addEventListener("load", opts.success, false);
  el.addEventListener("error", opts.error, false);
  document[opts.position].appendChild(el);
  return el;
};

/**
 * 删除动态载入的文件标签，loadFile失败后可用，建议私有化
 * @param  {String} type [description]
 * @param  {String} rel  [description]
 * @return {null}      [description]
 */
export const removeFile = (type = "script", position = "head", rel) => {
  let i = 0,
    tags = document[position].getElementsByTagName(type);
  for (; i < tags.length; i++) {
    if (tags[i].rel === rel)
      tags[i].parentNode.removeChild(tags[i]);
  }
};

/**
 * 生成 hash
 * @param  {string} s
 * @return {hash}
 */
export const hashCode = s => {
  return s.split("").reduce((a, b) => {
    a = ((a << 5) - a) + b.charCodeAt(0);
    return a & a;
  }, 0);
};

/**
 * 在浏览器关闭之前缓存ajax获取的json数据
 * @param  {String} url           [description]
 * @param  {Object} options [description]
 * @return {Object}               [description]
 */
export const cacheJSON = (url, options={}) => {
  const name = `_hc_json_${url.split("/").pop().split(".")[0]}`;
  let callback = assign({
    force: false, // true的时候强制ajax获取
    done(){}, fail(){}
  }, options);
  if ("localStorage" in window && cookie.get(`${name}`) && !callback.force) {
    setTimeout(() => {
      callback.done.call(null, JSON.parse(window.localStorage.getItem(name)));
    }, 0);
  }
  else{
    xhr(url).done(res => {
      // 关闭浏览器失效，保证下次浏览获取新的资源列表
      cookie.set(name, "y");
      window.localStorage &&
      window.localStorage.setItem(name, JSON.stringify(res));
      callback.done.call(null, res);
    })
    .fail(status => {
      callback.fail.call(null, status);
    });
  }
  // 支持 done().fail()
  callback.done = fn => {
    callback.done = fn;
    return callback;
  };
  callback.fail = fn => {
    callback.fail = fn;
    return callback;
  };
  return callback;
};
<|endoftext|>


<commit_before>import { _, config, t } from 'azk';
var winston = require('winston');

var log = new winston.Logger();

// File log
log.add(winston.transports.File, {
  filename: config('paths:log'),
  level: config('logs_level:file')
});

// Console log
var console_opts = {
  handleExceptions: true,
  colorize: true,
  level: config('logs_level:console')
}
if (config('env') != 'test') {
  log.add(winston.transports.Console, console_opts);
}

_.each(winston.levels, (__, method) => {
  log[`${method}_t`] = function(...args) {
    return this[method](t(...args));
  }
});

log.setConsoleLevel = (level) => {
  log.remove(winston.transports.Console);
  console_opts.level = level;
  log.add(winston.transports.Console, console_opts);
}

export { log }
<commit_msg>Changing log file format.<commit_after>import { _, config, t } from 'azk';
var winston = require('winston');

var log = new winston.Logger();

// File log
log.add(winston.transports.File, {
  filename: config('paths:log'),
  level: config('logs_level:file'),
  colorize: true,
  json: false,
});

// Console log
var console_opts = {
  handleExceptions: true,
  colorize: true,
  level: config('logs_level:console')
}
if (config('env') != 'test') {
  log.add(winston.transports.Console, console_opts);
}

_.each(winston.levels, (__, method) => {
  log[`${method}_t`] = function(...args) {
    return this[method](t(...args));
  }
});

log.setConsoleLevel = (level) => {
  log.remove(winston.transports.Console);
  console_opts.level = level;
  log.add(winston.transports.Console, console_opts);
}

export { log }
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import ClassNames from 'classnames';
import MenuItem from './MenuItem';
import { events, utils } from '../utils';
import style from './style.menu';

const POSITION = {
  AUTO: 'auto',
  STATIC: 'static',
  TOP_LEFT: 'top-left',
  TOP_RIGHT: 'top-right',
  BOTTOM_LEFT: 'bottom-left',
  BOTTOM_RIGHT: 'bottom-right'
};

class Menu extends React.Component {
  static propTypes = {
    active: React.PropTypes.bool,
    children: React.PropTypes.node,
    className: React.PropTypes.string,
    onHide: React.PropTypes.func,
    onSelect: React.PropTypes.func,
    onShow: React.PropTypes.func,
    outline: React.PropTypes.bool,
    position: React.PropTypes.string,
    ripple: React.PropTypes.bool,
    selectable: React.PropTypes.bool,
    selected: React.PropTypes.any
  };

  static defaultProps = {
    active: false,
    outline: true,
    position: POSITION.STATIC,
    ripple: true,
    selectable: true
  };

  state = {
    active: this.props.active,
    rippled: false
  };

  componentDidMount () {
    setTimeout(() => {
      const { width, height } = this.refs.menu.getBoundingClientRect();
      const position = this.props.position === POSITION.AUTO ? this.calculatePosition() : this.props.position;
      this.setState({ position, width, height });
    });
  }

  componentWillReceiveProps (nextProps) {
    if (this.props.position !== nextProps.position) {
      const position = nextProps.position === POSITION.AUTO ? this.calculatePosition() : nextProps.position;
      this.setState({ position });
    }
  }

  shouldComponentUpdate (nextProps, nextState) {
    if (!this.state.active && nextState.active && this.props.position === POSITION.AUTO) {
      const position = this.calculatePosition();
      if (this.state.position !== position) {
        this.setState({ position, active: false }, () => {
          setTimeout(() => {this.setState({active: true}); }, 20);
        });
        return false;
      }
    }
    return true;
  }

  componentWillUpdate (nextProps, nextState) {
    if (!this.state.active && nextState.active) {
      events.addEventsToDocument({click: this.handleDocumentClick});
    }
  }

  componentDidUpdate (prevProps, prevState) {
    if (prevState.active && !this.state.active) {
      if (this.props.onHide) this.props.onHide();
      events.removeEventsFromDocument({click: this.handleDocumentClick});
    } else if (!prevState.active && this.state.active && this.props.onShow) {
      this.props.onShow();
    }
  }

  componentWillUnmount () {
    if (this.state.active) {
      events.removeEventsFromDocument({click: this.handleDocumentClick});
    }
  }

  handleDocumentClick = (event) => {
    if (this.state.active && !events.targetIsDescendant(event, ReactDOM.findDOMNode(this))) {
      this.setState({active: false, rippled: false});
    }
  };

  handleSelect = (item) => {
    const { value, onClick } = item.props;
    this.setState({ active: false, rippled: this.props.ripple }, () => {
      if (onClick) onClick();
      if (this.props.onSelect) this.props.onSelect(value);
    });
  };

  calculatePosition () {
    const parentNode = ReactDOM.findDOMNode(this).parentNode;
    if (!parentNode) return;
    const {top, left, height, width} = parentNode.getBoundingClientRect();
    const {height: wh, width: ww} = utils.getViewport();
    const toTop = top < ((wh / 2) - height / 2);
    const toLeft = left < ((ww / 2) - width / 2);
    return `${toTop ? 'top' : 'bottom'}-${toLeft ? 'left' : 'right'}`;
  }

  getMenuStyle () {
    const { width, height, position } = this.state;
    if (position !== POSITION.STATIC) {
      if (this.state.active) {
        return { clip: `rect(0 ${width}px ${height}px 0)` };
      } else if (position === POSITION.TOP_RIGHT) {
        return { clip: `rect(0 ${width}px 0 ${width}px)` };
      } else if (position === POSITION.BOTTOM_RIGHT) {
        return { clip: `rect(${height}px ${width}px ${height}px ${width}px)` };
      } else if (position === POSITION.BOTTOM_LEFT) {
        return { clip: `rect(${height}px 0 ${height}px 0)` };
      } else if (position === POSITION.TOP_LEFT) {
        return { clip: 'rect(0 0 0 0)' };
      }
    }
  }

  getRootStyle () {
    if (this.state.position !== POSITION.STATIC) {
      return { width: this.state.width, height: this.state.height };
    }
  }

  renderItems () {
    return React.Children.map(this.props.children, (item) => {
      if (!item) return item;
      if (item.type === MenuItem) {
        return React.cloneElement(item, {
          ripple: item.props.ripple || this.props.ripple,
          selected: typeof item.props.value !== 'undefined' && this.props.selectable && item.props.value === this.props.selected,
          onClick: this.handleSelect.bind(this, item)
        });
      } else {
        return React.cloneElement(item);
      }
    });
  }

  show () {
    const { width, height } = this.refs.menu.getBoundingClientRect();
    this.setState({active: true, width, height});
  }

  hide () {
    this.setState({active: false});
  }

  render () {
    const outlineStyle = { width: this.state.width, height: this.state.height };
    const className = ClassNames([style.root, style[this.state.position]], {
      [style.active]: this.state.active,
      [style.rippled]: this.state.rippled
    }, this.props.className);

    return (

        {this.props.outline ?

 : null}

          {this.renderItems()}

    );
  }
}

export default Menu;
<commit_msg>Cleanup timers when Menu component unmounts to prevent calls on undefined objects<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import ClassNames from 'classnames';
import MenuItem from './MenuItem';
import { events, utils } from '../utils';
import style from './style.menu';

const POSITION = {
  AUTO: 'auto',
  STATIC: 'static',
  TOP_LEFT: 'top-left',
  TOP_RIGHT: 'top-right',
  BOTTOM_LEFT: 'bottom-left',
  BOTTOM_RIGHT: 'bottom-right'
};

class Menu extends React.Component {
  static propTypes = {
    active: React.PropTypes.bool,
    children: React.PropTypes.node,
    className: React.PropTypes.string,
    onHide: React.PropTypes.func,
    onSelect: React.PropTypes.func,
    onShow: React.PropTypes.func,
    outline: React.PropTypes.bool,
    position: React.PropTypes.string,
    ripple: React.PropTypes.bool,
    selectable: React.PropTypes.bool,
    selected: React.PropTypes.any
  };

  static defaultProps = {
    active: false,
    outline: true,
    position: POSITION.STATIC,
    ripple: true,
    selectable: true
  };

  state = {
    active: this.props.active,
    rippled: false
  };

  componentDidMount () {
    this.positionTimeoutHandle = setTimeout(() => {
      const { width, height } = this.refs.menu.getBoundingClientRect();
      const position = this.props.position === POSITION.AUTO ? this.calculatePosition() : this.props.position;
      this.setState({ position, width, height });
    });
  }

  componentWillReceiveProps (nextProps) {
    if (this.props.position !== nextProps.position) {
      const position = nextProps.position === POSITION.AUTO ? this.calculatePosition() : nextProps.position;
      this.setState({ position });
    }
  }

  shouldComponentUpdate (nextProps, nextState) {
    if (!this.state.active && nextState.active && this.props.position === POSITION.AUTO) {
      const position = this.calculatePosition();
      if (this.state.position !== position) {
        this.setState({ position, active: false }, () => {
          this.activateTimeoutHandle = setTimeout(() => {this.setState({active: true}); }, 20);
        });
        return false;
      }
    }
    return true;
  }

  componentWillUpdate (nextProps, nextState) {
    if (!this.state.active && nextState.active) {
      events.addEventsToDocument({click: this.handleDocumentClick});
    }
  }

  componentDidUpdate (prevProps, prevState) {
    if (prevState.active && !this.state.active) {
      if (this.props.onHide) this.props.onHide();
      events.removeEventsFromDocument({click: this.handleDocumentClick});
    } else if (!prevState.active && this.state.active && this.props.onShow) {
      this.props.onShow();
    }
  }

  componentWillUnmount () {
    if (this.state.active) {
      events.removeEventsFromDocument({click: this.handleDocumentClick});
    }
    clearTimeout(this.positionTimeoutHandle)
    clearTimeout(this.activateTimeoutHandle)
  }

  handleDocumentClick = (event) => {
    if (this.state.active && !events.targetIsDescendant(event, ReactDOM.findDOMNode(this))) {
      this.setState({active: false, rippled: false});
    }
  };

  handleSelect = (item) => {
    const { value, onClick } = item.props;
    this.setState({ active: false, rippled: this.props.ripple }, () => {
      if (onClick) onClick();
      if (this.props.onSelect) this.props.onSelect(value);
    });
  };

  calculatePosition () {
    const parentNode = ReactDOM.findDOMNode(this).parentNode;
    if (!parentNode) return;
    const {top, left, height, width} = parentNode.getBoundingClientRect();
    const {height: wh, width: ww} = utils.getViewport();
    const toTop = top < ((wh / 2) - height / 2);
    const toLeft = left < ((ww / 2) - width / 2);
    return `${toTop ? 'top' : 'bottom'}-${toLeft ? 'left' : 'right'}`;
  }

  getMenuStyle () {
    const { width, height, position } = this.state;
    if (position !== POSITION.STATIC) {
      if (this.state.active) {
        return { clip: `rect(0 ${width}px ${height}px 0)` };
      } else if (position === POSITION.TOP_RIGHT) {
        return { clip: `rect(0 ${width}px 0 ${width}px)` };
      } else if (position === POSITION.BOTTOM_RIGHT) {
        return { clip: `rect(${height}px ${width}px ${height}px ${width}px)` };
      } else if (position === POSITION.BOTTOM_LEFT) {
        return { clip: `rect(${height}px 0 ${height}px 0)` };
      } else if (position === POSITION.TOP_LEFT) {
        return { clip: 'rect(0 0 0 0)' };
      }
    }
  }

  getRootStyle () {
    if (this.state.position !== POSITION.STATIC) {
      return { width: this.state.width, height: this.state.height };
    }
  }

  renderItems () {
    return React.Children.map(this.props.children, (item) => {
      if (!item) return item;
      if (item.type === MenuItem) {
        return React.cloneElement(item, {
          ripple: item.props.ripple || this.props.ripple,
          selected: typeof item.props.value !== 'undefined' && this.props.selectable && item.props.value === this.props.selected,
          onClick: this.handleSelect.bind(this, item)
        });
      } else {
        return React.cloneElement(item);
      }
    });
  }

  show () {
    const { width, height } = this.refs.menu.getBoundingClientRect();
    this.setState({active: true, width, height});
  }

  hide () {
    this.setState({active: false});
  }

  render () {
    const outlineStyle = { width: this.state.width, height: this.state.height };
    const className = ClassNames([style.root, style[this.state.position]], {
      [style.active]: this.state.active,
      [style.rippled]: this.state.rippled
    }, this.props.className);

    return (

        {this.props.outline ?

 : null}

          {this.renderItems()}

    );
  }
}

export default Menu;
<|endoftext|>