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

import SPELLS from 'common/SPELLS';
import ITEMS from 'common/ITEMS';
import ISSUE_IMPORTANCE from 'Parser/Core/ISSUE_IMPORTANCE';

import CoreAbilities from 'Parser/Core/Modules/Abilities';

//import SpellLink from 'common/SpellLink';

class Abilities extends CoreAbilities {
  spellbook() {
    const combatant = this.combatants.selected;
    return [
      {
        spell: SPELLS.CONSECRATION_CAST,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 9 / (1 + haste),
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
      },
      {
        spell: SPELLS.HAMMER_OF_THE_RIGHTEOUS,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        charges: 2,
        cooldown: haste => 4.5 / (1 + haste),
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.HOLY_SHIELD_TALENT.id),
        castEfficiency: {
          suggestion: true,
        },
      },
      {
        spell: SPELLS.HAMMER_OF_THE_RIGHTEOUS,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        charges: 2,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.CONSECRATED_HAMMER_TALENT.id),
      },
      {
        spell: SPELLS.BLESSED_HAMMER_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 4.5 / (1 + haste),
        charges: 3,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.BLESSED_HAMMER_TALENT.id),
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
      },
      {
        spell: SPELLS.JUDGMENT_CAST,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 12 / (1+ haste),
        charges: combatant.hasTalent(SPELLS.CRUSADERS_JUDGMENT_TALENT.id) ? 2 : 1,
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
        },
      },
      {
        spell: SPELLS.AVENGERS_SHIELD,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 15 / (1 + haste),
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
      },
      // Probably useless to try to count the number of casts
      {
        spell: SPELLS.HAND_OF_THE_PROTECTOR_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: combatant.hasHead(ITEMS.SARUANS_RESOLVE.id) ? 9 : 10,
        charges: combatant.hasHead(ITEMS.SARUANS_RESOLVE.id) ? 2 : 1,
        enabled: combatant.hasTalent(SPELLS.HAND_OF_THE_PROTECTOR_TALENT.id),
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6,
          importance: ISSUE_IMPORTANCE.MINOR,
        },
      },
      {
        spell: SPELLS.LIGHT_OF_THE_PROTECTOR,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: combatant.hasHead(ITEMS.SARUANS_RESOLVE.id) ? 13.5 : 15,
        charges: combatant.hasHead(ITEMS.SARUANS_RESOLVE.id) ? 2 : 1,
        enabled: !combatant.hasTalent(SPELLS.HAND_OF_THE_PROTECTOR_TALENT.id),
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6,
          importance: ISSUE_IMPORTANCE.MINOR,
        },
      },
      // Probably useless to try to count the number of casts
      //Note by yajinni: Since this is thier main source of damage mitigation, without it they get hit like by a truck.
      //And a main source of damage, it should be tracked somewhat. Keeping it at 80% for now.
      {
        spell: SPELLS.SHIELD_OF_THE_RIGHTEOUS,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 16 / (1 + haste),
        charges: 3,
        castEfficiency: {
          suggestion: true,
        },
      },
      //COOLDOWNS
      {
        spell: SPELLS.EYE_OF_TYR,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: (haste, combatant) => (combatant.hasShoulder(ITEMS.PILLARS_OF_INMOST_LIGHT.id) ? 45 : 60),
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.85,
        },
      },
      {
        spell: SPELLS.ARDENT_DEFENDER,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: (_, combatant) => 120 - (combatant.traitsBySpellId[SPELLS.UNFLINCHING_DEFENSE.id] || 0) * 10,
        castEfficiency: {
          suggestion: true,
        },
      },
      {
        spell: SPELLS.GUARDIAN_OF_ANCIENT_KINGS,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 300,
        castEfficiency: {
          suggestion: true,
        }
      },
      {
        spell: SPELLS.AEGIS_OF_LIGHT_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.AEGIS_OF_LIGHT_TALENT.id),
        castEfficiency: {
          suggestion: true,
        }
      },
      {
        spell: SPELLS.SERAPHIM_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 45,
        enabled:combatant.hasTalent(SPELLS.SERAPHIM_TALENT.id),
        castEfficiency: {
          suggestion: true,
        }
      },
      {
        spell: SPELLS.DIVINE_SHIELD,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 300,
        isOnGCD: true,
      },
      {
        spell: SPELLS.BLESSING_OF_PROTECTION,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 300,
        isOnGCD: true,
        enabled: !combatant.hasTalent(SPELLS.BLESSING_OF_SPELLWARDING_TALENT.id),
      },
      {
        spell: SPELLS.BLESSING_OF_SPELLWARDING_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isOnGCD: true,
      },
      {
        spell: SPELLS.BLESSING_OF_SACRIFICE,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: (_, combatant) => 150 - (combatant.traitsBySpellId[SPELLS.SACRIFICE_OF_THE_JUST.id] || 0) * 60,
      },
      {
        spell: SPELLS.AVENGING_WRATH_RET,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 120,
      },
      //Utility
      {
        spell: SPELLS.BLINDING_LIGHT_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 90,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.BLINDING_LIGHT_TALENT.id),
      },
      {
        spell: SPELLS.REPENTANCE_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.REPENTANCE_TALENT.id),
      },
      {
        spell: SPELLS.DIVINE_STEED,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        charges: combatant.hasTalent(SPELLS.CAVALIER_TALENT.id) ? 2 : 1,
        cooldown: combatant.hasTalent(SPELLS.KNIGHT_TEMPLAR_TALENT.id) ? 22.5 : 45,
        isOnGCD: true,
      },
      {
        spell: SPELLS.HAMMER_OF_JUSTICE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 60,
      },
      {
        spell: SPELLS.LAY_ON_HANDS,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 600,
        castEfficiency: {
          recommendedEfficiency: 0.1,
        },
      },
      {
        spell: SPELLS.BLESSING_OF_FREEDOM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 25,
        isOnGCD: true,
      },
      {
        spell: SPELLS.HAND_OF_RECKONING,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 8,
      },
      {
        spell: SPELLS.FLASH_OF_LIGHT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        isOnGCD: true,
      },
    ];
  }
}

export default Abilities;
<commit_msg>fixed a lint error<commit_after>//import React from 'react';

import SPELLS from 'common/SPELLS';
import ITEMS from 'common/ITEMS';
import ISSUE_IMPORTANCE from 'Parser/Core/ISSUE_IMPORTANCE';

import CoreAbilities from 'Parser/Core/Modules/Abilities';

//import SpellLink from 'common/SpellLink';

class Abilities extends CoreAbilities {
  spellbook() {
    const combatant = this.combatants.selected;
    return [
      {
        spell: SPELLS.CONSECRATION_CAST,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 9 / (1 + haste),
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
      },
      {
        spell: SPELLS.HAMMER_OF_THE_RIGHTEOUS,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        charges: 2,
        cooldown: haste => 4.5 / (1 + haste),
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.HOLY_SHIELD_TALENT.id),
        castEfficiency: {
          suggestion: true,
        },
      },
      {
        spell: SPELLS.HAMMER_OF_THE_RIGHTEOUS,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        charges: 2,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.CONSECRATED_HAMMER_TALENT.id),
      },
      {
        spell: SPELLS.BLESSED_HAMMER_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 4.5 / (1 + haste),
        charges: 3,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.BLESSED_HAMMER_TALENT.id),
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
      },
      {
        spell: SPELLS.JUDGMENT_CAST,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 12 / (1+ haste),
        charges: combatant.hasTalent(SPELLS.CRUSADERS_JUDGMENT_TALENT.id) ? 2 : 1,
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
        },
      },
      {
        spell: SPELLS.AVENGERS_SHIELD,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 15 / (1 + haste),
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.9,
        },
      },
      // Probably useless to try to count the number of casts
      {
        spell: SPELLS.HAND_OF_THE_PROTECTOR_TALENT,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: combatant.hasHead(ITEMS.SARUANS_RESOLVE.id) ? 9 : 10,
        charges: combatant.hasHead(ITEMS.SARUANS_RESOLVE.id) ? 2 : 1,
        enabled: combatant.hasTalent(SPELLS.HAND_OF_THE_PROTECTOR_TALENT.id),
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6,
          importance: ISSUE_IMPORTANCE.MINOR,
        },
      },
      {
        spell: SPELLS.LIGHT_OF_THE_PROTECTOR,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: combatant.hasHead(ITEMS.SARUANS_RESOLVE.id) ? 13.5 : 15,
        charges: combatant.hasHead(ITEMS.SARUANS_RESOLVE.id) ? 2 : 1,
        enabled: !combatant.hasTalent(SPELLS.HAND_OF_THE_PROTECTOR_TALENT.id),
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6,
          importance: ISSUE_IMPORTANCE.MINOR,
        },
      },
      // Probably useless to try to count the number of casts
      //Note by yajinni: Since this is thier main source of damage mitigation, without it they get hit like by a truck.
      //And a main source of damage, it should be tracked somewhat. Keeping it at 80% for now.
      {
        spell: SPELLS.SHIELD_OF_THE_RIGHTEOUS,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: haste => 16 / (1 + haste),
        charges: 3,
        castEfficiency: {
          suggestion: true,
        },
      },
      //COOLDOWNS
      {
        spell: SPELLS.EYE_OF_TYR,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: (haste, combatant) => (combatant.hasShoulder(ITEMS.PILLARS_OF_INMOST_LIGHT.id) ? 45 : 60),
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.85,
        },
      },
      {
        spell: SPELLS.ARDENT_DEFENDER,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: (_, combatant) => 120 - (combatant.traitsBySpellId[SPELLS.UNFLINCHING_DEFENSE.id] || 0) * 10,
        castEfficiency: {
          suggestion: true,
        },
      },
      {
        spell: SPELLS.GUARDIAN_OF_ANCIENT_KINGS,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 300,
        castEfficiency: {
          suggestion: true,
        },
      },
      {
        spell: SPELLS.AEGIS_OF_LIGHT_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.AEGIS_OF_LIGHT_TALENT.id),
        castEfficiency: {
          suggestion: true,
        },
      },
      {
        spell: SPELLS.SERAPHIM_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 45,
        enabled:combatant.hasTalent(SPELLS.SERAPHIM_TALENT.id),
        castEfficiency: {
          suggestion: true,
        },
      },
      {
        spell: SPELLS.DIVINE_SHIELD,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 300,
        isOnGCD: true,
      },
      {
        spell: SPELLS.BLESSING_OF_PROTECTION,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 300,
        isOnGCD: true,
        enabled: !combatant.hasTalent(SPELLS.BLESSING_OF_SPELLWARDING_TALENT.id),
      },
      {
        spell: SPELLS.BLESSING_OF_SPELLWARDING_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isOnGCD: true,
      },
      {
        spell: SPELLS.BLESSING_OF_SACRIFICE,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: (_, combatant) => 150 - (combatant.traitsBySpellId[SPELLS.SACRIFICE_OF_THE_JUST.id] || 0) * 60,
      },
      {
        spell: SPELLS.AVENGING_WRATH_RET,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 120,
      },
      //Utility
      {
        spell: SPELLS.BLINDING_LIGHT_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 90,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.BLINDING_LIGHT_TALENT.id),
      },
      {
        spell: SPELLS.REPENTANCE_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 15,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.REPENTANCE_TALENT.id),
      },
      {
        spell: SPELLS.DIVINE_STEED,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        charges: combatant.hasTalent(SPELLS.CAVALIER_TALENT.id) ? 2 : 1,
        cooldown: combatant.hasTalent(SPELLS.KNIGHT_TEMPLAR_TALENT.id) ? 22.5 : 45,
        isOnGCD: true,
      },
      {
        spell: SPELLS.HAMMER_OF_JUSTICE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 60,
      },
      {
        spell: SPELLS.LAY_ON_HANDS,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 600,
        castEfficiency: {
          recommendedEfficiency: 0.1,
        },
      },
      {
        spell: SPELLS.BLESSING_OF_FREEDOM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 25,
        isOnGCD: true,
      },
      {
        spell: SPELLS.HAND_OF_RECKONING,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 8,
      },
      {
        spell: SPELLS.FLASH_OF_LIGHT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        isOnGCD: true,
      },
    ];
  }
}

export default Abilities;
<|endoftext|>


<commit_before>var Promise = require('bluebird');
var blessed = require('blessed');
var _ = require('lazy.js');
var Point = require('text-buffer/lib/point');

var util = require('slap-util');
var baseWidgetOpts = require('./opts');

function BaseWidget (opts) {
  var self = this;

  if (!(self instanceof blessed.Node)) return new BaseWidget(opts);

  opts = _(baseWidgetOpts).merge(opts || {}).toObject();
  if (self instanceof BaseWidget) blessed.Box.call(self, opts); // this should not be called if an element inherits from built-in blessed classes
  self.focusable = opts.focusable;

  util.logger.debug(util.typeOf(self), 'init {'+Object.keys(opts).join(',')+'}');
  self.ready = Promise.delay(0)
    .then(function () { return self._initHandlers(); })
    .return(self)
    .tap(function () { util.logger.debug(util.typeOf(self), 'ready'); });
}
BaseWidget.prototype.__proto__ = blessed.Box.prototype;

BaseWidget.prototype.walkDepthFirst = function (direction, after, fn) {
  if (arguments.length === 2) fn = after;
  var children = this.children.slice();
  if (direction === -1) children.reverse();
  if (after) children = children.slice(children.indexOf(after) + 1);
  return children.some(function (child) {
    return fn.apply(child, arguments) || BaseWidget.prototype.walkDepthFirst.call(child, direction, fn);
  });
};
BaseWidget.prototype.focusFirst = function (direction, after) {
  return this.walkDepthFirst(direction, after, function () {
    if (this.visible && this.focusable) {
      this.focus();
      return true;
    }
  });
};
BaseWidget.prototype._focusDirection = function (direction) {
  var self = this;
  var descendantParent;
  var descendant = self.screen.focused;
  while (descendant.hasAncestor(self)) {
    descendantParent = descendant.parent;
    if (BaseWidget.prototype.focusFirst.call(descendantParent, direction, descendant)) return self;
    descendant = descendantParent;
  }
  if (!self.focusFirst(direction)) throw new Error("no focusable descendant");
  return self;
};
BaseWidget.prototype.focusNext = function () {
  return this._focusDirection(1);
};
BaseWidget.prototype.focusPrev = function () {
  return this._focusDirection(-1);
};
BaseWidget.prototype.focus = function () {
  if (!this.hasFocus()) return blessed.Box.prototype.focus.apply(this, arguments);
  return this;
};
BaseWidget.prototype.isAttached = function () {
  return this.hasAncestor(this.screen);
};
BaseWidget.prototype.hasFocus = function (asChild) {
  var self = this;
  var focused = self.screen.focused;
  return focused.visible && (focused === self || focused.hasAncestor(self) || (asChild && self.hasAncestor(focused)));
};

BaseWidget.prototype.pos = function () {
  return new Point(this.atop + this.itop, this.aleft + this.ileft);
};
BaseWidget.prototype.size = function () {
  if (!this.isAttached()) return new Point(0, 0); // hack
  return new Point(this.height - this.iheight, this.width - this.iwidth);
};

BaseWidget.prototype.shrinkWidth = function () { return this.content.length + this.iwidth; };
BaseWidget.prototype.getBindings = function () {
  return this.options.bindings;
};
BaseWidget.prototype.resolveBinding = function (key, source1, source2, etc) {
  return BaseWidget.resolveBinding.apply(null,
    [key, BaseWidget.prototype.getBindings.call(this)].concat([].slice.call(arguments, 1))
  );
};
BaseWidget.resolveBinding = function (key, source1, source2, etc) {
  var bindings = [].slice.call(arguments, 1).reduce(function (bindings, sourceBindings) {
    return bindings.merge(sourceBindings);
  }, _()).toObject();
  for (var name in bindings) {
    if (bindings.hasOwnProperty(name)) {
      var keyBindings = bindings[name];
      if (!keyBindings) continue;
      if (typeof keyBindings === 'string') keyBindings = [keyBindings];
      if (keyBindings.some(function (binding) { return binding === key.full || binding === key.sequence; }))
        return name;
    }
  }
};

BaseWidget.prototype._initHandlers = function () {
  var self = this;
  self.on('focus', function () {
    util.logger.debug('focus', util.typeOf(self));
    if (!self.focusable) self.focusNext();
  });
  self.on('blur', function () { util.logger.debug('blur', util.typeOf(self)); });
  self.on('show', function () { self.setFront(); });
  self.on('element keypress', function (el, ch, key) {
    switch (BaseWidget.prototype.resolveBinding.call(self, key)) {
      case 'hide': self.hide(); return false;
      case 'focusNext': self.focusNext(); return false;
      case 'focusPrev': self.focusPrev(); return false;
    }
  });
};

module.exports = BaseWidget;
<commit_msg>fix(bindings): use callBase<commit_after>var Promise = require('bluebird');
var blessed = require('blessed');
var _ = require('lazy.js');
var Point = require('text-buffer/lib/point');

var util = require('slap-util');
var baseWidgetOpts = require('./opts');

function BaseWidget (opts) {
  var self = this;

  if (!(self instanceof blessed.Node)) return new BaseWidget(opts);

  opts = _(baseWidgetOpts).merge(opts || {}).toObject();
  if (self instanceof BaseWidget) blessed.Box.call(self, opts); // this should not be called if an element inherits from built-in blessed classes
  self.focusable = opts.focusable;

  util.logger.debug(util.typeOf(self), 'init {'+Object.keys(opts).join(',')+'}');
  self.ready = Promise.delay(0)
    .then(function () { return self._initHandlers(); })
    .return(self)
    .tap(function () { util.logger.debug(util.typeOf(self), 'ready'); });
}
BaseWidget.prototype.__proto__ = blessed.Box.prototype;

BaseWidget.prototype.walkDepthFirst = function (direction, after, fn) {
  if (arguments.length === 2) fn = after;
  var children = this.children.slice();
  if (direction === -1) children.reverse();
  if (after) children = children.slice(children.indexOf(after) + 1);
  return children.some(function (child) {
    return fn.apply(child, arguments) || BaseWidget.prototype.walkDepthFirst.call(child, direction, fn);
  });
};
BaseWidget.prototype.focusFirst = function (direction, after) {
  return this.walkDepthFirst(direction, after, function () {
    if (this.visible && this.focusable) {
      this.focus();
      return true;
    }
  });
};
BaseWidget.prototype._focusDirection = function (direction) {
  var self = this;
  var descendantParent;
  var descendant = self.screen.focused;
  while (descendant.hasAncestor(self)) {
    descendantParent = descendant.parent;
    if (BaseWidget.prototype.focusFirst.call(descendantParent, direction, descendant)) return self;
    descendant = descendantParent;
  }
  if (!self.focusFirst(direction)) throw new Error("no focusable descendant");
  return self;
};
BaseWidget.prototype.focusNext = function () {
  return this._focusDirection(1);
};
BaseWidget.prototype.focusPrev = function () {
  return this._focusDirection(-1);
};
BaseWidget.prototype.focus = function () {
  if (!this.hasFocus()) return blessed.Box.prototype.focus.apply(this, arguments);
  return this;
};
BaseWidget.prototype.isAttached = function () {
  return this.hasAncestor(this.screen);
};
BaseWidget.prototype.hasFocus = function (asChild) {
  var self = this;
  var focused = self.screen.focused;
  return focused.visible && (focused === self || focused.hasAncestor(self) || (asChild && self.hasAncestor(focused)));
};

BaseWidget.prototype.pos = function () {
  return new Point(this.atop + this.itop, this.aleft + this.ileft);
};
BaseWidget.prototype.size = function () {
  if (!this.isAttached()) return new Point(0, 0); // hack
  return new Point(this.height - this.iheight, this.width - this.iwidth);
};

BaseWidget.prototype.shrinkWidth = function () { return this.content.length + this.iwidth; };
BaseWidget.prototype.getBindings = function () {
  return this.options.bindings;
};
BaseWidget.prototype.resolveBinding = function (key, source1, source2, etc) {
  return BaseWidget.resolveBinding.apply(null,
    [key, util.callBase(this, BaseWidget, 'getBindings')].concat([].slice.call(arguments, 1))
  );
};
BaseWidget.resolveBinding = function (key, source1, source2, etc) {
  var bindings = [].slice.call(arguments, 1).reduce(function (bindings, sourceBindings) {
    return bindings.merge(sourceBindings);
  }, _()).toObject();
  for (var name in bindings) {
    if (bindings.hasOwnProperty(name)) {
      var keyBindings = bindings[name];
      if (!keyBindings) continue;
      if (typeof keyBindings === 'string') keyBindings = [keyBindings];
      if (keyBindings.some(function (binding) { return binding === key.full || binding === key.sequence; }))
        return name;
    }
  }
};

BaseWidget.prototype._initHandlers = function () {
  var self = this;
  self.on('focus', function () {
    util.logger.debug('focus', util.typeOf(self));
    if (!self.focusable) self.focusNext();
  });
  self.on('blur', function () { util.logger.debug('blur', util.typeOf(self)); });
  self.on('show', function () { self.setFront(); });
  self.on('element keypress', function (el, ch, key) {
    switch (util.callBase(this, BaseWidget, 'resolveBinding', key)) {
      case 'hide': self.hide(); return false;
      case 'focusNext': self.focusNext(); return false;
      case 'focusPrev': self.focusPrev(); return false;
    }
  });
};

module.exports = BaseWidget;
<|endoftext|>


<commit_before>import React                  from "react"
import { connect }            from "react-redux"
import { bindActionCreators } from 'redux'
import { Link }               from "react-router"
import { Field, reduxForm }   from "redux-form"
import _                      from "lodash"
import Select, { Creatable }  from "react-select"
import Notifications          from 'react-notification-system-redux'
import { notificationOpts }   from '../../utils/NotificationUtils'

import CatalogSearchBar from "./CatalogSearchBar"
import CatalogItem      from "./CatalogItem"
import CatalogItemBoard from "./CatalogItemBoard"

import "./Catalog.css"

export default class Catalog extends React.Component {

  constructor(props) {
    super(props);

    this.handleDissmis = this.handleDissmis.bind(this)
    this.handleAddItemToBoard = this.handleAddItemToBoard.bind(this)
    this.handleCreateService = this.handleCreateService.bind(this)
    this.selectChange = this.selectChange.bind(this)
    this.updateCustomDomain = this.updateCustomDomain.bind(this)
  }

  componentDidMount() {
    this.props.fetchStacks()
    this.props.fetchCatalog()
  }

  handleDissmis(name) {
    const { removeItemFromBoard, dispatchNotification } = this.props
    removeItemFromBoard(name)
    dispatchNotification(
      Notifications.success,
      notificationOpts(
        'Success',
        `Successfully remove ${ name } from board!`
        )
      )
  }

  handleAddItemToBoard(name, category) {
    const { addItemToBoard, info, dispatchNotification } = this.props

    addItemToBoard(name, category)
    dispatchNotification(
      Notifications.success,
      notificationOpts(
        'Success',
        `Successfully added ${ name } to board!`,
        () => document.getElementById('#board').scrollIntoView()
      )
    )
  }

  handleCreateService(domain, newService) {
    this.props.deployService(
      domain,
      newService,
      Notifications.success,
      notificationOpts('Success', `Successfully deploy service!`)
      )
  }

  renderBoardItem(board) {
    const { info } = this.props
    return board.map((b, index) => {
      return (
        <CatalogItemBoard
          index={ index }
          key={ index }
          serviceName={ Object.keys(b.service)[0] }
          img={ b.manifest.logo }
          title={ b.manifest.title }
          name={ Object.keys(b.service)[0] }
          image={ "b.service" }
          dismiss={this.handleDissmis} />
      )
    })
  }

  renderOperatingSystem(data) {
    const arrData = _.map(data)

    return arrData.map((d) => {
      return (
        <CatalogItem
          key={ d.name }
          onAdd={this.handleAddItemToBoard}
          img={ d.logo }
          name={ d.name }
          title={ d.title }
          desc={ d.description }
          category="os" />
      )
    })
  }

  renderApplication(data) {
    const arrData = _.map(data)

    return arrData.map((d) => {
      return (
        <CatalogItem
          key={ d.name }
          onAdd={this.handleAddItemToBoard}
          img={ d.logo }
          name={ d.name }
          title={ d.title }
          desc={ d.description }
          category="applications" />
      )
    })
  }

  renderDatabase(data) {
    const arrData = _.map(data)

    return arrData.map((d) => {
      return (
        <CatalogItem
          key={ d.name }
          onAdd={this.handleAddItemToBoard}
          img={ d.logo }
          name={ d.name }
          title={ d.title }
          desc={ d.description }
          category="databases" />
      )
    })
  }

  renderFramework(data) {
    const arrData = _.map(data)

    return arrData.map((d) => {
      return (
        <CatalogItem
          key={ d.name }
          onAdd={this.handleAddItemToBoard}
          img={ d.logo }
          name={ d.name }
          title={ d.title }
          desc={ d.description }
          category="frameworks" />
      )
    })
  }

  renderLanguage(data) {
    const arrData = _.map(data)

    return arrData.map((d) => {
      return (
        <CatalogItem
          key={ d.name }
          onAdd={this.handleAddItemToBoard}
          img={ d.logo }
          name={ d.name }
          title={ d.title }
          desc={ d.description }
          category="languages" />
      )
    })
  }

  selectChange(val) {
    console.log("Selected: ", val)
    this.props.setStackName(val.value)
  }

  updateCustomDomain(val) {
    console.log(val.target.value)
    this.props.updateCustomDomain(val.target.value)
  }

	render() {
    const {
      fetching, fetched, error, catalog, board, info, user,
      newService, newService_loading, newService_done,
    } = this.props

    window.board = board
    window.newService = newService
    window.stacks = this.props.stacks
    window.SelectOptions = this.props.options

		return (

CATALOG

<Link to="/">HOME</Link>

CATALOG

        {/*

					<CatalogSearchBar />

*/}

                  PROJECT WIZARD

                        PROJECT NAME

                        <Creatable
                          name="groupName"
                          value={this.props.newService.opt ? this.props.newService.opt.stack : ''}
                          options={this.props.options}
                          onChange={this.selectChange}
                          clearable={true}
                          allowCreate={true}
                          disabled={ newService_loading }
                          promptTextCreator ={(label) => `Create New Project "${label}"`}
                          searchable={true}
                          noResultsText="No project found"
                          placeholder="Project Name..." />

                        CUSTOM DOMAIN

                        <input
                          name="domain"
                          className="form-control"
                          placeholder="Type your custom domain here ..."
                          readOnly={ newService_loading }
                          onChange={this.updateCustomDomain}
                          value={ newService &&
                                  newService.opt &&
                                  newService.opt.domain ?
                                  newService.opt.domain : '' } />

                        DOMAIN

                        { newService && newService.opt && newService.opt.domain ? newService.opt.domain :(fetched && user && newService && newService.opt && newService.opt.services ? `${user.data.id}-${newService.opt.stack}-${Object.keys(newService.opt.services)[0]}-${info.endpoint}`: 'select service and project first') }

                    {/* TODO: tambah loading status dan redirect ke halaman stack */}
                      { this.props.newService &&
                        this.props.newService.opt &&
                        this.props.newService.opt.stack &&
                        board &&
                        board.length > 0 ?
                        ( newService_loading ?
                          <button
                              disabled={ newService_loading }
                              style={{ border: "1px solid #209687" }}
                              className="btn btn-success create-button" >

                              Processing
                          </button> :
                          <button
                            style={{ border: "1px solid #209687" }}
                            className="btn btn-success create-button"
                            onClick={ () => this.handleCreateService(newService && newService.opt && newService.opt.domain ? newService.opt.domain :(fetched && user && newService && newService.opt && newService.opt.services ? `${user.data.id}-${newService.opt.stack}-${Object.keys(newService.opt.services)[0]}-${info.endpoint}`: undefined), newService) } >

                            Deploy Service
                        </button>)
                         :
                        <button
                            className="btn btn-default"
                            type="button"
                            disabled={true} >

                            Deploy Service
                        </button>
                      }

                    {
                      board.length > 0 ?
                      this.renderBoardItem(board) :

                        choose services from catalog below

                    }

          {/*
Operating System

          { fetching &&

          }

            { fetched &&
              this.renderOperatingSystem(catalog.categories.os)
            }

*/}

Applications

          { fetching &&

          }

            { fetched &&
              this.renderApplication(catalog.categories.applications)
            }

Database

          { fetching &&

          }

            { fetched &&
              this.renderDatabase(catalog.categories.databases)
            }

          {/*

Framework

          { fetching &&

          }

            { fetched &&
              this.renderFramework(catalog.categories.frameworks)
            }

Language Stack

          { fetching &&

          }

            { fetched &&
              this.renderLanguage(catalog.categories.languages)
            }

*/}

          {/*

                <button className="btn btn-primary btn-block create-button">BUILD NEW IMAGE</button>

          */}

		)
	}
}
<commit_msg>UPDATE: add enpoint checking before showing the domain<commit_after>import React                  from "react"
import { connect }            from "react-redux"
import { bindActionCreators } from 'redux'
import { Link }               from "react-router"
import { Field, reduxForm }   from "redux-form"
import _                      from "lodash"
import Select, { Creatable }  from "react-select"
import Notifications          from 'react-notification-system-redux'
import { notificationOpts }   from '../../utils/NotificationUtils'

import CatalogSearchBar from "./CatalogSearchBar"
import CatalogItem      from "./CatalogItem"
import CatalogItemBoard from "./CatalogItemBoard"

import "./Catalog.css"

export default class Catalog extends React.Component {

  constructor(props) {
    super(props);

    this.handleDissmis = this.handleDissmis.bind(this)
    this.handleAddItemToBoard = this.handleAddItemToBoard.bind(this)
    this.handleCreateService = this.handleCreateService.bind(this)
    this.selectChange = this.selectChange.bind(this)
    this.updateCustomDomain = this.updateCustomDomain.bind(this)
  }

  componentDidMount() {
    this.props.fetchStacks()
    this.props.fetchCatalog()
  }

  handleDissmis(name) {
    const { removeItemFromBoard, dispatchNotification } = this.props
    removeItemFromBoard(name)
    dispatchNotification(
      Notifications.success,
      notificationOpts(
        'Success',
        `Successfully remove ${ name } from board!`
        )
      )
  }

  handleAddItemToBoard(name, category) {
    const { addItemToBoard, info, dispatchNotification } = this.props

    addItemToBoard(name, category)
    dispatchNotification(
      Notifications.success,
      notificationOpts(
        'Success',
        `Successfully added ${ name } to board!`,
        () => document.getElementById('#board').scrollIntoView()
      )
    )
  }

  handleCreateService(domain, newService) {
    this.props.deployService(
      domain,
      newService,
      Notifications.success,
      notificationOpts('Success', `Successfully deploy service!`)
      )
  }

  renderBoardItem(board) {
    const { info } = this.props
    return board.map((b, index) => {
      return (
        <CatalogItemBoard
          index={ index }
          key={ index }
          serviceName={ Object.keys(b.service)[0] }
          img={ b.manifest.logo }
          title={ b.manifest.title }
          name={ Object.keys(b.service)[0] }
          image={ "b.service" }
          dismiss={this.handleDissmis} />
      )
    })
  }

  renderOperatingSystem(data) {
    const arrData = _.map(data)

    return arrData.map((d) => {
      return (
        <CatalogItem
          key={ d.name }
          onAdd={this.handleAddItemToBoard}
          img={ d.logo }
          name={ d.name }
          title={ d.title }
          desc={ d.description }
          category="os" />
      )
    })
  }

  renderApplication(data) {
    const arrData = _.map(data)

    return arrData.map((d) => {
      return (
        <CatalogItem
          key={ d.name }
          onAdd={this.handleAddItemToBoard}
          img={ d.logo }
          name={ d.name }
          title={ d.title }
          desc={ d.description }
          category="applications" />
      )
    })
  }

  renderDatabase(data) {
    const arrData = _.map(data)

    return arrData.map((d) => {
      return (
        <CatalogItem
          key={ d.name }
          onAdd={this.handleAddItemToBoard}
          img={ d.logo }
          name={ d.name }
          title={ d.title }
          desc={ d.description }
          category="databases" />
      )
    })
  }

  renderFramework(data) {
    const arrData = _.map(data)

    return arrData.map((d) => {
      return (
        <CatalogItem
          key={ d.name }
          onAdd={this.handleAddItemToBoard}
          img={ d.logo }
          name={ d.name }
          title={ d.title }
          desc={ d.description }
          category="frameworks" />
      )
    })
  }

  renderLanguage(data) {
    const arrData = _.map(data)

    return arrData.map((d) => {
      return (
        <CatalogItem
          key={ d.name }
          onAdd={this.handleAddItemToBoard}
          img={ d.logo }
          name={ d.name }
          title={ d.title }
          desc={ d.description }
          category="languages" />
      )
    })
  }

  selectChange(val) {
    console.log("Selected: ", val)
    this.props.setStackName(val.value)
  }

  updateCustomDomain(val) {
    console.log(val.target.value)
    this.props.updateCustomDomain(val.target.value)
  }

	render() {
    const {
      fetching, fetched, error, catalog, board, info, user,
      newService, newService_loading, newService_done,
    } = this.props

    window.board = board
    window.newService = newService
    window.stacks = this.props.stacks
    window.SelectOptions = this.props.options

		return (

CATALOG

<Link to="/">HOME</Link>

CATALOG

        {/*

					<CatalogSearchBar />

*/}

                  PROJECT WIZARD

                        PROJECT NAME

                        <Creatable
                          name="groupName"
                          value={this.props.newService.opt ? this.props.newService.opt.stack : ''}
                          options={this.props.options}
                          onChange={this.selectChange}
                          clearable={true}
                          allowCreate={true}
                          disabled={ newService_loading }
                          promptTextCreator ={(label) => `Create New Project "${label}"`}
                          searchable={true}
                          noResultsText="No project found"
                          placeholder="Project Name..." />

                        CUSTOM DOMAIN

                        <input
                          name="domain"
                          className="form-control"
                          placeholder="Type your custom domain here ..."
                          readOnly={ newService_loading }
                          onChange={this.updateCustomDomain}
                          value={ newService &&
                                  newService.opt &&
                                  newService.opt.domain ?
                                  newService.opt.domain : '' } />

                        DOMAIN

                        { board &&
                          board.length > 0 &&
                          newService &&
                          newService.opt &&
                          newService.opt.services ?
                          ( newService &&
                            newService.opt &&
                            newService.opt.domain ?
                            newService.opt.domain :
                            ( fetched &&
                              user && newService &&
                              newService.opt &&
                              newService.opt.services &&
                              newService.opt.services[Object.keys(newService.opt.services)[0]] &&
                              newService.opt.services[Object.keys(newService.opt.services)[0]].endpoint ?
                              `${user.data.id}-${newService.opt.stack}-${Object.keys(newService.opt.services)[0]}-${info.endpoint}` :
                              'no domain provided'
                            )
                          ) : 'select service and project first' }

                    {/* TODO: tambah loading status dan redirect ke halaman stack */}
                      { this.props.newService &&
                        this.props.newService.opt &&
                        this.props.newService.opt.stack &&
                        board &&
                        board.length > 0 ?
                        ( newService_loading ?
                          <button
                              disabled={ newService_loading }
                              style={{ border: "1px solid #209687" }}
                              className="btn btn-success create-button" >

                              Processing
                          </button> :
                          <button
                            style={{ border: "1px solid #209687" }}
                            className="btn btn-success create-button"
                            onClick={ () => this.handleCreateService(newService && newService.opt && newService.opt.domain ? newService.opt.domain :(fetched && user && newService && newService.opt && newService.opt.services ? `${user.data.id}-${newService.opt.stack}-${Object.keys(newService.opt.services)[0]}-${info.endpoint}`: undefined), newService) } >

                            Deploy Service
                        </button>)
                         :
                        <button
                            className="btn btn-default"
                            type="button"
                            disabled={true} >

                            Deploy Service
                        </button>
                      }

                    {
                      board.length > 0 ?
                      this.renderBoardItem(board) :

                        choose services from catalog below

                    }

          {/*
Operating System

          { fetching &&

          }

            { fetched &&
              this.renderOperatingSystem(catalog.categories.os)
            }

*/}

Applications

          { fetching &&

          }

            { fetched &&
              this.renderApplication(catalog.categories.applications)
            }

Database

          { fetching &&

          }

            { fetched &&
              this.renderDatabase(catalog.categories.databases)
            }

          {/*

Framework

          { fetching &&

          }

            { fetched &&
              this.renderFramework(catalog.categories.frameworks)
            }

Language Stack

          { fetching &&

          }

            { fetched &&
              this.renderLanguage(catalog.categories.languages)
            }

*/}

          {/*

                <button className="btn btn-primary btn-block create-button">BUILD NEW IMAGE</button>

          */}

		)
	}
}
<|endoftext|>


<commit_before>/*global Search*/
'use strict'

/**
 * @typedef {Object} Suggest~Result
 * @property {Array<Suggest~Text}>} texts
 * @property {string} type - either 'property' or 'value'
 * @property {Parse~Object|Parse~Base} context - Object for 'property' and Base for 'value'
 */

/**
 * @typedef {Object} Suggest~Text
 * @property {string} plain
 * @property {Array<string>} highlight - plain split into substrings. Odd indexes are highlights
 */

let Suggest = {}

/**
 * Make suggestions based on the cursor position and schema definition
 * @param {Parse~Object} parsed
 * @param {Sample~Schema} schema
 * @returns {?Suggest~Result}
 */
Suggest.getSuggestions = function (parsed, schema) {
	if (parsed.type !== 'object') {
		return
	}
	return Suggest._processCursorInFind(parsed, schema, '')
}

/*
 * The mongodb query structure is depicted bellow:
 *
 * {name: {$ne: 'John'}}
 * ^      ^     ^
 * |      |     +- value
 * |      +- field expression
 * +- find
 *
 * Each context have different suggestion mechanisms.
 * For example, in the find context fields are suggested,
 * while in the field expression context operators are suggested
 */

/**
 * Process cursor in a find construction
 * @param {Parse~Object} parsed
 * @param {Sample~Schema} schema
 * @param {string} prefix
 * @returns {?Suggest~Result}
 * @private
 */
Suggest._processCursorInFind = function (parsed, schema, prefix) {
	let keys = parsed.properties.map(prop => prop.key.name)

	if (parsed.cursor === -1) {
		// Nothing is focused
		return
	} else if (parsed.cursor === parsed.properties.length) {
		// Empty property focused
		return Suggest._suggestFields('', schema, prefix, keys, parsed)
	}

	let property = parsed.properties[parsed.cursor],
		key = property.key.name,
		subPrefix = prefix ? prefix + '.' + key : key
	if (property.key.cursor !== -1) {
		// Key has focus
		let search = key.slice(0, property.key.cursor)
		keys.splice(parsed.cursor, 1)
		return Suggest._suggestFields(search, schema, prefix, keys, parsed)
	} else if (key === '$or' || key === '$and' || key === '$nor') {
		// Multiple sub-finds
		if (property.value.type !== 'array') {
			return
		}

		let element = property.value.values[property.value.cursor]
		if (!element || element.type !== 'object') {
			return
		}

		return Suggest._processCursorInFind(element, schema, prefix)
	} else if (property.value.type === 'array' || property.value.type === 'source') {
		// Focus in field value
		return Suggest._suggestValues(property.value.raw, schema[subPrefix], property.value)
	} else if (property.value.type === 'object') {
		return Suggest._processCursorInFieldExp(property.value, schema, subPrefix)
	} else {
		// Unknown
		return
	}
}

/**
 * @param {Parse~Object} parsed
 * @param {Sample~Schema} schema
 * @param {string} prefix
 * @returns {?Suggest~Result}
 * @private
 */
Suggest._processCursorInFieldExp = function (parsed, schema, prefix) {
	let keys = parsed.properties.map(prop => prop.key.name)

	if (keys.some(key => key[0] !== '$')) {
		// Not actually a field expression, like in '{a: {b: 2}}',
		// '{b: 2}' is not a field expression
		return Suggest._suggestValues(parsed.raw, schema[prefix], parsed)
	}

	if (parsed.cursor === -1) {
		// Nothing is focused
		return
	} else if (parsed.cursor === parsed.properties.length) {
		// Empty property focused
		return Suggest._suggestOperators('', schema, prefix, keys, parsed)
	}

	let property = parsed.properties[parsed.cursor],
		key = property.key.name
	if (property.key.cursor !== -1) {
		// Key has focus
		let search = key.slice(0, property.key.cursor)
		keys.splice(parsed.cursor, 1)
		return Suggest._suggestOperators(search, schema, prefix, keys, parsed)
	} else if (key === '$eq' || key === '$ne' ||
		key === '$gt' || key === '$gte' ||
		key === '$lt' || key === '$lte') {
		// COMP VALUE
		return Suggest._suggestValues(property.value.raw, schema[prefix], property.value)
	} else if (key === '$in' || key === '$nin' || key === '$all') {
		// COMP [VALUE]
		if (property.value.type !== 'array') {
			return
		}

		let element = property.value.values[property.value.cursor]
		return Suggest._suggestValues(element ? element.raw : '', schema[prefix], property.value)
	} else if (key === '$not') {
		// $not FIELD-EXP
		if (property.value.type !== 'object') {
			return
		}

		return Suggest._processCursorInFieldExp(property.value, schema, prefix)
	} else if (key === '$exists') {
		// $exists BOOL
		return Suggest._suggestValues(property.value.raw, {
			bool: true
		}, property.value)
	} else if (key === '$type') {
		// $type STRING
		return Suggest._suggestValues(property.value.raw, {
			string: [
				'double',
				'string',
				'object',
				'array',
				'binData',
				'objectId',
				'bool',
				'date',
				'null',
				'regex',
				'javascript',
				'javascriptWithScope',
				'int',
				'timestamp',
				'long',
				'minKey',
				'maxKey',
				'number'
			]
		}, property.value)
	} else if (key === '$mod') {
		// $mod [NUM, NUM]
		return buildSimpleSuggestion('[divisor, remainder]', property.value)
	} else if (key === '$elemMatch') {
		// $elemMatch FIND
		if (property.value.type !== 'object') {
			return
		}

		return Suggest._processCursorInFind(property.value, schema, prefix)
	} else if (key === '$size') {
		// $size NUM
		return buildSimpleSuggestion('(number)', property.value)
	} else {
		// Unknown
		return
	}

	function buildSimpleSuggestion(text, context) {
		return {
			texts: [{
				plain: text,
				highlight: [text]
			}],
			type: 'value',
			context
		}
	}
}

/**
 * @param {string} search
 * @param {Sample~Schema} schema
 * @param {string} prefix
 * @param {Array<string>} blacklist - keys already used at this level
 * @param {Parse~Object} context
 * @returns {?Suggest~Result}
 * @private
 */
Suggest._suggestFields = function (search, schema, prefix, blacklist, context) {
	// search='a', prefix='' -> pathPrefix='', field='a'
	// search='a.b', prefix='' -> pathPrefix='a.', field='b'
	// search='a', prefix='x' -> pathPrefix='x.', field='a'
	// search='ab.cd.e', prefix='x' -> pathPrefix='x.ab.cd.', field='e'
	let lastDot = search.lastIndexOf('.'),
		keyPrefix = lastDot === -1 ? '' : search.slice(0, lastDot + 1),
		pathPrefix = (prefix ? prefix + '.' : '') + keyPrefix,
		field = lastDot === -1 ? search : search.slice(lastDot + 1),
		searchSpace = []

	if (lastDot === -1) {
		for (let operator of ['$or', '$and', '$nor']) {
			if (blacklist.indexOf(operator) === -1) {
				searchSpace.push(operator)
			}
		}
	}

	// Collect fields in the same and following levels
	for (let path in schema) {
		if (path.startsWith(pathPrefix)) {
			let text = path.slice(pathPrefix.length),
				key = path.slice((prefix ? prefix + '.' : '').length)

			if (blacklist.indexOf(key) !== -1) {
				continue
			}

			searchSpace.push(text)
		}
	}

	return {
		texts: Search.search(searchSpace, field),
		type: 'property',
		context: context
	}
}

/**
 * @param {string} search
 * @param {Sample~Schema} schema
 * @param {string} prefix
 * @param {Array<string>} blacklist
 * @param {Parse~Object} context
 * @returns {?Suggest~Result}
 * @private
 */
Suggest._suggestOperators = function (search, schema, prefix, blacklist, context) {
	let operators = [
			// Basic operators
			'$eq', '$ne', '$gt', '$gte', '$lt', '$lte',
			'$in', '$nin', '$not', '$type'
		],
		fieldSchema = schema[prefix]

	// Additional type-dependent operators
	if (fieldSchema) {
		if (fieldSchema.null) {
			operators.push('$exists')
		}
		if (fieldSchema.double) {
			operators.push('$mod')
		}
		if (fieldSchema.array && fieldSchema.object) {
			operators.push('$elemMatch')
		}
		if (fieldSchema.array) {
			operators.push('$size', '$all')
		}
	}

	return {
		texts: Search.search(operators, search),
		type: 'property',
		context: context
	}
}

/**
 * @param {string} search
 * @param {?Sample~FieldSchema} fieldSchema
 * @param {Parse~Base} context
 * @returns {?Suggest~Result}
 * @private
 */
Suggest._suggestValues = function (search, fieldSchema, context) {
	if (!fieldSchema) {
		// Nothing useful to provide
		return
	}

	search = search.trim()
	let values = [],
		quote = search[0] === '"' ? '"' : '\''
	if (Array.isArray(fieldSchema.double)) {
		values = values.concat(fieldSchema.double.map(e => String(e)))
	}
	if (Array.isArray(fieldSchema.string)) {
		values = values.concat(fieldSchema.string.map(e => quote + Suggest._escape(e) + quote))
	}
	if (fieldSchema.bool) {
		values.push('true')
		values.push('false')
	}

	let texts = Search.search(values, search)

	let types = [
		'double',
		'string',
		'object',
		'array',
		'binData',
		'objectId',
		'date',
		'null',
		'regex',
		'timestamp',
		'long',
		'minKey',
		'maxKey'
	]

	types.forEach(type => {
		if (fieldSchema[type]) {
			let text = '(' + type + ')'
			texts.push({
				plain: text,
				highlight: [text]
			})
		}
	})

	return {
		texts,
		type: 'value',
		context: context
	}
}

/**
 * Escape a string to put between quotes
 * @param {string} str
 * @returns {string}
 * @private
 */
Suggest._escape = function (str) {
	return str.replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/"/g, '\\"')
}<commit_msg>Respect blacklist of operators<commit_after>/*global Search*/
'use strict'

/**
 * @typedef {Object} Suggest~Result
 * @property {Array<Suggest~Text}>} texts
 * @property {string} type - either 'property' or 'value'
 * @property {Parse~Object|Parse~Base} context - Object for 'property' and Base for 'value'
 */

/**
 * @typedef {Object} Suggest~Text
 * @property {string} plain
 * @property {Array<string>} highlight - plain split into substrings. Odd indexes are highlights
 */

let Suggest = {}

/**
 * Make suggestions based on the cursor position and schema definition
 * @param {Parse~Object} parsed
 * @param {Sample~Schema} schema
 * @returns {?Suggest~Result}
 */
Suggest.getSuggestions = function (parsed, schema) {
	if (parsed.type !== 'object') {
		return
	}
	return Suggest._processCursorInFind(parsed, schema, '')
}

/*
 * The mongodb query structure is depicted bellow:
 *
 * {name: {$ne: 'John'}}
 * ^      ^     ^
 * |      |     +- value
 * |      +- field expression
 * +- find
 *
 * Each context have different suggestion mechanisms.
 * For example, in the find context fields are suggested,
 * while in the field expression context operators are suggested
 */

/**
 * Process cursor in a find construction
 * @param {Parse~Object} parsed
 * @param {Sample~Schema} schema
 * @param {string} prefix
 * @returns {?Suggest~Result}
 * @private
 */
Suggest._processCursorInFind = function (parsed, schema, prefix) {
	let keys = parsed.properties.map(prop => prop.key.name)

	if (parsed.cursor === -1) {
		// Nothing is focused
		return
	} else if (parsed.cursor === parsed.properties.length) {
		// Empty property focused
		return Suggest._suggestFields('', schema, prefix, keys, parsed)
	}

	let property = parsed.properties[parsed.cursor],
		key = property.key.name,
		subPrefix = prefix ? prefix + '.' + key : key
	if (property.key.cursor !== -1) {
		// Key has focus
		let search = key.slice(0, property.key.cursor)
		keys.splice(parsed.cursor, 1)
		return Suggest._suggestFields(search, schema, prefix, keys, parsed)
	} else if (key === '$or' || key === '$and' || key === '$nor') {
		// Multiple sub-finds
		if (property.value.type !== 'array') {
			return
		}

		let element = property.value.values[property.value.cursor]
		if (!element || element.type !== 'object') {
			return
		}

		return Suggest._processCursorInFind(element, schema, prefix)
	} else if (property.value.type === 'array' || property.value.type === 'source') {
		// Focus in field value
		return Suggest._suggestValues(property.value.raw, schema[subPrefix], property.value)
	} else if (property.value.type === 'object') {
		return Suggest._processCursorInFieldExp(property.value, schema, subPrefix)
	} else {
		// Unknown
		return
	}
}

/**
 * @param {Parse~Object} parsed
 * @param {Sample~Schema} schema
 * @param {string} prefix
 * @returns {?Suggest~Result}
 * @private
 */
Suggest._processCursorInFieldExp = function (parsed, schema, prefix) {
	let keys = parsed.properties.map(prop => prop.key.name)

	if (keys.some(key => key[0] !== '$')) {
		// Not actually a field expression, like in '{a: {b: 2}}',
		// '{b: 2}' is not a field expression
		return Suggest._suggestValues(parsed.raw, schema[prefix], parsed)
	}

	if (parsed.cursor === -1) {
		// Nothing is focused
		return
	} else if (parsed.cursor === parsed.properties.length) {
		// Empty property focused
		return Suggest._suggestOperators('', schema, prefix, keys, parsed)
	}

	let property = parsed.properties[parsed.cursor],
		key = property.key.name
	if (property.key.cursor !== -1) {
		// Key has focus
		let search = key.slice(0, property.key.cursor)
		keys.splice(parsed.cursor, 1)
		return Suggest._suggestOperators(search, schema, prefix, keys, parsed)
	} else if (key === '$eq' || key === '$ne' ||
		key === '$gt' || key === '$gte' ||
		key === '$lt' || key === '$lte') {
		// COMP VALUE
		return Suggest._suggestValues(property.value.raw, schema[prefix], property.value)
	} else if (key === '$in' || key === '$nin' || key === '$all') {
		// COMP [VALUE]
		if (property.value.type !== 'array') {
			return
		}

		let element = property.value.values[property.value.cursor]
		return Suggest._suggestValues(element ? element.raw : '', schema[prefix], property.value)
	} else if (key === '$not') {
		// $not FIELD-EXP
		if (property.value.type !== 'object') {
			return
		}

		return Suggest._processCursorInFieldExp(property.value, schema, prefix)
	} else if (key === '$exists') {
		// $exists BOOL
		return Suggest._suggestValues(property.value.raw, {
			bool: true
		}, property.value)
	} else if (key === '$type') {
		// $type STRING
		return Suggest._suggestValues(property.value.raw, {
			string: [
				'double',
				'string',
				'object',
				'array',
				'binData',
				'objectId',
				'bool',
				'date',
				'null',
				'regex',
				'javascript',
				'javascriptWithScope',
				'int',
				'timestamp',
				'long',
				'minKey',
				'maxKey',
				'number'
			]
		}, property.value)
	} else if (key === '$mod') {
		// $mod [NUM, NUM]
		return buildSimpleSuggestion('[divisor, remainder]', property.value)
	} else if (key === '$elemMatch') {
		// $elemMatch FIND
		if (property.value.type !== 'object') {
			return
		}

		return Suggest._processCursorInFind(property.value, schema, prefix)
	} else if (key === '$size') {
		// $size NUM
		return buildSimpleSuggestion('(number)', property.value)
	} else {
		// Unknown
		return
	}

	function buildSimpleSuggestion(text, context) {
		return {
			texts: [{
				plain: text,
				highlight: [text]
			}],
			type: 'value',
			context
		}
	}
}

/**
 * @param {string} search
 * @param {Sample~Schema} schema
 * @param {string} prefix
 * @param {Array<string>} blacklist - keys already used at this level
 * @param {Parse~Object} context
 * @returns {?Suggest~Result}
 * @private
 */
Suggest._suggestFields = function (search, schema, prefix, blacklist, context) {
	// search='a', prefix='' -> pathPrefix='', field='a'
	// search='a.b', prefix='' -> pathPrefix='a.', field='b'
	// search='a', prefix='x' -> pathPrefix='x.', field='a'
	// search='ab.cd.e', prefix='x' -> pathPrefix='x.ab.cd.', field='e'
	let lastDot = search.lastIndexOf('.'),
		keyPrefix = lastDot === -1 ? '' : search.slice(0, lastDot + 1),
		pathPrefix = (prefix ? prefix + '.' : '') + keyPrefix,
		field = lastDot === -1 ? search : search.slice(lastDot + 1),
		searchSpace = []

	if (lastDot === -1) {
		for (let operator of ['$or', '$and', '$nor']) {
			if (!blacklist.includes(operator)) {
				searchSpace.push(operator)
			}
		}
	}

	// Collect fields in the same and following levels
	for (let path in schema) {
		if (path.startsWith(pathPrefix)) {
			let text = path.slice(pathPrefix.length),
				key = path.slice((prefix ? prefix + '.' : '').length)

			if (!blacklist.includes(key)) {
				searchSpace.push(text)
			}
		}
	}

	return {
		texts: Search.search(searchSpace, field),
		type: 'property',
		context: context
	}
}

/**
 * @param {string} search
 * @param {Sample~Schema} schema
 * @param {string} prefix
 * @param {Array<string>} blacklist
 * @param {Parse~Object} context
 * @returns {?Suggest~Result}
 * @private
 */
Suggest._suggestOperators = function (search, schema, prefix, blacklist, context) {
	let operators = [
			// Basic operators
			'$eq', '$ne', '$gt', '$gte', '$lt', '$lte',
			'$in', '$nin', '$not', '$type'
		],
		fieldSchema = schema[prefix]

	// Additional type-dependent operators
	if (fieldSchema) {
		if (fieldSchema.null) {
			operators.push('$exists')
		}
		if (fieldSchema.double) {
			operators.push('$mod')
		}
		if (fieldSchema.array && fieldSchema.object) {
			operators.push('$elemMatch')
		}
		if (fieldSchema.array) {
			operators.push('$size', '$all')
		}
	}

	operators = operators.filter(op => !blacklist.includes(op))

	return {
		texts: Search.search(operators, search),
		type: 'property',
		context: context
	}
}

/**
 * @param {string} search
 * @param {?Sample~FieldSchema} fieldSchema
 * @param {Parse~Base} context
 * @returns {?Suggest~Result}
 * @private
 */
Suggest._suggestValues = function (search, fieldSchema, context) {
	if (!fieldSchema) {
		// Nothing useful to provide
		return
	}

	search = search.trim()
	let values = [],
		quote = search[0] === '"' ? '"' : '\''
	if (Array.isArray(fieldSchema.double)) {
		values = values.concat(fieldSchema.double.map(e => String(e)))
	}
	if (Array.isArray(fieldSchema.string)) {
		values = values.concat(fieldSchema.string.map(e => quote + Suggest._escape(e) + quote))
	}
	if (fieldSchema.bool) {
		values.push('true')
		values.push('false')
	}

	let texts = Search.search(values, search)

	let types = [
		'double',
		'string',
		'object',
		'array',
		'binData',
		'objectId',
		'date',
		'null',
		'regex',
		'timestamp',
		'long',
		'minKey',
		'maxKey'
	]

	types.forEach(type => {
		if (fieldSchema[type]) {
			let text = '(' + type + ')'
			texts.push({
				plain: text,
				highlight: [text]
			})
		}
	})

	return {
		texts,
		type: 'value',
		context: context
	}
}

/**
 * Escape a string to put between quotes
 * @param {string} str
 * @returns {string}
 * @private
 */
Suggest._escape = function (str) {
	return str.replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/"/g, '\\"')
}<|endoftext|>


<commit_before>import { endsWith } from 'lodash';
import { Set as makeSet, List as makeList } from 'immutable';

//
// top priority first
//
const TOPOLOGY_DISPLAY_PRIORITY = [
  'services',
  'deployments',
  'replica-sets',
  'pods',
  'containers',
];

export function getDefaultTopology(topologies) {
  const flatTopologies = topologies
    .flatMap(t => makeList([t]).concat(t.get('sub_topologies', makeList())));

  return flatTopologies
    .sortBy((t) => {
      const index = TOPOLOGY_DISPLAY_PRIORITY.indexOf(t.get('id'));
      return index === -1 ? Infinity : index;
    })
    .getIn([0, 'id']);
}

/**
 * Returns a cache ID based on the topologyId and optionsQuery
 * @param  {String} topologyId
 * @param  {object} topologyOptions (optional)
 * @return {String}
 */
export function buildTopologyCacheId(topologyId, topologyOptions) {
  let id = '';
  if (topologyId) {
    id = topologyId;
    if (topologyOptions) {
      id += JSON.stringify(topologyOptions);
    }
  }
  return id;
}

/**
 * Returns a topology object from the topology tree
 * @param  {List} subTree
 * @param  {String} topologyId
 * @return {Map} topology if found
 */
export function findTopologyById(subTree, topologyId) {
  let foundTopology;

  subTree.forEach((topology) => {
    if (endsWith(topology.get('url'), topologyId)) {
      foundTopology = topology;
    }
    if (!foundTopology && topology.has('sub_topologies')) {
      foundTopology = findTopologyById(topology.get('sub_topologies'), topologyId);
    }
  });

  return foundTopology;
}

export function updateNodeDegrees(nodes, edges) {
  return nodes.map((node) => {
    const nodeId = node.get('id');
    const degree = edges.count(edge => edge.get('source') === nodeId
      || edge.get('target') === nodeId);
    return node.set('degree', degree);
  });
}

/* set topology.id and parentId for sub-topologies in place */
export function updateTopologyIds(topologies, parentId) {
  return topologies.map((topology) => {
    const result = Object.assign({}, topology);
    result.id = topology.url.split('/').pop();
    if (parentId) {
      result.parentId = parentId;
    }
    if (topology.sub_topologies) {
      result.sub_topologies = updateTopologyIds(topology.sub_topologies, result.id);
    }
    return result;
  });
}

export function addTopologyFullname(topologies) {
  return topologies.map((t) => {
    if (!t.sub_topologies) {
      return Object.assign({}, t, {fullName: t.name});
    }
    return Object.assign({}, t, {
      fullName: t.name,
      sub_topologies: t.sub_topologies.map(st => (
        Object.assign({}, st, {fullName: `${t.name} ${st.name}`})
      ))
    });
  });
}

// adds ID field to topology (based on last part of URL path) and save urls in
// map for easy lookup
export function setTopologyUrlsById(topologyUrlsById, topologies) {
  let urlMap = topologyUrlsById;
  if (topologies) {
    topologies.forEach((topology) => {
      urlMap = urlMap.set(topology.id, topology.url);
      if (topology.sub_topologies) {
        topology.sub_topologies.forEach((subTopology) => {
          urlMap = urlMap.set(subTopology.id, subTopology.url);
        });
      }
    });
  }
  return urlMap;
}

export function filterHiddenTopologies(topologies) {
  return topologies.filter(t => (!t.hide_if_empty || t.stats.node_count > 0 ||
                               t.stats.filtered_nodes > 0));
}

export function getActiveTopologyOptions(state) {
  // options for current topology, sub-topologies share options with parent
  const parentId = state.getIn(['currentTopology', 'parentId']);
  if (parentId) {
    return state.getIn(['topologyOptions', parentId]);
  }
  return state.getIn(['topologyOptions', state.get('currentTopologyId')]);
}

export function getCurrentTopologyOptions(state) {
  return state.getIn(['currentTopology', 'options']);
}

export function isTopologyEmpty(state) {
  return state.getIn(['currentTopology', 'stats', 'node_count'], 0) === 0
    && state.get('nodes').size === 0;
}

export function getAdjacentNodes(state, originNodeId) {
  let adjacentNodes = makeSet();
  const nodeId = originNodeId || state.get('selectedNodeId');

  if (nodeId) {
    if (state.hasIn(['nodes', nodeId])) {
      adjacentNodes = makeSet(state.getIn(['nodes', nodeId, 'adjacency']));
      // fill up set with reverse edges
      state.get('nodes').forEach((node, id) => {
        if (node.get('adjacency') && node.get('adjacency').includes(nodeId)) {
          adjacentNodes = adjacentNodes.add(id);
        }
      });
    }
  }

  return adjacentNodes;
}

export function hasSelectedNode(state) {
  const selectedNodeId = state.get('selectedNodeId');
  return state.hasIn(['nodes', selectedNodeId]);
}

export function getCurrentTopologyUrl(state) {
  return state.getIn(['currentTopology', 'url']);
}

export function isNodeMatchingQuery(node, query) {
  return node.get('label').includes(query) || node.get('subLabel').includes(query);
}

export function graphExceedsComplexityThresh(stats) {
  // Check to see if complexity is high. Used to trigger table view on page load.
  return (stats.get('node_count') + (2 * stats.get('edge_count'))) > 500;
}
<commit_msg>Prioritize ecs topologies on initial load if available<commit_after>import { endsWith } from 'lodash';
import { Set as makeSet, List as makeList } from 'immutable';

//
// top priority first
//
const TOPOLOGY_DISPLAY_PRIORITY = [
  'ecs-services',
  'ecs-tasks',
  'services',
  'deployments',
  'replica-sets',
  'pods',
  'containers',
];

export function getDefaultTopology(topologies) {
  const flatTopologies = topologies
    .flatMap(t => makeList([t]).concat(t.get('sub_topologies', makeList())));

  return flatTopologies
    .sortBy((t) => {
      const index = TOPOLOGY_DISPLAY_PRIORITY.indexOf(t.get('id'));
      return index === -1 ? Infinity : index;
    })
    .getIn([0, 'id']);
}

/**
 * Returns a cache ID based on the topologyId and optionsQuery
 * @param  {String} topologyId
 * @param  {object} topologyOptions (optional)
 * @return {String}
 */
export function buildTopologyCacheId(topologyId, topologyOptions) {
  let id = '';
  if (topologyId) {
    id = topologyId;
    if (topologyOptions) {
      id += JSON.stringify(topologyOptions);
    }
  }
  return id;
}

/**
 * Returns a topology object from the topology tree
 * @param  {List} subTree
 * @param  {String} topologyId
 * @return {Map} topology if found
 */
export function findTopologyById(subTree, topologyId) {
  let foundTopology;

  subTree.forEach((topology) => {
    if (endsWith(topology.get('url'), topologyId)) {
      foundTopology = topology;
    }
    if (!foundTopology && topology.has('sub_topologies')) {
      foundTopology = findTopologyById(topology.get('sub_topologies'), topologyId);
    }
  });

  return foundTopology;
}

export function updateNodeDegrees(nodes, edges) {
  return nodes.map((node) => {
    const nodeId = node.get('id');
    const degree = edges.count(edge => edge.get('source') === nodeId
      || edge.get('target') === nodeId);
    return node.set('degree', degree);
  });
}

/* set topology.id and parentId for sub-topologies in place */
export function updateTopologyIds(topologies, parentId) {
  return topologies.map((topology) => {
    const result = Object.assign({}, topology);
    result.id = topology.url.split('/').pop();
    if (parentId) {
      result.parentId = parentId;
    }
    if (topology.sub_topologies) {
      result.sub_topologies = updateTopologyIds(topology.sub_topologies, result.id);
    }
    return result;
  });
}

export function addTopologyFullname(topologies) {
  return topologies.map((t) => {
    if (!t.sub_topologies) {
      return Object.assign({}, t, {fullName: t.name});
    }
    return Object.assign({}, t, {
      fullName: t.name,
      sub_topologies: t.sub_topologies.map(st => (
        Object.assign({}, st, {fullName: `${t.name} ${st.name}`})
      ))
    });
  });
}

// adds ID field to topology (based on last part of URL path) and save urls in
// map for easy lookup
export function setTopologyUrlsById(topologyUrlsById, topologies) {
  let urlMap = topologyUrlsById;
  if (topologies) {
    topologies.forEach((topology) => {
      urlMap = urlMap.set(topology.id, topology.url);
      if (topology.sub_topologies) {
        topology.sub_topologies.forEach((subTopology) => {
          urlMap = urlMap.set(subTopology.id, subTopology.url);
        });
      }
    });
  }
  return urlMap;
}

export function filterHiddenTopologies(topologies) {
  return topologies.filter(t => (!t.hide_if_empty || t.stats.node_count > 0 ||
                               t.stats.filtered_nodes > 0));
}

export function getActiveTopologyOptions(state) {
  // options for current topology, sub-topologies share options with parent
  const parentId = state.getIn(['currentTopology', 'parentId']);
  if (parentId) {
    return state.getIn(['topologyOptions', parentId]);
  }
  return state.getIn(['topologyOptions', state.get('currentTopologyId')]);
}

export function getCurrentTopologyOptions(state) {
  return state.getIn(['currentTopology', 'options']);
}

export function isTopologyEmpty(state) {
  return state.getIn(['currentTopology', 'stats', 'node_count'], 0) === 0
    && state.get('nodes').size === 0;
}

export function getAdjacentNodes(state, originNodeId) {
  let adjacentNodes = makeSet();
  const nodeId = originNodeId || state.get('selectedNodeId');

  if (nodeId) {
    if (state.hasIn(['nodes', nodeId])) {
      adjacentNodes = makeSet(state.getIn(['nodes', nodeId, 'adjacency']));
      // fill up set with reverse edges
      state.get('nodes').forEach((node, id) => {
        if (node.get('adjacency') && node.get('adjacency').includes(nodeId)) {
          adjacentNodes = adjacentNodes.add(id);
        }
      });
    }
  }

  return adjacentNodes;
}

export function hasSelectedNode(state) {
  const selectedNodeId = state.get('selectedNodeId');
  return state.hasIn(['nodes', selectedNodeId]);
}

export function getCurrentTopologyUrl(state) {
  return state.getIn(['currentTopology', 'url']);
}

export function isNodeMatchingQuery(node, query) {
  return node.get('label').includes(query) || node.get('subLabel').includes(query);
}

export function graphExceedsComplexityThresh(stats) {
  // Check to see if complexity is high. Used to trigger table view on page load.
  return (stats.get('node_count') + (2 * stats.get('edge_count'))) > 500;
}
<|endoftext|>


<commit_before>const root = '../../';

describe('The data parameter', function(){
  test('should record data to a trial', function(){

    require(root + 'jspsych.js');
    require(root + 'plugins/jspsych-text.js');

    return (new Promise(function(resolve, reject){

      var key_data = null;

      var trial = {
        type: 'text',
        text: 'hello',
        data: {added: true},
      }

      jsPsych.init({
        timeline: [trial],
        on_finish: function() {
          var d = jsPsych.data.get().values()[0].added;
          resolve(d);
        }
      });

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      //resolve();
    })).then(function(data) { expect(data).toBe(true) });
  });

  test('should record data to all nested trials', function(){

    require(root + 'jspsych.js');
    require(root + 'plugins/jspsych-text.js');

    return (new Promise(function(resolve, reject){

      var key_data = null;

      var trial = {
        type: 'text',
        timeline: [
          {text: 'a'},
          {text: 'b'}
        ],
        data: {added: true},
      }

      jsPsych.init({
        timeline: [trial],
        on_finish: function() {
          var d = jsPsych.data.get().filter({added: true}).count();
          resolve(d);
        }
      });

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      //resolve();
    })).then(function(data) { expect(data).toBe(2) });
  });

  test('should record data to all nested trials with timeline variables', function(){

    require(root + 'jspsych.js');
    require(root + 'plugins/jspsych-text.js');

    return (new Promise(function(resolve, reject){

      var key_data = null;

      var vars = [
        {text: 'a'},
        {text: 'b'}
      ];

      var trial = {
        timeline: [
          {type: 'text', text: jsPsych.timelineVariable('text')}
        ],
        timeline_variables: vars,
        data: {added: true},
      }

      jsPsych.init({
        timeline: [trial],
        on_finish: function() {
          var d = jsPsych.data.get().filter({added: true}).count();
          resolve(d);
        }
      });

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      //resolve();
    })).then(function(data) { expect(data).toBe(2) });
  });
});
<commit_msg>add broken test for #391<commit_after>const root = '../../';

describe('The data parameter', function(){
  test('should record data to a trial', function(){

    require(root + 'jspsych.js');
    require(root + 'plugins/jspsych-text.js');

    return (new Promise(function(resolve, reject){

      var key_data = null;

      var trial = {
        type: 'text',
        text: 'hello',
        data: {added: true},
      }

      jsPsych.init({
        timeline: [trial],
        on_finish: function() {
          var d = jsPsych.data.get().values()[0].added;
          resolve(d);
        }
      });

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      //resolve();
    })).then(function(data) { expect(data).toBe(true) });
  });

  test('should record data to all nested trials', function(){

    require(root + 'jspsych.js');
    require(root + 'plugins/jspsych-text.js');

    return (new Promise(function(resolve, reject){

      var key_data = null;

      var trial = {
        type: 'text',
        timeline: [
          {text: 'a'},
          {text: 'b'}
        ],
        data: {added: true},
      }

      jsPsych.init({
        timeline: [trial],
        on_finish: function() {
          var d = jsPsych.data.get().filter({added: true}).count();
          resolve(d);
        }
      });

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      //resolve();
    })).then(function(data) { expect(data).toBe(2) });
  });

  test('should record data to all nested trials with timeline variables', function(){

    require(root + 'jspsych.js');
    require(root + 'plugins/jspsych-text.js');

    return (new Promise(function(resolve, reject){

      var key_data = null;

      var vars = [
        {text: 'a'},
        {text: 'b'}
      ];

      var trial = {
        timeline: [
          {type: 'text', text: jsPsych.timelineVariable('text')}
        ],
        timeline_variables: vars,
        data: {added: true},
      }

      jsPsych.init({
        timeline: [trial],
        on_finish: function() {
          var d = jsPsych.data.get().filter({added: true}).count();
          resolve(d);
        }
      });

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      //resolve();
    })).then(function(data) { expect(data).toBe(2) });
  });

  test('should record data to all nested trials with timeline variables even when nested trials have own data', function(){

    require(root + 'jspsych.js');
    require(root + 'plugins/jspsych-text.js');

    return (new Promise(function(resolve, reject){

      var key_data = null;

      var vars = [
        {text: 'a'},
        {text: 'b'}
      ];

      var trial = {
        timeline: [
          {
            type: 'text',
            text: jsPsych.timelineVariable('text'),
            data: {foo: 1}
          }
        ],
        timeline_variables: vars,
        data: {added: true},
      }

      jsPsych.init({
        timeline: [trial],
        on_finish: function() {
          var d = jsPsych.data.get().filter({added: true}).count();
          resolve(d);
        }
      });

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keydown', {keyCode: 32}));
      document.querySelector('.jspsych-display-element').dispatchEvent(new KeyboardEvent('keyup', {keyCode: 32}));

      //resolve();
    })).then(function(data) { expect(data).toBe(2) });
  });
});
<|endoftext|>


<commit_before>/*
The MIT License (MIT)

Copyright (c) 2013-2014 CNRS

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

var fileSystem = require('fs'); //working with video streaming
var async = require('async');
var commonFuncs = require('../lib/commonFuncs');

//check if a id_media exists
exports.exist = function(req, res, next) {
	Media.findById(req.params.id_media, function(error, media){
		if (error) res.status(400).json(error);
		else if (!media) res.status(400).json({message:"id_media don't exists"});
		else next();
	});
}

// check if req.session.user._id have the good right to see this media.id_corpus
exports.AllowUser = function (list_right){
	return function(req, res, next) {
		async.waterfall([
			function(callback) {
				User.findById(req.session.user._id, function(error, user){
					callback(error, user);
				});
			},
			function(user, callback) {
				Group.find({'users_list' : {$regex : new RegExp('^'+ req.session.user._id + '$', "i")}}, function(error, groups) {
					callback(error, user, groups);
				});
			},
			function(user, groups, callback) {
				Media.findById(req.params.id_media, function(error, media){
					callback(error, user, groups, media);
	    		});
			},
			function(user, groups, media, callback) {
				Corpus.findById(media.id_corpus, function(error, corpus){
					if (commonFuncs.checkRightACL(corpus, user, groups, list_right)) next();
					else error = "Acces denied";
					callback(error);
	    		});
			},

		], function (error, trueOrFalse) {
			if (error) res.status(400).json({message:error});
		});
	}
}

//retrieve a particular user (with id)
exports.getInfo = function(req, res){
	Media.findById(req.params.id_media, 'name description id_corpus url history', function(error, media){
		if (error) res.status(400).json({message:error});
    	else res.status(200).json(media);
	});
}

//update information of a media
exports.update = function(req, res){
	var update = {};
	if (req.body.name) {
		if (req.body.name == "") res.status(400).json({message:"name can't be empty"});
		else update.name = req.body.name;
	}
	if (req.body.description) update.description = req.body.description;
	if (req.body.url) update.description = req.body.url;
	Media.findByIdAndUpdate(req.params.id_media, update, function (error, media) {
		if (error) res.status(400).json(error);
		else res.status(200).json(media);
	});
}

// remove a given media
exports.remove = function (req, res) {
	Media.remove({_id : req.params.id_media}, function (error, media) {
		if (!error && media == 1) res.status(200).json({message:"The media as been delete"});
		else res.status(400).json({message:error});
	});
}

function getVideoWithExtension(req, res, extension) {
	Media.findById(req.params.id_media, function(error, media){
		if (error) res.status(400).json({message:error});
		else if (media == null) res.status(400).json({message: 'no such id_media!'})
		else {
			var filePath = media.url + '.' + extension;
			if (media.url == undefined) return res.status(404).send({message:'not found the video corresponding to this media'});
			if (GLOBAL.video_path) filePath = GLOBAL.video_path + '/' + filePath;
			res.status(200).sendfile(filePath);
		}
	});
}

exports.getVideo = function(req, res) {
	getVideoWithExtension(req, res, 'webm');
}

exports.getVideoWEBM = function(req, res) {
	getVideoWithExtension(req, res, 'webm');
}

exports.getVideoMP4 = function(req, res) {
	getVideoWithExtension(req, res, 'mp4');
}

exports.getVideoOGV = function(req, res) {
	getVideoWithExtension(req, res, 'ogv');
}
<commit_msg>add getAll<commit_after>/*
The MIT License (MIT)

Copyright (c) 2013-2014 CNRS

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

var fileSystem = require('fs'); //working with video streaming
var async = require('async');
var commonFuncs = require('../lib/commonFuncs');

//check if a id_media exists
exports.exist = function(req, res, next) {
	Media.findById(req.params.id_media, function(error, media){
		if (error) res.status(400).json(error);
		else if (!media) res.status(400).json({message:"id_media don't exists"});
		else next();
	});
}

// check if req.session.user._id have the good right to see this media.id_corpus
exports.AllowUser = function (list_right){
	return function(req, res, next) {
		async.waterfall([
			function(callback) {
				User.findById(req.session.user._id, function(error, user){
					callback(error, user);
				});
			},
			function(user, callback) {
				Group.find({'users_list' : {$regex : new RegExp('^'+ req.session.user._id + '$', "i")}}, function(error, groups) {
					callback(error, user, groups);
				});
			},
			function(user, groups, callback) {
				Media.findById(req.params.id_media, function(error, media){
					callback(error, user, groups, media);
	    		});
			},
			function(user, groups, media, callback) {
				Corpus.findById(media.id_corpus, function(error, corpus){
					if (commonFuncs.checkRightACL(corpus, user, groups, list_right)) next();
					else error = "Acces denied";
					callback(error);
	    		});
			},

		], function (error, trueOrFalse) {
			if (error) res.status(400).json({message:error});
		});
	}
}

//retrieve a particular user (with id)
exports.getInfo = function(req, res){
	Media.findById(req.params.id_media, 'name description id_corpus url history', function(error, media){
		if (error) res.status(400).json({message:error});
    	else res.status(200).json(media);
	});
}

//update information of a media
exports.update = function(req, res){
	var update = {};
	if (req.body.name) {
		if (req.body.name == "") res.status(400).json({message:"name can't be empty"});
		else update.name = req.body.name;
	}
	if (req.body.description) update.description = req.body.description;
	if (req.body.url) update.description = req.body.url;
	Media.findByIdAndUpdate(req.params.id_media, update, function (error, media) {
		if (error) res.status(400).json(error);
		else res.status(200).json(media);
	});
}

// remove a given media
exports.remove = function (req, res) {
	Media.remove({_id : req.params.id_media}, function (error, media) {
		if (!error && media == 1) res.status(200).json({message:"The media as been delete"});
		else res.status(400).json({message:error});
	});
}

function getVideoWithExtension(req, res, extension) {
	Media.findById(req.params.id_media, function(error, media){
		if (error) res.status(400).json({message:error});
		else if (media == null) res.status(400).json({message: 'no such id_media!'})
		else {
			var filePath = media.url + '.' + extension;
			if (media.url == undefined) return res.status(404).send({message:'not found the video corresponding to this media'});
			if (GLOBAL.video_path) filePath = GLOBAL.video_path + '/' + filePath;
			res.status(200).sendfile(filePath);
		}
	});
}

// retrieve all media
exports.getAll = function (req, res) {
	Media.find({}, function (error, medias) {
    	if (error) res.status(400).json({error:"error", message:error});
    	if (medias) res.status(200).json(medias);
		else res.status(200).json([]);
	});
}

exports.getVideo = function(req, res) {
	getVideoWithExtension(req, res, 'webm');
}

exports.getVideoWEBM = function(req, res) {
	getVideoWithExtension(req, res, 'webm');
}

exports.getVideoMP4 = function(req, res) {
	getVideoWithExtension(req, res, 'mp4');
}

exports.getVideoOGV = function(req, res) {
	getVideoWithExtension(req, res, 'ogv');
}
<|endoftext|>


<commit_before>var keystone = require('keystone'),
	async = require('async'),
	_ = require('underscore'),
	moment = require('moment');

exports = module.exports = function(req, res) {

	var data = { meetups: {}, talks: {} };

	async.series([
		function(next) {
			if (!req.body.user) return next();
			keystone.list('User').model.findById(req.body.user).exec(function(err, user) {
				if (err || !user) return res.apiResponse({ success: false });
				data.user = user;
				return next();
			});
		},
		function(next) {
			keystone.list('Meetup').model.findOne()
				.where('state', 'past')
				.sort('-startDate')
				.exec(function(err, meetup) {
					data.meetups.last = meetup;
					return next();
				});
		},
		function(next) {
			keystone.list('Meetup').model.findOne()
				.where('state', 'active')
				.sort('-startDate')
				.exec(function(err, meetup) {
					data.meetups.next = meetup;
					return next();
				});
		},
		function(next) {
			keystone.list('Talk').model.find()
				.where('meetup', data.meetups.last)
				.populate('who')
				.sort('sortOrder')
				.exec(function(err, talks) {
					data.talks.last = talks;
					return next();
				});
		},
		function(next) {
			keystone.list('Talk').model.find()
				.where('meetup', data.meetups.next)
				.populate('who')
				.sort('sortOrder')
				.exec(function(err, talks) {
					data.talks.next = talks;
					return next();
				});
		},
		function(next) {
			if (!req.body.user) return next();
			keystone.list('RSVP').model.findOne()
				.where('who', data.user)
				.where('meetup', data.meetup)
				.exec(function(err, rsvp) {
					data.rsvp = rsvp;
					return next();
				});
		}
	], function(err) {

		var response = {
			success: true,
			config: {
				versions: {
					compatibility: process.env.APP_COMPATIBILITY_VERSION,
					production: process.env.APP_PRODUCTION_VERSION
				},
				killSwitch: false
			},
			meetups: {
				last: false,
				next: false
			},
			user: false
		}

		var meetup = function(m, current) {
			return {
				id: m._id,

				name: m.name,

				starts: m.startDate,
				ends: m.endDate,

				place: m.place,

				description: keystone.utils.cropString(keystone.utils.htmlToText(m.description), 250, '...', true),

				ticketsAvailable: m.rsvpsAvailable,
				ticketsRemaining: m.remainingRSVPs,

				talks: current ? data.talks.next : data.talks.last,

				rsvped: current && data.rsvp ? true : false,
				attending: current && data.rsvp && data.rsvp.attending ? true : false
			}
		}

		if (data.meetups.last) {
			response.meetups.last = meetup(data.meetups.last);
		}

		if (data.meetups.next && moment().isBefore(data.meetups.next.endDate)) {
			response.meetups.next = meetup(data.meetups.next, true);
		}

		if (data.user) {
			response.user = {
				date: new Date().getTime(),
				userId: data.user.id,
				name: {
					first: data.user.name.first,
					last: data.user.name.last,
					full: data.user.name.full
				},
				email: data.user.email
			}
		}

		res.apiResponse(response);

	});
}
<commit_msg>Adding avatar inside user data.<commit_after>var keystone = require('keystone'),
	async = require('async'),
	_ = require('underscore'),
	moment = require('moment');

exports = module.exports = function(req, res) {

	var data = { meetups: {}, talks: {} };

	async.series([
		function(next) {
			if (!req.body.user) return next();
			keystone.list('User').model.findById(req.body.user).exec(function(err, user) {
				if (err || !user) return res.apiResponse({ success: false });
				data.user = user;
				return next();
			});
		},
		function(next) {
			keystone.list('Meetup').model.findOne()
				.where('state', 'past')
				.sort('-startDate')
				.exec(function(err, meetup) {
					data.meetups.last = meetup;
					return next();
				});
		},
		function(next) {
			keystone.list('Meetup').model.findOne()
				.where('state', 'active')
				.sort('-startDate')
				.exec(function(err, meetup) {
					data.meetups.next = meetup;
					return next();
				});
		},
		function(next) {
			keystone.list('Talk').model.find()
				.where('meetup', data.meetups.last)
				.populate('who')
				.sort('sortOrder')
				.exec(function(err, talks) {
					data.talks.last = talks;
					return next();
				});
		},
		function(next) {
			keystone.list('Talk').model.find()
				.where('meetup', data.meetups.next)
				.populate('who')
				.sort('sortOrder')
				.exec(function(err, talks) {
					data.talks.next = talks;
					return next();
				});
		},
		function(next) {
			if (!req.body.user) return next();
			keystone.list('RSVP').model.findOne()
				.where('who', data.user)
				.where('meetup', data.meetup)
				.exec(function(err, rsvp) {
					data.rsvp = rsvp;
					return next();
				});
		}
	], function(err) {

		var response = {
			success: true,
			config: {
				versions: {
					compatibility: process.env.APP_COMPATIBILITY_VERSION,
					production: process.env.APP_PRODUCTION_VERSION
				},
				killSwitch: false
			},
			meetups: {
				last: false,
				next: false
			},
			user: false
		}

		var meetup = function(m, current) {
			return {
				id: m._id,

				name: m.name,

				starts: m.startDate,
				ends: m.endDate,

				place: m.place,

				description: keystone.utils.cropString(keystone.utils.htmlToText(m.description), 250, '...', true),

				ticketsAvailable: m.rsvpsAvailable,
				ticketsRemaining: m.remainingRSVPs,

				talks: current ? data.talks.next : data.talks.last,

				rsvped: current && data.rsvp ? true : false,
				attending: current && data.rsvp && data.rsvp.attending ? true : false
			}
		}

		if (data.meetups.last) {
			response.meetups.last = meetup(data.meetups.last);
		}

		if (data.meetups.next && moment().isBefore(data.meetups.next.endDate)) {
			response.meetups.next = meetup(data.meetups.next, true);
		}

		if (data.user) {
			response.user = {
				date: new Date().getTime(),
				userId: data.user.id,
				name: {
					first: data.user.name.first,
					last: data.user.name.last,
					full: data.user.name.full
				},
				email: data.user.email,
				avatar: data.user.avatarUrl
			}
		}

		res.apiResponse(response);

	});
}
<|endoftext|>