<commit_before>import React from 'react';

import { Yajinni, Mamtooth } from 'CONTRIBUTORS';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';

export default [
  {
    date: new Date('2018-07-28'),
    changes: <React.Fragment>Added new spells and talents.</React.Fragment>,
    contributors: [Mamtooth],
  },
    date: new Date('2018-07-25'),
    changes: <React.Fragment>Reworked soul fragment tracking code to make it more versatile. Added a consumed souls module that shows how/what spells were used to consume souls.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-07-23'),
    changes: <React.Fragment>Updated the wasted soul fragments tracker. Its now sould fragments inefficienctly used and it's info is clearer.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-07-23'),
    changes: <React.Fragment>Added <SpellLink id={SPELLS.FRACTURE_TALENT.id} /> casts to suggestions.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-07-22'),
    changes: <React.Fragment>Reworked <SpellLink id={SPELLS.DEMON_SPIKES.id} /> module to show stats/suggestion based on the physical hits you mitgated instead of cast efficiency.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-07-21'),
    changes: <React.Fragment>Cleaned up spells and traits. Removed old/outdated ones and updated the changed ones. No longer crashes due to prepatch.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-18'),
    changes: <React.Fragment>Added death recap tab and defensive abilities tracking.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-10'),
    changes: <React.Fragment>Reworked Empower Wards to show damage mitigated and if it was avalible to be used.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-10'),
    changes: <React.Fragment>Reworked <SpellLink id={SPELLS.SIGIL_OF_FLAME.id} /> to show damage and if its stacked.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-05'),
    changes: <React.Fragment>Added more spell suggestions.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-05'),
    changes: <React.Fragment>Updated <SpellLink id={SPELLS.DEMON_SPIKES.id} /> module to track hits taken while debuff is up and missed uses.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-04'),
    changes: <React.Fragment>Added tracking of Pain management/waste and a new Pain Usage Tab.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-04'),
    changes: <React.Fragment>Added tracking of <SpellLink id={SPELLS.PAINBRINGER.id} /> uptime and stacks.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-03'),
    changes: <React.Fragment>Added tracking of <SpellLink id={SPELLS.SOUL_BARRIER_TALENT.id} /> uptime and damage/healing stats.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2017-09-03'),
    changes: 'Changed the Github link for the issues of Vengeance spec.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-02'),
    changes: 'Pain chart now displays a error message for corrupted logs.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-10-13'),
    changes: 'Pain chart bug fix.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-10-12'),
    changes: <React.Fragment>Cooldown tab for <SpellLink id={SPELLS.METAMORPHOSIS_TANK.id} /> is now implemented.</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-10-12'),
    changes: 'Updated \'More Information\' tab, with introduction text and links.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-23'),
    changes: 'Tier 20 - 2 and 4 piece bonus is now being tracked.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-16'),
    changes: 'Created a Soul Fragments tracker (with total generated/spent/wasted/unused) and improvement suggestion for wasted Soul Fragments.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-16'),
    changes: <React.Fragment><SpellLink id={SPELLS.SPIRIT_BOMB_TALENT.id} /> is now on statistics boxes too.</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-09'),
    changes: 'Some nearly unused abilities and/or no CD abilities now doesn\'t show \'can be improved\' in Cast Efficiency tab.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-07'),
    changes: 'Abilities now trigger mouseover tooltips on statistic boxes.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-07'),
    changes: 'Updated timers in the statistics tooltips.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-06'),
    changes: <React.Fragment>Added more suggestions to <SpellLink id={SPELLS.IMMOLATION_AURA_BUFF.id} /> buff uptime.</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-06'),
    changes: <React.Fragment>Fixed <SpellLink id={SPELLS.SIGIL_OF_FLAME_DEBUFF.id} /> debuff uptime to best fit in more cases.</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-08-27'),
    changes: <React.Fragment>Fixed a bug with <SpellLink id={SPELLS.SPIRIT_BOMB_TALENT.id} /> uptime suggestions and modified the Dead GCD recommended time (ABC).</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-08-20'),
    changes: <React.Fragment>Added Pain tracker chart and changed <SpellLink id={SPELLS.SPIRIT_BOMB_TALENT.id} /> uptime buff to <SpellLink id={SPELLS.FRAILTY_SPIRIT_BOMB_DEBUFF.id} /> debuff uptime.</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-08-19'),
    changes: 'Added more useful and specific statistics tooltips.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-08-16'),
    changes: 'Added Vengeance Demon Hunter support.',
    contributors: [Mamtooth],
  },
];
<commit_msg>Update CHANGELOG.js<commit_after>import React from 'react';

import { Yajinni, Mamtooth } from 'CONTRIBUTORS';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';

export default [
  {
    date: new Date('2018-07-28'),
    changes: <React.Fragment>Added new spells and talents.</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2018-07-25'),
    changes: <React.Fragment>Reworked soul fragment tracking code to make it more versatile. Added a consumed souls module that shows how/what spells were used to consume souls.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-07-23'),
    changes: <React.Fragment>Updated the wasted soul fragments tracker. Its now sould fragments inefficienctly used and it's info is clearer.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-07-23'),
    changes: <React.Fragment>Added <SpellLink id={SPELLS.FRACTURE_TALENT.id} /> casts to suggestions.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-07-22'),
    changes: <React.Fragment>Reworked <SpellLink id={SPELLS.DEMON_SPIKES.id} /> module to show stats/suggestion based on the physical hits you mitgated instead of cast efficiency.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-07-21'),
    changes: <React.Fragment>Cleaned up spells and traits. Removed old/outdated ones and updated the changed ones. No longer crashes due to prepatch.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-18'),
    changes: <React.Fragment>Added death recap tab and defensive abilities tracking.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-10'),
    changes: <React.Fragment>Reworked Empower Wards to show damage mitigated and if it was avalible to be used.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-10'),
    changes: <React.Fragment>Reworked <SpellLink id={SPELLS.SIGIL_OF_FLAME.id} /> to show damage and if its stacked.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-05'),
    changes: <React.Fragment>Added more spell suggestions.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-05'),
    changes: <React.Fragment>Updated <SpellLink id={SPELLS.DEMON_SPIKES.id} /> module to track hits taken while debuff is up and missed uses.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-04'),
    changes: <React.Fragment>Added tracking of Pain management/waste and a new Pain Usage Tab.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-04'),
    changes: <React.Fragment>Added tracking of <SpellLink id={SPELLS.PAINBRINGER.id} /> uptime and stacks.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2018-05-03'),
    changes: <React.Fragment>Added tracking of <SpellLink id={SPELLS.SOUL_BARRIER_TALENT.id} /> uptime and damage/healing stats.</React.Fragment>,
    contributors: [Yajinni],
  },
  {
    date: new Date('2017-09-03'),
    changes: 'Changed the Github link for the issues of Vengeance spec.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-02'),
    changes: 'Pain chart now displays a error message for corrupted logs.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-10-13'),
    changes: 'Pain chart bug fix.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-10-12'),
    changes: <React.Fragment>Cooldown tab for <SpellLink id={SPELLS.METAMORPHOSIS_TANK.id} /> is now implemented.</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-10-12'),
    changes: 'Updated \'More Information\' tab, with introduction text and links.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-23'),
    changes: 'Tier 20 - 2 and 4 piece bonus is now being tracked.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-16'),
    changes: 'Created a Soul Fragments tracker (with total generated/spent/wasted/unused) and improvement suggestion for wasted Soul Fragments.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-16'),
    changes: <React.Fragment><SpellLink id={SPELLS.SPIRIT_BOMB_TALENT.id} /> is now on statistics boxes too.</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-09'),
    changes: 'Some nearly unused abilities and/or no CD abilities now doesn\'t show \'can be improved\' in Cast Efficiency tab.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-07'),
    changes: 'Abilities now trigger mouseover tooltips on statistic boxes.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-07'),
    changes: 'Updated timers in the statistics tooltips.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-06'),
    changes: <React.Fragment>Added more suggestions to <SpellLink id={SPELLS.IMMOLATION_AURA_BUFF.id} /> buff uptime.</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-09-06'),
    changes: <React.Fragment>Fixed <SpellLink id={SPELLS.SIGIL_OF_FLAME_DEBUFF.id} /> debuff uptime to best fit in more cases.</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-08-27'),
    changes: <React.Fragment>Fixed a bug with <SpellLink id={SPELLS.SPIRIT_BOMB_TALENT.id} /> uptime suggestions and modified the Dead GCD recommended time (ABC).</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-08-20'),
    changes: <React.Fragment>Added Pain tracker chart and changed <SpellLink id={SPELLS.SPIRIT_BOMB_TALENT.id} /> uptime buff to <SpellLink id={SPELLS.FRAILTY_SPIRIT_BOMB_DEBUFF.id} /> debuff uptime.</React.Fragment>,
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-08-19'),
    changes: 'Added more useful and specific statistics tooltips.',
    contributors: [Mamtooth],
  },
  {
    date: new Date('2017-08-16'),
    changes: 'Added Vengeance Demon Hunter support.',
    contributors: [Mamtooth],
  },
];
<|endoftext|>


<commit_before>import ITEMS from 'common/ITEMS';
import SPELLS from 'common/SPELLS';

import ISSUE_IMPORTANCE from 'Parser/Core/ISSUE_IMPORTANCE';

export const SPELL_CATEGORY = {
  ROTATIONAL: 'Rotational Spell',
  COOLDOWNS: 'Cooldown',
  OTHERS: 'Spell',
};

const CPM_ABILITIES = [
  // Riptide with EotE
  {
    spell: SPELLS.RIPTIDE,
    category: SPELL_CATEGORY.ROTATIONAL,
    getCooldown: haste => 6,
    isActive: combatant => combatant.lv90Talent === SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id,
    recommendedCastEfficiency: 0.90,
  },
  // Riptide without EotE
  {
    spell: SPELLS.RIPTIDE,
    category: SPELL_CATEGORY.ROTATIONAL,
    getCooldown: haste => 6,
    isActive: combatant => !(combatant.lv90Talent === SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id),
    recommendedCastEfficiency: 0.75,
  },
  {
    spell: SPELLS.HEALING_STREAM_TOTEM_CAST,
    category: SPELL_CATEGORY.ROTATIONAL,
    getCooldown: (haste, combatant) => {
        const has4PT19 = combatant.hasBuff(SPELLS.RESTORATION_SHAMAN_T19_4SET_BONUS_BUFF.id);

        if (!has4PT19) {
            return 30;
        }

        const abilityTracker = combatant.owner.modules.abilityTracker;

        const riptideCasts = abilityTracker.getAbility(SPELLS.RIPTIDE.id).casts || 0;
        const chainHealCasts = abilityTracker.getAbility(SPELLS.CHAIN_HEAL.id).casts || 0;
        const reducedCD = 3 * (riptideCasts + chainHealCasts);
        const extraHsts = reducedCD / 30;

        const fightDuration = combatant.owner.fightDuration/1000;
        const potentialHstCasts = fightDuration / 30 + 1;
        const newPotentialHstCasts = potentialHstCasts + extraHsts

        const cooldown = fightDuration / newPotentialHstCasts;

        return cooldown;
    },
    getOverhealing: (_, getAbility) => {
      const { healingEffective, healingAbsorbed, healingOverheal } = getAbility(SPELLS.HEALING_STREAM_TOTEM_HEAL.id);
      return healingOverheal / (healingEffective + healingAbsorbed + healingOverheal);
    },
  },
  {
    spell: SPELLS.ASTRAL_SHIFT,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 90,
    recommendedCastEfficiency: 0.6,
    importance: ISSUE_IMPORTANCE.MINOR,
  },
  {
    spell: SPELLS.ARCANE_TORRENT,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 90,
    hideWithZeroCasts: true,
  },
  {
    spell: SPELLS.VELENS_FUTURE_SIGHT,
    icon: SPELLS.VELENS_FUTURE_SIGHT.icon,
    name: SPELLS.VELENS_FUTURE_SIGHT.name,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 75,
    isActive: combatant => combatant.hasTrinket(ITEMS.VELENS_FUTURE_SIGHT.id),
  },
  {
    spell: SPELLS.HEALING_RAIN_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 10,
    recommendedCastEfficiency: 0.6,
    importance: ISSUE_IMPORTANCE.MINOR,
  },
  {
    spell: SPELLS.GIFT_OF_THE_QUEEN,
    category: SPELL_CATEGORY.COOLDOWNS,
    recommendedCastEfficiency: 0.7,
    getCooldown: haste => 45,
  },
  {
    spell: SPELLS.CLOUDBURST_TOTEM_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 30,
    isActive: combatant => combatant.lv90Talent === SPELLS.CLOUDBURST_TOTEM_TALENT.id,
    getOverhealing: (_, getAbility) => {
      const { healingEffective, healingAbsorbed, healingOverheal } = getAbility(SPELLS.CLOUDBURST_TOTEM_HEAL.id);
      return healingOverheal / (healingEffective + healingAbsorbed + healingOverheal);
    },
  },
  {
    spell: SPELLS.EARTHEN_SHIELD_TOTEM_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 60,
    isActive: combatant => combatant.lv75Talent === SPELLS.EARTHEN_SHIELD_TOTEM_TALENT.id,
  },
  {
    spell: SPELLS.ANCESTRAL_GUIDANCE_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 120,
    isActive: combatant => combatant.lv60Talent === SPELLS.ANCESTRAL_GUIDANCE_TALENT.id,
  },
  {
    spell: SPELLS.ASCENDANCE_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 180,
    isActive: combatant => combatant.lv100Talent === SPELLS.ASCENDANCE_TALENT.id,
  },
  {
    spell: SPELLS.HEALING_TIDE_TOTEM_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 180,
    getOverhealing: (_, getAbility) => {
      const { healingEffective, healingAbsorbed, healingOverheal } = getAbility(SPELLS.HEALING_TIDE_TOTEM_HEAL.id);
      return healingOverheal / (healingEffective + healingAbsorbed + healingOverheal);
    },
  },
  {
    spell: SPELLS.SPIRIT_LINK_TOTEM,
    category: SPELL_CATEGORY.COOLDOWNS,
    recommendedCastEfficiency: 0.7,
    getCooldown: haste => 180,
  },
  {
    spell: SPELLS.HEALING_WAVE,
    name: `Filler ${SPELLS.HEALING_WAVE.name}`,
    category: SPELL_CATEGORY.OTHERS,
    getCasts: castCount => (castCount.casts || 0) - (castCount.healingTwHits || 0),
    getCooldown: haste => null,
    getOverhealing: ({ healingEffective, healingAbsorbed, healingOverheal, healingTwHealing, healingTwAbsorbed, healingTwOverheal }) => (healingOverheal - healingTwOverheal) / ((healingEffective - healingTwHealing) + (healingAbsorbed - healingTwAbsorbed) + (healingOverheal - healingTwOverheal)),
  },
  {
    spell: SPELLS.HEALING_WAVE,
    name: `Tidal Waves ${SPELLS.HEALING_WAVE.name}`,
    category: SPELL_CATEGORY.OTHERS,
    getCasts: castCount => castCount.healingTwHits || 0,
    getCooldown: haste => null,
    getOverhealing: ({ healingTwHealing, healingTwAbsorbed, healingTwOverheal }) => healingTwOverheal / (healingTwHealing + healingTwAbsorbed + healingTwOverheal),
  },
  {
    spell: SPELLS.HEALING_SURGE,
    name: `Filler ${SPELLS.HEALING_SURGE.name}`,
    category: SPELL_CATEGORY.OTHERS,
    getCasts: castCount => (castCount.casts || 0) - (castCount.healingTwHits || 0),
    getCooldown: haste => null,
    getOverhealing: ({ healingEffective, healingAbsorbed, healingOverheal, healingTwHealing, healingTwAbsorbed, healingTwOverheal }) => (healingOverheal - healingTwOverheal) / ((healingEffective - healingTwHealing) + (healingAbsorbed - healingTwAbsorbed) + (healingOverheal - healingTwOverheal)),
  },
  {
    spell: SPELLS.HEALING_SURGE,
    name: `Tidal Waves ${SPELLS.HEALING_SURGE.name}`,
    category: SPELL_CATEGORY.OTHERS,
    getCasts: castCount => castCount.healingTwHits || 0,
    getCooldown: haste => null,
    getOverhealing: ({ healingTwHealing, healingTwAbsorbed, healingTwOverheal }) => healingTwOverheal / (healingTwHealing + healingTwAbsorbed + healingTwOverheal),
  },
];

export default CPM_ABILITIES;
<commit_msg>Add overhealing in Cast Efficiency tab for some resto shaman spells. Fix NaN% if no HW or HS cast.<commit_after>import ITEMS from 'common/ITEMS';
import SPELLS from 'common/SPELLS';

import ISSUE_IMPORTANCE from 'Parser/Core/ISSUE_IMPORTANCE';

export const SPELL_CATEGORY = {
  ROTATIONAL: 'Rotational Spell',
  COOLDOWNS: 'Cooldown',
  OTHERS: 'Spell',
};

const CPM_ABILITIES = [
  // Riptide with EotE
  {
    spell: SPELLS.RIPTIDE,
    category: SPELL_CATEGORY.ROTATIONAL,
    getCooldown: haste => 6,
    isActive: combatant => combatant.lv90Talent === SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id,
    recommendedCastEfficiency: 0.90,
  },
  // Riptide without EotE
  {
    spell: SPELLS.RIPTIDE,
    category: SPELL_CATEGORY.ROTATIONAL,
    getCooldown: haste => 6,
    isActive: combatant => !(combatant.lv90Talent === SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id),
    recommendedCastEfficiency: 0.75,
  },
  {
    spell: SPELLS.HEALING_STREAM_TOTEM_CAST,
    category: SPELL_CATEGORY.ROTATIONAL,
    getCooldown: (haste, combatant) => {
        const has4PT19 = combatant.hasBuff(SPELLS.RESTORATION_SHAMAN_T19_4SET_BONUS_BUFF.id);

        if (!has4PT19) {
            return 30;
        }

        const abilityTracker = combatant.owner.modules.abilityTracker;

        const riptideCasts = abilityTracker.getAbility(SPELLS.RIPTIDE.id).casts || 0;
        const chainHealCasts = abilityTracker.getAbility(SPELLS.CHAIN_HEAL.id).casts || 0;
        const reducedCD = 3 * (riptideCasts + chainHealCasts);
        const extraHsts = reducedCD / 30;

        const fightDuration = combatant.owner.fightDuration/1000;
        const potentialHstCasts = fightDuration / 30 + 1;
        const newPotentialHstCasts = potentialHstCasts + extraHsts

        const cooldown = fightDuration / newPotentialHstCasts;

        return cooldown;
    },
    getOverhealing: (_, getAbility) => {
      const { healingEffective, healingAbsorbed, healingOverheal } = getAbility(SPELLS.HEALING_STREAM_TOTEM_HEAL.id);
      return healingOverheal / (healingEffective + healingAbsorbed + healingOverheal);
    },
  },
  {
    spell: SPELLS.ASTRAL_SHIFT,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 90,
    recommendedCastEfficiency: 0.6,
    importance: ISSUE_IMPORTANCE.MINOR,
  },
  {
    spell: SPELLS.ARCANE_TORRENT,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 90,
    hideWithZeroCasts: true,
  },
  {
    spell: SPELLS.VELENS_FUTURE_SIGHT,
    icon: SPELLS.VELENS_FUTURE_SIGHT.icon,
    name: SPELLS.VELENS_FUTURE_SIGHT.name,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 75,
    isActive: combatant => combatant.hasTrinket(ITEMS.VELENS_FUTURE_SIGHT.id),
  },
  {
    spell: SPELLS.HEALING_RAIN_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 10,
    recommendedCastEfficiency: 0.6,
    importance: ISSUE_IMPORTANCE.MINOR,
    getOverhealing: (_, getAbility) => {
      const { healingEffective, healingAbsorbed, healingOverheal } = getAbility(SPELLS.HEALING_RAIN_HEAL.id);
      return healingOverheal / (healingEffective + healingAbsorbed + healingOverheal);
    },
  },
  {
    spell: SPELLS.GIFT_OF_THE_QUEEN,
    category: SPELL_CATEGORY.COOLDOWNS,
    recommendedCastEfficiency: 0.7,
    getCooldown: haste => 45,
  },
  {
    spell: SPELLS.CLOUDBURST_TOTEM_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 30,
    isActive: combatant => combatant.lv90Talent === SPELLS.CLOUDBURST_TOTEM_TALENT.id,
    getOverhealing: (_, getAbility) => {
      const { healingEffective, healingAbsorbed, healingOverheal } = getAbility(SPELLS.CLOUDBURST_TOTEM_HEAL.id);
      return healingOverheal / (healingEffective + healingAbsorbed + healingOverheal);
    },
  },
  {
    spell: SPELLS.EARTHEN_SHIELD_TOTEM_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 60,
    isActive: combatant => combatant.lv75Talent === SPELLS.EARTHEN_SHIELD_TOTEM_TALENT.id,
    getOverhealing: (_, getAbility, parser) => {

      const earthenShieldHealing = parser.modules.earthenShieldTotem.healing || 0;
      const earthenShieldPotentialHealing = parser.modules.earthenShieldTotem.potentialHealing || 0;
      const earthenShieldEfficiency = earthenShieldHealing / earthenShieldPotentialHealing;
      return 1-earthenShieldEfficiency
    },
  },
  {
    spell: SPELLS.ANCESTRAL_GUIDANCE_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 120,
    isActive: combatant => combatant.lv60Talent === SPELLS.ANCESTRAL_GUIDANCE_TALENT.id,
    getOverhealing: (_, getAbility) => {
      const { healingEffective, healingAbsorbed, healingOverheal } = getAbility(SPELLS.ANCESTRAL_GUIDANCE_HEAL.id);
      return healingOverheal / (healingEffective + healingAbsorbed + healingOverheal);
    },
  },
  {
    spell: SPELLS.ASCENDANCE_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 180,
    isActive: combatant => combatant.lv100Talent === SPELLS.ASCENDANCE_TALENT.id,
    getOverhealing: (_, getAbility) => {
      const { healingEffective, healingAbsorbed, healingOverheal } = getAbility(SPELLS.ASCENDANCE_HEAL.id);
      return healingOverheal / (healingEffective + healingAbsorbed + healingOverheal);
    },
  },
  {
    spell: SPELLS.HEALING_TIDE_TOTEM_CAST,
    category: SPELL_CATEGORY.COOLDOWNS,
    getCooldown: haste => 180,
    getOverhealing: (_, getAbility) => {
      const { healingEffective, healingAbsorbed, healingOverheal } = getAbility(SPELLS.HEALING_TIDE_TOTEM_HEAL.id);
      return healingOverheal / (healingEffective + healingAbsorbed + healingOverheal);
    },
  },
  {
    spell: SPELLS.SPIRIT_LINK_TOTEM,
    category: SPELL_CATEGORY.COOLDOWNS,
    recommendedCastEfficiency: 0.7,
    getCooldown: haste => 180,
  },
  {
    spell: SPELLS.HEALING_WAVE,
    name: `Filler ${SPELLS.HEALING_WAVE.name}`,
    category: SPELL_CATEGORY.OTHERS,
    getCasts: castCount => (castCount.casts || 0) - (castCount.healingTwHits || 0),
    getCooldown: haste => null,
    getOverhealing: ({ healingEffective, healingAbsorbed, healingOverheal, healingTwHealing, healingTwAbsorbed, healingTwOverheal }) => ((healingOverheal - healingTwOverheal) / ((healingEffective - healingTwHealing) + (healingAbsorbed - healingTwAbsorbed) + (healingOverheal - healingTwOverheal))) || null,
  },
  {
    spell: SPELLS.HEALING_WAVE,
    name: `Tidal Waves ${SPELLS.HEALING_WAVE.name}`,
    category: SPELL_CATEGORY.OTHERS,
    getCasts: castCount => castCount.healingTwHits || 0,
    getCooldown: haste => null,
    getOverhealing: ({ healingTwHealing, healingTwAbsorbed, healingTwOverheal }) => (healingTwOverheal / (healingTwHealing + healingTwAbsorbed + healingTwOverheal)) || null,
  },
  {
    spell: SPELLS.HEALING_SURGE,
    name: `Filler ${SPELLS.HEALING_SURGE.name}`,
    category: SPELL_CATEGORY.OTHERS,
    getCasts: castCount => (castCount.casts || 0) - (castCount.healingTwHits || 0),
    getCooldown: haste => null,
    getOverhealing: ({ healingEffective, healingAbsorbed, healingOverheal, healingTwHealing, healingTwAbsorbed, healingTwOverheal }) => ((healingOverheal - healingTwOverheal) / ((healingEffective - healingTwHealing) + (healingAbsorbed - healingTwAbsorbed) + (healingOverheal - healingTwOverheal))) || null,
  },
  {
    spell: SPELLS.HEALING_SURGE,
    name: `Tidal Waves ${SPELLS.HEALING_SURGE.name}`,
    category: SPELL_CATEGORY.OTHERS,
    getCasts: castCount => castCount.healingTwHits || 0,
    getCooldown: haste => null,
    getOverhealing: ({ healingTwHealing, healingTwAbsorbed, healingTwOverheal }) => (healingTwOverheal / (healingTwHealing + healingTwAbsorbed + healingTwOverheal)) || null,
  },
];

export default CPM_ABILITIES;
<|endoftext|>


<commit_before>/*jslint sloppy: true */
/*global define */

/**
 * Classify - Sugar syntax for Prototypal Inheritance
 *
 * @author Luís Couto <lcouto87@gmail.com>
 * @version 1.0.0
 *
 * @example
 *
 *      var MyClass = Classify({
 *          Implements: [SomeInterface, OtherInterface],
 *          Extends: ParentClass,
 *          Borrows: [SomeMixin, OtherMixin],
 *          Binds: ['method1', 'method2'],
 *          Statics: {
 *              staticMethod1: function () {},
 *              staticMethod2: function () {},
 *              staticMethod3: function () {},
 *          },
 *          initialize: function () {},
 *          method1: function () {},
 *          method2: function () {},
 *          method3: function () {}
 *      });
 */
define("Trinity/Classify", ["Classify.Abstract", "Classify.Interface", "Classify.Singleton"], function (Abstract, Singleton, Interface) {

    /**
     * Create a class definition.
     *
     * @param {Object} params An object containing methods and properties
     *
     * @returns {Function} The constructor
     */
    function Classify(params) {

        var classify;

        /**
         * Extends an object with another given object.
         *
         * @private
         *
         * @param {Object} source The object to copy from
         * @param {Object} target The object that will get the source properties and methods
         */
        function extend(source, target) {

            var k;

            for (k in source) {
                if (source.hasOwnProperty(k)) {
                    target[k] = source[k];
                }
            }
        }

        /**
         * Borrows the properties and methods of various source objects to the target
         *
         * @private
         *
         * @param {Array}  sources Array of objects that will give their methods
         * @param {Object} target  Target that will receive the methods
         */
        function borrows(sources, target) {

            var i,
                length = sources.length,
                constructorBck,
                current;

            for (i = 0; i < length; i += 1) {

                current = sources[i];

                if (current.prototype && current.prototype.constructor) {
                    constructorBck = current.prototype.constructor;
                    delete current.prototype.constructor;
                    extend(current.prototype, target.prototype);
                    current.prototype.constructor = constructorBck;
                } else {
                    extend(current.prototype || current, target.prototype);
                }
            }
        }

        /**
         * Fixes the context in given methods.
         *
         * @private
         *
         * @param {Array} fns The array of functions to be binded
         * @param {Object} context The context that will be bound
         * @param {Object} target The target class that will have these methods
         */
        function binds(fns, context, target) {

            var proxy = function (func) {

                if (Function.prototype.bind) {
                    return func.bind(context);
                }

                return function () {
                    return func.apply(context, arguments);
                };

            },
                i = fns.length - 1;

            for (i; i >= 0; i -= 1) {
                target[fns[i]] = proxy(target[fns[i]], classify);
            }
        }

        /**
         * Copies the given object into a freshly created empty function's prototype
         *
         * @private
         *
         * @param {Object} object Object
         *
         * @returns {Function} Thew new instance
         */
        function clone(object) {

            function F() {}
            F.prototype = object;

            return new F();
        }

        /**
         *
         */
        function interfaces(implementations, target) {

            var i = implementations.length - 1,
                k;

            for (; i >= 0; i -= 1) {
                for (k in implementations[i]) {
                    if (!target.hasOwnProperty(k) && (k !== "Extends" || k !== "Name")) {
                        throw new Error("Class does not implements Interface " + implementations[i].Name + "correctly");
                    }
                }
            }
        }

        classify = params.initialize || function () {};

        if (params.Extends) {
            classify.Parent = params.Extends.prototype;
            classify.prototype = clone(classify.Parent);
            extend(params, classify.prototype);
        } else {
            classify.prototype = params;
        }

        classify.prototype.constructor = classify;

        if (params.Borrows) {
            borrows(params.Borrows, classify);
        }

        if (params.Binds) {
            binds(params.Binds, classify, classify.prototype);
        }

        if (params.Statics) {
            extend(params.Statics, classify);
            delete classify.prototype.Static;
        }

        if (params.Implements) {
            interfaces(params.Implements, this);
        }

        return classify;
    }

    Classify.Abstract = Abstract;
    Classify.Interface = Interface;
    Classify.Singleton = Singleton;

    return Classify;
});
<commit_msg>Fix CS.<commit_after>/*jslint sloppy: true */
/*global define */

/**
 * Classify - Sugar syntax for Prototypal Inheritance
 *
 * @author Luís Couto <lcouto87@gmail.com>
 * @version 1.0.0
 *
 * @example
 *
 *      var MyClass = Classify({
 *          Implements: [SomeInterface, OtherInterface],
 *          Extends: ParentClass,
 *          Borrows: [SomeMixin, OtherMixin],
 *          Binds: ['method1', 'method2'],
 *          Statics: {
 *              staticMethod1: function () {},
 *              staticMethod2: function () {},
 *              staticMethod3: function () {},
 *          },
 *          initialize: function () {},
 *          method1: function () {},
 *          method2: function () {},
 *          method3: function () {}
 *      });
 */
define("Trinity/Classify", ["Classify.Abstract", "Classify.Interface", "Classify.Singleton"], function (Abstract, Singleton, Interface) {

    /**
     * Create a class definition.
     *
     * @param {Object} params An object containing methods and properties
     *
     * @returns {Function} The constructor
     */
    function Classify(params) {

        var classify;

        /**
         * Extends an object with another given object.
         *
         * @private
         *
         * @param {Object} source The object to copy from
         * @param {Object} target The object that will get the source properties and methods
         */
        function extend(source, target) {

            var k;

            for (k in source) {
                if (source.hasOwnProperty(k)) {
                    target[k] = source[k];
                }
            }
        }

        /**
         * Borrows the properties and methods of various source objects to the target
         *
         * @private
         *
         * @param {Array}  sources Array of objects that will give their methods
         * @param {Object} target  Target that will receive the methods
         */
        function borrows(sources, target) {

            var i,
                length = sources.length,
                constructorBck,
                current;

            for (i = 0; i < length; i += 1) {

                current = sources[i];

                if (current.prototype && current.prototype.constructor) {
                    constructorBck = current.prototype.constructor;
                    delete current.prototype.constructor;
                    extend(current.prototype, target.prototype);
                    current.prototype.constructor = constructorBck;
                } else {
                    extend(current.prototype || current, target.prototype);
                }
            }
        }

        /**
         * Fixes the context in given methods.
         *
         * @private
         *
         * @param {Array} fns The array of functions to be binded
         * @param {Object} context The context that will be bound
         * @param {Object} target The target class that will have these methods
         */
        function binds(fns, context, target) {

            var proxy = function (func) {

                if (Function.prototype.bind) {
                    return func.bind(context);
                }

                return function () {
                    return func.apply(context, arguments);
                };

            },
                i = fns.length - 1;

            for (i; i >= 0; i -= 1) {
                target[fns[i]] = proxy(target[fns[i]], classify);
            }
        }

        /**
         * Copies the given object into a freshly created empty function's prototype
         *
         * @private
         *
         * @param {Object} object Object
         *
         * @returns {Function} Thew new instance
         */
        function clone(object) {

            function F() {}
            F.prototype = object;

            return new F();
        }

        /**
         *
         */
        function interfaces(implementations, target) {

            var i,
                k;

            for (i = implementations.length - 1; i >= 0; i -= 1) {
                for (k in implementations[i]) {
                    if (!target.hasOwnProperty(k) && (k !== "Extends" || k !== "Name")) {
                        throw new Error("Class does not implements Interface " + implementations[i].Name + "correctly");
                    }
                }
            }
        }

        classify = params.initialize || function () {};

        if (params.Extends) {
            classify.Parent = params.Extends.prototype;
            classify.prototype = clone(classify.Parent);
            extend(params, classify.prototype);
        } else {
            classify.prototype = params;
        }

        classify.prototype.constructor = classify;

        if (params.Borrows) {
            borrows(params.Borrows, classify);
        }

        if (params.Binds) {
            binds(params.Binds, classify, classify.prototype);
        }

        if (params.Statics) {
            extend(params.Statics, classify);
            delete classify.prototype.Static;
        }

        if (params.Implements) {
            interfaces(params.Implements, this);
        }

        return classify;
    }

    Classify.Abstract = Abstract;
    Classify.Interface = Interface;
    Classify.Singleton = Singleton;

    return Classify;
});
<|endoftext|>


<commit_before>import co from "co"
import debug from "debug"
import rimraf from "rimraf"
import chokidar from "chokidar"
import Catenator from "concat-with-sourcemaps"
import { dirname, join, parse, sep } from "path"
import { readFile, appendFile } from "mz/fs"
import { log, alert, error, defer, flatten, expand } from "fly-util"
import Emitter from "./emitter"
import write from "./write"
const _ = debug("fly")
const clear = defer(rimraf)

export default class Fly extends Emitter {
  /**
    Create a new Fly instance.
    @param {String} flyfile path
    @param {Object} flyfile module
    @param {[Function]} array of plugins
  */
  constructor ({ file = ".", plugins = [], host = {} } = {}) {
    super(/* ✈ */)
    Object.assign(this, {
      log, alert, error, defer, file, plugins,
      root: dirname(file),
      host: host instanceof Function
        ? Object.assign(host, { default: host })
        : host,
      debug: _,
      tasks: Object.keys(host).reduce((_, key) =>
        Object.assign(_, { [key]: host[key].bind(this) }), {}),
      _: { filters: [] }
    })
    _("chdir %o", this.root)
    process.chdir(this.root)
    plugins.forEach(({ name, plugin }) =>
      plugin.call(this, debug(name.replace("-", ":")), _("load %o", name)))
  }
  /**
    Compose a new yieldable sequence.
    Reset globs, filters and writer.
    @param {...String} glob patterns
    @return Fly instance. Promises resolve to { file, source }
   */
   source (...globs) {
     Object.assign(this, { _: { write, filters: [], globs: flatten(globs) }})
     this._.catenator = undefined
     _("source %o", this._.globs)
     return this
   }
  /**
    Add filter / transform function.
    Create a closure bound to the current Fly instance.
    @param {String|Function} name or filter callback
    @param [{Function}] callback with the signature (cb, options) => {}
  */
  filter (name, cb) {
    if (name instanceof Function) this.filter({ cb: name })
    else if (typeof name === "object") this._.filters.push(name)
    else {
      if (this[name] instanceof Function)
        throw new RangeError(`${name} method already defined in instance.`)
      this[name] = function (options) {
        debug("fly")(`${name} %o`, options)
        return this.filter({ cb, options })
      }
    }
    return this
  }
  /**
    Watch IO events in globs and run tasks.
    @param {[String]} glob patterns to observe for changes
    @param {[String]} list of tasks to run on changes
    @param {Object} start options. See Fly.proto.start
  */
  watch (globs, tasks, options) {
    _("watch %o", globs)
    return this.emit("fly_watch").start(tasks, options)
      .then(() => chokidar.watch(flatten([globs]), { ignoreInitial: true })
        .on("all", () => this.start(tasks, options)))
  }
  /**
    Unwrap/expand source globs to files.
    @param {Function} onFulfilled
    @param {Function} onRejected
  */
  unwrap (onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      return Promise.all(this._.globs.map(glob => expand(glob)))
        .then((files) => resolve.call(this, files.reduce((arr, item) => arr.concat(item)))).catch(reject)
      }).then(onFulfilled).catch(onRejected)
  }
  /**
    @private Execute a task.
    @param {String} name of the task
    @param {Mixed} initial value to pass into the task
    @param {Object} Fly instance the task should be bound to
  */
  *exec (task, value, inject = this) {
    _("run %o", task)
    try {
      const start = new Date()
      this.emit("task_start", { task })
      value = (yield this.host[task].call(inject, value)) || value
      this.emit("task_complete", {
        task, duration: (new Date()).getTime() - start
      })
    } catch (error) { this.emit("task_error", { task, error }) }
    return value
  }
  /**
    Run one or more tasks. Each task's return value cascades on to the next
    task in a sequence.
    @param {Array} list of tasks
    @return {Promise}
   */
  start (tasks = "default", { parallel = false, value } = {}) {
    _(`start %o in ${parallel ? "parallel" : "sequence"}`, tasks)
    return co.call(this, function* (tasks) {
      if (parallel) {
        yield tasks.map((task) =>
          this.exec(task, value, Object.create(this)))
      } else {
        for (let task of tasks) value = yield this.exec(task, value)
      }
      return value
    }, [].concat(tasks).filter((task) => ~Object.keys(this.host)
      .indexOf(task) || !this.emit("task_not_found", { task })))
  }
  /**
    Deferred rimraf wrapper.
    @param {...String} paths
   */
  clear (...paths) {
    _("clear %o", paths)
    return flatten(paths).map((path) => clear(path))
  }
  /**
    Writer based in fs/mz appendFile.
    @param {String} file name
   */
  concat (base) {
    this._.catenator = new Catenator(true, base, "\n")
    this._.write = function* ({ dir, data, map }) {
      yield write({ dir, base, data, map, write: appendFile })
    }
    return this
  }
  /**
    Resolve a yieldable sequence.
    Reduce source with filters and invoke writer.
    @param {Array} target directories
    @return {Promise}
   */
  target (...dirs) {
    return co.call(this, function* () {
      for (let glob of this._.globs) {
        for (let file of yield expand(glob)) {
          let { base, ext } = parse(file), data = yield readFile(file)
          let concat = this._.catenator || new Catenator(true, base, "\n")
          for (let filter of this._.filters) {
            const res = yield Promise.resolve(filter.cb.call(this, data,
              Object.assign({ filename: base }, filter.options)))
            data = res.code || res.js || res.css || res.data || res || data
            ext = res.ext || ext
            if (res.map) {
              concat.add(`${base}`, data, res.map)
              if (ext === ".css")
                data += `\n/*# sourceMappingURL=${base}.map*/\n`
              if (ext === ".js")
                data += `\n//# sourceMappingURL=${base}.map\n`
            }
          }
          for (let dir of flatten(dirs)) {
            yield this._.write({
              dir, data,
              base: join(...parse(file).dir.split(sep)
                .filter((path) => !~glob.split(sep).indexOf(path)),
                `${parse(file).name}${ext}`),
              map: concat.contentParts.length && concat.sourceMap
            })
          }
        }
      }
    })
  }
}
<commit_msg>+ Print stack trace on "task_error" + Current transform/filter customized debug message for each filter + Now filters can handle either a field named `ext` or `extension` with the new file extension for target according to the current filter.<commit_after>import co from "co"
import debug from "debug"
import rimraf from "rimraf"
import chokidar from "chokidar"
import Catenator from "concat-with-sourcemaps"
import { dirname, join, parse, sep } from "path"
import { readFile, appendFile } from "mz/fs"
import { log, alert, error, defer, flatten, expand } from "fly-util"
import Emitter from "./emitter"
import write from "./write"
const _ = debug("fly")
const clear = defer(rimraf)

export default class Fly extends Emitter {
  /**
    Create a new Fly instance.
    @param {String} flyfile path
    @param {Object} flyfile module
    @param {[Function]} array of plugins
  */
  constructor ({ file = ".", plugins = [], host = {} } = {}) {
    super(/* ✈ */)
    Object.assign(this, {
      log, alert, error, defer, file, plugins,
      root: dirname(file),
      host: host instanceof Function
        ? Object.assign(host, { default: host })
        : host,
      debug: _,
      tasks: Object.keys(host).reduce((_, key) =>
        Object.assign(_, { [key]: host[key].bind(this) }), {}),
      _: { filters: [] }
    })
    _("chdir %o", this.root)
    process.chdir(this.root)

    plugins.forEach(({ name, plugin }) => {
      if (!plugin) throw new Error(`Did you forget to npm i -D ${name}?`)
      plugin.call(this, debug(name.replace("-", ":")), _("load %o", name))
    })
  }
  /**
    Compose a new yieldable sequence.
    Reset globs, filters and writer.
    @param {...String} glob patterns
    @return Fly instance. Promises resolve to { file, source }
   */
   source (...globs) {
     Object.assign(this, { _: { write, filters: [], globs: flatten(globs) }})
     this._.catenator = undefined
     _("source %o", this._.globs)
     return this
   }
  /**
    Add filter / transform function.
    Create a closure bound to the current Fly instance.
    @param {String|Function} name or filter callback
    @param [{Function}] callback with the signature (cb, options) => {}
  */
  filter (name, cb) {
    if (name instanceof Function) this.filter({ cb: name })
    else if (typeof name === "object") this._.filters.push(name)
    else {
      if (this[name] instanceof Function)
        throw new RangeError(`${name} method already defined in instance.`)
      this[name] = function (options, ...rest) {
        debug("fly")(`${name} %o, %o`, options, rest)
        return this.filter({ cb, options, rest })
      }
    }
    return this
  }
  /**
    Watch IO events in globs and run tasks.
    @param {[String]} glob patterns to observe for changes
    @param {[String]} list of tasks to run on changes
    @param {Object} start options. See Fly.proto.start
  */
  watch (globs, tasks, options) {
    _("watch %o", globs)
    return this.emit("fly_watch").start(tasks, options)
      .then(() => chokidar.watch(flatten([globs]), { ignoreInitial: true })
        .on("all", () => this.start(tasks, options)))
  }
  /**
    Unwrap/expand source globs to files.
    @param {Function} onFulfilled
    @param {Function} onRejected
  */
  unwrap (onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      return Promise.all(this._.globs.map(glob => expand(glob)))
        .then((files) => resolve.call(this, files.reduce((arr, item) => arr.concat(item)))).catch(reject)
      }).then(onFulfilled).catch(onRejected)
  }
  /**
    @private Execute a task.
    @param {String} name of the task
    @param {Mixed} initial value to pass into the task
    @param {Object} Fly instance the task should be bound to
  */
  *exec (task, value, inject = this) {
    _("run %o", task)
    try {
      const start = new Date()
      this.emit("task_start", { task })
      value = (yield this.host[task].call(inject, value)) || value
      this.emit("task_complete", {
        task, duration: (new Date()).getTime() - start
      })
    } catch (error) { this.emit("task_error", { task, error }) }
    return value
  }
  /**
    Run one or more tasks. Each task's return value cascades on to the next
    task in a sequence.
    @param {Array} list of tasks
    @return {Promise}
   */
  start (tasks = "default", { parallel = false, value } = {}) {
    _(`start %o in ${parallel ? "parallel" : "sequence"}`, tasks)
    return co.call(this, function* (tasks) {
      if (parallel) {
        yield tasks.map((task) =>
          this.exec(task, value, Object.create(this)))
      } else {
        for (let task of tasks) value = yield this.exec(task, value)
      }
      return value
    }, [].concat(tasks).filter((task) => ~Object.keys(this.host)
      .indexOf(task) || !this.emit("task_not_found", { task })))
  }
  /**
    Deferred rimraf wrapper.
    @param {...String} paths
   */
  clear (...paths) {
    _("clear %o", paths)
    return flatten(paths).map((path) => clear(path))
  }
  /**
    Writer based in fs/mz appendFile.
    @param {String} file name
   */
  concat (base) {
    this._.catenator = new Catenator(true, base, "\n")
    this._.write = function* ({ dir, data, map }) {
      yield write({ dir, base, data, map, write: appendFile })
    }
    return this
  }
  /**
    Resolve a yieldable sequence.
    Reduce source with filters and invoke writer.
    @param {Array} target directories
    @return {Promise}
   */
  target (...dirs) {
    return co.call(this, function* () {
      for (let glob of this._.globs) {
        for (let file of yield expand(glob)) {
          let { base, ext } = parse(file), data = yield readFile(file)
          let concat = this._.catenator || new Catenator(true, base, "\n")

          for (let filter of this._.filters) {
            const options = (filter.options || {}).sourceMap
              ? Object.assign({ filename: base }, filter.options)
              : filter.options
            const res = yield Promise.resolve(
              filter.cb.apply(this, [data, options].concat(filter.rest)))

            data = res.code || res.js || res.css || res.data || res || data
            ext = res.ext || res.extension || ext

            if (res.map) {
              concat.add(`${base}`, data, res.map)
              if (ext === ".css")
                data += `\n/*# sourceMappingURL=${base}.map*/\n`
              if (ext === ".js")
                data += `\n//# sourceMappingURL=${base}.map\n`
            }
          }

          for (let dir of flatten(dirs)) {
            yield this._.write({
              dir, data,
              base: join(...parse(file).dir.split(sep)
                .filter((path) => !~glob.split(sep).indexOf(path)),
                `${parse(file).name}${ext}`),
              map: concat.contentParts.length && concat.sourceMap
            })
          }
        }
      }
    })
  }
}
<|endoftext|>


<commit_before>;(function ($, window, document, undefined) {
    "use strict";

    // Create the defaults once
    var pluginName = "mztxScheduler";

    // The actual plugin constructor
    function Plugin(element /*, options */) {
        this.element = element;

        this.screen_colors = [
            {
                dark: '#010432',
                normal: '#9A9EF6',
                light: '#D9DAF6'
            },
            {
                dark: '#012E2D',
                normal: '#8AF4F0',
                light: '#D3F4F3'
            },
            {
                dark: '#4C3902',
                normal: '#FBDE8D',
                light: '#FBF2D8'
            },
            {
                dark: '#4C1002',
                normal: '#FBA28D',
                light: '#FBDFD8'
            },
            {
                dark: '#354802',
                normal: '#DBFA8D',
                light: '#F0FAD7'
            },
            {
                dark: '#1D0130',
                normal: '#CD91F5',
                light: '#E9D6F5'
            }
        ];
        this.screen_colors_count = this.screen_colors.length;
        this.global_evt_src = 1;

        this.url_get_schedule = $(this.element).data('urlGetSchedule');
        this.url_change_scheduled = $(this.element).data('urlChangeScheduled');
        this.url_delete_scheduled = $(this.element).data('urlDeleteScheduled');

        this._name = pluginName;
        this.init();
    }

    // Avoid Plugin.prototype conflicts
    $.extend(Plugin.prototype, {
        init: function () {
            var that = this;
            var today = moment();
            var tmpView = 'agendaWeek';
            var vars = window.location.hash.split("&");
            for (var url_i = 0; url_i < vars.length; url_i++) {
                if (vars[url_i].match("^#year")) {
                    today.year(vars[url_i].substring(6));
                }
                if (vars[url_i].match("^month")) {
                    today.month(vars[url_i].substring(6));
                }
                if (vars[url_i].match("^day")) {
                    today.day(vars[url_i].substring(4));
                }
                if (vars[url_i].match("^view")) {
                    tmpView = vars[url_i].substring(5);
                }
                if (vars[url_i].match("^selectedScreen")) {
                    this.setSelectedScreen(vars[url_i].substring(15));
                }
            }

            // @TODO visible screens

            $('.calendar', this.element).fullCalendar({
                header: {
                    left: 'prev,next today',
                    center: 'title',
                    right: 'month,agendaWeek,agendaDay'
                },
                height: 'auto',
                locale: 'de',
                defaultView: tmpView,
                defaultDate: today,
                viewRender: $.proxy(function (view) {
                    this.setUrl(view);
                }, this),
                navLinks: true,     // can click day/week names to navigate views
                editable: true,
                eventLimit: true,   // allow "more" link when too many events
                nowIndicator: true,
                allDaySlot: false,
                snapDuration: '00:05:00',
                events: [],
                selectable: true,
                selectHelper: true,
                select: $.proxy(this.placePresentationBySelection, this),
                eventResize: $.proxy(this.resizePresentation, this),
                eventRender: $.proxy(function(event, element) {
                    if (event.className[0] == 'fc-helper') {
                        var col = this.screen_colors[$(this.getSelectedScreen()).data('color-set')];
                        $(element).css('background-color', col.dark);
                        $(element).css('color', col.light);
                    }
                    if (!event.presentation) {
                        return;
                    }

                    element.append('
' + event.presentation.title + '
');
                }, this),
                eventDrop: $.proxy(this.movePresentation, this),
                eventClick: $.proxy(this.clickScheduledPresentation, this)
            });

            $('.sched-screen-list .item', this.element).on("click", $.proxy(function (e) {
                $('.sched-screen-list .item', this.element).removeClass('selected');
                $(e.currentTarget).addClass('selected');
                this.setUrl($('.calendar', this.element).fullCalendar('getView'));
            }, this));
            $('.sched-screen-list .item .selector', this.element).on("click", $.proxy(function (e) {
                var button = $(e.currentTarget).parent();
                button.toggleClass('visible');
                e.stopPropagation();

                if (button.hasClass('visible')) {
                    this.addEventSource(button.get(0), button.data('event-src'));
                    this.setScreenColor(button.get(0));
                } else {
                    $('.calendar', this.element).fullCalendar('removeEventSource', button.data('event-src'));
                    this.setScreenColor(button.get(0));
                }
            }, this));

            $('.sched-screen-list .item:first', this.element).trigger('click');

            var global_i = 0;
            $('.sched-screen-list .item', this.element).each($.proxy(function (k, o) {
                var i = global_i % this.screen_colors_count;
                $(o).data('color-set', i);
                this.setScreenColor(o);
                global_i++;
            }, this));

            $('.sched-screen-list .visible', this.element).each($.proxy(function(key, o) {
                $(o).data('event-src', this.global_evt_src);
                this.addEventSource(o, this.global_evt_src);
                this.global_evt_src++;
            }, this));

            $('#scheduler-diag-edit .action--delete').on('click', function (e) {
                var scheduledPresentation = $(e.currentTarget).closest('.modal')
                    .find('input[name="scheduledPresentationId"]').data('scheduled-presentation');
                that.hideEditDialog();
                that.deletePresentation(scheduledPresentation);
            });

            this.initEditDialog();
        },
        setUrl: function (view) {
            var moment = view.start;
            if (moment) {
                window.location.hash =
                    'year=' + moment.format('YYYY') +
                    '&month=' + moment.format('M') +
                    '&day=' + moment.format('DD') +
                    '&view=' + view.name +
                    '&selectedScreen=' + $(this.getSelectedScreen()).data('guid');
            }
        },
        addEventSource: function (screen, id) {
            var colset = this.screen_colors[$(screen).data('color-set')];
            var bg_col = colset.dark;
            var fg_col = colset.light;
            $('.calendar', this.element).fullCalendar('addEventSource',
                {
                    id: id,
                    url: this.url_get_schedule,
                    type: 'post',
                    data: {
                        screen:  $(screen).data('guid')
                    },
                    error: function () {
                        // @TODO use notify
                        alert('there was an error while fetching events!');
                    },
                    color: bg_col,
                    textColor: fg_col
                }
            );
        },
        saveChanged: function (event, revertFunc) {
            // @TODO Read messages from translations
            window.ajaxLoadShow();
            $.ajax({
                url: this.url_change_scheduled,
                method: 'post',
                data: {
                    'id':           event.id,
                    'screen':       event.screen,
                    'presentation': event.title,
                    'start':        event.start.format('YYYY-MM-DD HH:mm:ss'),
                    'end':          event.end.format('YYYY-MM-DD HH:mm:ss')
                }
            }).fail(function() {
                if (revertFunc != undefined) {
                    revertFunc();
                }
                $.notify("Leider ist beim Ändern des Eintrags ein Fehler aufgetreten.", "error");
            }).done(function() {
                $('.calendar', this.element).fullCalendar('refetchEventSources', event.source);
                $.notify("Änderung gespeichert.", "success");
            }).always(function () {
                window.ajaxLoadHide();
            });
        },
        getSelectedScreen: function () {
            return $('.sched-screen-list .selected', this.element).get(0);
        },
        getSelectedScreenGuid: function () {
            return $('.sched-screen-list .selected', this.element).data('guid');
        },
        setSelectedScreen: function (guid) {
            $('.sched-screen-list [data^="' + guid + '"]', this.element).removeClass('selected');
            $('.sched-screen-list [data="' + guid + '"]', this.element).addClass('selected');
            return this;
        },
        setScreenColor: function (screen) {
            var col = this.screen_colors[$(screen).data('color-set')];
            if ($(screen).hasClass('visible')) {
                $(screen).css('background-color', col.dark);
                $(screen).css('color', col.light);
                $(screen).children('.selector').css('background-color', col.normal);
            } else {
                $(screen).css('background-color', col.light);
                $(screen).css('color', col.normal);
            }
        },
        placePresentationBySelection: function (start, end) {
            this.showEditDialog(undefined, start, end);
        },
        resizePresentation: function(event, delta, revertFunc) {
            this.saveChanged(event, revertFunc);
        },
        movePresentation: function(event, delta, revertFunc) {
            this.saveChanged(event, revertFunc);
        },
        deletePresentation: function (event) {
            // @TODO Move this to dialog or anything other translatable
            var r = confirm("Soll die Präsentation  \"" + event.presentation.title + "\" (" +
                event.start.format('DD.MM.YYYY HH:mm') + ") wirklich entfernt werden?");

            if (r !== true) {
                return;
            }

            window.ajaxLoadShow();
            $.ajax({
                url: this.url_delete_scheduled,
                method: 'POST',
                data: {
                    'id':  event.id
                }
            }).done(function() {
                $('.calendar', this.element).fullCalendar('refetchEventSources', event.source);
                $.notify("Präsentation entfernt.", "success");
            }).always(function () {
                window.ajaxLoadHide();
            });
        },

        clickScheduledPresentation: function (e) {
            //this.initEditDialog();
            this.showEditDialog(e);
        },

        hideEditDialog: function () {
            $('#scheduler-diag-edit').modal('hide');
        },

        showEditDialog: function (scheduledPresentation, start, stop) {
            var $schedulerDialog = $('#scheduler-diag-edit');

            if (scheduledPresentation !== undefined) {
                // Edit an existing schedule item
                $schedulerDialog.find('.action--delete').css('visibility', 'visible');
                $schedulerDialog.data('messageSuccess', $schedulerDialog.data('messageEditSuccess'));
                $schedulerDialog.data('messageError', $schedulerDialog.data('messageEditError'));

                $schedulerDialog.find('input[name="scheduledPresentationId"]').val(scheduledPresentation.id);
                $schedulerDialog.find('input[name="scheduledPresentationId"]').data('scheduled-presentation', scheduledPresentation);
                $schedulerDialog.find('select[name="presentation"]').val(scheduledPresentation.presentation.id);
                $schedulerDialog.find('select[name="screen"]').val(scheduledPresentation.screen);

                start = scheduledPresentation.start;
                stop = scheduledPresentation.end;
            } else {
                // Create a new schedule item
                $schedulerDialog.find('.action--delete').css('visibility', 'hidden');
                $schedulerDialog.data('messageSuccess', $schedulerDialog.data('messageCreateSuccess'));
                $schedulerDialog.data('messageError', $schedulerDialog.data('messageCreateError'));

                $schedulerDialog.find('input[name="scheduledPresentationId"]').val('');
                $schedulerDialog.find('input[name="scheduledPresentationId"]').data('scheduled-presentation', undefined);
                $schedulerDialog.find('select[name="presentation"]').val('');

                // Preselect currently selected screen
                $schedulerDialog.find('select[name="screen"]').val(this.getSelectedScreenGuid());
            }

            $schedulerDialog.find('input[name="start"]').val(start.format('DD.MM.YYYY HH:mm'));
            $schedulerDialog.find('input[name="end"]').val(stop.format('DD.MM.YYYY HH:mm'));

            $schedulerDialog.modal('show');
        },

        initEditDialog: function () {
            var that = this;
            $('#scheduler-diag-edit').modal({
                closable: true,
                onApprove: function () {
                    var form = $('form', this);

                    window.ajaxLoadShow();
                    $.ajax({
                        type: 'post',
                        url: form.attr('action'),
                        data: form.serialize(),
                        success: function () {
                            $.notify($('#scheduler-diag-edit').data('messageSuccess'), 'success');
                        },
                        error: function () {
                            $.notify($('#scheduler-diag-edit').data('messageError'), 'error');
                        },
                        complete: function () {
                            window.ajaxLoadHide();
                            $('.calendar', that.element).fullCalendar('refetchEvents');
                        }
                    });
                },
                onHide: function () {
                    $('.calendar', that.element).fullCalendar('unselect');
                },
                onHidden: function () {
                    $('form', this).trigger('reset');
                }
            });
        }
    });

    // A really lightweight plugin wrapper around the constructor, preventing against multiple instantiations
    $.fn[pluginName] = function(options) {
        return this.each(function() {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new Plugin(this, options));
            }
        });
    };

    $('[data-scheduler="true"]').mztxScheduler();
})(jQuery, window, document);
<commit_msg>scheduler: fix JS glitches (wrong url parsing) + improve code<commit_after>;(function ($, window, document, undefined) {
    "use strict";

    // Create the defaults once
    var pluginName = "mztxScheduler";

    // The actual plugin constructor
    function Plugin(element /*, options */) {
        this.element = element;

        this.screen_colors = [
            {
                dark: '#010432',
                normal: '#9A9EF6',
                light: '#D9DAF6'
            },
            {
                dark: '#012E2D',
                normal: '#8AF4F0',
                light: '#D3F4F3'
            },
            {
                dark: '#4C3902',
                normal: '#FBDE8D',
                light: '#FBF2D8'
            },
            {
                dark: '#4C1002',
                normal: '#FBA28D',
                light: '#FBDFD8'
            },
            {
                dark: '#354802',
                normal: '#DBFA8D',
                light: '#F0FAD7'
            },
            {
                dark: '#1D0130',
                normal: '#CD91F5',
                light: '#E9D6F5'
            }
        ];
        this.screen_colors_count = this.screen_colors.length;
        this.global_evt_src = 1;

        this.url_get_schedule = $(this.element).data('urlGetSchedule');
        this.url_change_scheduled = $(this.element).data('urlChangeScheduled');
        this.url_delete_scheduled = $(this.element).data('urlDeleteScheduled');

        this._name = pluginName;
        this.init();
    }

    // Avoid Plugin.prototype conflicts
    $.extend(Plugin.prototype, {
        init: function () {
            var that = this;
            var today = moment();
            var tmpView = 'agendaWeek';
            var vars = {};
            window.location.hash.split("&").forEach(function(v) {
                var stripped = v.replace(new RegExp('^[#]+'), '');
                var splitted = stripped.split('=');
                vars[splitted[0]] = splitted[1];
            });

            // @TODO Represent visible screens in URL

            Object.keys(vars).forEach(function (key) {
                var value = vars[key];
                switch (key) {
                    case 'year':
                        today.year(parseInt(value, 10));
                        break;
                    case 'month':
                        today.month(parseInt(value, 10) - 1);
                        break;
                    case 'day':
                        today.date(parseInt(value, 10));
                        break;
                    case 'view':
                        tmpView = value;
                        break;
                    case 'selectedScreen':
                        that.setSelectedScreen(value);
                        break;
                }
            });

            $('.calendar', this.element).fullCalendar({
                header: {
                    left: 'prev,next today',
                    center: 'title',
                    right: 'month,agendaWeek,agendaDay'
                },
                height: 'auto',
                locale: 'de',
                defaultView: tmpView,
                defaultDate: today,
                viewRender: $.proxy(function (view) {
                    this.setUrl(view);
                }, this),
                navLinks: true,     // can click day/week names to navigate views
                editable: true,
                eventLimit: true,   // allow "more" link when too many events
                nowIndicator: true,
                allDaySlot: false,
                snapDuration: '00:05:00',
                events: [],
                selectable: true,
                selectHelper: true,
                select: $.proxy(this.placePresentationBySelection, this),
                eventResize: $.proxy(this.resizePresentation, this),
                eventRender: $.proxy(function(event, element) {
                    if (event.className[0] == 'fc-helper') {
                        var col = this.screen_colors[$(this.getSelectedScreen()).data('color-set')];
                        $(element).css('background-color', col.dark);
                        $(element).css('color', col.light);
                    }
                    if (!event.presentation) {
                        return;
                    }

                    element.append('
' + event.presentation.title + '
');
                }, this),
                eventDrop: $.proxy(this.movePresentation, this),
                eventClick: $.proxy(this.clickScheduledPresentation, this)
            });

            $('.sched-screen-list .item', this.element).on("click", $.proxy(function (e) {
                this.setSelectedScreen($(e.currentTarget).data('guid'));
                this.setUrl($('.calendar', this.element).fullCalendar('getView'));
            }, this));
            $('.sched-screen-list .item .selector', this.element).on("click", $.proxy(function (e) {
                var button = $(e.currentTarget).parent();
                button.toggleClass('visible');
                e.stopPropagation();

                if (button.hasClass('visible')) {
                    this.addEventSource(button.get(0), button.data('event-src'));
                    this.setScreenColor(button.get(0));
                } else {
                    $('.calendar', this.element).fullCalendar('removeEventSource', button.data('event-src'));
                    this.setScreenColor(button.get(0));
                }
            }, this));

            // If no screen is selected yet, preselect the first
            if ($('.sched-screen-list .selected', this.element).length < 1) {
                $('.sched-screen-list .item:first', this.element).trigger('click');
            }

            var global_i = 0;
            $('.sched-screen-list .item', this.element).each($.proxy(function (k, o) {
                var i = global_i % this.screen_colors_count;
                $(o).data('color-set', i);
                this.setScreenColor(o);
                global_i++;
            }, this));

            $('.sched-screen-list .visible', this.element).each($.proxy(function(key, o) {
                $(o).data('event-src', this.global_evt_src);
                this.addEventSource(o, this.global_evt_src);
                this.global_evt_src++;
            }, this));

            $('#scheduler-diag-edit .action--delete').on('click', function (e) {
                var scheduledPresentation = $(e.currentTarget).closest('.modal')
                    .find('input[name="scheduledPresentationId"]').data('scheduled-presentation');
                that.hideEditDialog();
                that.deletePresentation(scheduledPresentation);
            });

            this.initEditDialog();
        },
        setUrl: function (view) {
            var moment = view.start;
            if (moment) {
                window.location.hash =
                    'year=' + moment.format('YYYY') +
                    '&month=' + moment.format('M') +
                    '&day=' + moment.format('DD') +
                    '&view=' + view.name +
                    '&selectedScreen=' + $(this.getSelectedScreen()).data('guid');
            }
        },
        addEventSource: function (screen, id) {
            var colset = this.screen_colors[$(screen).data('color-set')];
            var bg_col = colset.dark;
            var fg_col = colset.light;
            $('.calendar', this.element).fullCalendar('addEventSource',
                {
                    id: id,
                    url: this.url_get_schedule,
                    type: 'post',
                    data: {
                        screen:  $(screen).data('guid')
                    },
                    error: function () {
                        // @TODO use notify
                        alert('there was an error while fetching events!');
                    },
                    color: bg_col,
                    textColor: fg_col
                }
            );
        },
        saveChanged: function (event, revertFunc) {
            // @TODO Read messages from translations
            window.ajaxLoadShow();
            $.ajax({
                url: this.url_change_scheduled,
                method: 'post',
                data: {
                    'id':           event.id,
                    'screen':       event.screen,
                    'presentation': event.title,
                    'start':        event.start.format('YYYY-MM-DD HH:mm:ss'),
                    'end':          event.end.format('YYYY-MM-DD HH:mm:ss')
                }
            }).fail(function() {
                if (revertFunc != undefined) {
                    revertFunc();
                }
                $.notify("Leider ist beim Ändern des Eintrags ein Fehler aufgetreten.", "error");
            }).done(function() {
                $('.calendar', this.element).fullCalendar('refetchEventSources', event.source);
                $.notify("Änderung gespeichert.", "success");
            }).always(function () {
                window.ajaxLoadHide();
            });
        },
        getSelectedScreen: function () {
            return $('.sched-screen-list .selected', this.element).get(0);
        },
        getSelectedScreenGuid: function () {
            return $('.sched-screen-list .selected', this.element).data('guid');
        },
        setSelectedScreen: function (guid) {
            $('.sched-screen-list .item[data-guid!="' + guid + '"]', this.element).removeClass('selected');
            $('.sched-screen-list .item[data-guid="' + guid + '"]', this.element).addClass('selected');
        },
        setScreenColor: function (screen) {
            var col = this.screen_colors[$(screen).data('color-set')];
            if ($(screen).hasClass('visible')) {
                $(screen).css('background-color', col.dark);
                $(screen).css('color', col.light);
                $(screen).children('.selector').css('background-color', col.normal);
            } else {
                $(screen).css('background-color', col.light);
                $(screen).css('color', col.normal);
            }
        },
        placePresentationBySelection: function (start, end) {
            this.showEditDialog(undefined, start, end);
        },
        resizePresentation: function(event, delta, revertFunc) {
            this.saveChanged(event, revertFunc);
        },
        movePresentation: function(event, delta, revertFunc) {
            this.saveChanged(event, revertFunc);
        },
        deletePresentation: function (event) {
            // @TODO Move this to dialog or anything other translatable
            var r = confirm("Soll die Präsentation  \"" + event.presentation.title + "\" (" +
                event.start.format('DD.MM.YYYY HH:mm') + ") wirklich entfernt werden?");

            if (r !== true) {
                return;
            }

            window.ajaxLoadShow();
            $.ajax({
                url: this.url_delete_scheduled,
                method: 'POST',
                data: {
                    'id':  event.id
                }
            }).done(function() {
                $('.calendar', this.element).fullCalendar('refetchEventSources', event.source);
                $.notify("Präsentation entfernt.", "success");
            }).always(function () {
                window.ajaxLoadHide();
            });
        },

        clickScheduledPresentation: function (e) {
            //this.initEditDialog();
            this.showEditDialog(e);
        },

        hideEditDialog: function () {
            $('#scheduler-diag-edit').modal('hide');
        },

        showEditDialog: function (scheduledPresentation, start, stop) {
            var $schedulerDialog = $('#scheduler-diag-edit');

            if (scheduledPresentation !== undefined) {
                // Edit an existing schedule item
                $schedulerDialog.find('.action--delete').css('visibility', 'visible');
                $schedulerDialog.data('messageSuccess', $schedulerDialog.data('messageEditSuccess'));
                $schedulerDialog.data('messageError', $schedulerDialog.data('messageEditError'));

                $schedulerDialog.find('input[name="scheduledPresentationId"]').val(scheduledPresentation.id);
                $schedulerDialog.find('input[name="scheduledPresentationId"]').data('scheduled-presentation', scheduledPresentation);
                $schedulerDialog.find('select[name="presentation"]').val(scheduledPresentation.presentation.id);
                $schedulerDialog.find('select[name="screen"]').val(scheduledPresentation.screen);

                start = scheduledPresentation.start;
                stop = scheduledPresentation.end;
            } else {
                // Create a new schedule item
                $schedulerDialog.find('.action--delete').css('visibility', 'hidden');
                $schedulerDialog.data('messageSuccess', $schedulerDialog.data('messageCreateSuccess'));
                $schedulerDialog.data('messageError', $schedulerDialog.data('messageCreateError'));

                $schedulerDialog.find('input[name="scheduledPresentationId"]').val('');
                $schedulerDialog.find('input[name="scheduledPresentationId"]').data('scheduled-presentation', undefined);
                $schedulerDialog.find('select[name="presentation"]').val('');

                // Preselect currently selected screen
                $schedulerDialog.find('select[name="screen"]').val(this.getSelectedScreenGuid());
            }

            $schedulerDialog.find('input[name="start"]').val(start.format('DD.MM.YYYY HH:mm'));
            $schedulerDialog.find('input[name="end"]').val(stop.format('DD.MM.YYYY HH:mm'));

            $schedulerDialog.modal('show');
        },

        initEditDialog: function () {
            var that = this;
            $('#scheduler-diag-edit').modal({
                closable: true,
                onApprove: function () {
                    var form = $('form', this);

                    window.ajaxLoadShow();
                    $.ajax({
                        type: 'post',
                        url: form.attr('action'),
                        data: form.serialize(),
                        success: function () {
                            $.notify($('#scheduler-diag-edit').data('messageSuccess'), 'success');
                        },
                        error: function () {
                            $.notify($('#scheduler-diag-edit').data('messageError'), 'error');
                        },
                        complete: function () {
                            window.ajaxLoadHide();
                            $('.calendar', that.element).fullCalendar('refetchEvents');
                        }
                    });
                },
                onHide: function () {
                    $('.calendar', that.element).fullCalendar('unselect');
                },
                onHidden: function () {
                    $('form', this).trigger('reset');
                }
            });
        }
    });

    // A really lightweight plugin wrapper around the constructor, preventing against multiple instantiations
    $.fn[pluginName] = function(options) {
        return this.each(function() {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new Plugin(this, options));
            }
        });
    };

    $('[data-scheduler="true"]').mztxScheduler();
})(jQuery, window, document);
<|endoftext|>


<commit_before>
var Opener = require('../Opener');
var RemoverPanel = require('./RemoverPanel');
var ui = require('georap-ui');
var emitter = require('component-emitter');

module.exports = function (params) {
  // Removal button and confirmation.
  //
  // Parameters:
  //   params, with required props
  //     submit
  //       function (), a submit handler.
  //         Called when user confirms deletion.
  //     cancelBtnText
  //       string
  //     deleteBtnText
  //       string
  //     infoText
  //       string
  //     youSureText
  //       string
  //
  // Emit
  //   submit
  //     on confirmed deletion attempt
  //
  var $container = null;
  var $button = null;
  var opener = null;
  var self = this;
  emitter(self);

  self.bind = function (mounts) {
    $container = mounts.$container;
    $button = mounts.$button;

    var panel = new RemoverPanel({
      cancelBtnText: params.cancelBtnText,
      deleteBtnText: params.deleteBtnText,
      infoText: params.infoText,
      youSureText: params.youSureText,
    });
    opener = new Opener(panel, false);
    opener.bind({
      $container: $container,
      $button: $button,
    });

    opener.on('submit', function (ev) {
      ev.preventDefault();
      if (typeof params.submit === 'function') {
        params.submit();
      }
    });
  };

  self.unbind = function () {
    if (opener) {
      opener.off('submit');
      opener.unbind();
      opener = null;
      $container = null;
      $button = null;
    }
  };
};
<commit_msg>remove submit parameter<commit_after>
var Opener = require('../Opener');
var RemoverPanel = require('./RemoverPanel');
var ui = require('georap-ui');
var emitter = require('component-emitter');

module.exports = function (params) {
  // Removal button and confirmation.
  //
  // Parameters:
  //   params, with required props
  //     cancelBtnText
  //       string
  //     deleteBtnText
  //       string
  //     infoText
  //       string
  //     youSureText
  //       string
  //
  // Emit
  //   submit
  //     on confirmed deletion attempt
  //
  var $container = null;
  var $button = null;
  var opener = null;
  var self = this;
  emitter(self);

  self.bind = function (mounts) {
    $container = mounts.$container;
    $button = mounts.$button;

    var panel = new RemoverPanel({
      cancelBtnText: params.cancelBtnText,
      deleteBtnText: params.deleteBtnText,
      infoText: params.infoText,
      youSureText: params.youSureText,
    });
    opener = new Opener(panel, false);
    opener.bind({
      $container: $container,
      $button: $button,
    });

    // Forward RemoverPanel submit
    opener.on('submit', function (ev) {
      if (ev) {
        ev.preventDefault(); // in case of submit button event is passed
      }
      self.emit('submit', ev);
    });
  };

  self.unbind = function () {
    if (opener) {
      opener.off('submit');
      opener.unbind();
      opener = null;
      $container = null;
      $button = null;
    }
  };
};
<|endoftext|>


<commit_before>module.exports = (function(Promise, instance, FOLDERNAMES, ds){

    var current;

    function update(){
        return new Promise(function(resolve, reject){
            Promise.all(ds.readJSONFile('data/rules.json'), ds.readJSONFile('data/remotes.json')).done(function(data){

                var rules = data[0],
                    remotes = data[1];

                var k, v;
                try{
                    current = instance(rules, { request: { remote: remotes } });
                    resolve();
                }catch(e){
                    for(k in remotes){
                        v = remotes[k];
                        if(v === '127.0.0.1'){
                            delete remotes[k];
                        }
                    }
                    ds.writeJSONFile('data/remotes.json', remotes).done(reject, reject);
                }
            }, reject);
        });
    }

    var queue = [];

    return {
        process: function(req, res){
            if(current){
                current(req, res);
            }else {
                if(queue.length === 0){
                    update().done(function(){
                        var args;
                        while(args = queue.shift()){
                            current(args[0], args[1]);
                        }
                    });
                }
                queue.push([req, res]);
            }
        },
        update: update
    };

}(require('promise'), require('flex-combo-plus'), require('./consts/foldernames'), require('./ds')));
<commit_msg>将存储转移到用户目录<commit_after>module.exports = (function(Promise, instance, FOLDERNAMES, ds){

    var current;

    function update(){
        return new Promise(function(resolve, reject){
            Promise.all([ds.readJSONFile('data/rules.json'), ds.readJSONFile('data/remotes.json')]).done(function(data){

                var rules = data[0], remotes = data[1];

                var k, v;
                try{
                    current = instance(rules, { request: { remote: remotes } });
                    resolve();
                }catch(e){
                    for(k in remotes){
                        v = remotes[k];
                        if(v === '127.0.0.1'){
                            delete remotes[k];
                        }
                    }
                    ds.writeJSONFile('data/remotes.json', remotes).done(reject, reject);
                }
            }, reject);
        });
    }

    var queue = [];

    return {
        process: function(req, res){
            if(current){
                current(req, res);
            }else {
                if(queue.length === 0){
                    update().done(function(){
                        var args;
                        while(args = queue.shift()){
                            current(args[0], args[1]);
                        }
                    });
                }
                queue.push([req, res]);
            }
        },
        update: update
    };

}(require('promise'), require('flex-combo-plus'), require('./consts/foldernames'), require('./ds')));
<|endoftext|>