<commit_before>import React from 'react';

import SPELLS from 'common/SPELLS';

export default {
  descriptions: {
    [SPELLS.THE_PENITENT_TALENT.id]: Penance may be cast on a friendly target, healing them for [(300% of Spell power) * 3] over 2 sec.,
    [SPELLS.CASTIGATION_TALENT.id]: Penance fires one additional bolt of holy light over its duration.,
    [SPELLS.SCHISM_TALENT.id]: Attack the enemy's soul with a surge of Shadow energy, dealing (425% of Spell power) Shadow damage and increasing damage that you deal to the target by 30% for 6 sec.,
    [SPELLS.ANGELIC_FEATHER_TALENT.id]: Places a feather at the target location, granting the first ally to walk through it 40% increased movement speed for 5 sec. Only 3 feathers can be placed at one time.  Maximum 3 charges.,
    [SPELLS.BODY_AND_SOUL_TALENT.id]: Power Word: Shield  Shadowincreases Disciplineand Leap of Faith increase your target's movement speed by 40% for 3 sec.,
    [SPELLS.MASOCHISM_TALENT.id]: When you cast Shadow Mend on yourself, its damage over time effect heals you instead, and reduces all damage you take by 10%.,
    [SPELLS.SHINING_FORCE_TALENT.id]: Creates a burst of light around a friendly target, knocking away nearby enemies and slowing their movement speed by 70% for 3 sec.3 seconds remaining,
    [SPELLS.PSYCHIC_VOICE_TALENT.id]: Reduces the cooldown of Psychic Scream by 30 sec.,
    [SPELLS.DOMINANT_MIND_TALENT.id]: You may also control your own character while Mind Control is active, but Mind Control has a 2 min cooldown, and it may not be used against players.,
    [SPELLS.POWER_WORD_SOLACE_TALENT.id]: Strikes an enemy with heavenly power, dealing (300% of Spell power) Holy damage and restoring 1.00% of your maximum mana.,
    [SPELLS.SHIELD_DISCIPLINE_TALENT.id]: When your Power Word: Shield is completely absorbed you instantly regenerate 1% of your maximum mana.,
    [SPELLS.MINDBENDER_TALENT.id]: Summons a Mindbender to attack the target for 12 sec. You regenerate 0.50% of maximum mana each time the Mindbender attacks.,
    [SPELLS.CONTRITION_TALENT.id]: Increases Atonement duration by 3 sec.,
    [SPELLS.POWER_INFUSION_TALENT.id]: Infuses you with power for 20 sec, increasing haste by 25% Shadowand increasing Insanity generation by 25% Disciplineand reducing the mana cost of all spells by 20%20 seconds remaining,
    [SPELLS.TWIST_OF_FATE_TALENT.id]: After  Shadowdamaging Disciplinehealing a target below 35% health, you deal 20% increased damage and 20% increased healing for 10 sec.,
    [SPELLS.CLARITY_OF_WILL_TALENT.id]: Shields the target with a protective ward for 20 sec, absorbing [Spell power * 9 * (1 + Versatility)] damage.20 seconds remaining,
    [SPELLS.DIVINE_STAR_TALENT.id]: Throw a Divine Star forward 24 yds, healing allies in its path for (90% of Spell power) and dealing (145% of Spell power) Holy damage to enemies. After reaching its destination, the Divine Star returns to you, healing allies and damaging enemies in its path again.,
    [SPELLS.HALO_TALENT.id]: Creates a ring of Holy energy around you that quickly expands to a 30 yd radius, healing allies for (287.4% of Spell power) and dealing (431.1% of Spell power) Holy damage to enemies.,
    [SPELLS.PURGE_THE_WICKED_TALENT.id]: Cleanses the target with fire, causing (100% of Spell power) Fire damage and an additional (480% of Spell power) Fire damage over 20 sec. Spreads to an additional nearby enemy when you cast Penance on the target.,
    [SPELLS.GRACE_TALENT.id]: Increases your non-Atonement healing and absorption by 30% on targets with Atonement.,
    [SPELLS.SHADOW_COVENANT_TALENT.id]: Draws on the power of shadow to heal up to 5 injured allies within 30 yds of the target for (450% of Spell power), but leaves a shell on them that absorbs the next [(450% of Spell power) * 50 / 100] healing they receive within 6 sec.,
  },
  // attribution: Parts of the descriptions for talents came from the Holy Paladin Wowhead guide by Pelinal.,
};
<commit_msg>7.2.5 Talent Descriptions<commit_after>import React from 'react';

import SPELLS from 'common/SPELLS';

export default {
  descriptions: {
    [SPELLS.TWIST_OF_FATE_TALENT.id]: After  Shadowdamaging Disciplinehealing a target below 35% health, you deal 20% increased damage and 20% increased healing for 10 sec.,
    [SPELLS.CASTIGATION_TALENT.id]: Penance fires one additional bolt of holy light over its duration.,
    [SPELLS.SCHISM_TALENT.id]: Attack the enemy's soul with a surge of Shadow energy, dealing (425% of Spell power) Shadow damage and increasing damage that you deal to the target by 30% for 6 sec.,

    [SPELLS.ANGELIC_FEATHER_TALENT.id]: Places a feather at the target location, granting the first ally to walk through it 40% increased movement speed for 5 sec. Only 3 feathers can be placed at one time.  Maximum 3 charges.,
    [SPELLS.BODY_AND_SOUL_TALENT.id]: Power Word: Shield  Shadowincreases Disciplineand Leap of Faith increase your target's movement speed by 40% for 3 sec.,
    [SPELLS.MASOCHISM_TALENT.id]: When you cast Shadow Mend on yourself, its damage over time effect heals you instead, and reduces all damage you take by 10%.,

    [SPELLS.SHINING_FORCE_TALENT.id]: Creates a burst of light around a friendly target, knocking away nearby enemies and slowing their movement speed by 70% for 3 sec.3 seconds remaining,
    [SPELLS.PSYCHIC_VOICE_TALENT.id]: Reduces the cooldown of Psychic Scream by 30 sec.,
    [SPELLS.DOMINANT_MIND_TALENT.id]: You may also control your own character while Mind Control is active, but Mind Control has a 2 min cooldown, and it may not be used against players.,

    [SPELLS.POWER_WORD_SOLACE_TALENT.id]: Strikes an enemy with heavenly power, dealing (300% of Spell power) Holy damage and restoring 1.00% of your maximum mana.,
    [SPELLS.SHIELD_DISCIPLINE_TALENT.id]: When your Power Word: Shield is completely absorbed you instantly regenerate 1% of your maximum mana.,
    [SPELLS.MINDBENDER_TALENT.id]: Summons a Mindbender to attack the target for 12 sec. You regenerate 0.50% of maximum mana each time the Mindbender attacks.,

    [SPELLS.SANCTUARY_TALENT.id]: Smite's absorption effect is increased by 50%.,
    [SPELLS.CLARITY_OF_WILL_TALENT.id]: Shields the target with a protective ward for 20 sec, absorbing [Spell power * 9 * (1 + Versatility)] damage.20 seconds remaining,
    [SPELLS.SHADOW_COVENANT_TALENT.id]: Draws on the power of shadow to heal up to 5 injured allies within 30 yds of the target for (450% of Spell power), but leaves a shell on them that absorbs the next [(450% of Spell power) * 50 / 100] healing they receive within 6 sec.,

    [SPELLS.PURGE_THE_WICKED_TALENT.id]: Cleanses the target with fire, causing (100% of Spell power) Fire damage and an additional (500% of Spell power) Fire damage over 20 sec. Spreads to an additional nearby enemy when you cast Penance on the target.,
    [SPELLS.DIVINE_STAR_TALENT.id]: Throw a Divine Star forward 24 yds, healing allies in its path for (90% of Spell power) and dealing (145% of Spell power) Holy damage to enemies. After reaching its destination, the Divine Star returns to you, healing allies and damaging enemies in its path again.,
    [SPELLS.HALO_TALENT.id]: Creates a ring of Holy energy around you that quickly expands to a 30 yd radius, healing allies for (287.4% of Spell power) and dealing (431.1% of Spell power) Holy damage to enemies.,

    [SPELLS.POWER_INFUSION_TALENT.id]: Infuses you with power for 20 sec, increasing haste by 25% Shadowand increasing Insanity generation by 25% Disciplineand reducing the mana cost of all spells by 20%20 seconds remaining,
    [SPELLS.GRACE_TALENT.id]: Increases your non-Atonement healing and absorption by 30% on targets with Atonement.,
    [SPELLS.EVANGELISM_TALENT.id]: Extends the duration of all of your active Atonements by 7 sec.,
},
  // attribution: Parts of the descriptions for talents came from the Holy Paladin Wowhead guide by Pelinal.,
};
<|endoftext|>


<commit_before>import React from 'react';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';
import SpellIcon from 'common/SpellIcon';
import Wrapper from 'common/Wrapper';
import { formatMilliseconds, formatPercentage } from 'common/format';
import StatisticBox, { STATISTIC_ORDER } from 'Main/StatisticBox';
import Combatants from 'Parser/Core/Modules/Combatants';
import Analyzer from 'Parser/Core/Analyzer';
import AbilityTracker from 'Parser/Core/Modules/AbilityTracker';
import EnemyInstances, { encodeTargetString } from 'Parser/Core/Modules/EnemyInstances';

const debug = false;

const GUARANTEE_CRIT_SPELLS = [
  SPELLS.FIRE_BLAST.id,
  SPELLS.PHOENIXS_FLAMES.id,
];

class HeatingUp extends Analyzer {
  static dependencies = {
    combatants: Combatants,
    abilityTracker: AbilityTracker,
    enemies: EnemyInstances,
  };

  fireBlastWithoutHeatingUp = 0;
  phoenixFlamesWithoutHeatingUp = 0;
  fireBlastWithHotStreak = 0;
  phoenixFlamesWithHotStreak = 0;
  targetId = 0;

  on_byPlayer_cast(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.PHOENIXS_FLAMES.id) {
      return;
    }
    this.targetId = encodeTargetString(event.targetID, event.targetInstance);
  }

  on_byPlayer_damage(event) {
    const spellId = event.ability.guid;
    const damageTarget = encodeTargetString(event.targetID, event.targetInstance);
    if (!GUARANTEE_CRIT_SPELLS.includes(spellId) || (spellId === SPELLS.PHOENIXS_FLAMES.id && (this.targetId !== damageTarget))) {
      return;
    }
    const hasHeatingUp = this.combatants.selected.hasBuff(SPELLS.HEATING_UP.id);
    const hasHotStreak = this.combatants.selected.hasBuff(SPELLS.HOT_STREAK.id);

    switch (spellId) {
      case SPELLS.FIRE_BLAST.id:
        if (hasHotStreak) {
          this.fireBlastWithHotStreak += 1;
          debug && console.log("Fire Blast with Hot Streak @ " + formatMilliseconds(event.timestamp - this.owner.fight.start_time));
        }
        if (!hasHeatingUp && !hasHotStreak) {
          this.fireBlastWithoutHeatingUp += 1;
          debug && console.log("Fire Blast without Heating Up @ " + formatMilliseconds(event.timestamp - this.owner.fight.start_time));
        }
        break;
      case SPELLS.PHOENIXS_FLAMES.id:
        if (hasHotStreak) {
          this.phoenixFlamesWithHotStreak += 1;
          debug && console.log("Phoenix Flames with Hot Streak @ " + formatMilliseconds(event.timestamp - this.owner.fight.start_time));
        }
        if (!hasHeatingUp && !hasHotStreak) {
          this.phoenixFlamesWithoutHeatingUp += 1;
          debug && console.log("Phoenix Flames without Heating Up @ " + formatMilliseconds(event.timestamp - this.owner.fight.start_time));
        }
        break;
      default:
        return;
    }
  }

  get fireBlastWasted() {
    return this.fireBlastWithoutHeatingUp + this.fireBlastWithHotStreak;
  }

  get phoenixFlamesWasted() {
    return this.phoenixFlamesWithoutHeatingUp + this.phoenixFlamesWithHotStreak;
  }

  get totalWasted() {
    return this.fireBlastWasted + this.phoenixFlamesWasted;
  }

  get fireBlastUtil() {
    return 1 - this.fireBlastMissedPercent;
  }

  get phoenixFlamesUtil() {
    return 1 - this.phoenixFlamesMissedPercent;
  }

  get fireBlastMissedPercent() {
    return this.fireBlastWasted / this.abilityTracker.getAbility(SPELLS.FIRE_BLAST.id).casts;
  }

  get phoenixFlamesMissedPercent() {
    return this.phoenixFlamesWasted / this.abilityTracker.getAbility(SPELLS.PHOENIXS_FLAMES.id).casts;
  }

  suggestions(when) {
		when(this.fireBlastMissedPercent).isGreaterThan(0.1)
			.addSuggestion((suggest, actual, recommended) => {
				return suggest(<Wrapper>You cast <SpellLink id={SPELLS.FIRE_BLAST.id} /> {this.fireBlastWithHotStreak} times while <SpellLink id={SPELLS.HOT_STREAK.id}/> was active and {this.fireBlastWithoutHeatingUp} times while you didnt have <SpellLink id={SPELLS.HEATING_UP.id}/>. Make sure that you are only using Fire Blast to convert Heating Up into Hot Streak.</Wrapper>)
					.icon(SPELLS.FIRE_BLAST.icon)
					.actual(`${formatPercentage(this.fireBlastMissedPercent)}% missed`)
					.recommended(`<${formatPercentage(recommended)}% is recommended`)
					.regular(0.1).major(0.2);
			});
    when(this.phoenixFlamesMissedPercent).isGreaterThan(0.1)
			.addSuggestion((suggest, actual, recommended) => {
				return suggest(<Wrapper>You cast <SpellLink id={SPELLS.PHOENIXS_FLAMES.id} /> {this.phoenixFlamesWithHotStreak} times while <SpellLink id={SPELLS.HOT_STREAK.id}/> was active and {this.phoenixFlamesWithoutHeatingUp} times while you didnt have <SpellLink id={SPELLS.HEATING_UP.id}/>. Make sure that you are only using Phoenixs Flames to convert Heating Up into Hot Streak.</Wrapper>)
					.icon(SPELLS.FIRE_BLAST.icon)
					.actual(`${formatPercentage(this.phoenixFlamesMissedPercent)}% missed`)
					.recommended(`<${formatPercentage(recommended)}% is recommended`)
					.regular(0.1).major(0.2);
			});
	}

  statistic() {
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.HEATING_UP.id} />}
        value={(

            <SpellIcon
              id={SPELLS.FIRE_BLAST.id}
              style={{
                height: '1.2em',
                marginBottom: '.15em',
              }}
            />
            {' '}{formatPercentage(this.fireBlastUtil, 0)}{' %'}

            <SpellIcon
              id={SPELLS.PHOENIXS_FLAMES.id}
              style={{
                height: '1.2em',
                marginBottom: '.15em',
              }}
            />
            {' '}{formatPercentage(this.phoenixFlamesUtil, 0)}{' %'}

        )}
        label="Heating Up Utilization"
        tooltip={`Spells that are guaranteed to crit like Fire Blast and Phoenix's Flames should only be used to convert Heating Up to Hot Streak. While there are minor exceptions to this (like baiting a crit just before using Combustion), the goal should be to waste as few of these as possible.

Fireblast Used with no procs: ${this.fireBlastWithoutHeatingUp}

Fireblast used during Hot Streak: ${this.fireBlastWithHotStreak}

Phoenix's Flames used with no procs: ${this.phoenixFlamesWithoutHeatingUp}

Phoenix's Flames used during Hot Streak: ${this.phoenixFlamesWithHotStreak}

`}
      />
    );
  }
  statisticOrder = STATISTIC_ORDER.CORE(14);

}

export default HeatingUp;
<commit_msg>suggested changes<commit_after>import React from 'react';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';
import SpellIcon from 'common/SpellIcon';
import Wrapper from 'common/Wrapper';
import { formatMilliseconds, formatPercentage } from 'common/format';
import StatisticBox, { STATISTIC_ORDER } from 'Main/StatisticBox';
import Combatants from 'Parser/Core/Modules/Combatants';
import Analyzer from 'Parser/Core/Analyzer';
import AbilityTracker from 'Parser/Core/Modules/AbilityTracker';
import EnemyInstances, { encodeTargetString } from 'Parser/Core/Modules/EnemyInstances';

const debug = false;

const GUARANTEE_CRIT_SPELLS = [
  SPELLS.FIRE_BLAST.id,
  SPELLS.PHOENIXS_FLAMES.id,
];

class HeatingUp extends Analyzer {
  static dependencies = {
    combatants: Combatants,
    abilityTracker: AbilityTracker,
    enemies: EnemyInstances,
  };

  fireBlastWithoutHeatingUp = 0;
  phoenixFlamesWithoutHeatingUp = 0;
  fireBlastWithHotStreak = 0;
  phoenixFlamesWithHotStreak = 0;
  targetId = 0;

  on_byPlayer_cast(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.PHOENIXS_FLAMES.id) {
      return;
    }
    this.targetId = encodeTargetString(event.targetID, event.targetInstance);
  }

  on_byPlayer_damage(event) {
    const spellId = event.ability.guid;
    const damageTarget = encodeTargetString(event.targetID, event.targetInstance);
    if (!GUARANTEE_CRIT_SPELLS.includes(spellId) || (spellId === SPELLS.PHOENIXS_FLAMES.id && (this.targetId !== damageTarget))) {
      return;
    }
    const hasHeatingUp = this.combatants.selected.hasBuff(SPELLS.HEATING_UP.id);
    const hasHotStreak = this.combatants.selected.hasBuff(SPELLS.HOT_STREAK.id);

    if (spellId === SPELLS.FIRE_BLAST.id) {
      if (hasHotStreak) {
        this.fireBlastWithHotStreak += 1;
        debug && console.log("Fire Blast with Hot Streak @ " + formatMilliseconds(event.timestamp - this.owner.fight.start_time));
      } else if (!hasHeatingUp) {
        this.fireBlastWithoutHeatingUp += 1;
        debug && console.log("Fire Blast without Heating Up @ " + formatMilliseconds(event.timestamp - this.owner.fight.start_time));
      }
    } else if (spellId === SPELLS.PHOENIXS_FLAMES.id) {
        if (hasHotStreak) {
          this.phoenixFlamesWithHotStreak += 1;
          debug && console.log("Phoenix Flames with Hot Streak @ " + formatMilliseconds(event.timestamp - this.owner.fight.start_time));
        } else if (!hasHeatingUp) {
          this.phoenixFlamesWithoutHeatingUp += 1;
          debug && console.log("Phoenix Flames without Heating Up @ " + formatMilliseconds(event.timestamp - this.owner.fight.start_time));
        }
    }
  }

  get fireBlastWasted() {
    return this.fireBlastWithoutHeatingUp + this.fireBlastWithHotStreak;
  }

  get phoenixFlamesWasted() {
    return this.phoenixFlamesWithoutHeatingUp + this.phoenixFlamesWithHotStreak;
  }

  get totalWasted() {
    return this.fireBlastWasted + this.phoenixFlamesWasted;
  }

  get fireBlastUtil() {
    return 1 - this.fireBlastMissedPercent;
  }

  get phoenixFlamesUtil() {
    return 1 - this.phoenixFlamesMissedPercent;
  }

  get fireBlastMissedPercent() {
    return this.fireBlastWasted / this.abilityTracker.getAbility(SPELLS.FIRE_BLAST.id).casts;
  }

  get phoenixFlamesMissedPercent() {
    return this.phoenixFlamesWasted / this.abilityTracker.getAbility(SPELLS.PHOENIXS_FLAMES.id).casts;
  }

  get fireBlastUtilSuggestionThresholds() {
    return {
      actual: this.fireBlastUtil,
      isLessThan: {
        minor: 0.95,
        average: 0.85,
        major: 0.70,
      },
      style: 'percentage',
    };
  }

  get phoenixFlamesUtilSuggestionThresholds() {
    return {
      actual: this.phoenixFlamesUtil,
      isLessThan: {
        minor: 0.90,
        average: 0.80,
        major: 0.70,
      },
      style: 'percentage',
    };
  }

  suggestions(when) {
		when(this.fireBlastMissedPercent).isLessThan(this.fireBlastUtilSuggestionThresholds.isLessThan.minor)
			.addSuggestion((suggest, actual, recommended) => {
				return suggest(<Wrapper>You cast <SpellLink id={SPELLS.FIRE_BLAST.id} /> {this.fireBlastWithHotStreak} times while <SpellLink id={SPELLS.HOT_STREAK.id}/> was active and {this.fireBlastWithoutHeatingUp} times while you didnt have <SpellLink id={SPELLS.HEATING_UP.id}/>. Make sure that you are only using Fire Blast to convert Heating Up into Hot Streak.</Wrapper>)
					.icon(SPELLS.FIRE_BLAST.icon)
					.actual(`${formatPercentage(this.fireBlastMissedPercent)}% missed`)
					.recommended(`<${formatPercentage(recommended)}% is recommended`)
					.regular(this.fireBlastUtilSuggestionThresholds.isLessThan.average).major(this.fireBlastUtilSuggestionThresholds.isLessThan.major);
			});
    when(this.phoenixFlamesMissedPercent).isLessThan(this.phoenixFlamesUtilSuggestionThresholds.isLessThan.minor)
			.addSuggestion((suggest, actual, recommended) => {
				return suggest(<Wrapper>You cast <SpellLink id={SPELLS.PHOENIXS_FLAMES.id} /> {this.phoenixFlamesWithHotStreak} times while <SpellLink id={SPELLS.HOT_STREAK.id}/> was active and {this.phoenixFlamesWithoutHeatingUp} times while you didnt have <SpellLink id={SPELLS.HEATING_UP.id}/>. While ideally you should only be using these to convert Heating Up into Hot Streak, there are some minor circumstances where it is acceptable (i.e. If you are about to cap on Phoenixs Flames charges or when used alongside <SpellLink id={SPELLS.FIREBALL.id}/> to bait Heating Up or Hot Streak just before <SpellLink id={SPELLS.COMBUSTION.id}/>.</Wrapper>)
					.icon(SPELLS.FIRE_BLAST.icon)
					.actual(`${formatPercentage(this.phoenixFlamesMissedPercent)}% missed`)
					.recommended(`<${formatPercentage(recommended)}% is recommended`)
					.regular(this.phoenixFlamesUtilSuggestionThresholds.isLessThan.average).major(this.phoenixFlamesUtilSuggestionThresholds.isLessThan.major);
			});
	}

  statistic() {
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.HEATING_UP.id} />}
        value={(

            <SpellIcon
              id={SPELLS.FIRE_BLAST.id}
              style={{
                height: '1.2em',
                marginBottom: '.15em',
              }}
            />
            {' '}{formatPercentage(this.fireBlastUtil, 0)}{' %'}

            <SpellIcon
              id={SPELLS.PHOENIXS_FLAMES.id}
              style={{
                height: '1.2em',
                marginBottom: '.15em',
              }}
            />
            {' '}{formatPercentage(this.phoenixFlamesUtil, 0)}{' %'}

        )}
        label="Heating Up Utilization"
        tooltip={`Spells that are guaranteed to crit like Fire Blast and Phoenix's Flames should only be used to convert Heating Up to Hot Streak. While there are minor exceptions to this (like if you are about to cap on Phoenixs Flames charges or using Fireball & Phoenixs Flames to bait Heating Up/Hot Streak just before Combustion), the goal should be to waste as few of these as possible.

Fireblast Used with no procs: ${this.fireBlastWithoutHeatingUp}

Fireblast used during Hot Streak: ${this.fireBlastWithHotStreak}

Phoenix's Flames used with no procs: ${this.phoenixFlamesWithoutHeatingUp}

Phoenix's Flames used during Hot Streak: ${this.phoenixFlamesWithHotStreak}

`}
      />
    );
  }
  statisticOrder = STATISTIC_ORDER.CORE(14);

}

export default HeatingUp;
<|endoftext|>


<commit_before>// See Purple/license.txt for Google BSD license
// Copyright 2011 Google, Inc. johnjbarton@johnjbarton.com

define(['../lib/domplate/lib/domplate', '../resources/Resources', '../lib/reps', '../lib/Rep'], function (domplate, Resources, Reps, Rep) {

  var thePurple = window.purple;

  with(domplate.tags) {

    var PartLinkRep = domplate.domplate(Rep, {
      tag: A({
          "class":"$object|getPartLinkClass PartLink-$targetPart a11yFocus",
          _target: "$targetPart",
          _repObject: '$object',
          'onclick': '$clickLink'
      }, "$object|getPartLinkText" ),
      getResourceName: function(object) {
        var url = this.getURL(object);
        if (!url) {
          return "No URL for "+object;
        }
        var splits = url.split('/');
        return splits.slice(-1);
      },
      getPartLinkText: function(object) {
        return this.getResourceName(object);
      },
      getPartLinkClass: function(object) {
        var url = this.getURL(object);
        var resource = Resources.get(url);
        return (resource && resource.hasSource) ? 'partLink' : 'noSource';
      },

      // Implements Rep
      name: "PartLinkRep",
      getRequiredPropertyNames: function() {
        return ['url'];
      },
      getOptionalPropertyNames: function() {
        return ['lineNumber', 'columnNumber'];
      }
    });

  }

  PartLinkRep.targetPart = 'editor', // constant for all instances for now

  PartLinkRep.getURL = function(object) {
    // override in reps
    return object.url;
  }

  PartLinkRep.getLineNumber = function(object) {
    // override in reps
    return object.lineNumber;
  }

  PartLinkRep.getColumnNumber = function(object) {
    // override in reps
    return object.columnNumber;
  }

  // Will be called with |this| bound to domplate (rep)
  PartLinkRep.clickLink = function(event) {
    var target = event.currentTarget;  // the element with the handler
    var destinationFeature = target.getAttribute('target');
    var repObject = target.repObject;
    var rep = repObject.rep || PartLinkRep;
    var resource = repObject;
    if (!repObject.fetchContent) {
      var url = rep.getURL(repObject);
      if (url) {
        resource = Resources.get(url);
      }
      if (! resource.fetchContent) {
        PartLinkRep.onError("No source associated with clickLink target");
        return;
      }
    }
    var lineNumber = rep.getLineNumber(repObject); // the line comes from eg the error message
    var columnNumber = rep.getColumnNumber(repObject); // the line comes from eg the error message
    try {
      rep.openPartWith(destinationFeature, resource, lineNumber, columnNumber);
    } catch(exc) {
      PartLinkRep.onError(exc);
    }
    event.stopPropagation(); // we only want this one action
    event.preventDefault();
  };

  PartLinkRep.openPartWith = function(feature, resource, lineNumber, columnNumber) {
    var destinationPart = thePurple.getPartByFeature(feature);
    if (destinationPart) {
      destinationPart.open(resource, lineNumber, columnNumber);
    } else {
      PartLinkRep.onError("No part with feature "+feature+" found");
    }
  }

  PartLinkRep.onError = function() {
    console.error.apply(console, arguments);
    alert(arguments[0]);
  }

  Reps.registerPart(PartLinkRep);

  return PartLinkRep;
});<commit_msg>refactor to define getResource<commit_after>// See Purple/license.txt for Google BSD license
// Copyright 2011 Google, Inc. johnjbarton@johnjbarton.com

define(['../lib/domplate/lib/domplate', '../resources/Resources', '../lib/reps', '../lib/Rep'], function (domplate, Resources, Reps, Rep) {

  var thePurple = window.purple;

  with(domplate.tags) {

    var PartLinkRep = domplate.domplate(Rep, {
      tag: A({
          "class":"$object|getPartLinkClass PartLink-$targetPart a11yFocus",
          _target: "$targetPart",
          _repObject: '$object',
          'onclick': '$clickLink'
      }, "$object|getPartLinkText" ),
      getResourceName: function(object) {
        var url = this.getURL(object);
        if (!url) {
          return "No URL for "+object;
        }
        var splits = url.split('/');
        return splits.slice(-1);
      },
      getPartLinkText: function(object) {
        return this.getResourceName(object);
      },
      getPartLinkClass: function(object) {
        var resource = this.getResource(object);
        return (resource && resource.hasSource) ? 'partLink' : 'noSource';
      },

      getResource: function(object) {
        var url = this.getURL(object);
        var resource = Resources.get(url);
        return resource;
      },

      // Implements Rep
      name: "PartLinkRep",
      getRequiredPropertyNames: function() {
        return ['url'];
      },
      getOptionalPropertyNames: function() {
        return ['lineNumber', 'columnNumber'];
      }
    });

  }

  PartLinkRep.targetPart = 'editor', // constant for all instances for now

  PartLinkRep.getURL = function(object) {
    // override in reps
    return object.url;
  }

  PartLinkRep.getLineNumber = function(object) {
    // override in reps
    return object.lineNumber;
  }

  PartLinkRep.getColumnNumber = function(object) {
    // override in reps
    return object.columnNumber;
  }

  // Will be called with |this| bound to domplate (rep)
  PartLinkRep.clickLink = function(event) {
    var target = event.currentTarget;  // the element with the handler
    var destinationFeature = target.getAttribute('target');
    var repObject = target.repObject;
    var rep = repObject.rep || PartLinkRep;
    var resource = repObject;
    if (!repObject.fetchContent) {
      var url = rep.getURL(repObject);
      if (url) {
        resource = Resources.get(url);
      }
      if (! resource.fetchContent) {
        PartLinkRep.onError("No source associated with clickLink target");
        return;
      }
    }
    var lineNumber = rep.getLineNumber(repObject); // the line comes from eg the error message
    var columnNumber = rep.getColumnNumber(repObject); // the line comes from eg the error message
    try {
      rep.openPartWith(destinationFeature, resource, lineNumber, columnNumber);
    } catch(exc) {
      PartLinkRep.onError(exc);
    }
    event.stopPropagation(); // we only want this one action
    event.preventDefault();
  };

  PartLinkRep.openPartWith = function(feature, resource, lineNumber, columnNumber) {
    var destinationPart = thePurple.getPartByFeature(feature);
    if (destinationPart) {
      destinationPart.open(resource, lineNumber, columnNumber);
    } else {
      PartLinkRep.onError("No part with feature "+feature+" found");
    }
  }

  PartLinkRep.onError = function() {
    console.error.apply(console, arguments);
    alert(arguments[0]);
  }

  Reps.registerPart(PartLinkRep);

  return PartLinkRep;
});<|endoftext|>


<commit_before>// -*- mode: js; indent-tabs-mode: nil; -*-
requirejs.config({
  shim: {
    'jquery-ui': ['jquery'],
    'jquery.pnotify': ['jquery-ui'],
    'engine/socket.io': ['socket.io']
  }
});

require(['engine/splash'], function (splash) {
  var socket, loader = splash.create({
    element: '#splash'
  }), modules;

  modules = [
    // oldschool/shim
    'ace/lib/fixoldbrowsers', 'jquery-ui', 'jquery.pnotify',
    // main
    'engine/window', 'engine/minibuffer', 'engine/socket.io', 'engine/commander',
    'engine/buffer', 'engine/eventuality',
    // supplementary
    'theme/engine'
  ];

  (function (modules, rest) {
    var load = function () {
      var args = Array.prototype.splice.call(arguments, 0), current;
      if (args.length) {
        current = args.shift();
        loader.queue(30/modules.length, 'Loading AMD module ' + current, function () {
          require([current], function () { load.apply(null, args); });
        });
      } else {
        rest();
      }
    };

    load.apply(null, modules);
  }) (modules, function () {
    loader.queue(5, 'Establishing WebSocket connection', function () { socket = io.connect(); });
    require(['engine/jquery', 'engine/window', 'engine/minibuffer', 'engine/socket.io'],
    function ($,               window,          minibuffer,          io) {
      loader.queue(65, 'Creating GUI', function () {
        var editor, minibuffer_editor;

        socket.on('error-message', function (message, title) {
          $.pnotify({
            title: title,
            type: 'error',
            icon: 'ui-icon ui-icon-script',
            text: message
          });
        });

        minibuffer_editor = minibuffer.create({
          element: $('#minibuffer'),
          io: socket,
          theme: 'theme/engine',
          fontSize: '13px'
        });

        socket.on('execute-extended-command', minibuffer_editor.activate);

        editor = window.create({
          element: $('#editor'),
          bufferName: '*scratch*',
          io: socket,
          theme: 'theme/engine',
          fontSize: '13px'
        });

        editor.focus();
      });
    });

    $.extend($.pnotify.defaults, {
      styling: 'jqueryui',
      history: false,
      delay: 3000,
      animation: {
        effect_in: 'drop',
        effect_out: 'drop',
        options_in: { direction: 'up' },
        options_out: { direction: 'up' }
      }
    });

    window.alert = function (message) {
      $.pnotify({
        title: 'Alert',
        type: 'error',
        icon: 'ui-icon ui-icon-script',
        text: message
      });
    };
  });
});
<commit_msg>error-message argument order fix<commit_after>// -*- mode: js; indent-tabs-mode: nil; -*-
requirejs.config({
  shim: {
    'jquery-ui': ['jquery'],
    'jquery.pnotify': ['jquery-ui'],
    'engine/socket.io': ['socket.io']
  }
});

require(['engine/splash'], function (splash) {
  var socket, loader = splash.create({
    element: '#splash'
  }), modules;

  modules = [
    // oldschool/shim
    'ace/lib/fixoldbrowsers', 'jquery-ui', 'jquery.pnotify',
    // main
    'engine/window', 'engine/minibuffer', 'engine/socket.io', 'engine/commander',
    'engine/buffer', 'engine/eventuality',
    // supplementary
    'theme/engine'
  ];

  (function (modules, rest) {
    var load = function () {
      var args = Array.prototype.splice.call(arguments, 0), current;
      if (args.length) {
        current = args.shift();
        loader.queue(30/modules.length, 'Loading AMD module ' + current, function () {
          require([current], function () { load.apply(null, args); });
        });
      } else {
        rest();
      }
    };

    load.apply(null, modules);
  }) (modules, function () {
    loader.queue(5, 'Establishing WebSocket connection', function () { socket = io.connect(); });
    require(['engine/jquery', 'engine/window', 'engine/minibuffer', 'engine/socket.io'],
    function ($,               window,          minibuffer,          io) {
      loader.queue(65, 'Creating GUI', function () {
        var editor, minibuffer_editor;

        socket.on('error-message', function (title, message) {
          $.pnotify({
            title: title,
            type: 'error',
            icon: 'ui-icon ui-icon-script',
            text: message
          });
        });

        minibuffer_editor = minibuffer.create({
          element: $('#minibuffer'),
          io: socket,
          theme: 'theme/engine',
          fontSize: '13px'
        });

        socket.on('execute-extended-command', minibuffer_editor.activate);

        editor = window.create({
          element: $('#editor'),
          bufferName: '*scratch*',
          io: socket,
          theme: 'theme/engine',
          fontSize: '13px'
        });

        editor.focus();
      });
    });

    $.extend($.pnotify.defaults, {
      styling: 'jqueryui',
      history: false,
      delay: 3000,
      animation: {
        effect_in: 'drop',
        effect_out: 'drop',
        options_in: { direction: 'up' },
        options_out: { direction: 'up' }
      }
    });

    window.alert = function (message) {
      $.pnotify({
        title: 'Alert',
        type: 'error',
        icon: 'ui-icon ui-icon-script',
        text: message
      });
    };
  });
});
<|endoftext|>


<commit_before>(function($) {
//$.entwine('ss', function($) {

	$('.field').entwine({

		getFormField: function() {
			return this.find('[name='+this.getFieldName()+']');
		},

		getFieldName: function() {
			return this.attr('id');
		},

		getFieldValue: function() {
			return this.getFormField().val();
		},

		evaluateEqualTo: function(val) {
			return this.getFieldValue() === val;
		},

		evaluateNotEqualTo: function(val) {
			return this.getFieldValue() !== val;
		},

		evaluateLessThan: function(val) {
			num = parseFloat(val);
			return this.getFieldValue() < num;
		},

		evaluateGreaterThan: function(val) {
			num = parseFloat(val);
			return parseFloat(this.getFieldValue()) > num;
		},

		evaluateLessThan: function(val) {
			num = parseFloat(val);
			return parseFloat(this.getFieldValue()) < num;
		},

		evaluateContains: function(val) {
			return this.getFieldValue().match(val) !== null;
		},

		evaluateStartsWith: function(val) {
			return this.getFieldValue().match(new RegExp('^'+val)) !== null;
		},

		evaluateEndsWith: function(val) {
			return this.getFieldValue().match(new RegExp(val+'$')) !== null;
		},

		evaluateEmpty: function() {
			return $.trim(this.getFieldValue()).length === 0;
		},

		evaluateNotEmpty: function() {
			return !this.evaluateEmpty();
		},

		evaluateBetween: function(minmax) {
			v = parseFloat(this.getFieldValue());
			parts = minmax.split("-");
			if(parts.length === 2) {
				return v > parseFloat(parts[0]) && v < parseFloat(parts[1]);
			}
			return false;
		},

		evaluateChecked: function() {
			return this.getFormField().is(":checked");
		}

	});

	$('.field.display-logic').entwine({
		onmatch: function () {
			masters = this.getMasters();
			for(m in masters) {
				this.closest('form').find('#'+masters[m]).addClass("display-logic-master");
			}
		},

		getLogic: function() {
			return $.trim(this.find('.display-logic-eval').text());
		},

		parseLogic: function() {
			js = this.getLogic();
			result = eval(js);
			return result;
		},

		getMasters: function() {
			var masters = this.data('display-logic-masters');

			return (masters) ? masters.split(",") : [];
		}

	});

	$('.field.optionset').entwine({

		getFormField: function() {
			f = this._super().filter(":checked");
			return f;
		}

	});

	$('.field.optionset.checkboxset').entwine({

		evaluateHasCheckedOption: function(val) {
			var found = false;
			this.find(':checkbox').filter(':checked').each(function() {
				found = (found || ($(this).val() === val || $(this).getLabel().text() === val));
			})

			return found;
		},

		evaluateHasCheckedAtLeast: function(num) {
			return this.find(':checked').length >= num;
		},

		evaluateHasCheckedLessThan: function(num) {
			return this.find(':checked').length <= num;
		}
	});

	$('.field input[type=checkbox]').entwine({
		getLabel: function() {
			return this.closest('form').find('label[for='+this.attr('id')+']');
		}
	})

	$('.field.display-logic.display-logic-display').entwine({
		testLogic: function() {
			this.toggle(this.parseLogic());
		}
	});

	$('.field.display-logic.display-logic-hide').entwine({
		testLogic: function() {
			this.toggle(!this.parseLogic());
		}
	});

	$('.field.display-logic-master :text, .field.display-logic-master :hidden:not(option), .field.display-logic-master select').entwine({
		onmatch: function() {
			this.closest(".field").notify();
		},

		onchange: function() {
			this.closest(".field").notify();
		}
	});

	$('.field.display-logic-master :checkbox, .field.display-logic-master :radio').entwine({
		onmatch: function() {
			this.closest(".field").notify();
		},

		onclick: function() {
			this.closest(".field").notify();
		}
	});

	$('.field.display-logic-master').entwine({
		Listeners: null,

		notify: function() {
			$.each(this.getListeners(), function() {
				$(this).testLogic();
			});
		},

		getListeners: function() {
			if(l = this._super()) {
				return l;
			}
			var self = this;
			var listeners = [];
			this.closest("form").find('.display-logic').each(function() {
				masters = $(this).getMasters();
				for(m in  masters) {
					if(masters[m] == self.attr('id')) {
						listeners.push($(this));
						break;
					}
				}
			});
			this.setListeners(listeners);
			return this.getListeners();
		}
	});

	$('.field.display-logic-master.checkboxset').entwine({

	})

	$('.field.display-logic *').entwine({
		getHolder: function() {
			return this.closest('.display-logic');
		}
	});

//})
})(jQuery);
<commit_msg>Prevent readonly fields from being hidden on load, e.g. page history<commit_after>(function($) {
//$.entwine('ss', function($) {

	$('.field').entwine({

		getFormField: function() {
			return this.find('[name='+this.getFieldName()+']');
		},

		getFieldName: function() {
			return this.attr('id');
		},

		getFieldValue: function() {
			return this.getFormField().val();
		},

		evaluateEqualTo: function(val) {
			return this.getFieldValue() === val;
		},

		evaluateNotEqualTo: function(val) {
			return this.getFieldValue() !== val;
		},

		evaluateLessThan: function(val) {
			num = parseFloat(val);
			return this.getFieldValue() < num;
		},

		evaluateGreaterThan: function(val) {
			num = parseFloat(val);
			return parseFloat(this.getFieldValue()) > num;
		},

		evaluateLessThan: function(val) {
			num = parseFloat(val);
			return parseFloat(this.getFieldValue()) < num;
		},

		evaluateContains: function(val) {
			return this.getFieldValue().match(val) !== null;
		},

		evaluateStartsWith: function(val) {
			return this.getFieldValue().match(new RegExp('^'+val)) !== null;
		},

		evaluateEndsWith: function(val) {
			return this.getFieldValue().match(new RegExp(val+'$')) !== null;
		},

		evaluateEmpty: function() {
			return $.trim(this.getFieldValue()).length === 0;
		},

		evaluateNotEmpty: function() {
			return !this.evaluateEmpty();
		},

		evaluateBetween: function(minmax) {
			v = parseFloat(this.getFieldValue());
			parts = minmax.split("-");
			if(parts.length === 2) {
				return v > parseFloat(parts[0]) && v < parseFloat(parts[1]);
			}
			return false;
		},

		evaluateChecked: function() {
			return this.getFormField().is(":checked");
		}

	});

	$('.field.display-logic:not(.readonly)').entwine({
		onmatch: function () {
			masters = this.getMasters();
			for(m in masters) {
				this.closest('form').find('#'+masters[m]).addClass("display-logic-master");
			}
		},

		getLogic: function() {
			return $.trim(this.find('.display-logic-eval').text());
		},

		parseLogic: function() {
			js = this.getLogic();
			result = eval(js);
			return result;
		},

		getMasters: function() {
			var masters = this.data('display-logic-masters');

			return (masters) ? masters.split(",") : [];
		}

	});

	$('.field.optionset').entwine({

		getFormField: function() {
			f = this._super().filter(":checked");
			return f;
		}

	});

	$('.field.optionset.checkboxset').entwine({

		evaluateHasCheckedOption: function(val) {
			var found = false;
			this.find(':checkbox').filter(':checked').each(function() {
				found = (found || ($(this).val() === val || $(this).getLabel().text() === val));
			})

			return found;
		},

		evaluateHasCheckedAtLeast: function(num) {
			return this.find(':checked').length >= num;
		},

		evaluateHasCheckedLessThan: function(num) {
			return this.find(':checked').length <= num;
		}
	});

	$('.field input[type=checkbox]').entwine({
		getLabel: function() {
			return this.closest('form').find('label[for='+this.attr('id')+']');
		}
	})

	$('.field.display-logic.display-logic-display').entwine({
		testLogic: function() {
			this.toggle(this.parseLogic());
		}
	});

	$('.field.display-logic.display-logic-hide').entwine({
		testLogic: function() {
			this.toggle(!this.parseLogic());
		}
	});

	$('.field.display-logic-master :text, .field.display-logic-master :hidden:not(option), .field.display-logic-master select').entwine({
		onmatch: function() {
			this.closest(".field").notify();
		},

		onchange: function() {
			this.closest(".field").notify();
		}
	});

	$('.field.display-logic-master :checkbox, .field.display-logic-master :radio').entwine({
		onmatch: function() {
			this.closest(".field").notify();
		},

		onclick: function() {
			this.closest(".field").notify();
		}
	});

	$('.field.display-logic-master').entwine({
		Listeners: null,

		notify: function() {
			$.each(this.getListeners(), function() {
				$(this).testLogic();
			});
		},

		getListeners: function() {
			if(l = this._super()) {
				return l;
			}
			var self = this;
			var listeners = [];
			this.closest("form").find('.display-logic').each(function() {
				masters = $(this).getMasters();
				for(m in  masters) {
					if(masters[m] == self.attr('id')) {
						listeners.push($(this));
						break;
					}
				}
			});
			this.setListeners(listeners);
			return this.getListeners();
		}
	});

	$('.field.display-logic-master.checkboxset').entwine({

	})

	$('.field.display-logic *').entwine({
		getHolder: function() {
			return this.closest('.display-logic');
		}
	});

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


<commit_before>document.body.innerHTML = document.body.innerHTML.replace(new RegExp("(?!.*\")(0[xX][0-9a-fA-F]{40})(?!\")(!?<\s|\<|$)", "g"), `<a href="https://etherscan.io/address/$1" class="ext-etheraddresslookup-link">$1</a>$2`);<commit_msg>Split logic on separate lines<commit_after>(function() {
    document.body.innerHTML = document.body.innerHTML.replace(
        new RegExp("(?!.*\")(0[xX][0-9a-fA-F]{40})(?!\")(!?<\s|\<|$)", "g"),
        `<a href="https://etherscan.io/address/$1" class="ext-etheraddresslookup-link">$1</a>$2`
    );
})();<|endoftext|>


<commit_before>/** @jsx React.DOM */

		    var GameLog = React.createClass({
		    	render: function() {
		    		return

		    				<form className={"form-horizontal"}>
		    					<fieldset>
		    					<legend>Game log</legend>
		    					<textarea className={"form-control"} rows="10" id="textArea"></textarea>
		    					</fieldset>
		    				</form>

		    	},
		    });

		    var Dice = React.createClass({
		    	render: function() {
		    		console.log(this.props);
		    		var icon = ["fi-die-one","fi-die-two","fi-die-three","fi-die-four","fi-die-five","fi-die-six"];
		    		var color = this.props.isSuccess ? "btn-success" : "btn-default";
		    		return

		    				<button type="button" className={"btn "+color+" btn-lg"}>

		    				</button>

		    	},
		    });

		    var DiceList = React.createClass({
		    	render: function() {
		    		var that = this;
		    		var dices = this.props.dices.map(function(dice){
		    			return <Dice score={dice} isSuccess={that.props.isSuccess}/>
		    		});

		    		var colBufferSize = (12 - this.props.dices.length)/2;

		    		return

		    		       {dices}

		    	},
		    });

        var Lives = React.createClass({
          render: function() {
            return

Lives

                  {this.props.lives}

          },
        });

		    var Score = React.createClass({

		    	render: function() {
		    		var scoreStr = "";
		    		if (this.props.bonus > 0) {
		    			return

Score

	    								{this.props.score} {'(+'+this.props.bonus+')'}

		    		}

		    		return

Score

    								{this.props.score}

		    	},
		    });

		    var Game = React.createClass({

		    	getInitialState: function() {
		    		return {
		    			dices: [1,1],
		    			score:0,
		    			success: false,
		    			bonus: 0,
              lives: 10,
		    		}
		    	},

		    	render: function() {
		    		return

				    				<DiceList dices={this.state.dices} isSuccess={this.state.success}/>

                        <Lives lives={this.state.lives}/>

				    			   		 	<Score score={this.state.score} bonus={this.state.bonus}/>

				    			   		<button className={"btn btn-default"} onClick={this.roll} >Roll!</button>

		    	},

		    	roll: function(e) {
		    		var success = false;
		    		var bonus = 0;
		    		var newDices = [];
            var lives = this.state.lives;
		    		for(var i = 0; i < this.state.dices.length; i++) {
		    			var d = Math.floor(Math.random() * 6) + 1;
		    			newDices.push(d);
		    		}

		    		var score = this.state.score;

		    		function isSameAsPrev(element, index, array) {
  						if(index < 1) return true;
  						return element === array[index-1];
					}

					if(newDices.every(isSameAsPrev)) {
						bonus = newDices.reduce(function(a,b) {return a + b;});
						success = true;
          } else {
            lives = lives - 1;
          }

		    		this.setState({
		    			score: score + bonus,
		    			dices: newDices,
		    			success: success,
		    			bonus: bonus,
              lives: lives,
		    		});
		    	},
		    });

			React.renderComponent(<Game/>, document.getElementById('content'));
<commit_msg>Add GameOver component<commit_after>/** @jsx React.DOM */

		    var GameLog = React.createClass({
		    	render: function() {
		    		return

		    				<form className={"form-horizontal"}>
		    					<fieldset>
		    					<legend>Game log</legend>
		    					<textarea className={"form-control"} rows="10" id="textArea"></textarea>
		    					</fieldset>
		    				</form>

		    	},
		    });

		    var Dice = React.createClass({
		    	render: function() {
		    		console.log(this.props);
		    		var icon = ["fi-die-one","fi-die-two","fi-die-three","fi-die-four","fi-die-five","fi-die-six"];
		    		var color = this.props.isSuccess ? "btn-success" : "btn-default";
		    		return

		    				<button type="button" className={"btn "+color+" btn-lg"}>

		    				</button>

		    	},
		    });

		    var DiceList = React.createClass({
		    	render: function() {
		    		var that = this;
		    		var dices = this.props.dices.map(function(dice){
		    			return <Dice score={dice} isSuccess={that.props.isSuccess}/>
		    		});

		    		var colBufferSize = (12 - this.props.dices.length)/2;

		    		return

		    		       {dices}

		    	},
		    });

        var Lives = React.createClass({
          render: function() {
            return

Lives

                  {this.props.lives}

          },
        });

		    var Score = React.createClass({

		    	render: function() {
		    		var scoreStr = "";
		    		if (this.props.bonus > 0) {
		    			return

Score

	    								{this.props.score} {'(+'+this.props.bonus+')'}

		    		}

		    		return

Score

    								{this.props.score}

		    	},
		    });

        var GameOver = React.createClass({
          render: function() {
            return

                    <button type="button" className={"close"} data-dismiss="modal" aria-hidden="true">×</button>

Game Over!

Press 'Play' to play again.

                              <button type="button" className={"btn btn-primary"} onClick={this.reset}>Play</button>

          },

        });

		    var Game = React.createClass({

		    	getInitialState: function() {
		    		return {
		    			dices: [1,1],
		    			score:0,
		    			success: false,
		    			bonus: 0,
              lives: 10,
		    		}
		    	},

		    	render: function() {
		    		return

                    <GameOver score={this.state.score}/>
				    				<DiceList dices={this.state.dices} isSuccess={this.state.success}/>

                        <Lives lives={this.state.lives}/>

				    			   		 	<Score score={this.state.score} bonus={this.state.bonus}/>

				    			   		<button className={"btn btn-default"} onClick={this.roll} >Roll!</button>

		    	},

          reset: function(e) {
            this.setState({
		    			score: 0,
		    			dices: [1,1],
		    			success: false,
		    			bonus: 0,
              lives: 10,
		    		});
          },

		    	roll: function(e) {
		    		var success = false;
		    		var bonus = 0;
		    		var newDices = [];
            var lives = this.state.lives;
		    		for(var i = 0; i < this.state.dices.length; i++) {
		    			var d = Math.floor(Math.random() * 6) + 1;
		    			newDices.push(d);
		    		}

		    		var score = this.state.score;

		    		function isSameAsPrev(element, index, array) {
  						if(index < 1) return true;
  						return element === array[index-1];
					}

					if(newDices.every(isSameAsPrev)) {
						bonus = newDices.reduce(function(a,b) {return a + b;});
						success = true;
          } else {
            lives = lives - 1;
          }

		    		this.setState({
		    			score: score + bonus,
		    			dices: newDices,
		    			success: success,
		    			bonus: bonus,
              lives: lives,
		    		});

            if(this.state.lives < 1) {
               $("#gameOver").modal('show');
            }
		    	},
		    });

			React.renderComponent(<Game/>, document.getElementById('content'));
<|endoftext|>


<commit_before>module.exports = {

  updateInfo: function (req, res) {
    var result = {}
    var iTunesItemIdentifier = '1002185140'
    var forceUpdate = {
      type: 'force',
      title: 'A new version of the app is available',
      iTunesItemIdentifier
    }
    var suggestUpdate = {
      type: 'suggest',
      title: 'An update is available',
      iTunesItemIdentifier
    }

    /*
    Format for result:
    result = {
      type: 'force',  // can be 'force' or 'suggest'
      title: 'A new version of the app is available',
      message: 'You can go to the App Store now to update',
      iTunesItemIdentifier: '1002185140'
    }
    */
    var version = req.param('ios-version') || req.param('android-version')
    switch (version) {
      case '1.6':
        result = Object.assign(
          suggestUpdate,
          { message: 'The version you are using is not longer up to date. Please go to the App Store to update.' }
        )
        break
      case '1.65':
        result = Object.assign(
          forceUpdate,
          { message: 'The version you are using is no longer compatible with the site. Please go to the App Store now to update' }
        )
        break
      case undefined:
        break
      default:
        result = Object.assign(
          forceUpdate,
          { message: 'The version you are using is no longer compatible with the site. Please go to the App Store now to update' }
        )
    }
    res.ok(result)
  }
}
<commit_msg>Split android and ios version checks<commit_after>module.exports = {

  updateInfo: function (req, res) {
    var result = {}
    var iTunesItemIdentifier = '1002185140'
    var forceUpdate = {
      type: 'force',
      title: 'A new version of the app is available',
      iTunesItemIdentifier
    }
    var suggestUpdate = {
      type: 'suggest',
      title: 'An update is available',
      iTunesItemIdentifier
    }

    /*
    Format for result:
    result = {
      type: 'force',  // can be 'force' or 'suggest'
      title: 'A new version of the app is available',
      message: 'You can go to the App Store now to update',
      iTunesItemIdentifier: '1002185140'
    }
    */
    switch (req.param('ios-version')) {
      case '2.0':
        result = Object.assign(
          suggestUpdate,
          { message: 'The version you are using is not longer up to date. Please go to the App Store to update.' }
        )
        break
      case '1.65':
        result = Object.assign(
          forceUpdate,
          { message: 'The version you are using is no longer compatible with the site. Please go to the App Store now to update' }
        )
        break
      case undefined:
        break
      default:
        result = Object.assign(
          forceUpdate,
          { message: 'The version you are using is no longer compatible with the site. Please go to the App Store now to update' }
        )
    }

    switch (req.param('android-version')) {
      case '2.0':
        result = Object.assign(
          suggestUpdate,
          { message: 'The version you are using is not longer up to date. Please go to the App Store to update.' }
        )
        break
      case '1.65':
        result = Object.assign(
          forceUpdate,
          { message: 'The version you are using is no longer compatible with the site. Please go to the App Store now to update' }
        )
        break
      case undefined:
        break
      default:
        result = Object.assign(
          forceUpdate,
          { message: 'The version you are using is no longer compatible with the site. Please go to the App Store now to update' }
        )
    }
    res.ok(result)
  }
}
<|endoftext|>


<commit_before>/* eslint-disable global-require, import/no-dynamic-require */
const path = require('path');

function getProjectPackagePath() {
  return path.resolve('./package.json');
}

function getProjectPackage() {
  return require(getProjectPackagePath());
}

function hasReactNativeDependency(pkg) {
  return !!(pkg.dependencies && pkg.dependencies['react-native'])
}

function hasVueDependency(pkg) {
  return !!pkg.dependencies.vue;
}

function isReactNativeProject() {
  return hasReactNativeDependency(getProjectPackage());
}

function isVueProject() {
  return hasVueDependency(getProjectPackage());
}

module.exports = {
  getProjectPackagePath,
  getProjectPackage,
  hasReactNativeDependency,
  hasVueDependency,
  isReactNativeProject,
  isVueProject,
};
<commit_msg>Fix lint<commit_after>/* eslint-disable global-require, import/no-dynamic-require */
const path = require('path');

function getProjectPackagePath() {
  return path.resolve('./package.json');
}

function getProjectPackage() {
  return require(getProjectPackagePath());
}

function hasReactNativeDependency(pkg) {
  return !!(pkg.dependencies && pkg.dependencies['react-native']);
}

function hasVueDependency(pkg) {
  return !!pkg.dependencies.vue;
}

function isReactNativeProject() {
  return hasReactNativeDependency(getProjectPackage());
}

function isVueProject() {
  return hasVueDependency(getProjectPackage());
}

module.exports = {
  getProjectPackagePath,
  getProjectPackage,
  hasReactNativeDependency,
  hasVueDependency,
  isReactNativeProject,
  isVueProject,
};
<|endoftext|>


<commit_before>'use strict';

import https from 'https';
import querystring from 'querystring';
import { version } from './package.json';

function instapago(keyId, publicKeyId, strict = true) {
  if (!keyId || !publicKeyId) {
    throw new Error('Los parámetros keyId y publicKeyId son requeridos.');
  } else if (typeof keyId !== 'string' || typeof publicKeyId !== 'string') {
    throw new Error('Los parámetros keyId y publicKeyId deben ser String.');
  }

  const config = {
    strict,
    keys: {
      keyId,
      publicKeyId
    }
  };

  return {
    pay: data => processPayment('pay', config, data),
    resume: data => processPayment('resume', config, data),
    cancel: data => processPayment('cancel', config, data),
    view: data => processPayment('view', config, data)
  }
}

function processPayment(type, config, data) {
  const validation = validatePaymentData(type, data);
  const params = Object.assign({}, config.keys, data);
  let endpoint = 'payment';
  let method = 'POST';

  if (type !== 'pay') {
    if (type === 'resume') endpoint = 'complete';
    if (type === 'cancel') method = 'DELETE';
    if (type === 'view') method = 'GET';
  }

  if (config.strict && validation.error) {
    return new Promise((resolve, reject) => reject(validation.error));
  } else {
    return new Promise((resolve, reject) => {
      const qs = querystring.stringify(params);
      const request = https.request({
        hostname: 'api.instapago.com',
        path: `/${endpoint}?${qs}`,
        method: method,
        headers: {
          'Cache-Control': 'no-cache',
          'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
          'Content-Length': Buffer.byteLength(qs),
          'User-Agent': `node-instapago/${version}`
        }
      });

      request.on('error', error => reject(error));
      request.on('response', response => {
        const raw = [];

        response.on('data', chunk => raw.push(chunk));
        response.on('end', () => {
          const _data = Buffer.concat(raw).toString();
          const _meta = response;

          resolve(handleResponse(_data, _meta));
        });
      });

      request.write(qs);
      request.end();
    });
  }
}

function validatePaymentData(type, data) {
  const result = {};
  const _data = {};

  Object.keys(data).forEach(key => {
    Object.assign(_data, {[key.toLowerCase()]: data[key]});
  });

  const rules = [
    {
      param: 'id',
      rule: 'Cadena de caracteres con el indicador único de pago.',
      valid: typeof _data.id === 'string'
    },
    {
      param: 'amount',
      rule: 'Sólo caracteres numéricos y punto (.) como separador decimal.',
      valid: /[0-9]/.test(_data.amount)
    },
    {
      param: 'description',
      rule: 'Cadena de caracteres con los detalles de la operación.',
      valid: typeof _data.description === 'string'
    },
    {
      param: 'cardholder',
      rule: 'Sólo caracteres alfabéticos, incluyendo la letra ñ y espacio.',
      valid: /^[ñA-Za-z\s]*$/.test(_data.cardholder)
    },
    {
      param: 'cardholderid',
      rule: 'Sólo caracteres numéricos; mínimo 6 dígitos y máximo 8.',
      valid: /^[0-9]{6,8}$/.test(_data.cardholderid)
    },
    {
      param: 'cardnumber',
      rule: 'Sólo caracteres numéricos; mínimo 15 dígitos y máximo 16.',
      valid: /^[0-9]{15,16}$/.test(_data.cardnumber)
    },
    {
      param: 'cvc',
      rule: 'Sólo caracteres numéricos; deben ser 3 dígitos.',
      valid: /^[0-9]{3}$/.test(_data.cvc)
    },
    {
      param: 'expirationdate',
      rule: 'Sólo fechas mayores a la fecha en curso, en formato MM/YYYY.',
      valid: isCardExpired(_data.expirationdate)
    },
    {
      param: 'statusid',
      rule: 'Sólo caracteres numéricos; debe ser 1 dígito.',
      valid: /^[1-2]{1}$/.test(_data.statusid)
    },
    {
      param: 'ip',
      rule: 'Dirección IP del cliente que genera la solicitud del pago.',
      valid: typeof _data.description === 'string'
    }
  ];
  let requiredParams = ['id'];

  if (type === 'pay') {
    requiredParams = [
      'amount',
      'description',
      'cardholder',
      'cardholderid',
      'cardnumber',
      'cvc',
      'expirationdate',
      'statusid',
      'ip'
    ];
  } else if (type === 'resume') {
    requiredParams = ['id', 'amount'];
  }

  requiredParams.every(param => {
    const _param = rules.find(rule => rule.param === param);

    if (_data[param] && _param.valid) {
      return true;
    }

    result.error = new Error(`Parámetro inválido (${param}): ${_param.rule}`);

    return false;
  });

  return result;
}

function handleResponse(data, metadata) {
  const response = {
    statusCode: metadata.statusCode,
    statusMessage: metadata.statusMessage,
    headers: metadata.headers,
    data: JSON.parse(data)
  };

  switch(response.data.code) {
    case '201':
      response.code = 'TRANSACTION_SUCCEED';
      response.message = 'La transacción ha sido procesada satisfactoriamente.';
      break;

    case '400':
      response.code = 'INVALID_PAYMENT_INFO';
      response.message = 'Error al validar los datos enviados.';
      break;

    case '401':
      response.code = 'INSTAPAGO_AUTH_ERROR';
      response.message = 'Ha ocurrido un error con las llaves utilizadas.';
      break;

    case '403':
      response.code = 'TRANSACTION_FAILED';
      response.message = 'La transacción ha sido rechazada por el banco.';
      break;

    case '500':
      response.code = 'SOMETHING_WENT_WRONG';
      response.message = 'Ha ocurrido un error dentro del servidor de Instapago.';
      break;

    case '503':
      response.code = 'INSTAPAGO_IS_TEMPORARILY_UNAVAILABLE';
      response.message = 'Ha ocurrido un error al procesar los parámetros de entrada.';
      break;
  }

  return response;
}

function isCardExpired(date) {
  const _date = date ? date.split('/') : null;

  if (_date) {
    const yearOnCard = parseInt(_date[1], 10);
    const monthOnCard = parseInt(_date[0], 10);
    const year = new Date().getFullYear();
    const month = new Date().getUTCMonth() + 1;

    return ((yearOnCard === year && monthOnCard >= month) || yearOnCard > year);
  }

  return false;
}

export default instapago;
<commit_msg>Mejora respuesta al validar parámetros<commit_after>'use strict';

import https from 'https';
import querystring from 'querystring';
import { version } from './package.json';

function instapago(keyId, publicKeyId, strict = true) {
  if (!keyId || !publicKeyId) {
    throw new Error('Los parámetros keyId y publicKeyId son requeridos.');
  } else if (typeof keyId !== 'string' || typeof publicKeyId !== 'string') {
    throw new Error('Los parámetros keyId y publicKeyId deben ser String.');
  }

  const config = {
    strict,
    keys: {
      keyId,
      publicKeyId
    }
  };

  return {
    pay: data => processPayment('pay', config, data),
    resume: data => processPayment('resume', config, data),
    cancel: data => processPayment('cancel', config, data),
    view: data => processPayment('view', config, data)
  }
}

function processPayment(type, config, data) {
  const validation = validatePaymentData(type, data);
  const params = Object.assign({}, config.keys, data);
  let endpoint = 'payment';
  let method = 'POST';

  if (type !== 'pay') {
    if (type === 'resume') endpoint = 'complete';
    if (type === 'cancel') method = 'DELETE';
    if (type === 'view') method = 'GET';
  }

  if (config.strict && validation.error) {
    return new Promise((resolve, reject) => reject(validation));
  } else {
    return new Promise((resolve, reject) => {
      const qs = querystring.stringify(params);
      const request = https.request({
        hostname: 'api.instapago.com',
        path: `/${endpoint}?${qs}`,
        method: method,
        headers: {
          'Cache-Control': 'no-cache',
          'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
          'Content-Length': Buffer.byteLength(qs),
          'User-Agent': `node-instapago/${version}`
        }
      });

      request.on('error', error => reject(error));
      request.on('response', response => {
        const raw = [];

        response.on('data', chunk => raw.push(chunk));
        response.on('end', () => {
          const _data = Buffer.concat(raw).toString();
          const _meta = response;

          resolve(handleResponse(_data, _meta));
        });
      });

      request.write(qs);
      request.end();
    });
  }
}

function validatePaymentData(type, data) {
  const result = {};
  const _data = {};

  Object.keys(data).forEach(key => {
    Object.assign(_data, {[key.toLowerCase()]: data[key]});
  });

  const rules = [
    {
      param: 'id',
      rule: 'Cadena de caracteres con el indicador único de pago.',
      valid: typeof _data.id === 'string'
    },
    {
      param: 'amount',
      rule: 'Sólo caracteres numéricos y punto (.) como separador decimal.',
      valid: /[0-9]/.test(_data.amount)
    },
    {
      param: 'description',
      rule: 'Cadena de caracteres con los detalles de la operación.',
      valid: typeof _data.description === 'string'
    },
    {
      param: 'cardholder',
      rule: 'Sólo caracteres alfabéticos, incluyendo la letra ñ y espacio.',
      valid: /^[ñA-Za-z\s]*$/.test(_data.cardholder)
    },
    {
      param: 'cardholderid',
      rule: 'Sólo caracteres numéricos; mínimo 6 dígitos y máximo 8.',
      valid: /^[0-9]{6,8}$/.test(_data.cardholderid)
    },
    {
      param: 'cardnumber',
      rule: 'Sólo caracteres numéricos; mínimo 15 dígitos y máximo 16.',
      valid: /^[0-9]{15,16}$/.test(_data.cardnumber)
    },
    {
      param: 'cvc',
      rule: 'Sólo caracteres numéricos; deben ser 3 dígitos.',
      valid: /^[0-9]{3}$/.test(_data.cvc)
    },
    {
      param: 'expirationdate',
      rule: 'Sólo fechas mayores a la fecha en curso, en formato MM/YYYY.',
      valid: isCardExpired(_data.expirationdate)
    },
    {
      param: 'statusid',
      rule: 'Sólo caracteres numéricos; debe ser 1 dígito.',
      valid: /^[1-2]{1}$/.test(_data.statusid)
    },
    {
      param: 'ip',
      rule: 'Dirección IP del cliente que genera la solicitud del pago.',
      valid: typeof _data.description === 'string'
    }
  ];
  let requiredParams = ['id'];

  if (type === 'pay') {
    requiredParams = [
      'amount',
      'description',
      'cardholder',
      'cardholderid',
      'cardnumber',
      'cvc',
      'expirationdate',
      'statusid',
      'ip'
    ];
  } else if (type === 'resume') {
    requiredParams = ['id', 'amount'];
  }

  requiredParams.every(param => {
    const _param = rules.find(rule => rule.param === param);

    if (_data[param] && _param.valid) {
      return true;
    }

    result.error = true;
    result.code = 'INVALID_PAYMENT_INFO';
    result.message = `Parámetro "${param}" inválido. ${_param.rule}`;

    return false;
  });

  return result;
}

function handleResponse(data, metadata) {
  const response = {
    statusCode: metadata.statusCode,
    statusMessage: metadata.statusMessage,
    headers: metadata.headers,
    data: JSON.parse(data)
  };

  switch(response.data.code) {
    case '201':
      response.code = 'TRANSACTION_SUCCEED';
      response.message = 'La transacción ha sido procesada satisfactoriamente.';
      break;

    case '400':
      response.code = 'INVALID_PAYMENT_INFO';
      response.message = 'Error al validar los datos enviados.';
      break;

    case '401':
      response.code = 'INSTAPAGO_AUTH_ERROR';
      response.message = 'Ha ocurrido un error con las llaves utilizadas.';
      break;

    case '403':
      response.code = 'TRANSACTION_FAILED';
      response.message = 'La transacción ha sido rechazada por el banco.';
      break;

    case '500':
      response.code = 'SOMETHING_WENT_WRONG';
      response.message = 'Ha ocurrido un error dentro del servidor de Instapago.';
      break;

    case '503':
      response.code = 'INSTAPAGO_IS_TEMPORARILY_UNAVAILABLE';
      response.message = 'Ha ocurrido un error al procesar los parámetros de entrada.';
      break;
  }

  return response;
}

function isCardExpired(date) {
  const _date = date ? date.split('/') : null;

  if (_date) {
    const yearOnCard = parseInt(_date[1], 10);
    const monthOnCard = parseInt(_date[0], 10);
    const year = new Date().getFullYear();
    const month = new Date().getUTCMonth() + 1;

    return ((yearOnCard === year && monthOnCard >= month) || yearOnCard > year);
  }

  return false;
}

export default instapago;
<|endoftext|>


<commit_before>'use strict';

const BlogCategory = use('App/Models/BlogCategory');
const BlogPost = use('App/Models/BlogPost');
const User = use('App/Models/User');

const showdown  = require('showdown');

class BlogController {

  async index({ view }) {
    const categories = await BlogCategory.all();

    // Eagerloading to avoid n+1 requests.
    const posts = await BlogPost
      .query()
      .orderBy('updated_at', 'desc')
      .fetch();

    console.log(posts.toJSON());

    return view.render('frontend.blog.home', {
      categories: categories.toJSON(),
      posts: posts.toJSON(),
    });
  }

  async show({ view, params, request, response }) {
    const categories = await BlogCategory.all();
    const postPromise = await BlogPost.query().where('slug', params.slug).first();
    const postJSON = postPromise.toJSON();
    const author = await User.find(postJSON.user_id);

    const parser = new showdown.Converter();
    const postContent = parser.makeHtml(postJSON.markdown);

    return view.render('frontend.blog.show', {
      post: postJSON,
      postContent: postContent,
      author: author.toJSON(),
      categories: categories.toJSON(),
    });
  }

  async showTag({ view, params }) {
    const categories = await BlogCategory.all();
    const tag = await BlogCategory.query().where('slug', params.slug).first();

    // Eagerloading to avoid n+1 requests.
    const posts = await BlogPost
      .query()
      .where('category_id', tag.id)
      .orderBy('updated_at', 'desc')
      .fetch();

    return view.render('frontend.blog.home', {
      categories: categories.toJSON(),
      posts: posts.toJSON(),
    });
  }
}

module.exports = BlogController;
<commit_msg>Set Markdown Flavor to Github Flavored Markdown one.<commit_after>'use strict';

const BlogCategory = use('App/Models/BlogCategory');
const BlogPost = use('App/Models/BlogPost');
const User = use('App/Models/User');

const showdown  = require('showdown');

class BlogController {

  async index({ view }) {
    const categories = await BlogCategory.all();

    // Eagerloading to avoid n+1 requests.
    const posts = await BlogPost
      .query()
      .orderBy('updated_at', 'desc')
      .fetch();

    console.log(posts.toJSON());

    return view.render('frontend.blog.home', {
      categories: categories.toJSON(),
      posts: posts.toJSON(),
    });
  }

  async show({ view, params, request, response }) {
    const categories = await BlogCategory.all();
    const postPromise = await BlogPost.query().where('slug', params.slug).first();
    const postJSON = postPromise.toJSON();
    const author = await User.find(postJSON.user_id);

    const parser = new showdown.Converter();
    parser.setFlavor('github');
    const postContent = parser.makeHtml(postJSON.markdown);

    return view.render('frontend.blog.show', {
      post: postJSON,
      postContent: postContent,
      author: author.toJSON(),
      categories: categories.toJSON(),
    });
  }

  async showTag({ view, params }) {
    const categories = await BlogCategory.all();
    const tag = await BlogCategory.query().where('slug', params.slug).first();

    // Eagerloading to avoid n+1 requests.
    const posts = await BlogPost
      .query()
      .where('category_id', tag.id)
      .orderBy('updated_at', 'desc')
      .fetch();

    return view.render('frontend.blog.home', {
      categories: categories.toJSON(),
      posts: posts.toJSON(),
    });
  }
}

module.exports = BlogController;
<|endoftext|>


<commit_before>import { easeLinear } from 'd3-ease';
import { pack } from 'd3-hierarchy';
import { select } from 'd3-selection';
import { transition } from 'd3-transition';

import VisComponent from 'candela/VisComponent';

import content from './index.jade';
import Clusters from '~/util/Clusters';

export default class Bubble extends VisComponent {
  constructor (el, options) {
    super(el, options);

    // Respond to "add" and "delete" events from the data window; these are all
    // that will be needed to update the hierarchy data.
    options.dataWindow.on('added', d => this.add(d));
    options.dataWindow.on('deleted', d => this.remove(d));

    // Retain a function that specifies what the interval is between data
    // updates.
    this.interval = options.interval || (() => 0);

    // Grab a colormap object from the options.
    this.color = options.color;

    // Construct an initial hierarchy.
    this.data = new Clusters();

    const width = 620.5;
    const height = 400;
    select(this.el)
      .html(content({
        width,
        height
      }));

    this.bubbles = pack()
      .size([width, height])
      .padding(3);
  }

  render () {
    const root = this.bubbles(this.data.hierarchy());

    select(this.el)
      .select('svg')
      .selectAll('g')
      .data(root.descendants())
      .enter()
      .append('g')
      .append('circle')
      .style('stroke-width', '1px')
      .style('stroke', 'black');

    const t = transition()
      .duration(this.interval())
      .ease(easeLinear);

    select(this.el)
      .select('svg')
      .selectAll('g')
      .transition(t)
      .attr('transform', d => `translate(${d.x}, ${d.y})`);

    select(this.el)
      .select('svg')
      .selectAll('g')
      .select('circle')
      .transition(t)
      .attr('r', d => d.r)
      .style('fill', (d, i) => {
        if (d.depth === 0) {
          return 'lightgray';
        } else if (d.depth === 1) {
          return 'gray';
        } else {
          return this.color(d.data.cluster);
        }
      });
  }

  add (d) {
    if (d.anomalous) {
      this.data.addAnomalous(d.cluster);
    } else {
      this.data.add();
    }
  }

  remove (d) {
    if (d.anomalous) {
      this.data.removeAnomalous(d.cluster);
    } else {
      this.data.remove();
    }
  }
}
<commit_msg>Toggle "selected" class when a circle is clicked<commit_after>import { easeLinear } from 'd3-ease';
import { pack } from 'd3-hierarchy';
import { select } from 'd3-selection';
import { transition } from 'd3-transition';

import VisComponent from 'candela/VisComponent';

import content from './index.jade';
import Clusters from '~/util/Clusters';

export default class Bubble extends VisComponent {
  constructor (el, options) {
    super(el, options);

    // Respond to "add" and "delete" events from the data window; these are all
    // that will be needed to update the hierarchy data.
    options.dataWindow.on('added', d => this.add(d));
    options.dataWindow.on('deleted', d => this.remove(d));

    // Retain a function that specifies what the interval is between data
    // updates.
    this.interval = options.interval || (() => 0);

    // Grab a colormap object from the options.
    this.color = options.color;

    // Construct an initial hierarchy.
    this.data = new Clusters();

    const width = 620.5;
    const height = 400;
    select(this.el)
      .html(content({
        width,
        height
      }));

    this.bubbles = pack()
      .size([width, height])
      .padding(3);
  }

  render () {
    const root = this.bubbles(this.data.hierarchy());

    select(this.el)
      .select('svg')
      .selectAll('g')
      .data(root.descendants())
      .enter()
      .append('g')
      .append('circle')
      .style('stroke-width', '1px')
      .style('stroke', 'black')
      .on('click', function (d) {
        console.log('clicked on circle', d);
        const selected = select(this).classed('selected');
        select(this)
          .classed('selected', !selected);
      });

    const t = transition()
      .duration(this.interval())
      .ease(easeLinear);

    select(this.el)
      .select('svg')
      .selectAll('g')
      .transition(t)
      .attr('transform', d => `translate(${d.x}, ${d.y})`);

    select(this.el)
      .select('svg')
      .selectAll('g')
      .select('circle')
      .transition(t)
      .attr('r', d => d.r)
      .style('fill', (d, i) => {
        if (d.depth === 0) {
          return 'lightgray';
        } else if (d.depth === 1) {
          return 'gray';
        } else {
          return this.color(d.data.cluster);
        }
      });
  }

  add (d) {
    if (d.anomalous) {
      this.data.addAnomalous(d.cluster);
    } else {
      this.data.add();
    }
  }

  remove (d) {
    if (d.anomalous) {
      this.data.removeAnomalous(d.cluster);
    } else {
      this.data.remove();
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

let http = require('http'),
  fs = require('fs'),
  formidable = require('formidable'),
  util = require('util'),
  url = require('url'),
  jade = require('jade'),
  path = require('path'),
  Guid = require('guid');

let port = 6666;

// Create the web server
http.createServer(function (req, res) {

  // Function visualizing the files, uploaded on the server
  function visualizeUploadedFiles() {
    fs.readFile('uploadedFiles.jade', function (err, template) {
      if (err) {
        res.end(err);
        return;
      }

      fs.readdir('uploaded', function (error, files) {
        if (error) {
          resp.end(error);
          return;
        }

        let output = jade.compile(template)({
          files: files
        });

        res.writeHead(200, {'Content-Type': 'text/html'});
        res.write(output);
        res.end();
      })
    });
  }

  if (req.url == '/upload' && req.method.toLowerCase() == 'post') {

    // parse the file upload
    var form = new formidable.IncomingForm();
    var guid = Guid.create();

    form.parse(req, function (err, fields, files) {
      if (err) {
        throw 'Something bad happened!';
      }
    });

    form.on('end', function (fields, files) {
      let uploadedFilePath = this.openedFiles[0].path;
      let originalFileName = this.openedFiles[0].name;
      let originalFileExtension = originalFileName.substr(originalFileName.lastIndexOf('.') + 1);
      let new_location = __dirname + '/uploaded/';

      // Prompt user to select file in no file is selected & upload is clicked
      if (!originalFileName) {
        res.writeHead(200, {'content-type': 'text/html'});
        res.end(
          '
Please select a file to upload!
' +
          '
' +
          'Back' +
          '
'
        );
        return;
      }

      // Copy the uploaded file in the upload directory
      fs.createReadStream(uploadedFilePath).pipe(fs.createWriteStream(new_location + guid.value + '.' + originalFileExtension));
      visualizeUploadedFiles();
    });

    return;
  }

  // Display page listing the files in the 'uploaded' folder
  if (req.url.indexOf('files') >= 0 && req.method.toLowerCase() == 'get') {
    visualizeUploadedFiles();
    return;
  }

  if (req.url.indexOf('download') >= 0) {
    let fileNameToDownload = req.url.substr(req.url.lastIndexOf('/') + 1);
    let filePath = 'uploaded/' + fileNameToDownload;

    fs.exists(filePath, function (exists) {

      if (exists) {
        fs.readFile(filePath, function (error, content) {
          if (error) {
            res.writeHead(500);
            res.end();
          }
          else {
            res.writeHead(200, {'Content-Type': 'Magic'});
            res.end(content, 'utf-8');
          }
        });
      }
      else {
        res.writeHead(404);
        res.end();
      }
    });

    return;
  }

  // show a file upload form
  res.writeHead(200, {'content-type': 'text/html'});
  res.end(
    '<form action="/upload" enctype="multipart/form-data" method="post">' +
    '<input id="uploadInput" type="file" name="upload" multiple="multiple">
' +
    '<input type="submit" value="Upload">' +
    '
' +
    'See uploaded files' +
    '</form>'
  );
}).listen(port);

console.log(`Server running at port ${port}!`);
<commit_msg>Initial check for the uploaded folder added<commit_after>'use strict';

let http = require('http'),
  fs = require('fs'),
  formidable = require('formidable'),
  util = require('util'),
  url = require('url'),
  jade = require('jade'),
  path = require('path'),
  Guid = require('guid');

let port = 6666;

// Create the web server
http.createServer(function (req, res) {

  // Check if the "uploaded" directory exists and create it if necessary
  fs.existsSync('uploaded') || fs.mkdirSync('uploaded');

  // Function visualizing the files, uploaded on the server
  function visualizeUploadedFiles() {
    fs.readFile('uploadedFiles.jade', function (err, template) {
      if (err) {
        res.end(err);
        return;
      }

      fs.readdir('uploaded', function (error, files) {
        if (error) {
          resp.end(error);
          return;
        }

        let output = jade.compile(template)({
          files: files
        });

        res.writeHead(200, {'Content-Type': 'text/html'});
        res.write(output);
        res.end();
      })
    });
  }

  if (req.url == '/upload' && req.method.toLowerCase() == 'post') {

    // parse the file upload
    var form = new formidable.IncomingForm();
    var guid = Guid.create();

    form.parse(req, function (err, fields, files) {
      if (err) {
        throw 'Something bad happened!';
      }
    });

    form.on('end', function (fields, files) {
      let uploadedFilePath = this.openedFiles[0].path;
      let originalFileName = this.openedFiles[0].name;
      let originalFileExtension = originalFileName.substr(originalFileName.lastIndexOf('.') + 1);
      let new_location = __dirname + '/uploaded/';

      // Prompt user to select file in no file is selected & upload is clicked
      if (!originalFileName) {
        res.writeHead(200, {'content-type': 'text/html'});
        res.end(
          '
Please select a file to upload!
' +
          '
' +
          'Back' +
          '
'
        );
        return;
      }

      // Copy the uploaded file in the upload directory
      fs.createReadStream(uploadedFilePath).pipe(fs.createWriteStream(new_location + guid.value + '.' + originalFileExtension));
      visualizeUploadedFiles();
    });

    return;
  }

  // Display page listing the files in the 'uploaded' folder
  if (req.url.indexOf('files') >= 0 && req.method.toLowerCase() == 'get') {
    visualizeUploadedFiles();
    return;
  }

  if (req.url.indexOf('download') >= 0) {
    let fileNameToDownload = req.url.substr(req.url.lastIndexOf('/') + 1);
    let filePath = 'uploaded/' + fileNameToDownload;

    fs.exists(filePath, function (exists) {

      if (exists) {
        fs.readFile(filePath, function (error, content) {
          if (error) {
            res.writeHead(500);
            res.end();
          }
          else {
            res.writeHead(200, {'Content-Type': 'Magic'});
            res.end(content, 'utf-8');
          }
        });
      }
      else {
        res.writeHead(404);
        res.end();
      }
    });

    return;
  }

  // show a file upload form
  res.writeHead(200, {'content-type': 'text/html'});
  res.end(
    '<form action="/upload" enctype="multipart/form-data" method="post">' +
    '<input id="uploadInput" type="file" name="upload" multiple="multiple">
' +
    '<input type="submit" value="Upload">' +
    '
' +
    'See uploaded files' +
    '</form>'
  );
}).listen(port);

console.log(`Server running at port ${port}!`);
<|endoftext|>


<commit_before>'use strict';

// Imports
const {testingUtils} = require('@gkalpak/cli-utils');
const {join} = require('path');
const {ROOT_DIR} = require('../test-utils');

// Constants
const SCRIPT_DIR = 'bin/node/';

// Tests
describe(SCRIPT_DIR, testingUtils.withJasmineTimeout(30000, () => {
  describe('nls', () => {
    const alias = 'npm list --depth=0';
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nls'));

    it(`should be an alias for \`${alias}\``, async () => {
      // `npm list` fails due to missing peerDependency.
      const result1 = await testingUtils.testCmd(`${alias} 2>&1 || true`);
      const result2 = await testScript('2>&1 || true');

      expect(result2).toContain(result1);
    });
  });

  describe('nls1', () => {
    const alias = 'npm list --depth=1';
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nls1'));

    it(`should be an alias for \`${alias}\``, async () => {
      // `npm list` fails due to missing peerDependency.
      const result1 = await testingUtils.testCmd(`${alias} 2>&1 || true`);
      const result2 = await testScript('2>&1 || true');

      expect(result2).toContain(result1);
    });
  });

  describe('nlsg', () => {
    const alias = 'npm list --depth=0 --global';
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nlsg'));

    it(`should be an alias for \`${alias}\``, async () => {
      const result1 = await testingUtils.testCmd(alias);
      const result2 = await testScript();

      expect(result2).toContain(result1);
    });
  });

  describe('nlsg1', () => {
    const alias = 'npm list --depth=1 --global';
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nlsg1'));

    it(`should be an alias for \`${alias}\``, async () => {
      const result1 = await testingUtils.testCmd(alias);
      const result2 = await testScript();

      expect(result2).toContain(result1);
    });
  });

  describe('nv', () => {
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nv'));

    it('should print the Node.js version', async () => {
      const result = await testScript();
      expect(result).toBe(process.version);
    });
  });

  // `nvm` is being funny on non-Windows platforms,
  // giving errors when run during tests (but not directly in the terminal).
  describe('nvls', onlyOnWindows(() => {
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nvls'));

    it('should at least list (pun intended) the current Node.js version', async () => {
      const result = await testScript();
      expect(result).toContain(process.version.replace(/^v/, ''));
    });
  }));

  // `nvm` is being funny on non-Windows platforms,
  // giving errors when run during tests (but not directly in the terminal).
  describe('nvlsa', onlyOnWindows(() => {
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nvlsa'));

    it('should list some Node.js versions', async () => {
      const versionRe = /\d+\.\d+\.\d+/;
      const result = await testScript();
      const linesWithVersions = result.split('\n').filter(line => versionRe.test(line));

      expect(linesWithVersions.length).toBeGreaterThan(5);
    });

    it('should include the current Node.js version', async () => {
      const result = await testScript();
      expect(result).toContain(process.version.replace(/^v/, ''));
    });
  }));
}));

// Helpers
function onlyOnWindows(testSuite) {
  return (process.platform === 'win32') ? testSuite : () => undefined;
}
<commit_msg>test(node): skip `nvm`-related tests when `nvm` is not available<commit_after>'use strict';

// Imports
const {testingUtils} = require('@gkalpak/cli-utils');
const {join} = require('path');
const {which} = require('shelljs');
const {ROOT_DIR} = require('../test-utils');

// Constants
const SCRIPT_DIR = 'bin/node/';
const NVM_EXISTS = !!which('nvm');

// Tests
describe(SCRIPT_DIR, testingUtils.withJasmineTimeout(30000, () => {
  describe('nls', () => {
    const alias = 'npm list --depth=0';
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nls'));

    it(`should be an alias for \`${alias}\``, async () => {
      // `npm list` fails due to missing peerDependency.
      const result1 = await testingUtils.testCmd(`${alias} 2>&1 || true`);
      const result2 = await testScript('2>&1 || true');

      expect(result2).toContain(result1);
    });
  });

  describe('nls1', () => {
    const alias = 'npm list --depth=1';
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nls1'));

    it(`should be an alias for \`${alias}\``, async () => {
      // `npm list` fails due to missing peerDependency.
      const result1 = await testingUtils.testCmd(`${alias} 2>&1 || true`);
      const result2 = await testScript('2>&1 || true');

      expect(result2).toContain(result1);
    });
  });

  describe('nlsg', () => {
    const alias = 'npm list --depth=0 --global';
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nlsg'));

    it(`should be an alias for \`${alias}\``, async () => {
      const result1 = await testingUtils.testCmd(alias);
      const result2 = await testScript();

      expect(result2).toContain(result1);
    });
  });

  describe('nlsg1', () => {
    const alias = 'npm list --depth=1 --global';
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nlsg1'));

    it(`should be an alias for \`${alias}\``, async () => {
      const result1 = await testingUtils.testCmd(alias);
      const result2 = await testScript();

      expect(result2).toContain(result1);
    });
  });

  describe('nv', () => {
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nv'));

    it('should print the Node.js version', async () => {
      const result = await testScript();
      expect(result).toBe(process.version);
    });
  });

  // - `nvm` might not be available on some environments; e.g. Windows on Travis.
  // - `nvm` is being funny on non-Windows platforms, giving errors when run during tests
  //   (but not directly in the terminal).
  describe('nvls', onlyWithNvm(onlyOnWindows(() => {
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nvls'));

    it('should at least list (pun intended) the current Node.js version', async () => {
      const result = await testScript();
      expect(result).toContain(process.version.replace(/^v/, ''));
    });
  })));

  // - `nvm` might not be available on some environments; e.g. Windows on Travis.
  // - `nvm` is being funny on non-Windows platforms, giving errors when run during tests
  //   (but not directly in the terminal).
  describe('nvlsa', onlyWithNvm(onlyOnWindows(() => {
    const testScript = testingUtils.testScriptFactory(join(ROOT_DIR, SCRIPT_DIR, 'nvlsa'));

    it('should list some Node.js versions', async () => {
      const versionRe = /\d+\.\d+\.\d+/;
      const result = await testScript();
      const linesWithVersions = result.split('\n').filter(line => versionRe.test(line));

      expect(linesWithVersions.length).toBeGreaterThan(5);
    });

    it('should include the current Node.js version', async () => {
      const result = await testScript();
      expect(result).toContain(process.version.replace(/^v/, ''));
    });
  })));
}));

// Helpers
function onlyIf(condition, testSuite) {
  return condition ? testSuite : () => undefined;
}

function onlyOnWindows(testSuite) {
  return onlyIf(process.platform === 'win32', testSuite);
}

function onlyWithNvm(testSuite) {
  return onlyIf(NVM_EXISTS, testSuite);
}
<|endoftext|>


<commit_before>// fs.writeFile that's safe for simultaneous calls for same file.
// In such event write that is ongoing is exited and new one is initialized

'use strict';

var fs      = require('fs')
  , resolve = require('path').resolve

  , next, writeAll, cache = {};

next = function (path) {
	var args;
	if (cache[path] === true) {
		delete cache[path];
	} else {
		args = cache[path];
		delete cache[path];
		exports.apply(null, args);
	}
};

writeAll = function (path, fd, buffer, offset, length, callback) {
	// write(fd, buffer, offset, length, position, callback)
	fs.write(fd, buffer, offset, length, offset, function(writeErr, written) {
		if (writeErr) {
			fs.close(fd, function() {
				if (callback) callback(writeErr);
				next(path);
			});
		} else {
			if ((written === length) || (cache[path] !== true)) {
				fs.close(fd, function () {
					if (callback) callback.apply(null, arguments);
					next(path);
				});
			} else {
				writeAll(path, fd, buffer, offset + written, length - written, callback);
			}
		}
	});
};

exports = module.exports = function (path, data, encoding_, callback) {
	var encoding, callback_;
	path = resolve(String(path));
	if (cache[path]) {
		cache[path] = arguments;
		return;
	}
	cache[path] = true;

	encoding = (typeof(encoding_) == 'string' ? encoding_ : 'utf8');
	callback_ = arguments[arguments.length - 1];
	callback = (typeof(callback_) == 'function' ? callback_ : null);
	fs.open(path, 'w', 438 /*=0666*/, function (openErr, fd) {
		if (openErr) {
			if (callback) callback(openErr);
			next(path);
		} else  if (cache[path] === true) {
			var buffer = Buffer.isBuffer(data) ? data : new Buffer('' + data,
				encoding);
			writeAll(path, fd, buffer, 0, buffer.length, callback);
		} else {
			fs.close(fd, function () {
				if (callback) callback.apply(null, arguments);
				next(path);
			});
		}
	});
};
<commit_msg>Invoke callbacks of overriden calls<commit_after>// fs.writeFile that's safe for simultaneous calls for same file.
// In such event write that is ongoing is exited and new one is initialized

'use strict';

var fs      = require('fs')
  , resolve = require('path').resolve

  , next, writeAll, cache = {};

next = function (path) {
	var args;
	if (cache[path] === true) {
		delete cache[path];
	} else {
		args = cache[path];
		delete cache[path];
		exports.apply(null, args);
	}
};

writeAll = function (path, fd, buffer, offset, length, callback) {
	// write(fd, buffer, offset, length, position, callback)
	fs.write(fd, buffer, offset, length, offset, function(writeErr, written) {
		if (writeErr) {
			fs.close(fd, function() {
				if (callback) callback(writeErr);
				next(path);
			});
		} else {
			if ((written === length) || (cache[path] !== true)) {
				fs.close(fd, function () {
					if (callback) callback.apply(null, arguments);
					next(path);
				});
			} else {
				writeAll(path, fd, buffer, offset + written, length - written, callback);
			}
		}
	});
};

exports = module.exports = function (path, data, encoding_, callback) {
	var encoding, callback_;
	path = resolve(String(path));
	if (cache[path]) {
		if (cache[path] !== true) {
			callback_ = cache[path][cache[path].length - 1];
			if (typeof callback_ == 'function') {
				callback_(null);
			}
		}
		cache[path] = arguments;
		return;
	}
	cache[path] = true;

	encoding = (typeof(encoding_) == 'string' ? encoding_ : 'utf8');
	callback_ = arguments[arguments.length - 1];
	callback = (typeof(callback_) == 'function' ? callback_ : null);
	fs.open(path, 'w', 438 /*=0666*/, function (openErr, fd) {
		if (openErr) {
			if (callback) callback(openErr);
			next(path);
		} else  if (cache[path] === true) {
			var buffer = Buffer.isBuffer(data) ? data : new Buffer('' + data,
				encoding);
			writeAll(path, fd, buffer, 0, buffer.length, callback);
		} else {
			fs.close(fd, function () {
				if (callback) callback.apply(null, arguments);
				next(path);
			});
		}
	});
};
<|endoftext|>


<commit_before>import React, {useMemo} from 'react';
import {Container, Row, Col, Jumbotron} from 'react-bootstrap';
import {ProductListing} from '../../components/ProductListing/ProductListing.js';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';

/**
 * @param {!Object} items The items stored in the site state
 * @return {!JSX} Page component where we thank the user
 *     for their purchase(s).
 */
export const ThankScreenBase = function({items}) {
  const /** Array<!JSX> */ productListings = useMemo(() => {
    const ITEMS_TO_DISPLAY = 6;
    const products = [];
    const usedItemSet = new Set();

    let i;
    for (i = 0; i < ITEMS_TO_DISPLAY; ++i) {
      let maxPrice = 0;
      let maxPriceItem;
      let maxPriceID;

      // Finds and pushes the most expensive item that has not yet been used.
      for (const [itemID, item] of Object.entries(items)) {
        if (item.cost > maxPrice && !usedItemSet.has(itemID)) {
          maxPriceItem = item;
          maxPriceID = itemID;
          maxPrice = item.cost;
        }
      }
      products.push(

            <ProductListing
              id={maxPriceID}
              title={maxPriceItem.name}
              price={maxPriceItem.cost}
              imageSrc={maxPriceItem.thumbnail}
              description={maxPriceItem.description}
            />

,
      );
      usedItemSet.add(maxPriceID);
    }

    return products;
  }, [items]);

  return (
    <Container>
      <Row>
        <Col/>
        <Col xs={10}>
          <Jumbotron>

              {'Thank you for your purchase!'}

            {'Here are some other items you may be interested in:'}

              {productListings}

          </Jumbotron>
        </Col>
        <Col/>
      </Row>
    </Container>
  );
};

ThankScreenBase.propTypes = {
  items: PropTypes.object,
};

/**
  * @param {{items:Itemstore}} state
  * @return {{items:Itemstore}} The items stored in the site state.
  */
const mapStateToProps = (state) => ({
  items: state.items,
});

/*
 * Decorate the ProductScreenBase function, implicity passing in
 * information about the global site state. Also pass mapDispatchToProps
 * function to allow modification of global cart.
 */
export const ThankScreen = connect(mapStateToProps)(ThankScreenBase);
<commit_msg>Use more appropriate html tags<commit_after>import React, {useMemo} from 'react';
import {Container, Row, Col, Jumbotron} from 'react-bootstrap';
import {ProductListing} from '../../components/ProductListing/ProductListing.js';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';

/**
 * @param {!Object} items The items stored in the site state
 * @return {!JSX} Page component where we thank the user
 *     for their purchase(s).
 */
export const ThankScreenBase = function({items}) {
  const /** Array<!JSX> */ productListings = useMemo(() => {
    const ITEMS_TO_DISPLAY = 6;
    const products = [];
    const usedItemSet = new Set();

    let i;
    for (i = 0; i < ITEMS_TO_DISPLAY; ++i) {
      let maxPrice = 0;
      let maxPriceItem;
      let maxPriceID;

      // Finds and pushes the most expensive item that has not yet been used.
      for (const [itemID, item] of Object.entries(items)) {
        if (item.cost > maxPrice && !usedItemSet.has(itemID)) {
          maxPriceItem = item;
          maxPriceID = itemID;
          maxPrice = item.cost;
        }
      }
      products.push(

            <ProductListing
              id={maxPriceID}
              title={maxPriceItem.name}
              price={maxPriceItem.cost}
              imageSrc={maxPriceItem.thumbnail}
              description={maxPriceItem.description}
            />

,
      );
      usedItemSet.add(maxPriceID);
    }

    return products;
  }, [items]);

  return (
    <Container>
      <Row>
        <Col/>
        <Col xs={10}>
          <Jumbotron>

              Thank you for your purchase!

              Here are some other items you may be interested in:

              {productListings}

          </Jumbotron>
        </Col>
        <Col/>
      </Row>
    </Container>
  );
};

ThankScreenBase.propTypes = {
  items: PropTypes.object,
};

/**
  * @param {{items:Itemstore}} state
  * @return {{items:Itemstore}} The items stored in the site state.
  */
const mapStateToProps = (state) => ({
  items: state.items,
});

/*
 * Decorate the ProductScreenBase function, implicity passing in
 * information about the global site state. Also pass mapDispatchToProps
 * function to allow modification of global cart.
 */
export const ThankScreen = connect(mapStateToProps)(ThankScreenBase);
<|endoftext|>