<commit_before>/**
  @module ember-data
*/

import Ember from 'ember';

/**
  `DS.Serializer` is an abstract base class that you should override in your
  application to customize it for your backend. The minimum set of methods
  that you should implement is:

    * `normalizeResponse()`
    * `serialize()`

  And you can optionally override the following methods:

    * `normalize()`

  For an example implementation, see
  [DS.JSONSerializer](DS.JSONSerializer.html), the included JSON serializer.

  @class Serializer
  @namespace DS
  @extends Ember.Object
*/

export default Ember.Object.extend({

  /**
    The `store` property is the application's `store` that contains
    all records.  It can be used to look up serializer for other model
    type that may be nested inside the payload response.

    Example:

    ```js
    Serializer.extend({
      extractRelationship: function(relationshipModelName, relationshipHash) {
        var modelClass = this.store.modelFor(relationshipModelName);
        var relationshipSerializer = this.store.serializerFor(relationshipModelName);
        return relationshipSerializer.normalize(modelClass, relationshipHash);
      }
    });
    ```

    @property store
    @type {DS.Store}
    @public
  */

  /**
    The `normalizeResponse` method is used to normalize a payload from the
    server to a JSON-API Document.

    http://jsonapi.org/format/#document-structure

    Example:

    ```js
    Serializer.extend({
      normalizeResponse(store, primaryModelClass, payload, id, requestType) {
        if (requestType === 'findRecord') {
          return this.normalize(primaryModelClass, payload);
        } else {
          return payload.reduce(function(documentHash, item) {
            let { data, included } = this.normalize(primaryModelClass, item);
            documentHash.included.push(...included);
            documentHash.data.push(data);
            return documentHash;
          }, { data: [], included: [] })
        }
      }
    });
    ```

    @since 1.13.0
    @method normalizeResponse
    @param {DS.Store} store
    @param {DS.Model} primaryModelClass
    @param {Object} payload
    @param {String|Number} id
    @param {String} requestType
    @return {Object} JSON-API Document
  */
  normalizeResponse: null,

  /**
    The `serialize` method is used when a record is saved in order to convert
    the record into the form that your external data source expects.

    `serialize` takes an optional `options` hash with a single option:

    - `includeId`: If this is `true`, `serialize` should include the ID
      in the serialized object it builds.

    Example:

    ```js
    Serializer.extend({
      serialize(snapshot, options) {
        var json = {
          id: snapshot.id
        };

        snapshot.eachAttribute((key, attribute) => {
          json[key] = snapshot.attr(key);
        });

        snapshot.eachRelationship((key, relationship) => {
          if (relationship.kind === 'belongsTo') {
            json[key] = snapshot.belongsTo(key, { id: true });
          } else if (relationship.kind === 'hasMany') {
            json[key] = snapshot.hasMany(key, { ids: true });
          }
        });

        return json;
      },
    });
    ```

    @method serialize
    @param {DS.Snapshot} record
    @param {Object} [options]
    @return {Object}
  */
  serialize: null,

  /**
    The `normalize` method is used to convert a payload received from your
    external data source into the normalized form `store.push()` expects. You
    should override this method, munge the hash and return the normalized
    payload.

    Example:

    ```js
    Serializer.extend({
      normalize(modelClass, resourceHash) {
        var data = {
          id:            resourceHash.id,
          type:          modelClass.modelName,
          attributes:    resourceHash
        };
        return { data: data };
      }
    })
    ```

    @method normalize
    @param {DS.Model} typeClass
    @param {Object} hash
    @return {Object}
  */
  normalize(typeClass, hash) {
    return hash;
  }

});
<commit_msg>[DOC] Minor changes to addon/serializer.js<commit_after>/**
  @module ember-data
*/

import Ember from 'ember';

/**
  `DS.Serializer` is an abstract base class that you should override in your
  application to customize it for your backend. The minimum set of methods
  that you should implement is:

    * `normalizeResponse()`
    * `serialize()`

  And you can optionally override the following methods:

    * `normalize()`

  For an example implementation, see
  [DS.JSONSerializer](DS.JSONSerializer.html), the included JSON serializer.

  @class Serializer
  @namespace DS
  @extends Ember.Object
*/

export default Ember.Object.extend({

  /**
    The `store` property is the application's `store` that contains
    all records. It can be used to look up serializers for other model
    types that may be nested inside the payload response.

    Example:

    ```js
    Serializer.extend({
      extractRelationship: function(relationshipModelName, relationshipHash) {
        var modelClass = this.store.modelFor(relationshipModelName);
        var relationshipSerializer = this.store.serializerFor(relationshipModelName);
        return relationshipSerializer.normalize(modelClass, relationshipHash);
      }
    });
    ```

    @property store
    @type {DS.Store}
    @public
  */

  /**
    The `normalizeResponse` method is used to normalize a payload from the
    server to a JSON-API Document.

    http://jsonapi.org/format/#document-structure

    Example:

    ```js
    Serializer.extend({
      normalizeResponse(store, primaryModelClass, payload, id, requestType) {
        if (requestType === 'findRecord') {
          return this.normalize(primaryModelClass, payload);
        } else {
          return payload.reduce(function(documentHash, item) {
            let { data, included } = this.normalize(primaryModelClass, item);
            documentHash.included.push(...included);
            documentHash.data.push(data);
            return documentHash;
          }, { data: [], included: [] })
        }
      }
    });
    ```

    @since 1.13.0
    @method normalizeResponse
    @param {DS.Store} store
    @param {DS.Model} primaryModelClass
    @param {Object} payload
    @param {String|Number} id
    @param {String} requestType
    @return {Object} JSON-API Document
  */
  normalizeResponse: null,

  /**
    The `serialize` method is used when a record is saved in order to convert
    the record into the form that your external data source expects.

    `serialize` takes an optional `options` hash with a single option:

    - `includeId`: If this is `true`, `serialize` should include the ID
      in the serialized object it builds.

    Example:

    ```js
    Serializer.extend({
      serialize(snapshot, options) {
        var json = {
          id: snapshot.id
        };

        snapshot.eachAttribute((key, attribute) => {
          json[key] = snapshot.attr(key);
        });

        snapshot.eachRelationship((key, relationship) => {
          if (relationship.kind === 'belongsTo') {
            json[key] = snapshot.belongsTo(key, { id: true });
          } else if (relationship.kind === 'hasMany') {
            json[key] = snapshot.hasMany(key, { ids: true });
          }
        });

        return json;
      },
    });
    ```

    @method serialize
    @param {DS.Snapshot} snapshot
    @param {Object} [options]
    @return {Object}
  */
  serialize: null,

  /**
    The `normalize` method is used to convert a payload received from your
    external data source into the normalized form `store.push()` expects. You
    should override this method, munge the hash and return the normalized
    payload.

    Example:

    ```js
    Serializer.extend({
      normalize(modelClass, resourceHash) {
        var data = {
          id:            resourceHash.id,
          type:          modelClass.modelName,
          attributes:    resourceHash
        };
        return { data: data };
      }
    })
    ```

    @method normalize
    @param {DS.Model} typeClass
    @param {Object} hash
    @return {Object}
  */
  normalize(typeClass, hash) {
    return hash;
  }

});
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';

import Field from '../util/styled/Field';

import Ability from './Ability';
import AbilityGroup from './AbilityGroup';

class Racials extends React.Component {
  constructor(props) {
    super(props);

    this.renderAbilities = this.renderAbilities.bind(this);
  }

  renderAbilities(abilities, source='race') {
    const options = abilities.filter(rule => rule.category == 'Option');
    const choices = abilities.filter(rule => rule.category == 'Choice');
    const normal = abilities.filter(rule => {
      return rule.count > 0 && rule.category != 'Option' && rule.category != 'Choice'
      && (!rule.group || choices.find(c => c.name == rule.group && c.count > 0));
    });
    const availableOptions = options.filter(rule => {
      return !rule.group || choices.find(c => c.name == rule.group && c.count > 0);
    });
    return (

        {normal.map(ability => {
          return (
            <Ability
              key={ability._id}
              source={source}
              id={ability._id}
              name={ability.name}
              display={ability.display}
              count={ability.count}
              viewDescription={this.props.viewDescription}
              editCharacter={this.props.editCharacter}
            />
          );
        })}
        {!availableOptions.length ? null :
}
        {availableOptions.map(ability => {
          return (

              {ability.name + (ability.max ? ' (' + ability.max + ')' : '')}
              <AbilityGroup
                source={source}
                abilities={choices.filter(rule => rule.group == ability.name)}
                viewDescription={this.props.viewDescription}
                editCharacter={this.props.editCharacter}
              />

          );
        })}

    );
  }

  render() {
    const rest = Object.assign({}, this.props);
    delete rest.race;
    delete rest.culture;
    delete rest.prodigy;
    delete rest.racials;
    delete rest.viewDescription;
    delete rest.editCharacter;
    return (

        {!this.props.prodigy ? null :

            Prodigy

        }
        {this.renderAbilities(this.props.racials.filter(r => {
          return r.race == this.props.race && !r.culture;
        }), 'race')}
        {!this.props.race ? null :
}
        {this.renderAbilities(this.props.racials.filter(r => {
          return (r.culture && r.culture == this.props.culture) || r.category == 'Choice';
        }), 'culture')}

    );
  }
}

Racials.defaultProps = {
  race: '',
  culture: '',
  prodigy: false,
  racials: []
};

Racials.propTypes = {
  race: PropTypes.string,
  culture: PropTypes.string,
  prodigy: PropTypes.bool,
  racials: PropTypes.array,
  viewDescription: PropTypes.func.isRequired,
  editCharacter: PropTypes.func.isRequired
};

export default Racials;
<commit_msg>tweak to Racial for Varn Alpha Strength<commit_after>import React from 'react';
import PropTypes from 'prop-types';

import Field from '../util/styled/Field';

import Ability from './Ability';
import AbilityGroup from './AbilityGroup';

class Racials extends React.Component {
  constructor(props) {
    super(props);

    this.renderAbilities = this.renderAbilities.bind(this);
  }

  renderAbilities(abilities, source='race') {
    const options = abilities.filter(rule => rule.category == 'Option');
    const choices = abilities.filter(rule => rule.category == 'Choice');
    const normal = abilities.filter(rule => {
      return rule.count > 0 && rule.category != 'Option' && rule.category != 'Choice'
      && (!rule.group || choices.find(c => c.name == rule.group && c.count > 0));
    });
    const availableOptions = options.filter(rule => {
      return !rule.group && rule.count > 0 || choices.find(c => c.name == rule.group && c.count > 0);
    });
    return (

        {normal.map(ability => {
          return (
            <Ability
              key={ability._id}
              source={source}
              id={ability._id}
              name={ability.name}
              display={ability.display}
              count={ability.count}
              viewDescription={this.props.viewDescription}
              editCharacter={this.props.editCharacter}
            />
          );
        })}
        {!availableOptions.length ? null :
}
        {availableOptions.map(ability => {
          return (

              {ability.name + (ability.max ? ' (' + ability.max + ')' : '')}
              <AbilityGroup
                source={source}
                abilities={choices.filter(rule => rule.group == ability.name)}
                viewDescription={this.props.viewDescription}
                editCharacter={this.props.editCharacter}
              />

          );
        })}

    );
  }

  render() {
    const rest = Object.assign({}, this.props);
    delete rest.race;
    delete rest.culture;
    delete rest.prodigy;
    delete rest.racials;
    delete rest.viewDescription;
    delete rest.editCharacter;
    return (

        {!this.props.prodigy ? null :

            Prodigy

        }
        {this.renderAbilities(this.props.racials.filter(r => {
          return r.race == this.props.race && !r.culture;
        }), 'race')}
        {!this.props.race ? null :
}
        {this.renderAbilities(this.props.racials.filter(r => {
          return (r.culture && r.culture == this.props.culture) || r.category == 'Choice';
        }), 'culture')}

    );
  }
}

Racials.defaultProps = {
  race: '',
  culture: '',
  prodigy: false,
  racials: []
};

Racials.propTypes = {
  race: PropTypes.string,
  culture: PropTypes.string,
  prodigy: PropTypes.bool,
  racials: PropTypes.array,
  viewDescription: PropTypes.func.isRequired,
  editCharacter: PropTypes.func.isRequired
};

export default Racials;
<|endoftext|>


<commit_before><commit_msg>Deleted Empty File 'offCanvas.js'<commit_after><|endoftext|>


<commit_before>import { combineLatestObj, exists } from '../../utils/obsUtils'

// import Comments from '../Comments'
import view from './view'
import intent from './intent'
import assign from 'fast.js/object/assign' // faster object.assign

import { pluck, head } from 'ramda'

/* pipeiline
  ask geometry for actual "raw" bounds
  ask transforms for scale
  "real bounds" => rawBounds * scale

  wanna update scale based on bounds ?
  - what is the current real bounds?
  - what is raw bounds ?
  - compute new scale

  VOILA!
*/

function reduceToAverage (acc, cur) {
  if (!acc) return cur
  return [acc[0] + cur[0], acc[1] + cur[1], acc[2] + cur[2]].map(x => x * 0.5)
}

function averageAndSetByFieldname(fieldName, changed, data){
  const average = pluck(fieldName)(data)
    .reduce(reduceToAverage, undefined)

  let value = Object.assign([], currentAvg)
  value[changed.idx] = changed.val
  return {value, average}
}

// "spread" data to a list, by ids value over a list of ids
function spreadToAll (fieldNames){
  return function(data) {
    const {ids} = data
    return ids.map(function (id) {
      let result = {}
      fieldNames.forEach(f => {result[f] = data[f]})
      result['id'] = id
      return result
    })
  }
}

function model (props$, actions) {
  return props$.map(function (props) {
    const {comments, meta, transforms, settings} = props
    return {comments, meta, transforms, settings}
  })
    .startWith({comments: undefined, meta: undefined, transforms: undefined, settings: undefined})
    .distinctUntilChanged()
    .shareReplay(1)
}

// err bad naming ..also should this be part of the model
function refineActions (props$, actions) {
  const selections$ = props$.pluck('selections')
  const transforms$ = props$.pluck('transforms')
    .filter(exists)
    .filter(x => x.length > 0)
  const bounds$ = props$.pluck('bounds')
    .filter(exists)
    .filter(x => x.length > 0)
  const activeTool$ = props$.pluck('settings', 'activeTool')
    .distinctUntilChanged()

  // bounds$.forEach(e=>console.log('GNAGNAbounds',e[0].min,e[0].max))

  const changeBoundsBase$ = actions.changeBounds$
    .withLatestFrom(bounds$, selections$, function (changed, bounds, selections) {
      const currentAvg = pluck('size')(bounds)
        .reduce(reduceToAverage, undefined)

      let newValue = Object.assign([], currentAvg)
      newValue[changed.idx] = changed.val
      return {oldValue: currentAvg, value: newValue, ids: selections}
    })
    .filter(exists)
    .shareReplay(1)

  const changeBounds$ = changeBoundsBase$
    .map(spreadToAll(['value', 'trans']))
    .share()

  const boundsChange$ = changeBoundsBase$
    .withLatestFrom(bounds$, transforms$, selections$, function (changedBounds, bounds, transforms, selections) {
      const currentAvg = changedBounds.oldValue
      const newValue = changedBounds.value
      let scaleChange = [newValue[0]/ currentAvg[0], newValue[1]/ currentAvg[1],
        newValue[2] / currentAvg[2]]
      return {value: scaleChange, trans: 'sca', ids: selections}
    })
    //.tap(e => console.log('scaleChange', e.value[0]))

  const changeTransforms$ = actions.changeTransforms$
    .withLatestFrom(transforms$, selections$, function (changed, transforms, selections) {
      let avg = pluck(changed.trans)(transforms)
        .reduce(reduceToAverage, undefined)
      avg[changed.idx] = changed.val

      return {value: avg, trans: changed.trans, ids: selections}
    })
    .merge(boundsChange$)
    .filter(x => x.value !== undefined)
    .map(spreadToAll(['value', 'trans']))
    .share()
    // .tap(e=>console.log('transforms',e))

  const resetScaling$ = actions.resetScaling$
    .withLatestFrom(selections$, function (_, selections) {
      return {ids: selections}
    })
    .filter(exists)
    .map(spreadToAll([]))

  return {
    changeMeta$: actions.changeMeta$,
    changeBounds$,
    changeTransforms$,
  resetScaling$}
}

function EntityInfos ({DOM, props$}, name = '') {
  const {changeMeta$, changeTransforms$, changeBounds$, resetScaling$} = refineActions(props$, intent(DOM))
  const state$ = model(props$)
  const vtree$ = view(state$)

  return {
    DOM: vtree$,
    events: {
      changeMeta$,
      changeBounds$,
      changeTransforms$,
    resetScaling$}
  }
}

export default EntityInfos
<commit_msg>refactor(entityInfos): cleanup & refactor<commit_after>import { combineLatestObj, exists } from '../../utils/obsUtils'

// import Comments from '../Comments'
import view from './view'
import intent from './intent'
import assign from 'fast.js/object/assign' // faster object.assign

import { pluck, head } from 'ramda'

/* pipeiline
  ask geometry for actual "raw" bounds
  ask transforms for scale
  "real bounds" => rawBounds * scale

  wanna update scale based on bounds ?
  - what is the current real bounds?
  - what is raw bounds ?
  - compute new scale

  VOILA!
*/

function reduceToAverage (acc, cur) {
  if (!acc) return cur
  return [acc[0] + cur[0], acc[1] + cur[1], acc[2] + cur[2]].map(x => x * 0.5)
}

function averageAndSetByFieldname(fieldName, changed, data){
  const average = pluck(fieldName)(data)
    .reduce(reduceToAverage, undefined)

  let value = Object.assign([], currentAvg)
  value[changed.idx] = changed.val
  return {value, average}
}

// "spread" data to a list, by ids value over a list of ids
function spreadToAll (fieldNames){
  return function(data) {
    const {ids} = data
    return ids.map(function (id) {
      let result = {}
      fieldNames.forEach(f => {result[f] = data[f]})
      result['id'] = id
      return result
    })
  }
}

function model (props$, actions) {
  return props$.map(function (props) {
    const {comments, meta, transforms, settings} = props
    return {comments, meta, transforms, settings}
  })
    .startWith({comments: undefined, meta: undefined, transforms: undefined, settings: undefined})
    .distinctUntilChanged()
    .shareReplay(1)
}

// err bad naming ..also should this be part of the model
function refineActions (props$, actions) {
  const selections$ = props$.pluck('selections')
  const transforms$ = props$.pluck('transforms')
    .filter(exists)
    .filter(x => x.length > 0)
  const bounds$ = props$.pluck('bounds')
    .filter(exists)
    .filter(x => x.length > 0)
  const activeTool$ = props$.pluck('settings', 'activeTool')
    .distinctUntilChanged()

  // bounds$.forEach(e=>console.log('GNAGNAbounds',e[0].min,e[0].max))

  const changeBoundsBase$ = actions.changeBounds$
    .withLatestFrom(bounds$, selections$, function (changed, bounds, selections) {
      const currentAvg = pluck('size')(bounds)
        .reduce(reduceToAverage, undefined)

      let newValue = Object.assign([], currentAvg)
      newValue[changed.idx] = changed.val
      return {oldValue: currentAvg, value: newValue, ids: selections}
    })
    .filter(exists)
    .shareReplay(1)

  const changeBounds$ = changeBoundsBase$
    .map(spreadToAll(['value', 'trans']))
    .share()

  const scaleFromBounds$ = changeBoundsBase$
    .withLatestFrom(bounds$, transforms$, selections$, function (changedBounds, bounds, transforms, selections) {
      const currentAvg = changedBounds.oldValue
      const newValue = changedBounds.value
      let scaleChange = [newValue[0]/ currentAvg[0], newValue[1]/ currentAvg[1],
        newValue[2] / currentAvg[2]]
      return {value: scaleChange, trans: 'sca', ids: selections}
    })
    //.tap(e => console.log('scaleChange', e.value[0]))

  const changeTransforms$ = actions.changeTransforms$
    .withLatestFrom(transforms$, selections$, function (changed, transforms, selections) {
      let avg = pluck(changed.trans)(transforms)
        .reduce(reduceToAverage, undefined)
      avg[changed.idx] = changed.val

      return {value: avg, trans: changed.trans, ids: selections}
    })
    .merge(scaleFromBounds$)
    .filter(x => x.value !== undefined)
    .map(spreadToAll(['value', 'trans']))
    .share()
    // .tap(e=>console.log('transforms',e))

  const resetScaling$ = actions.resetScaling$
    .withLatestFrom(selections$, function (_, selections) {
      return {ids: selections}
    })
    .filter(exists)
    .map(spreadToAll([]))

  return {
    changeMeta$: actions.changeMeta$,
    changeBounds$,
    changeTransforms$,
  resetScaling$}
}

function EntityInfos ({DOM, props$}, name = '') {
  const {changeMeta$, changeTransforms$, changeBounds$, resetScaling$} = refineActions(props$, intent(DOM))
  const state$ = model(props$)
  const vtree$ = view(state$)

  return {
    DOM: vtree$,
    events: {
      changeMeta$,
      changeBounds$,
      changeTransforms$,
    resetScaling$}
  }
}

export default EntityInfos
<|endoftext|>


<commit_before>import {
  isRadiant,
  isSupport,
  getLevelFromXp,
  unpackPositionData,
} from 'utility';
import heroes from 'dotaconstants/json/heroes.json';
import specific from 'dotaconstants/json/specific.json';
import laneRole from 'dotaconstants/json/lane_role.json';
import immutable from 'seamless-immutable';
import _ from 'lodash/fp';

import analysis from './analysis';

const expanded = {};
Object.keys(specific).forEach((key) => {
  for (let i = 1; i < 5; i += 1) {
    expanded[key.replace('#', i)] = specific[key];
  }
});

const getMaxKeyOfObject = field =>
 (field ? Object.keys(field).sort((a, b) => Number(b) - Number(a))[0] : '');

/**
 * Generates data for c3 charts in a match
 **/
function generateGraphData(match) {
  if (match.players && match.players[0] && match.radiant_gold_adv && match.radiant_xp_adv) {
    // compute graphs
    const goldDifference = ['Gold', ...match.radiant_gold_adv];
    const xpDifference = ['XP', ...match.radiant_xp_adv];
    const time = ['time', ...match.players[0].times];
    const data = {
      difference: [time, xpDifference, goldDifference],
      gold: [time],
      xp: [time],
      lh: [time],
    };
    match.players.forEach((player) => {
      let hero = heroes[player.hero_id] || {};
      hero = hero.localized_name;
      if (player.gold_t) {
        data.gold.push([hero, ...player.gold_t]);
      }
      if (player.xp_t) {
        data.xp.push([hero, ...player.xp_t]);
      }
      if (player.lh_t) {
        data.lh.push([hero, ...player.lh_t]);
      }
    });
    return data;
  }
  return {};
}

function generateTeamfights(match) {
  const computeTfData = (tf) => {
    const newtf = {
      ...tf,
      deaths_pos: [],
      radiant_gold_delta: 0,
      radiant_xp_delta: 0,
      radiant_participation: 0,
      radiant_deaths: 0,
      dire_participation: 0,
      dire_deaths: 0,
    };
    newtf.players = match.players.map((player) => {
      const tfplayer = tf.players[player.player_slot % (128 - 5)];
      // compute team gold/xp deltas
      if (isRadiant(player.player_slot)) {
        newtf.radiant_gold_delta += tfplayer.gold_delta;
        newtf.radiant_xp_delta += tfplayer.xp_delta;
        newtf.radiant_participation += tfplayer.participate ? 1 : 0;
        newtf.radiant_deaths += tfplayer.deaths ? 1 : 0;
      } else {
        newtf.radiant_gold_delta -= tfplayer.gold_delta;
        newtf.radiant_xp_delta -= tfplayer.xp_delta;
        newtf.dire_participation += tfplayer.participate ? 1 : 0;
        newtf.dire_deaths += tfplayer.deaths ? 1 : 0;
      }
      return {
        ...player,
        ...tfplayer,
        participate: tfplayer.deaths > 0 || tfplayer.damage > 0 || tfplayer.healing > 0,
        level_start: getLevelFromXp(tfplayer.xp_start),
        level_end: getLevelFromXp(tfplayer.xp_end),
        deaths_pos: unpackPositionData(tfplayer.deaths_pos),
      };
    });
    return newtf;
  };
  return (match.teamfights || []).map(tf => computeTfData(tf));
}

// create a detailed history of each wards
function generateWardLog(match) {
  const computeWardData = (player, i) => {
    const sameWard = _.curry((w1, w2) => w1.ehandle === w2.ehandle);

    // let's coerce some value to be sure the structure is what we expect.
    const safePlayer = {
      ...player,
      obs_log: player.obs_log || [],
      sen_log: player.sen_log || [],
      obs_left_log: player.obs_left_log || [],
      sen_left_log: player.sen_left_log || [],
    };

    // let's zip the *_log and the *_left log in a 2-tuples
    const extractWardLog = (type, enteredLog, leftLog) =>
      enteredLog.map((e) => {
        const wards = [e, leftLog.find(sameWard(e))];
        return {
          player: i,
          key: wards[0].ehandle,
          type,
          entered: wards[0],
          left: wards[1],
        };
      })
    ;

    const observers = extractWardLog('observer', safePlayer.obs_log, safePlayer.obs_left_log);
    const sentries = extractWardLog('sentry', safePlayer.sen_log, safePlayer.sen_left_log);
    return _.concat(observers, sentries);
  };

  const imap = _.map.convert({ cap: false }); // cap: false to keep the index
  const wardLog = _.flow(
    imap(computeWardData),
    _.flatten,
    _.sortBy(xs => xs.entered.time),
    imap((x, i) => ({ ...x, key: i })),
  );
  return wardLog(match.players || []);
}

function renderMatch(m) {
  const newPlayers = m.players.map((player) => {
    const newPlayer = {
      ...player,
      desc: [laneRole[player.lane_role], isSupport(player) ? 'Support' : 'Core'].join('/'),
      multi_kills_max: getMaxKeyOfObject(player.multi_kills),
      kill_streaks_max: getMaxKeyOfObject(player.kill_streaks),
      analysis: analysis(m, player),
    };
    // filter interval data to only be >= 0
    if (player.times) {
      const intervals = ['lh_t', 'gold_t', 'xp_t', 'times'];
      intervals.forEach((key) => {
        newPlayer[key] = player[key].filter((el, i) => player.times[i] >= 0);
      });
    }
    // compute damage to towers/rax/roshan
    if (player.damage) {
      // npc_dota_goodguys_tower2_top
      // npc_dota_goodguys_melee_rax_top
      // npc_dota_roshan
      // npc_dota_neutral_giant_wolf
      // npc_dota_creep
      newPlayer.objective_damage = {};
      Object.keys(player.damage).forEach((key) => {
        let identifier = null;
        if (key.indexOf('tower') !== -1) {
          identifier = key.split('_').slice(3).join('_');
        }
        if (key.indexOf('rax') !== -1) {
          identifier = key.split('_').slice(4).join('_');
        }
        if (key.indexOf('roshan') !== -1) {
          identifier = 'roshan';
        }
        if (key.indexOf('fort') !== -1) {
          identifier = 'fort';
        }
        newPlayer.objective_damage[identifier] = newPlayer.objective_damage[identifier] ?
                                                 newPlayer.objective_damage[identifier] + player.damage[key] :
                                                 player.damage[key];
      });
    }
    if (player.killed) {
      newPlayer.specific = {};
      // expand keys in specific by # (1-4)
      // map to friendly name
      // iterate through keys in killed
      // if in expanded, put in pm.specific
      Object.keys(player.killed).forEach((key) => {
        if (key in expanded) {
          const name = expanded[key];
          newPlayer.specific[name] = newPlayer.specific[name] ? newPlayer.specific[name] + newPlayer.killed[key] : newPlayer.killed[key];
        }
      });
    }
    if (player.purchase) {
      newPlayer.purchase_tpscroll = player.purchase.tpscroll;
      newPlayer.purchase_ward_observer = player.purchase.ward_observer;
      newPlayer.purchase_ward_sentry = player.purchase.ward_sentry;
      newPlayer.purchase_smoke_of_deceit = player.purchase.smoke_of_deceit;
      newPlayer.purchase_dust = player.purchase.dust;
      newPlayer.purchase_gem = player.purchase.gem;
    }
    return newPlayer;
  });

  const newObjectives = (m.objectives || []).map((obj) => {
    if (obj.slot > 0) {
      return {
        ...obj,
        player_slot: obj.slot > 4 ? obj.slot + 123 : obj.slot,
      };
    }
    return {
      ...obj,
    };
  });

  return {
    ...m,
    graphData: generateGraphData(m),
    teamfights: generateTeamfights(m),
    players: newPlayers,
    wards_log: generateWardLog(immutable(m)),
    objectives: newObjectives,
  };
}

export default renderMatch;
<commit_msg>fix renderMatch so that it properly adds deaths to deaths_pos in the teamfight object<commit_after>import {
  isRadiant,
  isSupport,
  getLevelFromXp,
  unpackPositionData,
} from 'utility';
import heroes from 'dotaconstants/json/heroes.json';
import specific from 'dotaconstants/json/specific.json';
import laneRole from 'dotaconstants/json/lane_role.json';
import immutable from 'seamless-immutable';
import _ from 'lodash/fp';

import analysis from './analysis';

const expanded = {};
Object.keys(specific).forEach((key) => {
  for (let i = 1; i < 5; i += 1) {
    expanded[key.replace('#', i)] = specific[key];
  }
});

const getMaxKeyOfObject = field =>
 (field ? Object.keys(field).sort((a, b) => Number(b) - Number(a))[0] : '');

/**
 * Generates data for c3 charts in a match
 **/
function generateGraphData(match) {
  if (match.players && match.players[0] && match.radiant_gold_adv && match.radiant_xp_adv) {
    // compute graphs
    const goldDifference = ['Gold', ...match.radiant_gold_adv];
    const xpDifference = ['XP', ...match.radiant_xp_adv];
    const time = ['time', ...match.players[0].times];
    const data = {
      difference: [time, xpDifference, goldDifference],
      gold: [time],
      xp: [time],
      lh: [time],
    };
    match.players.forEach((player) => {
      let hero = heroes[player.hero_id] || {};
      hero = hero.localized_name;
      if (player.gold_t) {
        data.gold.push([hero, ...player.gold_t]);
      }
      if (player.xp_t) {
        data.xp.push([hero, ...player.xp_t]);
      }
      if (player.lh_t) {
        data.lh.push([hero, ...player.lh_t]);
      }
    });
    return data;
  }
  return {};
}

function generateTeamfights({ players, teamfights = [] }) {
  const computeTfData = (tf) => {
    const newtf = {
      ...tf,
      deaths_pos: [],
      radiant_gold_delta: 0,
      radiant_xp_delta: 0,
      radiant_participation: 0,
      radiant_deaths: 0,
      dire_participation: 0,
      dire_deaths: 0,
    };
    newtf.players = players.map((player) => {
      const tfplayer = tf.players[player.player_slot % (128 - 5)];
      // compute team gold/xp deltas
      if (isRadiant(player.player_slot)) {
        newtf.radiant_gold_delta += tfplayer.gold_delta;
        newtf.radiant_xp_delta += tfplayer.xp_delta;
        newtf.radiant_participation += tfplayer.participate ? 1 : 0;
        newtf.radiant_deaths += tfplayer.deaths ? 1 : 0;
      } else {
        newtf.radiant_gold_delta -= tfplayer.gold_delta;
        newtf.radiant_xp_delta -= tfplayer.xp_delta;
        newtf.dire_participation += tfplayer.participate ? 1 : 0;
        newtf.dire_deaths += tfplayer.deaths ? 1 : 0;
      }
      const playerDeathsPos = unpackPositionData(tfplayer.deaths_pos);
      newtf.deaths_pos = newtf.deaths_pos.concat(playerDeathsPos);
      return {
        ...player,
        ...tfplayer,
        participate: tfplayer.deaths > 0 || tfplayer.damage > 0 || tfplayer.healing > 0,
        level_start: getLevelFromXp(tfplayer.xp_start),
        level_end: getLevelFromXp(tfplayer.xp_end),
        deaths_pos: playerDeathsPos,
      };
    });
    return newtf;
  };
  return teamfights.map(computeTfData);
}

// create a detailed history of each wards
function generateWardLog(match) {
  const computeWardData = (player, i) => {
    const sameWard = _.curry((w1, w2) => w1.ehandle === w2.ehandle);

    // let's coerce some value to be sure the structure is what we expect.
    const safePlayer = {
      ...player,
      obs_log: player.obs_log || [],
      sen_log: player.sen_log || [],
      obs_left_log: player.obs_left_log || [],
      sen_left_log: player.sen_left_log || [],
    };

    // let's zip the *_log and the *_left log in a 2-tuples
    const extractWardLog = (type, enteredLog, leftLog) =>
      enteredLog.map((e) => {
        const wards = [e, leftLog.find(sameWard(e))];
        return {
          player: i,
          key: wards[0].ehandle,
          type,
          entered: wards[0],
          left: wards[1],
        };
      })
    ;

    const observers = extractWardLog('observer', safePlayer.obs_log, safePlayer.obs_left_log);
    const sentries = extractWardLog('sentry', safePlayer.sen_log, safePlayer.sen_left_log);
    return _.concat(observers, sentries);
  };

  const imap = _.map.convert({ cap: false }); // cap: false to keep the index
  const wardLog = _.flow(
    imap(computeWardData),
    _.flatten,
    _.sortBy(xs => xs.entered.time),
    imap((x, i) => ({ ...x, key: i })),
  );
  return wardLog(match.players || []);
}

function renderMatch(m) {
  const newPlayers = m.players.map((player) => {
    const newPlayer = {
      ...player,
      desc: [laneRole[player.lane_role], isSupport(player) ? 'Support' : 'Core'].join('/'),
      multi_kills_max: getMaxKeyOfObject(player.multi_kills),
      kill_streaks_max: getMaxKeyOfObject(player.kill_streaks),
      analysis: analysis(m, player),
    };
    // filter interval data to only be >= 0
    if (player.times) {
      const intervals = ['lh_t', 'gold_t', 'xp_t', 'times'];
      intervals.forEach((key) => {
        newPlayer[key] = player[key].filter((el, i) => player.times[i] >= 0);
      });
    }
    // compute damage to towers/rax/roshan
    if (player.damage) {
      // npc_dota_goodguys_tower2_top
      // npc_dota_goodguys_melee_rax_top
      // npc_dota_roshan
      // npc_dota_neutral_giant_wolf
      // npc_dota_creep
      newPlayer.objective_damage = {};
      Object.keys(player.damage).forEach((key) => {
        let identifier = null;
        if (key.indexOf('tower') !== -1) {
          identifier = key.split('_').slice(3).join('_');
        }
        if (key.indexOf('rax') !== -1) {
          identifier = key.split('_').slice(4).join('_');
        }
        if (key.indexOf('roshan') !== -1) {
          identifier = 'roshan';
        }
        if (key.indexOf('fort') !== -1) {
          identifier = 'fort';
        }
        newPlayer.objective_damage[identifier] = newPlayer.objective_damage[identifier] ?
                                                 newPlayer.objective_damage[identifier] + player.damage[key] :
                                                 player.damage[key];
      });
    }
    if (player.killed) {
      newPlayer.specific = {};
      // expand keys in specific by # (1-4)
      // map to friendly name
      // iterate through keys in killed
      // if in expanded, put in pm.specific
      Object.keys(player.killed).forEach((key) => {
        if (key in expanded) {
          const name = expanded[key];
          newPlayer.specific[name] = newPlayer.specific[name] ? newPlayer.specific[name] + newPlayer.killed[key] : newPlayer.killed[key];
        }
      });
    }
    if (player.purchase) {
      newPlayer.purchase_tpscroll = player.purchase.tpscroll;
      newPlayer.purchase_ward_observer = player.purchase.ward_observer;
      newPlayer.purchase_ward_sentry = player.purchase.ward_sentry;
      newPlayer.purchase_smoke_of_deceit = player.purchase.smoke_of_deceit;
      newPlayer.purchase_dust = player.purchase.dust;
      newPlayer.purchase_gem = player.purchase.gem;
    }
    return newPlayer;
  });

  const newObjectives = (m.objectives || []).map((obj) => {
    if (obj.slot > 0) {
      return {
        ...obj,
        player_slot: obj.slot > 4 ? obj.slot + 123 : obj.slot,
      };
    }
    return {
      ...obj,
    };
  });

  return {
    ...m,
    graphData: generateGraphData(m),
    teamfights: generateTeamfights(m),
    players: newPlayers,
    wards_log: generateWardLog(immutable(m)),
    objectives: newObjectives,
  };
}

export default renderMatch;
<|endoftext|>


<commit_before>// @flow
import React from 'react'
import { connect } from 'react-redux'
import { actions } from 'ducks/postForm'
import { onSubmit } from 'utils'
import PostFormIsTutorial from './PostFormIsTutorial'
import PostFormFlow from './PostFormFlow'
import PostFormTitle from './PostFormTitle'
import PostFormContent from './PostFormContent'
import PostFormSearchHub from './PostFormSearchHub'
import PostFormHubs from './PostFormHubs'
import PostFormIsTranslation from './PostFormIsTranslation'
import PostFormSourceAuthor from './PostFormSourceAuthor'
import PostFormSourceLink from './PostFormSourceLink'
import PostFormSubmit from './PostFormSubmit'
import PostFormAddPage from './PostFormAddPage'
import PostFormEditPage from './PostFormEditPage'

// Q: можно ли объявить компонент чистым, если в props - router?
// A: да, PureComponent применяет неглубокое сравнение
//
// применение defaultValue нарушает работу Redux DevTools TimeTravel
//
// применение value без onChange={doChange} ругается (но не Material-UI):
// Warning: Failed form propType: You provided a `value` prop to a form field
// without an `onChange` handler. This will render a read-only field.
// If the field should be mutable use `defaultValue`. Otherwise, set either
// `onChange` or `readOnly`.

type Props = {
  id?: number,
  flow: {
    id?: string,
    name: string,
  },
  title: string,
  content: string,
  hubs: Array<{
    id: string,
    name: string,
  }>,
  isTranslation: boolean,
  sourceAuthor: string,
  sourceLink: string,
  isTutorial: boolean,
  errors: { [string]: string },
  isSubmitting: boolean,
  mainError: string,
  sourceFlows: Array<{
    id: string,
    name: string,
  }>,
  searchHub: string,
  sourceHubs: Array<{
    id: string,
    name: string,
  }>,
  input: Function,
  save: Function,
}

const PostForm = ({
  id, flow, title, content, hubs, isTranslation, sourceAuthor, sourceLink,
  isTutorial, searchHub, sourceFlows, sourceHubs, errors, isSubmitting, mainError,
  input, save
}: Props) => (

{console.log('input', input) || id ? 'Редактирование публикации' : 'Хочу разместить публикацию'}

    <form onSubmit={onSubmit(isSubmitting, save)} autoComplete='off'>
      <PostFormIsTutorial {...{ isTutorial, input }} />
      <PostFormFlow {...{ flowId: flow.id, sourceFlows, input, error: errors.flow }} />
      <PostFormTitle {...{ title, input, error: errors.title }} />
      <PostFormContent {...{ content, input, error: errors.content }} />
      <PostFormSearchHub {...{ searchHub, sourceHubs, hubs, input, error: errors.searchHub }} />
      <PostFormHubs {...{ hubs, input, error: errors.searchHub }} />
      <PostFormIsTranslation {...{ isTranslation, input }} />
      <PostFormSourceAuthor {...{ sourceAuthor, isTranslation, input, error: errors.sourceAuthor }} />
      <PostFormSourceLink {...{ sourceLink, isTranslation, input, error: errors.sourceLink }} />
      <PostFormSubmit {...{ isSubmitting }} />
    </form>

    {!!mainError &&
{mainError}
}

)

// PostForm.propTypes = {
//   id: PropTypes.number,
//   flow: PropTypes.shape({
//     id: PropTypes.string,
//     name: PropTypes.string,
//   }),
//   title: PropTypes.string,
//   content: PropTypes.string,
//   hubs: PropTypes.arrayOf(
//     PropTypes.shape({
//       id: PropTypes.string,
//       name: PropTypes.string,
//     })
//   ),
//   isTranslation: PropTypes.bool,
//   sourceAuthor: PropTypes.string,
//   sourceLink: PropTypes.string,
//   isTutorial: PropTypes.bool,
//   errors: PropTypes.object,
//   isSubmitting: PropTypes.bool,
//   mainError: PropTypes.string,
//   sourceFlows: PropTypes.arrayOf(
//     PropTypes.shape({
//       id: PropTypes.string,
//       name: PropTypes.string,
//     })
//   ),
//   searchHub: PropTypes.string,
//   sourceHubs: PropTypes.arrayOf(
//     PropTypes.shape({
//       id: PropTypes.string,
//       name: PropTypes.string,
//     })
//   ),
//   input: PropTypes.func,
//   save: PropTypes.func,
// }

const mapStateToProps = (state) => ({
  ...state.postForm,
  mainError: state.app.mainError,
  sourceFlows: state.flows,
  sourceHubs: state.hubs
})

const { input, save } = actions
const mapDispatchToProps = { input, save }

export { PostFormAddPage, PostFormEditPage }
export { PostForm } // тупой компонент для тестирования
export default connect(mapStateToProps, mapDispatchToProps)(PostForm)
<commit_msg>Испытать prettier<commit_after>// @flow
import React from 'react'
import { connect } from 'react-redux'
import { actions } from 'ducks/postForm'
import { onSubmit } from 'utils'
import PostFormIsTutorial from './PostFormIsTutorial'
import PostFormFlow from './PostFormFlow'
import PostFormTitle from './PostFormTitle'
import PostFormContent from './PostFormContent'
import PostFormSearchHub from './PostFormSearchHub'
import PostFormHubs from './PostFormHubs'
import PostFormIsTranslation from './PostFormIsTranslation'
import PostFormSourceAuthor from './PostFormSourceAuthor'
import PostFormSourceLink from './PostFormSourceLink'
import PostFormSubmit from './PostFormSubmit'
import PostFormAddPage from './PostFormAddPage'
import PostFormEditPage from './PostFormEditPage'

// Q: можно ли объявить компонент чистым, если в props - router?
// A: да, PureComponent применяет неглубокое сравнение
//
// применение defaultValue нарушает работу Redux DevTools TimeTravel
//
// применение value без onChange={doChange} ругается (но не Material-UI):
// Warning: Failed form propType: You provided a `value` prop to a form field
// without an `onChange` handler. This will render a read-only field.
// If the field should be mutable use `defaultValue`. Otherwise, set either
// `onChange` or `readOnly`.

type Props = {
  id?: number,
  flow: {
    id?: string,
    name: string,
  },
  title: string,
  content: string,
  hubs: Array<{
    id: string,
    name: string,
  }>,
  isTranslation: boolean,
  sourceAuthor: string,
  sourceLink: string,
  isTutorial: boolean,
  errors: { [string]: string },
  isSubmitting: boolean,
  mainError: string,
  sourceFlows: Array<{
    id: string,
    name: string,
  }>,
  searchHub: string,
  sourceHubs: Array<{
    id: string,
    name: string,
  }>,
  input: Function,
  save: Function,
}

const PostForm = ({
  id,
  flow,
  title,
  content,
  hubs,
  isTranslation,
  sourceAuthor,
  sourceLink,
  isTutorial,
  searchHub,
  sourceFlows,
  sourceHubs,
  errors,
  isSubmitting,
  mainError,
  input,
  save,
}: Props) => (

      {console.log('input', input) || id
        ? 'Редактирование публикации'
        : 'Хочу разместить публикацию'}

    <form onSubmit={onSubmit(isSubmitting, save)} autoComplete="off">
      <PostFormIsTutorial {...{ isTutorial, input }} />
      <PostFormFlow {...{ flowId: flow.id, sourceFlows, input, error: errors.flow }} />
      <PostFormTitle {...{ title, input, error: errors.title }} />
      <PostFormContent {...{ content, input, error: errors.content }} />
      <PostFormSearchHub {...{ searchHub, sourceHubs, hubs, input, error: errors.searchHub }} />
      <PostFormHubs {...{ hubs, input, error: errors.searchHub }} />
      <PostFormIsTranslation {...{ isTranslation, input }} />
      <PostFormSourceAuthor
        {...{ sourceAuthor, isTranslation, input, error: errors.sourceAuthor }}
      />
      <PostFormSourceLink {...{ sourceLink, isTranslation, input, error: errors.sourceLink }} />
      <PostFormSubmit {...{ isSubmitting }} />
    </form>

    {!!mainError &&
{mainError}
}

)

// PostForm.propTypes = {
//   id: PropTypes.number,
//   flow: PropTypes.shape({
//     id: PropTypes.string,
//     name: PropTypes.string,
//   }),
//   title: PropTypes.string,
//   content: PropTypes.string,
//   hubs: PropTypes.arrayOf(
//     PropTypes.shape({
//       id: PropTypes.string,
//       name: PropTypes.string,
//     })
//   ),
//   isTranslation: PropTypes.bool,
//   sourceAuthor: PropTypes.string,
//   sourceLink: PropTypes.string,
//   isTutorial: PropTypes.bool,
//   errors: PropTypes.object,
//   isSubmitting: PropTypes.bool,
//   mainError: PropTypes.string,
//   sourceFlows: PropTypes.arrayOf(
//     PropTypes.shape({
//       id: PropTypes.string,
//       name: PropTypes.string,
//     })
//   ),
//   searchHub: PropTypes.string,
//   sourceHubs: PropTypes.arrayOf(
//     PropTypes.shape({
//       id: PropTypes.string,
//       name: PropTypes.string,
//     })
//   ),
//   input: PropTypes.func,
//   save: PropTypes.func,
// }

const mapStateToProps = state => ({
  ...state.postForm,
  mainError: state.app.mainError,
  sourceFlows: state.flows,
  sourceHubs: state.hubs,
})

const { input, save } = actions
const mapDispatchToProps = { input, save }

export { PostFormAddPage, PostFormEditPage }
export { PostForm } // тупой компонент для тестирования
export default connect(mapStateToProps, mapDispatchToProps)(PostForm)
<|endoftext|>


<commit_before>import { Component, createElement, cloneElement, PropTypes } from 'react';
import connect from 'react-redux/lib/components/connect';
import _get from '../utils/get';
import merge from '../utils/merge';

import { invertValidity, getFieldFromState, getValidity } from '../utils';
import { sequenceEventActions } from '../utils/sequence';
import actions from '../actions';

function mapStateToProps(state, props) {
  const { model, mapProps } = props;

  if (!mapProps) return props;

  const modelString = typeof model === 'function'
    ? model(state)
    : model;
  const fieldValue = getFieldFromState(state, modelString);

  return {
    model,
    modelValue: _get(state, modelString),
    fieldValue,
  };
}

class Control extends Component {
  constructor(props) {
    super(props);

    const { controlProps, mapProps } = props;

    this.handleKeyPress = this.handleKeyPress.bind(this);

    this.state = {
      value: props.modelValue,
      mappedProps: mapProps({
        ...props,
        ...controlProps,
        ...sequenceEventActions(props),
      }),
    };
  }

  componentWillMount() {
    const { modelValue } = this.props;
    const { onLoad } = this.state.mappedProps;

    if (onLoad) {
      onLoad(modelValue);
    }
  }

  componentWillReceiveProps(nextProps) {
    const { mapProps } = nextProps;

    this.setState({
      mappedProps: mapProps({
        ...nextProps,
        ...nextProps.controlProps,
        ...sequenceEventActions(nextProps),
      }),
    });
  }

  componentDidUpdate(prevProps) {
    const {
      modelValue,
      fieldValue,
      validateOn,
    } = this.props;

    if (fieldValue
      && !fieldValue.validated
      && modelValue !== prevProps.modelValue
      && validateOn === 'change'
    ) {
      this.validate();
    }
  }

  componentWillUnmount() {
    const { model, fieldValue, dispatch } = this.props;

    if (!fieldValue.valid) {
      dispatch(actions.resetValidity(model));
    }
  }

  handleKeyPress(event) {
    const { onSubmit } = this.state.mappedProps;

    if (onSubmit && event.key === 'Enter') {
      onSubmit(event);
    }
  }

  validate() {
    const {
      model,
      modelValue,
      validators,
      errors: errorValidators,
      dispatch,
    } = this.props;

    const fieldValidity = getValidity(validators, modelValue);
    const fieldErrors = getValidity(errorValidators, modelValue);

    const errors = validators
      ? merge(invertValidity(fieldValidity), fieldErrors)
      : fieldErrors;

    dispatch(actions.setErrors(model, errors));

    return modelValue;
  }

  render() {
    const {
      controlProps = {},
      component,
      control,
    } = this.props;

    // If there is an existing control, clone it
    if (control) {
      return cloneElement(
        control,
        {
          ...this.state.mappedProps,
          onKeyPress: this.handleKeyPress,
        });
    }

    return createElement(
      component,
      {
        ...controlProps,
        ...this.state.mappedProps,
        onKeyPress: this.handleKeyPress,
      },
      controlProps.children);
  }
}

Control.propTypes = {
  model: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.string,
  ]),
  control: PropTypes.any,
  onLoad: PropTypes.func,
  onSubmit: PropTypes.func,
  modelValue: PropTypes.any,
  fieldValue: PropTypes.object,
  mapProps: PropTypes.func,
  changeAction: PropTypes.func,
  updateOn: PropTypes.string,
  validateOn: PropTypes.string,
  validators: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.object,
  ]),
  errors: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.object,
  ]),
  controlProps: PropTypes.object,
  component: PropTypes.any,
  dispatch: PropTypes.func,
};

Control.defaultProps = {
  changeAction: actions.change,
  updateOn: 'change',
};

export default connect(mapStateToProps)(Control);
<commit_msg>Adding check for fieldValue in Control unmount<commit_after>import { Component, createElement, cloneElement, PropTypes } from 'react';
import connect from 'react-redux/lib/components/connect';
import _get from '../utils/get';
import merge from '../utils/merge';

import { invertValidity, getFieldFromState, getValidity } from '../utils';
import { sequenceEventActions } from '../utils/sequence';
import actions from '../actions';

function mapStateToProps(state, props) {
  const { model, mapProps } = props;

  if (!mapProps) return props;

  const modelString = typeof model === 'function'
    ? model(state)
    : model;
  const fieldValue = getFieldFromState(state, modelString);

  return {
    model,
    modelValue: _get(state, modelString),
    fieldValue,
  };
}

class Control extends Component {
  constructor(props) {
    super(props);

    const { controlProps, mapProps } = props;

    this.handleKeyPress = this.handleKeyPress.bind(this);

    this.state = {
      value: props.modelValue,
      mappedProps: mapProps({
        ...props,
        ...controlProps,
        ...sequenceEventActions(props),
      }),
    };
  }

  componentWillMount() {
    const { modelValue } = this.props;
    const { onLoad } = this.state.mappedProps;

    if (onLoad) {
      onLoad(modelValue);
    }
  }

  componentWillReceiveProps(nextProps) {
    const { mapProps } = nextProps;

    this.setState({
      mappedProps: mapProps({
        ...nextProps,
        ...nextProps.controlProps,
        ...sequenceEventActions(nextProps),
      }),
    });
  }

  componentDidUpdate(prevProps) {
    const {
      modelValue,
      fieldValue,
      validateOn,
    } = this.props;

    if (fieldValue
      && !fieldValue.validated
      && modelValue !== prevProps.modelValue
      && validateOn === 'change'
    ) {
      this.validate();
    }
  }

  componentWillUnmount() {
    const { model, fieldValue, dispatch } = this.props;

    if (!fieldValue) return;

    if (!fieldValue.valid) {
      dispatch(actions.resetValidity(model));
    }
  }

  handleKeyPress(event) {
    const { onSubmit } = this.state.mappedProps;

    if (onSubmit && event.key === 'Enter') {
      onSubmit(event);
    }
  }

  validate() {
    const {
      model,
      modelValue,
      validators,
      errors: errorValidators,
      dispatch,
    } = this.props;

    const fieldValidity = getValidity(validators, modelValue);
    const fieldErrors = getValidity(errorValidators, modelValue);

    const errors = validators
      ? merge(invertValidity(fieldValidity), fieldErrors)
      : fieldErrors;

    dispatch(actions.setErrors(model, errors));

    return modelValue;
  }

  render() {
    const {
      controlProps = {},
      component,
      control,
    } = this.props;

    // If there is an existing control, clone it
    if (control) {
      return cloneElement(
        control,
        {
          ...this.state.mappedProps,
          onKeyPress: this.handleKeyPress,
        });
    }

    return createElement(
      component,
      {
        ...controlProps,
        ...this.state.mappedProps,
        onKeyPress: this.handleKeyPress,
      },
      controlProps.children);
  }
}

Control.propTypes = {
  model: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.string,
  ]),
  control: PropTypes.any,
  onLoad: PropTypes.func,
  onSubmit: PropTypes.func,
  modelValue: PropTypes.any,
  fieldValue: PropTypes.object,
  mapProps: PropTypes.func,
  changeAction: PropTypes.func,
  updateOn: PropTypes.string,
  validateOn: PropTypes.string,
  validators: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.object,
  ]),
  errors: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.object,
  ]),
  controlProps: PropTypes.object,
  component: PropTypes.any,
  dispatch: PropTypes.func,
};

Control.defaultProps = {
  changeAction: actions.change,
  updateOn: 'change',
};

export default connect(mapStateToProps)(Control);
<|endoftext|>


<commit_before>import Device from '../../../utils/device';
import Support from '../../../utils/support';
import Browser from '../../../utils/browser';

import onTouchStart from './onTouchStart';
import onTouchMove from './onTouchMove';
import onTouchEnd from './onTouchEnd';
import onResize from './onResize';
import onClick from './onClick';

let onTouchStartBound;
let onTouchMoveBound;
let onTouchEndBound;
let onClickBound;

function attachEvents() {
  const swiper = this;

  const { params, touchEvents, el, wrapperEl } = swiper;

  if (process.env.TARGET !== 'desktop') {
    onTouchStartBound = onTouchStart.bind(swiper);
    onTouchMoveBound = onTouchMove.bind(swiper);
    onTouchEndBound = onTouchEnd.bind(swiper);
  }

  onClickBound = onClick.bind(swiper);

  const target = params.touchEventsTarget === 'container' ? el : wrapperEl;
  const capture = !!params.nested;

  // Touch Events
  if (process.env.TARGET !== 'desktop') {
    if (Browser.ie) {
      target.addEventListener(touchEvents.start, onTouchStartBound, false);
      (Support.touch ? target : document).addEventListener(touchEvents.move, onTouchMoveBound, capture);
      (Support.touch ? target : document).addEventListener(touchEvents.end, onTouchEndBound, false);
    } else {
      if (Support.touch) {
        const passiveListener = touchEvents.start === 'onTouchStart' && Support.passiveListener && params.passiveListeners ? { passive: true, capture: false } : false;
        target.addEventListener(touchEvents.start, onTouchStartBound, passiveListener);
        target.addEventListener(touchEvents.move, onTouchMoveBound, capture);
        target.addEventListener(touchEvents.end, onTouchEndBound, passiveListener);
      }
      if ((params.simulateTouch && !Device.ios && !Device.android) || (params.simulateTouch && !Support.touch && Device.ios)) {
        target.addEventListener('mousedown', onTouchStartBound, false);
        document.addEventListener('mousemove', onTouchMoveBound, capture);
        document.addEventListener('mouseup', onTouchEndBound, false);
      }
    }
    // Prevent Links Clicks
    if (params.preventClicks || params.preventClicksPropagation) {
      target.addEventListener('click', onClickBound, true);
    }
  } else {
    target.addEventListener('click', onClickBound, false);
  }

  // Resize handler
  swiper.on('resize observerUpdate', onResize);
}

function detachEvents() {
  const swiper = this;

  const { params, touchEvents, el, wrapperEl } = swiper;

  const target = params.touchEventsTarget === 'container' ? el : wrapperEl;
  const capture = !!params.nested;

  // Touch Events
  if (process.env.TARGET !== 'desktop') {
    if (Browser.ie) {
      target.removeEventListener(touchEvents.start, onTouchStartBound, false);
      (Support.touch ? target : document).removeEventListener(touchEvents.move, onTouchMoveBound, capture);
      (Support.touch ? target : document).removeEventListener(touchEvents.end, onTouchEndBound, false);
    } else {
      if (Support.touch) {
        const passiveListener = touchEvents.start === 'onTouchStart' && Support.passiveListener && params.passiveListeners ? { passive: true, capture: false } : false;
        target.removeEventListener(touchEvents.start, onTouchStartBound, passiveListener);
        target.removeEventListener(touchEvents.move, onTouchMoveBound, capture);
        target.removeEventListener(touchEvents.end, onTouchEndBound, passiveListener);
      }
      if ((params.simulateTouch && !Device.ios && !Device.android) || (params.simulateTouch && !Support.touch && Device.ios)) {
        target.removeEventListener('mousedown', onTouchStartBound, false);
        document.removeEventListener('mousemove', onTouchMoveBound, capture);
        document.removeEventListener('mouseup', onTouchEndBound, false);
      }
    }
    // Prevent Links Clicks
    if (params.preventClicks || params.preventClicksPropagation) {
      target.removeEventListener('click', onClickBound, true);
    }
  } else {
    target.removeEventListener('click', onClickBound, true);
  }

  // Resize handler
  swiper.off('resize observerUpdate', onResize);
}

export default {
  attachEvents,
  detachEvents,
};
<commit_msg>Fix swiper events sharing<commit_after>import Device from '../../../utils/device';
import Support from '../../../utils/support';
import Browser from '../../../utils/browser';

import onTouchStart from './onTouchStart';
import onTouchMove from './onTouchMove';
import onTouchEnd from './onTouchEnd';
import onResize from './onResize';
import onClick from './onClick';

function attachEvents() {
  const swiper = this;

  const { params, touchEvents, el, wrapperEl } = swiper;

  if (process.env.TARGET !== 'desktop') {
    swiper.onTouchStart = onTouchStart.bind(swiper);
    swiper.onTouchMove = onTouchMove.bind(swiper);
    swiper.onTouchEnd = onTouchEnd.bind(swiper);
  }

  swiper.onClick = onClick.bind(swiper);

  const target = params.touchEventsTarget === 'container' ? el : wrapperEl;
  const capture = !!params.nested;

  // Touch Events
  if (process.env.TARGET !== 'desktop') {
    if (Browser.ie) {
      target.addEventListener(touchEvents.start, swiper.onTouchStart, false);
      (Support.touch ? target : document).addEventListener(touchEvents.move, swiper.onTouchMove, capture);
      (Support.touch ? target : document).addEventListener(touchEvents.end, swiper.onTouchEnd, false);
    } else {
      if (Support.touch) {
        const passiveListener = touchEvents.start === 'onTouchStart' && Support.passiveListener && params.passiveListeners ? { passive: true, capture: false } : false;
        target.addEventListener(touchEvents.start, swiper.onTouchStart, passiveListener);
        target.addEventListener(touchEvents.move, swiper.onTouchMove, capture);
        target.addEventListener(touchEvents.end, swiper.onTouchEnd, passiveListener);
      }
      if ((params.simulateTouch && !Device.ios && !Device.android) || (params.simulateTouch && !Support.touch && Device.ios)) {
        target.addEventListener('mousedown', swiper.onTouchStart, false);
        document.addEventListener('mousemove', swiper.onTouchMove, capture);
        document.addEventListener('mouseup', swiper.onTouchEnd, false);
      }
    }
    // Prevent Links Clicks
    if (params.preventClicks || params.preventClicksPropagation) {
      target.addEventListener('click', swiper.onClick, true);
    }
  } else {
    target.addEventListener('click', swiper.onClick, false);
  }

  // Resize handler
  swiper.on('resize observerUpdate', onResize);
}

function detachEvents() {
  const swiper = this;

  const { params, touchEvents, el, wrapperEl } = swiper;

  const target = params.touchEventsTarget === 'container' ? el : wrapperEl;
  const capture = !!params.nested;

  // Touch Events
  if (process.env.TARGET !== 'desktop') {
    if (Browser.ie) {
      target.removeEventListener(touchEvents.start, swiper.onTouchStart, false);
      (Support.touch ? target : document).removeEventListener(touchEvents.move, swiper.onTouchMove, capture);
      (Support.touch ? target : document).removeEventListener(touchEvents.end, swiper.onTouchEnd, false);
    } else {
      if (Support.touch) {
        const passiveListener = touchEvents.start === 'onTouchStart' && Support.passiveListener && params.passiveListeners ? { passive: true, capture: false } : false;
        target.removeEventListener(touchEvents.start, swiper.onTouchStart, passiveListener);
        target.removeEventListener(touchEvents.move, swiper.onTouchMove, capture);
        target.removeEventListener(touchEvents.end, swiper.onTouchEnd, passiveListener);
      }
      if ((params.simulateTouch && !Device.ios && !Device.android) || (params.simulateTouch && !Support.touch && Device.ios)) {
        target.removeEventListener('mousedown', swiper.onTouchStart, false);
        document.removeEventListener('mousemove', swiper.onTouchMove, capture);
        document.removeEventListener('mouseup', swiper.onTouchEnd, false);
      }
    }
    // Prevent Links Clicks
    if (params.preventClicks || params.preventClicksPropagation) {
      target.removeEventListener('click', swiper.onClick, true);
    }
  } else {
    target.removeEventListener('click', swiper.onClick, true);
  }

  // Resize handler
  swiper.off('resize observerUpdate', onResize);
}

export default {
  attachEvents,
  detachEvents,
};
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import Reflux from 'reflux';
import Codemirror from 'react-codemirror';

import CMInstance from 'codemirror';
import CT3000 from '../../config/codemirrorCustomMode';

import 'codemirror/addon/mode/simple';
import 'codemirror/addon/selection/active-line';

import ObjectActions from '../../actions/object';
import ObjectStore from '../../stores/object';

export default class EditorPane extends React.Component {

  constructor(props) {
    super(props);

    this.updateCode = this.updateCode.bind(this);
    this.parseUntilLine = this.parseUntilLine.bind(this);

    this.lineInterval = null;
    this.lineTimeoutDuration = 5000;

    this.state = {
      code: '',
      mode: '',
      languageInitiated: false
    };
  }

  componentDidMount() {
    this.unsubscribe = ObjectStore.listen((data) => {
      this.onUpdate(data);
    });

    this.cm = this.refs.editor.getCodeMirror();
    this.cm.on('cursorActivity', () => {
      this.parseUntilLine();
    });
    this.setLineInterval();

    // Example 'syntax error':
    // const doc = this.cm.getDoc();
    // doc.addLineClass(0,"wrap","syntax-error");
  }

  componentWillUnmount() {
    this.clearLineInterval();
    this.unsubscribe();
  }

  onUpdate(data) {
    if(!this.state.languageInitiated) {
      this.initLanguage();
    }
  }

  initLanguage() {
    let availableObjects = ObjectStore.getAvailableObjects();
    let availableValues = ObjectStore.getAvailableValues();

    var language = new CT3000();
    CMInstance.defineSimpleMode('ct-3000', language.getMode(availableObjects, availableValues));
    this.setState({
      mode: 'ct-3000',
      languageInitiated: true
    });
  }

  updateCode(newCode) {
    this.setState({
      code: newCode
    });
  }

  setLineInterval() {
    this.clearLineInterval();
    this.lineInterval = setInterval(this.parseUntilLine, this.lineTimeoutDuration);
  }

  clearLineInterval() {
    clearInterval(this.lineInterval);
  }

  parseUntilLine() {
    const currentLineNr = this.cm.getCursor().line;
    let lines = {};

    this.cm.eachLine(handle => {
      const lineNr = this.cm.getLineNumber(handle);
      const line = this.cm.getLine(lineNr);

      if(line && line.trim().length > 0) {
        lines[lineNr] = {
          code: line,
          current: currentLineNr === lineNr
        }
      }
    });

    if(Object.keys(lines).length > 0) {
      ObjectActions.parseMulti(lines);
    }
  }

  render() {
    var options = {
      lineNumbers: true,
      lineWrapping: true,
      indentUnit: 2,
      tabSize: 2,
      theme: 'monokai',
      styleActiveLine: true,
      mode: this.state.mode
    };

    return (

        <Codemirror ref="editor" value={ this.state.code } onChange={ this.updateCode } options={ options } />

    );
  }

}
<commit_msg>Highlight syntax errors.<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import Reflux from 'reflux';
import Codemirror from 'react-codemirror';

import CMInstance from 'codemirror';
import CT3000 from '../../config/codemirrorCustomMode';

import 'codemirror/addon/mode/simple';
import 'codemirror/addon/selection/active-line';

import ObjectActions from '../../actions/object';
import ObjectStore from '../../stores/object';

export default class EditorPane extends React.Component {

  constructor(props) {
    super(props);

    this.updateCode = this.updateCode.bind(this);
    this.parseUntilLine = this.parseUntilLine.bind(this);

    this.lineInterval = null;
    this.lineTimeoutDuration = 5000;

    this.previouslyFailedLines = [];

    this.state = {
      code: '',
      mode: '',
      languageInitiated: false
    };
  }

  componentDidMount() {
    this.unsubscribe = ObjectStore.listen((data) => {
      this.onUpdate(data);
    });

    this.cm = this.refs.editor.getCodeMirror();
    this.cmDoc = this.cm.getDoc();
    this.cm.on('cursorActivity', () => {
      this.parseUntilLine();
    });
    this.setLineInterval();
  }

  componentWillUnmount() {
    this.clearLineInterval();
    this.unsubscribe();
  }

  onUpdate(data) {
    if(!this.state.languageInitiated) {
      this.initLanguage();
    }

    // Remove previous syntax errors
    this.previouslyFailedLines.map(line => {
      this.cmDoc.removeLineClass(line, 'wrap', 'syntax-error');
    });

    // Highlight current syntax errors
    if(data.failedLines.length > 0) {
      data.failedLines.map(line => {
        this.cmDoc.addLineClass(line, 'wrap', 'syntax-error');
      });
    }

    // Store for the next parse round
    this.previouslyFailedLines = data.failedLines;
  }

  initLanguage() {
    let availableObjects = ObjectStore.getAvailableObjects();
    let availableValues = ObjectStore.getAvailableValues();

    var language = new CT3000();
    CMInstance.defineSimpleMode('ct-3000', language.getMode(availableObjects, availableValues));
    this.setState({
      mode: 'ct-3000',
      languageInitiated: true
    });
  }

  updateCode(newCode) {
    this.setState({
      code: newCode
    });
  }

  setLineInterval() {
    this.clearLineInterval();
    this.lineInterval = setInterval(this.parseUntilLine, this.lineTimeoutDuration);
  }

  clearLineInterval() {
    clearInterval(this.lineInterval);
  }

  parseUntilLine() {
    const currentLineNr = this.cm.getCursor().line;
    let lines = {};

    this.cm.eachLine(handle => {
      const lineNr = this.cm.getLineNumber(handle);
      const line = this.cm.getLine(lineNr);

      if(line && line.trim().length > 0) {
        lines[lineNr] = {
          code: line,
          current: currentLineNr === lineNr
        }
      }
    });

    if(Object.keys(lines).length > 0) {
      ObjectActions.parseMulti(lines);
    }
  }

  render() {
    var options = {
      lineNumbers: true,
      lineWrapping: true,
      indentUnit: 2,
      tabSize: 2,
      theme: 'monokai',
      styleActiveLine: true,
      mode: this.state.mode
    };

    return (

        <Codemirror ref="editor" value={ this.state.code } onChange={ this.updateCode } options={ options } />

    );
  }

}
<|endoftext|>


<commit_before>var co = require('co');
var e = require('./co-e');

function *A() {
  throw new Error('Something wrong');
}

function *B(msg) {
  return 'B:' + msg;
}

function *C(msg) {
  return msg + ':C';
}

// Normal
co(function *test() {
  var msg = 'Hello Co!';

  var a = yield e.defer(A()); //=> null
  var b = yield e(B(msg));    //=> 'B:Hello Co!'
  var c = yield e(C(b));      //=> 'B:Hello Co!:C'

  if (e.hasError()) { // Check the last time e.defer
    console.error(e.error().stack); //=> Error Full Stack
  }

  console.log('---------');
  console.log(c);
}).catch(function(err) {
  console.log(err.stack);
});

// Throw errors
co(function *anotherTest() {
  yield e(A()); //=> Throw
}).catch(function(err) {
  console.log(err.stack);
});

// With labels
co(function *withLabelsTest() {
  yield e.defer('deferred', A());

  if (e.hasError('deferred')) {
    console.error(e.error('deferred').stack);
  }

  yield e('not deferred', A());
}).catch(function(err) {
  console.log(err.stack);
});<commit_msg>Update examples.js<commit_after>var co = require('co');
var e = require('./');

function *A() {
  throw new Error('Something wrong');
}

function *B(msg) {
  return 'B:' + msg;
}

function *C(msg) {
  return msg + ':C';
}

// Normal
co(function *test() {
  var msg = 'Hello Co!';

  var a = yield e.defer(A()); //=> null
  var b = yield e(B(msg));    //=> 'B:Hello Co!'
  var c = yield e(C(b));      //=> 'B:Hello Co!:C'

  if (e.hasError()) { // Check the last time e.defer
    console.error(e.error().stack); //=> Error Full Stack
  }

  console.log('---------');
  console.log(c);
}).catch(function(err) {
  console.log(err.stack);
});

// Throw errors
co(function *anotherTest() {
  yield e(A()); //=> Throw
}).catch(function(err) {
  console.log(err.stack);
});

// With labels
co(function *withLabelsTest() {
  yield e.defer('deferred', A());

  if (e.hasError('deferred')) {
    console.error(e.error('deferred').stack);
  }

  yield e('not deferred', A());
}).catch(function(err) {
  console.log(err.stack);
});<|endoftext|>


<commit_before>import React from 'react';
import { addStoryInGroup, LOW_LEVEL_BLOCKS } from '../../../.storybook/utils';
import { boolean, select } from '@storybook/addon-knobs/react';
import { IconAddSmallOutline } from '@teamleader/ui-icons';
import { COLORS, TINTS, Link, TextBody, TextDisplay, TextSmall } from '../../index';

const elements = ['a', 'button'];
const iconPositions = ['left', 'right'];

export default {
  title: addStoryInGroup(LOW_LEVEL_BLOCKS, 'Link'),

  parameters: {
    info: {
      propTablesExclude: [TextBody, TextDisplay, TextSmall],
    },
  },
};

export const linkOnly = () => (
  <Link
    disabled={boolean('Disabled', false)}
    href="https://www.teamleader.be"
    target="_blank"
    inherit={boolean('Inherit', false)}
    inverse={boolean('Inverse', false)}
  >
    link
  </Link>
);

linkOnly.story = {
  name: 'Link only',
};

export const withText = () => (
  <TextBody color={select('Color', COLORS, 'teal')} tint={select('Tint', TINTS, 'darkest')}>
    Display text with a{' '}
    <Link
      href="https://www.teamleader.be"
      target="_blank"
      disabled={boolean('Disabled', false)}
      inherit={boolean('Inherit', false)}
      inverse={boolean('Inverse', false)}
    >
      link
    </Link>{' '}
    inside
  </TextBody>
);

withText.story = {
  name: 'With text',
};

export const withTextAndIcon = () => (
  <TextBody color={select('Color', COLORS, 'teal')} tint={select('Tint', TINTS, 'darkest')}>
    <Link
      disabled={boolean('Disabled', false)}
      href="https://www.teamleader.be"
      icon={<IconAddSmallOutline />}
      iconPlacement={select('Icon placement', iconPositions, 'left')}
      target="_blank"
      inherit={boolean('Inherit', false)}
      inverse={boolean('Inverse', false)}
    >
      link
    </Link>
  </TextBody>
);

withTextAndIcon.story = {
  name: 'With text and icon',
};

export const withCustomElement = () => (
  <TextBody color={select('Color', COLORS, 'teal')} tint={select('Tint', TINTS, 'darkest')}>
    <Link
      disabled={boolean('Disabled', false)}
      element={select('Element', elements, 'button')}
      icon={<IconAddSmallOutline />}
      iconPlacement={select('Icon placement', iconPositions, 'left')}
      inherit={boolean('Inherit', false)}
      inverse={boolean('Inverse', false)}
    >
      link
    </Link>
  </TextBody>
);

withCustomElement.story = {
  name: 'With custom element',
};

export const subtleIconLink = () => (
  <TextSmall color={select('Color', COLORS, 'neutral')} tint={select('Tint', TINTS, 'darkest')}>
    <Link
      disabled={boolean('Disabled', false)}
      element={select('Element', elements, 'button')}
      icon={<IconAddSmallOutline />}
      iconPlacement={select('Icon placement', iconPositions, 'left')}
      inherit={boolean('Inherit', true)}
      inverse={boolean('Inverse', false)}
    >
      Discount
    </Link>
  </TextSmall>
);

subtleIconLink.story = {
  name: 'Subtle icon link',
};
<commit_msg>Update stories<commit_after>import React from 'react';
import { addStoryInGroup, LOW_LEVEL_BLOCKS } from '../../../.storybook/utils';
import { boolean, select } from '@storybook/addon-knobs/react';
import { IconAddSmallOutline } from '@teamleader/ui-icons';
import { COLORS, TINTS, Box, Link, TextBody, TextDisplay, TextSmall } from '../../index';

const elements = ['a', 'button'];
const iconPositions = ['left', 'right'];

export default {
  title: addStoryInGroup(LOW_LEVEL_BLOCKS, 'Link'),

  parameters: {
    info: {
      propTablesExclude: [TextBody, TextDisplay, TextSmall],
    },
  },
};

export const linkOnly = () => (
  <Link
    disabled={boolean('Disabled', false)}
    href="https://www.teamleader.be"
    target="_blank"
    inherit={boolean('Inherit', false)}
    inverse={boolean('Inverse', false)}
  >
    link
  </Link>
);

linkOnly.story = {
  name: 'Link only',
};

export const withText = () => (
  <TextBody color={select('Color', COLORS, 'teal')} tint={select('Tint', TINTS, 'darkest')}>
    Display text with a{' '}
    <Link
      href="https://www.teamleader.be"
      target="_blank"
      disabled={boolean('Disabled', false)}
      inherit={boolean('Inherit', false)}
      inverse={boolean('Inverse', false)}
    >
      link
    </Link>{' '}
    inside
  </TextBody>
);

withText.story = {
  name: 'With text',
};

export const withTextAndIcon = () => (
  <TextBody color={select('Color', COLORS, 'teal')} tint={select('Tint', TINTS, 'darkest')}>
    <Link
      disabled={boolean('Disabled', false)}
      href="https://www.teamleader.be"
      icon={<IconAddSmallOutline />}
      iconPlacement={select('Icon placement', iconPositions, 'left')}
      target="_blank"
      inherit={boolean('Inherit', false)}
      inverse={boolean('Inverse', false)}
    >
      link
    </Link>
  </TextBody>
);

withTextAndIcon.story = {
  name: 'With text and icon',
};

export const withCustomElement = () => (
  <TextBody color={select('Color', COLORS, 'teal')} tint={select('Tint', TINTS, 'darkest')}>
    <Link
      disabled={boolean('Disabled', false)}
      element={select('Element', elements, 'button')}
      icon={<IconAddSmallOutline />}
      iconPlacement={select('Icon placement', iconPositions, 'left')}
      inherit={boolean('Inherit', false)}
      inverse={boolean('Inverse', false)}
    >
      link
    </Link>
  </TextBody>
);

withCustomElement.story = {
  name: 'With custom element',
};

export const subtleIconLink = () => (
  <TextSmall color={select('Color', COLORS, 'neutral')} tint={select('Tint', TINTS, 'darkest')}>
    <Link
      disabled={boolean('Disabled', false)}
      element={select('Element', elements, 'button')}
      icon={<IconAddSmallOutline />}
      iconPlacement={select('Icon placement', iconPositions, 'left')}
      inherit={boolean('Inherit', true)}
      inverse={boolean('Inverse', false)}
    >
      Discount
    </Link>
  </TextSmall>
);

subtleIconLink.story = {
  name: 'Subtle icon link',
};

export const badgedLink = () => (
  <Box>
    <TextSmall color={select('Color', COLORS, 'neutral')} tint={select('Tint', TINTS, 'darkest')} marginVertical={5}>
      <Link
        badged={boolean('Badged', true)}
        disabled={boolean('Disabled', false)}
        element={select('Element', elements, 'button')}
        inherit={boolean('Inherit', false)}
        inverse={boolean('Inverse', false)}
        marginLeft={-2}
      >
        Badged link
      </Link>
    </TextSmall>
    <TextBody color={select('Color', COLORS, 'neutral')} tint={select('Tint', TINTS, 'darkest')} marginVertical={5}>
      <Link
        badged={boolean('Badged', true)}
        disabled={boolean('Disabled', false)}
        element={select('Element', elements, 'button')}
        inherit={boolean('Inherit', false)}
        inverse={boolean('Inverse', false)}
        marginLeft={-2}
      >
        Badged link
      </Link>
    </TextBody>
    <TextDisplay color={select('Color', COLORS, 'neutral')} tint={select('Tint', TINTS, 'darkest')} marginVertical={5}>
      <Link
        badged={boolean('Badged', true)}
        disabled={boolean('Disabled', false)}
        element={select('Element', elements, 'button')}
        inherit={boolean('Inherit', false)}
        inverse={boolean('Inverse', false)}
        marginLeft={-2}
      >
        Badged link
      </Link>
    </TextDisplay>
    <TextSmall color={select('Color', COLORS, 'neutral')} tint={select('Tint', TINTS, 'darkest')} marginVertical={5}>
      A small text paragraph with a
      <Link
        badged={boolean('Badged', true)}
        disabled={boolean('Disabled', false)}
        element={select('Element', elements, 'button')}
        inherit={boolean('Inherit', false)}
        inverse={boolean('Inverse', false)}
      >
        badged link
      </Link>
      inside.
    </TextSmall>
    <TextBody color={select('Color', COLORS, 'neutral')} tint={select('Tint', TINTS, 'darkest')} marginVertical={5}>
      A small text paragraph with a
      <Link
        badged={boolean('Badged', true)}
        disabled={boolean('Disabled', false)}
        element={select('Element', elements, 'button')}
        inherit={boolean('Inherit', false)}
        inverse={boolean('Inverse', false)}
      >
        badged link
      </Link>
      inside.
    </TextBody>
    <TextDisplay color={select('Color', COLORS, 'neutral')} tint={select('Tint', TINTS, 'darkest')} marginVertical={5}>
      A small text paragraph with a
      <Link
        badged={boolean('Badged', true)}
        disabled={boolean('Disabled', false)}
        element={select('Element', elements, 'button')}
        inherit={boolean('Inherit', false)}
        inverse={boolean('Inverse', false)}
      >
        badged link
      </Link>
      inside.
    </TextDisplay>
  </Box>
);

badgedLink.story = {
  name: 'Badged link',
};
<|endoftext|>


<commit_before>import React from 'react';
import st from './style.module.css';
import Link from 'gatsby-link';
import Helmet from 'react-helmet';
import marked from 'marked';
import {defaultHelmetMeta} from '../../layouts/index';
import BackButton from '../back-button';
import TelegramIcon from '-!svg-react-loader?name=Icon!../../../static/telegram.svg';
import VkIcon from '-!svg-react-loader?name=Icon!../../../static/vk.svg';
import SlackIcon from '-!svg-react-loader?name=Icon!../../../static/slack.svg';
import TwitterIcon from '-!svg-react-loader?name=Icon!../../../static/twitter.svg';

const SocialIcon = ({user, link, children}) => {
  const icon = user ? (

        {children}

  ) : null;
  return icon;
};

const correctRussianEnding = num => {
  if (num === 1) {
    return '';
  }
  if (num === 2) {
    return 'а';
  }
  if (num > 4) {
    return 'ов';
  }
  return '';
};

export default ({history, pathContext: {data}}) => {
  const {
    name,
    lastname,
    vk,
    telegram,
    twitter,
    slack,
    bio,
    podcasts,
    talks,
    photo,
    slug,
  } = data;

  const stats = `${
    podcasts
      ? `${podcasts.length} подкаст${correctRussianEnding(podcasts.length)}; `
      : ''
  }${
    talks
      ? `${talks.length} доклад${correctRussianEnding(podcasts.length)};`
      : ''
  }`;

  const title = `${name} ${lastname}`;
  const description = bio ? `${bio.bio}; ${stats}` : stats;
  const metaImage = `${photo.file.url}?w=450&h=315&q=100`;
  const twitterImage = `https:${photo.file.url}?w=300&h=157&q=100`;

  const url = `https://spb-frontend.ru/persons/${slug}`;
  return (

      <Helmet
        meta={[
          {name: 'author', content: title},

          {name: 'twitter:card', content: 'summary'},
          {name: 'twitter:description', content: description},
          {name: 'twitter:site', content: '@spb_frontend'},
          {name: 'twitter:creator', content: '@spb_frontend'},
          {name: 'twitter:title', content: title},
          {name: 'twitter:url', content: url},
          {name: 'twitter:image', content: twitterImage},
          {name: 'twitter:image:src', content: twitterImage},
          {name: 'twitter:image:width', content: '300'},
          {name: 'twitter:image:height', content: '157'},

          {property: 'og:title', content: title},
          {property: 'og:site_name', content: title},
          {property: 'og:type', content: 'website'},

          {
            property: 'og:url',
            content: url,
          },
          {property: 'og:description', content: description},
          {property: 'og:image', content: metaImage},
          {property: 'og:site_name', content: title},

          {name: 'viewport', content: 'width=device-width, maximum-scale=1'},
        ]}
      />

        <BackButton history={history} />

            {name} {lastname}

            <SocialIcon link={`https://vk.com/${vk}`} user={vk}>
              <VkIcon />
            </SocialIcon>
            <SocialIcon link={`https://t.me/${telegram}`} user={telegram}>
              <TelegramIcon />
            </SocialIcon>
            <SocialIcon link={`https://twitter.com/${twitter}`} user={twitter}>
              <TwitterIcon />
            </SocialIcon>
            {slack ? (

                <SlackIcon /> {slack}

            ) : null}
            {bio &&
}

          {photo ? (
            <img src={`https:${photo.file.url}?fit=thumb&h=200&w=200`} />
          ) : (

          )}

      {podcasts && (

Подкасты:

          {podcasts
            .sort((prev, next) => {
              return parseInt(next.number) - parseInt(prev.number);
            })
            .map(({title, number}) => {
              return (

                  <Link to={`/podcast/${number}`}>{title}</Link>

              );
            })}

      )}
      {talks && (

Доклады:

          {talks
            .sort((prev, next) => {
              return next.path - prev.path;
            })
            .map(({title, slug}, key) => {
              return (

                  <Link to={`/talks/${slug}`}>{title}</Link>

              );
            })}

      )}

  );
};
<commit_msg>adjust meta description<commit_after>import React from 'react';
import st from './style.module.css';
import Link from 'gatsby-link';
import Helmet from 'react-helmet';
import marked from 'marked';
import {defaultHelmetMeta} from '../../layouts/index';
import BackButton from '../back-button';
import TelegramIcon from '-!svg-react-loader?name=Icon!../../../static/telegram.svg';
import VkIcon from '-!svg-react-loader?name=Icon!../../../static/vk.svg';
import SlackIcon from '-!svg-react-loader?name=Icon!../../../static/slack.svg';
import TwitterIcon from '-!svg-react-loader?name=Icon!../../../static/twitter.svg';

const SocialIcon = ({user, link, children}) => {
  const icon = user ? (

        {children}

  ) : null;
  return icon;
};

const correctRussianEnding = num => {
  if (num === 1) {
    return '';
  }
  if (num === 2) {
    return 'а';
  }
  if (num > 4) {
    return 'ов';
  }
  return '';
};

export default ({history, pathContext: {data}}) => {
  const {
    name,
    lastname,
    vk,
    telegram,
    twitter,
    slack,
    bio,
    podcasts,
    talks,
    photo,
    slug,
  } = data;

  const stats = `${
    podcasts
      ? `${podcasts.length} подкаст${correctRussianEnding(podcasts.length)}; `
      : ''
  }${
    talks ? `${talks.length} доклад${correctRussianEnding(talks.length)};` : ''
  }`;

  const title = `${name} ${lastname}`;
  const description = bio ? `${bio.bio}; ${stats}` : stats;
  const metaImage = `${photo.file.url}?w=450&h=315&q=100`;
  const twitterImage = `https:${photo.file.url}?w=300&h=157&q=100`;

  const url = `https://spb-frontend.ru/persons/${slug}`;
  return (

      <Helmet
        meta={[
          {name: 'author', content: title},

          {name: 'twitter:card', content: 'summary'},
          {name: 'twitter:description', content: description},
          {name: 'twitter:site', content: '@spb_frontend'},
          {name: 'twitter:creator', content: '@spb_frontend'},
          {name: 'twitter:title', content: title},
          {name: 'twitter:url', content: url},
          {name: 'twitter:image', content: twitterImage},
          {name: 'twitter:image:src', content: twitterImage},
          {name: 'twitter:image:width', content: '300'},
          {name: 'twitter:image:height', content: '157'},

          {property: 'og:title', content: title},
          {property: 'og:site_name', content: title},
          {property: 'og:type', content: 'website'},

          {
            property: 'og:url',
            content: url,
          },
          {property: 'og:description', content: description},
          {property: 'og:image', content: metaImage},
          {property: 'og:site_name', content: title},

          {name: 'viewport', content: 'width=device-width, maximum-scale=1'},
        ]}
      />

        <BackButton history={history} />

            {name} {lastname}

            <SocialIcon link={`https://vk.com/${vk}`} user={vk}>
              <VkIcon />
            </SocialIcon>
            <SocialIcon link={`https://t.me/${telegram}`} user={telegram}>
              <TelegramIcon />
            </SocialIcon>
            <SocialIcon link={`https://twitter.com/${twitter}`} user={twitter}>
              <TwitterIcon />
            </SocialIcon>
            {slack ? (

                <SlackIcon /> {slack}

            ) : null}
            {bio &&
}

          {photo ? (
            <img src={`https:${photo.file.url}?fit=thumb&h=200&w=200`} />
          ) : (

          )}

      {podcasts && (

Подкасты:

          {podcasts
            .sort((prev, next) => {
              return parseInt(next.number) - parseInt(prev.number);
            })
            .map(({title, number}) => {
              return (

                  <Link to={`/podcast/${number}`}>{title}</Link>

              );
            })}

      )}
      {talks && (

Доклады:

          {talks
            .sort((prev, next) => {
              return next.path - prev.path;
            })
            .map(({title, slug}, key) => {
              return (

                  <Link to={`/talks/${slug}`}>{title}</Link>

              );
            })}

      )}

  );
};
<|endoftext|>