<commit_before>import store from "../store/index.js.erb";

import { isElement,
         isText,
         isBR,
         getLength,
         getAttrsMap } from "../libs/html_helpers.js";

import ResourceSectionWrapper from "./ResourceSectionWrapper";
import ElisionAnnotation from "./ElisionAnnotation";
import ReplacementAnnotation from "./ReplacementAnnotation";
import HighlightAnnotation from "./HighlightAnnotation";
import LinkAnnotation from "./LinkAnnotation";
import NoteAnnotation from "./NoteAnnotation";
import FootnoteLink from "./FootnoteLink";

/////////////
// Helpers //
/////////////

const kindToComponent = (kind) =>
  ({elide: "elision",
    replace: "replacement"}[kind] || kind) + "-annotation";

const isFootnoteLink = (node) =>
  node.hash && node.origin == location.origin && node.pathname == location.pathname;

const getTagName = (node) =>
  isFootnoteLink(node) ? "footnote-link" : node.tagName;

const last = (array) => {
  return array[array.length - 1];
};

///////////////////////////
// Munging and filtering //
///////////////////////////

const isValidNodeType = (node) =>
  isElement(node) || isText(node);

const transformToTuplesWithOffsets = (parentStart) =>
  (tuples, node) => {
    let [prevNode, prevStart, prevEnd] = last(tuples) ||
        [null, null, parentStart];
    return tuples.concat([[node, prevEnd, prevEnd + getLength(node)]]);
  };

const splitTextAt = (breakpoints, [node, start, end]) =>
  breakpoints
  // remove any offsets that fall on or outside of the Text node
    .filter(breakpoint =>
            breakpoint > start &&
            breakpoint < end)
  // split the Text node; splitText() mutates the existing node
  // in our array, truncating it, and returns a new node with
  // the remaining text
    .reduce((tuples, breakpoint) => {
      let [prevNode, prevStart, prevEnd] = last(tuples);
      let node = prevNode.splitText(breakpoint - prevStart);
      last(tuples)[2] = breakpoint;
      return tuples.concat([[node, breakpoint, prevEnd]]);
    }, [[node, start, end]]);

const annotateAndConvertToVNodes = (h, tuples, index, enclosingAnnotationIds) =>
      tuples
      .reduce(insertAnnotations(h, index, enclosingAnnotationIds), [])
      .map(tupleToVNode(h, index, enclosingAnnotationIds));

// Given an annotation and the previous tuple in the list, use the
// annotation's offset information to move forward in the list,
// eagerly grabbing tuples that fall within its range.
// This is the logic that allows annotations to wrap around
// existing elements on the page.
const groupIntoAnnotation = (h, index, tuples, enclosingAnnotationIds) =>
  (prevTuple, annotation) => {
    // Figure out how far to reach forward for elements to group into this annotation.
    // If the annotation extends beyond this section / pararaph,
    // use the end offset of this parent element
    let annotationEndInSection = annotation.end_paragraph == index ? annotation.end_offset : last(tuples)[2];
    // get the forward elements that fall within our range
    let childTuples = [prevTuple, ...tuples.filter(t => t[1] >= prevTuple[2] && t[2] <= annotationEndInSection)];
    let props = {startOffset: prevTuple[1],
                 endOffset: last(childTuples)[2]};

    return [h(kindToComponent(annotation.kind),
              {props: {...props,
                       annotation: annotation}},
              annotateAndConvertToVNodes(h, childTuples, index, enclosingAnnotationIds.concat([annotation.id]))),
            props.startOffset,
            props.endOffset];
  };

// Loop through the tuples and add annotations when found
const insertAnnotations = (h, index, enclosingAnnotationIds) =>
  (modifiedTuples, tuple, idx, orgTuples) => {
    let [node, start, end] = tuple;
    let [prevNode, prevStart, prevEnd] = last(modifiedTuples) ||
        [null, null, orgTuples[0][1]];

    // if the previous tuple's end offset is greater than the current tuple's
    // start offset, the current tuple has already been grouped into an
    // annotation's children list so skip it in modifiedTuples so as not
    // to duplicate it in the render
    return prevEnd > start
      ? modifiedTuples
      : modifiedTuples.concat([
        store.getters['annotations/getAtIndexAndOffset'](index, start)
        // longest to shortest
          .sort((a, b) =>
                (b.end_paragraph == index ? b.end_offset : Number.MAX_VALUE) -
                (a.end_paragraph == index ? a.end_offset : Number.MAX_VALUE))
        // Remove any annotations that have already been rendered upstream
          .filter(a => !enclosingAnnotationIds.includes(a.id))
        // We only want the first annotation, for now, but keeping
        // it as an array conveniently allows reduce to
        // return the normal tuple as a default if no annotations exist
          .slice(0, 1)
          .reduce(groupIntoAnnotation(h, index, orgTuples, enclosingAnnotationIds), tuple)]);
  };

// Vue component children arrays must contain either VNodes or
// Strings (which get converted to VNodes automatically)
const tupleToVNode = (h, index, enclosingAnnotationIds = []) =>
  ([node, start, end]) => {
    if(isText(node)) {
      return node.textContent;
    } else if(isElement(node)) {
      let tag = getTagName(node),
          data = {attrs: getAttrsMap(node)},
          children = annotateAndConvertToVNodes(h, filterAndSplitNodeList(node.childNodes, index, start, end), index, enclosingAnnotationIds);
      switch(tag) {
      case "footnote-link":
        data.props = {enclosingAnnotationIds: enclosingAnnotationIds};
        break;
      }
      return h(tag, data, children);
    } else {
      return node;
    }
  };

// Return the offsets within this element where
// annotations need to start or end
const annotationBreakpoints = (index, start, end) =>
      store.getters['annotations/getWithinIndexAndOffsets'](index, start, end)
      .reduce((offsets, annotation) =>
              offsets.concat(
                ["start", "end"]
                  .filter(s => annotation[`${s}_paragraph`] == index)
                  .map(s => annotation[`${s}_offset`]))
              , [])
      .filter((n, i, s) => s.indexOf(n) === i) // remove dupes
      .sort((a, b) => a - b); // sort lowest to highest

const filterAndSplitNodeList = (nodeList, index, start, end) => {
  const breakpoints = annotationBreakpoints(index, start, end);
  return Array.from(nodeList)
  // remove anything that isn't an Element or Text node
  // i.e. no script or comment tags etc
    .filter(isValidNodeType)
  // transform our Node array to an array of [Node, start, end] tuples
    .reduce(transformToTuplesWithOffsets(start), [])
  // break next nodes at points where annotations exist
    .reduce((tuples, tuple) => tuples.concat(
      isText(tuple[0])
        ? splitTextAt(breakpoints, tuple)
        : [tuple]
    ), []);
};

export default {
  components: {
    ResourceSectionWrapper,
    ElisionAnnotation,
    ReplacementAnnotation,
    HighlightAnnotation,
    LinkAnnotation,
    NoteAnnotation,
    FootnoteLink
  },
  props: {
    el: {type: HTMLElement},
    index: {type: Number}
  },
  render(h) {
    return h("resource-section-wrapper",
             {props: {index: this.index,
                      length: getLength(this.el)}},
             [tupleToVNode(h, this.index)([this.el, 0, getLength(this.el)])]);
  }
};
<commit_msg>Provide a key to prevent strange rendering issues. Fixes #646<commit_after>import store from "../store/index.js.erb";

import { isElement,
         isText,
         isBR,
         getLength,
         getAttrsMap } from "../libs/html_helpers.js";

import ResourceSectionWrapper from "./ResourceSectionWrapper";
import ElisionAnnotation from "./ElisionAnnotation";
import ReplacementAnnotation from "./ReplacementAnnotation";
import HighlightAnnotation from "./HighlightAnnotation";
import LinkAnnotation from "./LinkAnnotation";
import NoteAnnotation from "./NoteAnnotation";
import FootnoteLink from "./FootnoteLink";

/////////////
// Helpers //
/////////////

const kindToComponent = (kind) =>
  ({elide: "elision",
    replace: "replacement"}[kind] || kind) + "-annotation";

const isFootnoteLink = (node) =>
  node.hash && node.origin == location.origin && node.pathname == location.pathname;

const getTagName = (node) =>
  isFootnoteLink(node) ? "footnote-link" : node.tagName;

const last = (array) => {
  return array[array.length - 1];
};

///////////////////////////
// Munging and filtering //
///////////////////////////

const isValidNodeType = (node) =>
  isElement(node) || isText(node);

const transformToTuplesWithOffsets = (parentStart) =>
  (tuples, node) => {
    let [prevNode, prevStart, prevEnd] = last(tuples) ||
        [null, null, parentStart];
    return tuples.concat([[node, prevEnd, prevEnd + getLength(node)]]);
  };

const splitTextAt = (breakpoints, [node, start, end]) =>
  breakpoints
  // remove any offsets that fall on or outside of the Text node
    .filter(breakpoint =>
            breakpoint > start &&
            breakpoint < end)
  // split the Text node; splitText() mutates the existing node
  // in our array, truncating it, and returns a new node with
  // the remaining text
    .reduce((tuples, breakpoint) => {
      let [prevNode, prevStart, prevEnd] = last(tuples);
      let node = prevNode.splitText(breakpoint - prevStart);
      last(tuples)[2] = breakpoint;
      return tuples.concat([[node, breakpoint, prevEnd]]);
    }, [[node, start, end]]);

const annotateAndConvertToVNodes = (h, tuples, index, enclosingAnnotationIds) =>
      tuples
      .reduce(insertAnnotations(h, index, enclosingAnnotationIds), [])
      .map(tupleToVNode(h, index, enclosingAnnotationIds));

// Given an annotation and the previous tuple in the list, use the
// annotation's offset information to move forward in the list,
// eagerly grabbing tuples that fall within its range.
// This is the logic that allows annotations to wrap around
// existing elements on the page.
const groupIntoAnnotation = (h, index, tuples, enclosingAnnotationIds) =>
  (prevTuple, annotation) => {
    // Figure out how far to reach forward for elements to group into this annotation.
    // If the annotation extends beyond this section / pararaph,
    // use the end offset of this parent element
    let annotationEndInSection = annotation.end_paragraph == index ? annotation.end_offset : last(tuples)[2];
    // get the forward elements that fall within our range
    let childTuples = [prevTuple, ...tuples.filter(t => t[1] >= prevTuple[2] && t[2] <= annotationEndInSection)];
    let props = {startOffset: prevTuple[1],
                 endOffset: last(childTuples)[2]};

    return [h(kindToComponent(annotation.kind),
              {key: annotation.id,
               props: {...props,
                       annotation: annotation}},
              annotateAndConvertToVNodes(h, childTuples, index, enclosingAnnotationIds.concat([annotation.id]))),
            props.startOffset,
            props.endOffset];
  };

// Loop through the tuples and add annotations when found
const insertAnnotations = (h, index, enclosingAnnotationIds) =>
  (modifiedTuples, tuple, idx, orgTuples) => {
    let [node, start, end] = tuple;
    let [prevNode, prevStart, prevEnd] = last(modifiedTuples) ||
        [null, null, orgTuples[0][1]];

    // if the previous tuple's end offset is greater than the current tuple's
    // start offset, the current tuple has already been grouped into an
    // annotation's children list so skip it in modifiedTuples so as not
    // to duplicate it in the render
    return prevEnd > start
      ? modifiedTuples
      : modifiedTuples.concat([
        store.getters['annotations/getAtIndexAndOffset'](index, start)
        // longest to shortest
          .sort((a, b) =>
                (b.end_paragraph == index ? b.end_offset : Number.MAX_VALUE) -
                (a.end_paragraph == index ? a.end_offset : Number.MAX_VALUE))
        // Remove any annotations that have already been rendered upstream
          .filter(a => !enclosingAnnotationIds.includes(a.id))
        // We only want the first annotation, for now, but keeping
        // it as an array conveniently allows reduce to
        // return the normal tuple as a default if no annotations exist
          .slice(0, 1)
          .reduce(groupIntoAnnotation(h, index, orgTuples, enclosingAnnotationIds), tuple)]);
  };

// Vue component children arrays must contain either VNodes or
// Strings (which get converted to VNodes automatically)
const tupleToVNode = (h, index, enclosingAnnotationIds = []) =>
  ([node, start, end]) => {
    if(isText(node)) {
      return node.textContent;
    } else if(isElement(node)) {
      let tag = getTagName(node),
          data = {attrs: getAttrsMap(node)},
          children = annotateAndConvertToVNodes(h, filterAndSplitNodeList(node.childNodes, index, start, end), index, enclosingAnnotationIds);
      switch(tag) {
      case "footnote-link":
        data.props = {enclosingAnnotationIds: enclosingAnnotationIds};
        break;
      }
      return h(tag, data, children);
    } else {
      return node;
    }
  };

// Return the offsets within this element where
// annotations need to start or end
const annotationBreakpoints = (index, start, end) =>
      store.getters['annotations/getWithinIndexAndOffsets'](index, start, end)
      .reduce((offsets, annotation) =>
              offsets.concat(
                ["start", "end"]
                  .filter(s => annotation[`${s}_paragraph`] == index)
                  .map(s => annotation[`${s}_offset`]))
              , [])
      .filter((n, i, s) => s.indexOf(n) === i) // remove dupes
      .sort((a, b) => a - b); // sort lowest to highest

const filterAndSplitNodeList = (nodeList, index, start, end) => {
  const breakpoints = annotationBreakpoints(index, start, end);
  return Array.from(nodeList)
  // remove anything that isn't an Element or Text node
  // i.e. no script or comment tags etc
    .filter(isValidNodeType)
  // transform our Node array to an array of [Node, start, end] tuples
    .reduce(transformToTuplesWithOffsets(start), [])
  // break next nodes at points where annotations exist
    .reduce((tuples, tuple) => tuples.concat(
      isText(tuple[0])
        ? splitTextAt(breakpoints, tuple)
        : [tuple]
    ), []);
};

export default {
  components: {
    ResourceSectionWrapper,
    ElisionAnnotation,
    ReplacementAnnotation,
    HighlightAnnotation,
    LinkAnnotation,
    NoteAnnotation,
    FootnoteLink
  },
  props: {
    el: {type: HTMLElement},
    index: {type: Number}
  },
  render(h) {
    return h("resource-section-wrapper",
             {props: {index: this.index,
                      length: getLength(this.el)}},
             [tupleToVNode(h, this.index)([this.el, 0, getLength(this.el)])]);
  }
};
<|endoftext|>


<commit_before>goog.provide('ol.View');
goog.provide('ol.ViewHint');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('ol.IView');
goog.require('ol.Object');

/**
 * @enum {number}
 */
ol.ViewHint = {
  ANIMATING: 0,
  INTERACTING: 1
};

/**
 * @constructor
 * @implements {ol.IView}
 * @extends {ol.Object}
 */
ol.View = function() {

  goog.base(this);

  /**
   * @private
   * @type {Array.<number>}
   */
  this.hints_ = [0, 0];

};
goog.inherits(ol.View, ol.Object);

/**
 * @return {Array.<number>} Hint.
 */
ol.View.prototype.getHints = function() {
  return goog.array.clone(this.hints_);
};

/**
 * @inheritDoc
 */
ol.View.prototype.getView2D = goog.abstractMethod;

/**
 * @inheritDoc
 */
ol.View.prototype.getView3D = goog.abstractMethod;

/**
 * @param {ol.ViewHint} hint Hint.
 * @param {number} delta Delta.
 */
ol.View.prototype.setHint = function(hint, delta) {
  goog.asserts.assert(0 <= hint && hint < this.hints_.length);
  this.hints_[hint] += delta;
  goog.asserts.assert(this.hints_[hint] >= 0);
};
<commit_msg>setHint returns new hint value<commit_after>goog.provide('ol.View');
goog.provide('ol.ViewHint');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('ol.IView');
goog.require('ol.Object');

/**
 * @enum {number}
 */
ol.ViewHint = {
  ANIMATING: 0,
  INTERACTING: 1
};

/**
 * @constructor
 * @implements {ol.IView}
 * @extends {ol.Object}
 */
ol.View = function() {

  goog.base(this);

  /**
   * @private
   * @type {Array.<number>}
   */
  this.hints_ = [0, 0];

};
goog.inherits(ol.View, ol.Object);

/**
 * @return {Array.<number>} Hint.
 */
ol.View.prototype.getHints = function() {
  return goog.array.clone(this.hints_);
};

/**
 * @inheritDoc
 */
ol.View.prototype.getView2D = goog.abstractMethod;

/**
 * @inheritDoc
 */
ol.View.prototype.getView3D = goog.abstractMethod;

/**
 * @param {ol.ViewHint} hint Hint.
 * @param {number} delta Delta.
 * @return {number} New value.
 */
ol.View.prototype.setHint = function(hint, delta) {
  goog.asserts.assert(0 <= hint && hint < this.hints_.length);
  this.hints_[hint] += delta;
  goog.asserts.assert(this.hints_[hint] >= 0);
  return this.hints_[hint];
};
<|endoftext|>


<commit_before>
const RG = require('./rg.js');
RG.System = require('./system.js');
RG.Map = require('./map.js');
RG.Time = require('./time.js');

/* Game engine which handles turn scheduling, systems updates and in-game
 * messaging between objects. */
const Engine = function(eventPool) {

    // Ignore GUI commands by default
    this.isGUICommand = () => false;
    this.doGUICommand = null;

    this.nextActor = null;
    this.animation = null;
    this.animationCallback = null;

    const _levelMap = {}; // All levels, ID -> level
    const _activeLevels = []; // Only these levels are simulated
    const _scheduler = new RG.Time.Scheduler();
    const _msg = new RG.MessageHandler();
    const _eventPool = eventPool;

    this.getMessages = () => _msg.getMessages();
    this.hasNewMessages = () => _msg.hasNew();
    this.clearMessages = () => { _msg.clear();};

    //--------------------------------------------------------------
    // ECS SYSTEMS
    //--------------------------------------------------------------

    // These systems updated after each action. Order is important, for example,
    // animations should be seen before actors are killed
    this.systemOrder = ['Disability', 'SpiritBind', 'Attack', 'Chat',
        'Shop', 'SpellCast',
        'SpellEffect', 'Missile', 'Movement', 'Animation', 'Damage',
        'Skills', 'ExpPoints', 'Communication'];

    this.systems = {};
    this.systems.Disability = new RG.System.Disability(
        ['Stun', 'Paralysis']);
    this.systems.SpiritBind = new RG.System.SpiritBind(['SpiritBind']);
    this.systems.Chat = new RG.System.Chat(['Chat']);
    this.systems.Shop = new RG.System.Shop(['Transaction']);
    this.systems.Attack = new RG.System.Attack(['Attack']);
    this.systems.Missile = new RG.System.Missile(['Missile']);
    this.systems.Movement = new RG.System.Movement(['Movement']);
    this.systems.SpellCast = new RG.System.SpellCast(['SpellCast',
        'PowerDrain']);
    this.systems.SpellEffect = new RG.System.SpellEffect(
        ['SpellRay', 'SpellCell', 'SpellMissile', 'SpellArea']);
    this.systems.Animation = new RG.System.Animation(
        ['Animation']);
    this.systems.Damage = new RG.System.Damage(['Damage', 'Health']);
    this.systems.Skills = new RG.System.Skills(['SkillsExp']);
    this.systems.ExpPoints = new RG.System.ExpPoints(
        ['ExpPoints', 'Experience']);
    this.systems.Communication = new RG.System.Communication(
        ['Communication']);

    // Systems updated once each game loop (once for each player action)
    this.loopSystemOrder = ['Hunger'];
    this.loopSystems = {};
    this.loopSystems.Hunger = new RG.System.Hunger(['Action', 'Hunger']);

    // Time-based systems are added to the scheduler directly
    this.timeSystems = {};

    const effects = new RG.System.TimeEffects(['Expiration', 'Poison']);

    this.updateSystems = function() {
        for (let i = 0; i < this.systemOrder.length; i++) {
            const sysName = this.systemOrder[i];
            this.systems[sysName].update();
        }
    };

    this.updateLoopSystems = function() {
        for (let i = 0; i < this.loopSystemOrder.length; i++) {
            const sysName = this.loopSystemOrder[i];
            this.loopSystems[sysName].update();
        }
    };

    //--------------------------------------------------------------
    // SCHEDULING/ACTIONS
    //--------------------------------------------------------------

    /* Returns next actor from the scheduling queue.*/
    this.getNextActor = () => _scheduler.next();

    /* Adds an actor to the scheduler. */
    this.addActor = actor => {
        _scheduler.add(actor, true, 0);
    };

    /* Removes an actor from a scheduler.*/
    this.removeActor = actor => {
        _scheduler.remove(actor);
    };

    /* Adds an event to the scheduler.*/
    this.addEvent = gameEvent => {
        const repeat = gameEvent.getRepeat();
        const offset = gameEvent.getOffset();
        _scheduler.add(gameEvent, repeat, offset);
    };

    /* Performs one game action.*/
    this.doAction = action => {
        _scheduler.setAction(action);
        action.doAction();
        if (action.hasOwnProperty('energy')) {
            if (action.hasOwnProperty('actor')) {
                const actor = action.actor;
                if (actor.has('Action')) {
                    actor.get('Action').addEnergy(action.energy);
                }
            }
        }
    };

    //--------------------------------------------------------------
    // GAME LOOPS
    //--------------------------------------------------------------

    /* Main update command. Call this either with cmd to perform, or object
     * containing the pressed keycode. */
    this.update = function(obj) {
        if (!this.isGameOver()) {
            this.clearMessages();

            if (this.nextActor !== null) {
                if (obj.hasOwnProperty('code')) {
                    const code = obj.code;
                    if (this.isGUICommand(code)) {
                        this.doGUICommand(code);
                    }
                    else {
                        this.updateGameLoop({code});
                    }
                }
                else {
                    this.updateGameLoop(obj);
                }
            }

        }
        else {
            this.clearMessages();
            _eventPool.emitEvent(RG.EVT_MSG, {msg: 'GAME OVER!'});
            this.simulateGame(100);
        }
    };

    /* Updates the loop by executing one player command, then looping until
     * next player command.*/
    this.updateGameLoop = function(obj) {
        this.playerCommand(obj);
        this.currPlayer = this.nextActor;
        this.nextActor = this.getNextActor();

        // Next/act until player found, then go back waiting for key...
        while (!this.nextActor.isPlayer() && !this.isGameOver()) {
            const action = this.nextActor.nextAction();
            this.doAction(action);

            this.updateSystems(); // All systems for each actor

            // TODO check any animations that should be shown

            this.nextActor = this.getNextActor();
            if (RG.isNullOrUndef([this.nextActor])) {
                RG.err('Game.Engine', 'updateGameLoop',
                    'Game loop out of events! Fatal!');
                break; // if errors suppressed (testing), breaks the loop
            }
        }

        this.updateLoopSystems(); // Loop systems once per player action

    };

    /* Simulates the game without a player.*/
    this.simulateGame = function(nTurns = 1) {
        for (let i = 0; i < nTurns; i++) {
            this.nextActor = this.getNextActor();

            if (!this.nextActor.isPlayer()) {
                const action = this.nextActor.nextAction();
                this.doAction(action);
                this.updateSystems();
            }
            else {
                RG.err('Engine', 'simulateGame',
                    "Doesn't work with player.");
            }
        }
    };

    this.playerCommand = function(obj) {
        if (this.nextActor.isPlayer() === false) {
            let msg = '';
            if (this.nextActor.hasOwnProperty('isEvent')) {
                msg = 'Expected player, got an event: ';
            }
            else {
                msg = 'Expected player, got: ' + this.nextActor.getName();
            }
            msg += '\n' + JSON.stringify(this.nextActor);
            RG.err('Engine', 'playerCommand', msg);
        }
        const action = this.nextActor.nextAction(obj);
        this.doAction(action);
        this.updateSystems();
        this.playerCommandCallback(this.nextActor);
    };

    //--------------------------------------------------------------
    // MANAGING ACTIVE LEVELS
    //--------------------------------------------------------------

    this.numActiveLevels = () => _activeLevels.length;

    this.hasLevel = level => _levelMap.hasOwnProperty(level.getID());

    /* Adds one level to the game database.*/
    this.addLevel = level => {
        const id = level.getID();
        if (!_levelMap.hasOwnProperty(id)) {
            _levelMap[level.getID()] = level;
        }
        else {
            RG.err('Game.Engine', 'addLevel',
                'Level ID ' + id + ' already exists!');
        }
    };

    /* Adds an active level. Only these levels are simulated.*/
    this.addActiveLevel = function(level) {
        const levelID = level.getID();
        const index = _activeLevels.indexOf(levelID);

        // Check if a level must be removed
        if (_activeLevels.length === (RG.MAX_ACTIVE_LEVELS)) {
            if (index === -1) { // No room for new level, pop one
                const removedLevelID = _activeLevels.pop();
                const removedLevel = _levelMap[removedLevelID];
                if (removedLevel) {
                    const rmvActors = removedLevel.getActors();
                    for (let i = 0; i < rmvActors.length; i++) {
                        rmvActors[i].get('Action').disable();
                    }
                    RG.debug(this, 'Removed active level to make space...');
                }
                else {
                    const levelIDs = Object.keys(_levelMap).join(', ');
                    RG.err('Game.Engine', 'addActiveLevel',
                        `Failed to remove level ID ${removedLevelID}.
                        IDs: ${levelIDs}`);
                }
            }
            else { // Level already in actives, move to the front only
                _activeLevels.splice(index, 1);
                _activeLevels.unshift(levelID);
                RG.debug(this, 'Moved level to the front of active levels.');
            }
        }

        // This is a new level, enable all actors by enabling Action comp
        if (index === -1) {
            _activeLevels.unshift(levelID);
            const actActors = level.getActors();
            for (let j = 0; j < actActors.length; j++) {
                actActors[j].get('Action').enable();
            }
        }
    };

    this.isGameOver = () => false;

    this.isActiveLevel = level => {
        const index = _activeLevels.indexOf(level.getID());
        return index >= 0;
    };

    /* Adds a TimeSystem into the engine. Each system can be updated with given
     * intervals instead of every turn or loop.*/
    this.addTimeSystem = function(name, obj) {
        this.timeSystems[name] = obj;
        // Must schedule the system to activate it
        const updateEvent = new RG.Time.GameEvent(100,
            obj.update.bind(obj), true, 0);
        this.addEvent(updateEvent);
    };
    this.addTimeSystem('TimeEffects', effects);

    //--------------------------------------------------------------
    // EVENT LISTENING
    //--------------------------------------------------------------

    this.hasNotify = true;
    this.notify = function(evtName, args) {
        if (evtName === RG.EVT_DESTROY_ITEM) {
            const item = args.item;

            // chaining due to inventory container
            const owner = item.getOwner().getOwner();
            if (!owner.getInvEq().removeItem(item)) {
                RG.err('Game.Engine', 'notify - DESTROY_ITEM',
                    'Failed to remove item from inventory.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_ADDED) {
            if (args.hasOwnProperty('actor')) {
                this.addActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_ADDED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_REMOVED) {
            if (args.hasOwnProperty('actor')) {
                this.removeActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_REMOVED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_ENABLED) {
            if (args.hasOwnProperty('actor')) {
                this.addActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_ENABLED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_DISABLED) {
            if (args.hasOwnProperty('actor')) {
                this.removeActor(args.actor);
            }
            else {
                RG.err('Game', 'notify - ACT_COMP_DISABLED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_LEVEL_PROP_ADDED) {
            if (args.propType === 'actors') {
                if (this.isActiveLevel(args.level)) {
                    // args.obj is actor
                    args.obj.get('Action').enable();
                }
            }
        }
        else if (evtName === RG.EVT_LEVEL_CHANGED) {
            const actor = args.actor;
            if (actor.isPlayer()) {
                this.addActiveLevel(actor.getLevel());
                args.src.onExit();
                args.src.onFirstExit();
                args.target.onEnter();
                args.target.onFirstEnter();
            }
        }
        else if (evtName === RG.EVT_ANIMATION) {
            if (this.animationCallback) {
                this.animation = args.animation;
                this.animationCallback(this.animation);
            }
        }
    };
    _eventPool.listenEvent(RG.EVT_DESTROY_ITEM, this);
    _eventPool.listenEvent(RG.EVT_ACT_COMP_ADDED, this);
    _eventPool.listenEvent(RG.EVT_ACT_COMP_REMOVED, this);
    _eventPool.listenEvent(RG.EVT_ACT_COMP_ENABLED, this);
    _eventPool.listenEvent(RG.EVT_ACT_COMP_DISABLED, this);
    _eventPool.listenEvent(RG.EVT_LEVEL_PROP_ADDED, this);
    _eventPool.listenEvent(RG.EVT_LEVEL_CHANGED, this);
    _eventPool.listenEvent(RG.EVT_ANIMATION, this);

    this.hasAnimation = function() {
        return this.animation !== null &&
            this.animation.hasFrames();
    };

};

module.exports = Engine;
<commit_msg>Added System.Battle.<commit_after>
const RG = require('./rg.js');
RG.System = require('./system.js');
RG.Map = require('./map.js');
RG.Time = require('./time.js');

/* Game engine which handles turn scheduling, systems updates and in-game
 * messaging between objects. */
const Engine = function(eventPool) {

    // Ignore GUI commands by default
    this.isGUICommand = () => false;
    this.doGUICommand = null;

    this.nextActor = null;
    this.animation = null;
    this.animationCallback = null;

    const _levelMap = {}; // All levels, ID -> level
    const _activeLevels = []; // Only these levels are simulated
    const _scheduler = new RG.Time.Scheduler();
    const _msg = new RG.MessageHandler();
    const _eventPool = eventPool;

    this.getMessages = () => _msg.getMessages();
    this.hasNewMessages = () => _msg.hasNew();
    this.clearMessages = () => { _msg.clear();};

    //--------------------------------------------------------------
    // ECS SYSTEMS
    //--------------------------------------------------------------

    // These systems updated after each action. Order is important, for example,
    // animations should be seen before actors are killed
    this.systemOrder = ['Disability', 'SpiritBind', 'Attack', 'Chat',
        'Shop', 'SpellCast',
        'SpellEffect', 'Missile', 'Movement', 'Animation', 'Damage',
        'Battle',
        'Skills', 'ExpPoints', 'Communication'];

    this.systems = {};
    this.systems.Disability = new RG.System.Disability(
        ['Stun', 'Paralysis']);
    this.systems.SpiritBind = new RG.System.SpiritBind(['SpiritBind']);
    this.systems.Chat = new RG.System.Chat(['Chat']);
    this.systems.Shop = new RG.System.Shop(['Transaction']);
    this.systems.Attack = new RG.System.Attack(['Attack']);
    this.systems.Missile = new RG.System.Missile(['Missile']);
    this.systems.Movement = new RG.System.Movement(['Movement']);
    this.systems.SpellCast = new RG.System.SpellCast(['SpellCast',
        'PowerDrain']);
    this.systems.SpellEffect = new RG.System.SpellEffect(
        ['SpellRay', 'SpellCell', 'SpellMissile', 'SpellArea']);
    this.systems.Animation = new RG.System.Animation(
        ['Animation']);
    this.systems.Damage = new RG.System.Damage(['Damage', 'Health']);
    this.systems.Battle = new RG.System.Battle(['BattleBadge']);
    this.systems.Skills = new RG.System.Skills(['SkillsExp']);
    this.systems.ExpPoints = new RG.System.ExpPoints(
        ['ExpPoints', 'Experience']);
    this.systems.Communication = new RG.System.Communication(
        ['Communication']);

    // Systems updated once each game loop (once for each player action)
    this.loopSystemOrder = ['Hunger'];
    this.loopSystems = {};
    this.loopSystems.Hunger = new RG.System.Hunger(['Action', 'Hunger']);

    // Time-based systems are added to the scheduler directly
    this.timeSystems = {};

    const effects = new RG.System.TimeEffects(['Expiration', 'Poison']);

    this.updateSystems = function() {
        for (let i = 0; i < this.systemOrder.length; i++) {
            const sysName = this.systemOrder[i];
            this.systems[sysName].update();
        }
    };

    this.updateLoopSystems = function() {
        for (let i = 0; i < this.loopSystemOrder.length; i++) {
            const sysName = this.loopSystemOrder[i];
            this.loopSystems[sysName].update();
        }
    };

    //--------------------------------------------------------------
    // SCHEDULING/ACTIONS
    //--------------------------------------------------------------

    /* Returns next actor from the scheduling queue.*/
    this.getNextActor = () => _scheduler.next();

    /* Adds an actor to the scheduler. */
    this.addActor = actor => {
        _scheduler.add(actor, true, 0);
    };

    /* Removes an actor from a scheduler.*/
    this.removeActor = actor => {
        _scheduler.remove(actor);
    };

    /* Adds an event to the scheduler.*/
    this.addEvent = gameEvent => {
        const repeat = gameEvent.getRepeat();
        const offset = gameEvent.getOffset();
        _scheduler.add(gameEvent, repeat, offset);
    };

    /* Performs one game action.*/
    this.doAction = action => {
        _scheduler.setAction(action);
        action.doAction();
        if (action.hasOwnProperty('energy')) {
            if (action.hasOwnProperty('actor')) {
                const actor = action.actor;
                if (actor.has('Action')) {
                    actor.get('Action').addEnergy(action.energy);
                }
            }
        }
    };

    //--------------------------------------------------------------
    // GAME LOOPS
    //--------------------------------------------------------------

    /* Main update command. Call this either with cmd to perform, or object
     * containing the pressed keycode. */
    this.update = function(obj) {
        if (!this.isGameOver()) {
            this.clearMessages();

            if (this.nextActor !== null) {
                if (obj.hasOwnProperty('code')) {
                    const code = obj.code;
                    if (this.isGUICommand(code)) {
                        this.doGUICommand(code);
                    }
                    else {
                        this.updateGameLoop({code});
                    }
                }
                else {
                    this.updateGameLoop(obj);
                }
            }

        }
        else {
            this.clearMessages();
            _eventPool.emitEvent(RG.EVT_MSG, {msg: 'GAME OVER!'});
            this.simulateGame(100);
        }
    };

    /* Updates the loop by executing one player command, then looping until
     * next player command.*/
    this.updateGameLoop = function(obj) {
        this.playerCommand(obj);
        this.currPlayer = this.nextActor;
        this.nextActor = this.getNextActor();

        // Next/act until player found, then go back waiting for key...
        while (!this.nextActor.isPlayer() && !this.isGameOver()) {
            const action = this.nextActor.nextAction();
            this.doAction(action);

            this.updateSystems(); // All systems for each actor

            // TODO check any animations that should be shown

            this.nextActor = this.getNextActor();
            if (RG.isNullOrUndef([this.nextActor])) {
                RG.err('Game.Engine', 'updateGameLoop',
                    'Game loop out of events! Fatal!');
                break; // if errors suppressed (testing), breaks the loop
            }
        }

        this.updateLoopSystems(); // Loop systems once per player action

    };

    /* Simulates the game without a player.*/
    this.simulateGame = function(nTurns = 1) {
        for (let i = 0; i < nTurns; i++) {
            this.nextActor = this.getNextActor();

            if (!this.nextActor.isPlayer()) {
                const action = this.nextActor.nextAction();
                this.doAction(action);
                this.updateSystems();
            }
            else {
                RG.err('Engine', 'simulateGame',
                    "Doesn't work with player.");
            }
        }
    };

    this.playerCommand = function(obj) {
        if (this.nextActor.isPlayer() === false) {
            let msg = '';
            if (this.nextActor.hasOwnProperty('isEvent')) {
                msg = 'Expected player, got an event: ';
            }
            else {
                msg = 'Expected player, got: ' + this.nextActor.getName();
            }
            msg += '\n' + JSON.stringify(this.nextActor);
            RG.err('Engine', 'playerCommand', msg);
        }
        const action = this.nextActor.nextAction(obj);
        this.doAction(action);
        this.updateSystems();
        this.playerCommandCallback(this.nextActor);
    };

    //--------------------------------------------------------------
    // MANAGING ACTIVE LEVELS
    //--------------------------------------------------------------

    this.numActiveLevels = () => _activeLevels.length;

    this.hasLevel = level => _levelMap.hasOwnProperty(level.getID());

    /* Adds one level to the game database.*/
    this.addLevel = level => {
        const id = level.getID();
        if (!_levelMap.hasOwnProperty(id)) {
            _levelMap[level.getID()] = level;
        }
        else {
            RG.err('Game.Engine', 'addLevel',
                'Level ID ' + id + ' already exists!');
        }
    };

    /* Adds an active level. Only these levels are simulated.*/
    this.addActiveLevel = function(level) {
        const levelID = level.getID();
        const index = _activeLevels.indexOf(levelID);

        // Check if a level must be removed
        if (_activeLevels.length === (RG.MAX_ACTIVE_LEVELS)) {
            if (index === -1) { // No room for new level, pop one
                const removedLevelID = _activeLevels.pop();
                const removedLevel = _levelMap[removedLevelID];
                if (removedLevel) {
                    const rmvActors = removedLevel.getActors();
                    for (let i = 0; i < rmvActors.length; i++) {
                        rmvActors[i].get('Action').disable();
                    }
                    RG.debug(this, 'Removed active level to make space...');
                }
                else {
                    const levelIDs = Object.keys(_levelMap).join(', ');
                    RG.err('Game.Engine', 'addActiveLevel',
                        `Failed to remove level ID ${removedLevelID}.
                        IDs: ${levelIDs}`);
                }
            }
            else { // Level already in actives, move to the front only
                _activeLevels.splice(index, 1);
                _activeLevels.unshift(levelID);
                RG.debug(this, 'Moved level to the front of active levels.');
            }
        }

        // This is a new level, enable all actors by enabling Action comp
        if (index === -1) {
            _activeLevels.unshift(levelID);
            const actActors = level.getActors();
            for (let j = 0; j < actActors.length; j++) {
                actActors[j].get('Action').enable();
            }
        }
    };

    this.isGameOver = () => false;

    this.isActiveLevel = level => {
        const index = _activeLevels.indexOf(level.getID());
        return index >= 0;
    };

    /* Adds a TimeSystem into the engine. Each system can be updated with given
     * intervals instead of every turn or loop.*/
    this.addTimeSystem = function(name, obj) {
        this.timeSystems[name] = obj;
        // Must schedule the system to activate it
        const updateEvent = new RG.Time.GameEvent(100,
            obj.update.bind(obj), true, 0);
        this.addEvent(updateEvent);
    };
    this.addTimeSystem('TimeEffects', effects);

    //--------------------------------------------------------------
    // EVENT LISTENING
    //--------------------------------------------------------------

    this.hasNotify = true;
    this.notify = function(evtName, args) {
        if (evtName === RG.EVT_DESTROY_ITEM) {
            const item = args.item;

            // chaining due to inventory container
            const owner = item.getOwner().getOwner();
            if (!owner.getInvEq().removeItem(item)) {
                RG.err('Game.Engine', 'notify - DESTROY_ITEM',
                    'Failed to remove item from inventory.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_ADDED) {
            if (args.hasOwnProperty('actor')) {
                this.addActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_ADDED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_REMOVED) {
            if (args.hasOwnProperty('actor')) {
                this.removeActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_REMOVED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_ENABLED) {
            if (args.hasOwnProperty('actor')) {
                this.addActor(args.actor);
            }
            else {
                RG.err('Game.Engine', 'notify - ACT_COMP_ENABLED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_ACT_COMP_DISABLED) {
            if (args.hasOwnProperty('actor')) {
                this.removeActor(args.actor);
            }
            else {
                RG.err('Game', 'notify - ACT_COMP_DISABLED',
                    'No actor specified for the event.');
            }
        }
        else if (evtName === RG.EVT_LEVEL_PROP_ADDED) {
            if (args.propType === 'actors') {
                if (this.isActiveLevel(args.level)) {
                    // args.obj is actor
                    args.obj.get('Action').enable();
                }
            }
        }
        else if (evtName === RG.EVT_LEVEL_CHANGED) {
            const actor = args.actor;
            if (actor.isPlayer()) {
                this.addActiveLevel(actor.getLevel());
                args.src.onExit();
                args.src.onFirstExit();
                args.target.onEnter();
                args.target.onFirstEnter();
            }
        }
        else if (evtName === RG.EVT_ANIMATION) {
            if (this.animationCallback) {
                this.animation = args.animation;
                this.animationCallback(this.animation);
            }
        }
    };
    _eventPool.listenEvent(RG.EVT_DESTROY_ITEM, this);
    _eventPool.listenEvent(RG.EVT_ACT_COMP_ADDED, this);
    _eventPool.listenEvent(RG.EVT_ACT_COMP_REMOVED, this);
    _eventPool.listenEvent(RG.EVT_ACT_COMP_ENABLED, this);
    _eventPool.listenEvent(RG.EVT_ACT_COMP_DISABLED, this);
    _eventPool.listenEvent(RG.EVT_LEVEL_PROP_ADDED, this);
    _eventPool.listenEvent(RG.EVT_LEVEL_CHANGED, this);
    _eventPool.listenEvent(RG.EVT_ANIMATION, this);

    this.hasAnimation = function() {
        return this.animation !== null &&
            this.animation.hasFrames();
    };

};

module.exports = Engine;
<|endoftext|>


<commit_before>"use strict";
/**
 * JSONバリデーションAngular.jsディレクティブ。
 * @module ./websrc/directives/validate-json
 */
export default /* @ngInject */ function() {
	return {
		require: 'ngModel',
		scope: {},
		/**
		 * JSONのバリデーションを行う。
		 * @function link
		 * @param {Object} scope スコープ。
		 * @param {Object} elm 要素。
		 * @param {Object} attrs 引数。
		 * @param {Object} ctrl コントローラ。
		 */
		link: function(scope, elm, attrs, ctrl) {
			// 最初に呼ばれたタイミングだとパラメータが渡らない場合があるので、監視する
			scope.$watch(() => attrs.validateJson, (attr) => {
				if (attr && attr !== "false" && attr !== "FALSE" && attr !== "0") {
					ctrl.$validators.validateJson = validate;
				} else {
					delete ctrl.$validators.validateJson;
				}
			});

			/**
			 * バリデーションを実施する。
			 * @function validate
			 * @param {string} modelValue モデルの値。
			 * @param {string} viewValue ビューの値。
			 * @returns {boolean} バリデーションOKの場合true。
			 */
			function validate(modelValue, viewValue) {
				// JSONとして無効な値が設定されている場合NG
				if (ctrl.$isEmpty(modelValue)) {
					return true;
				}

				try {
					JSON.parse(modelValue);
				} catch (e) {
					return false;
				}
				return true;
			}
		}
	};
}<commit_msg>jsdocのtypo修正<commit_after>"use strict";
/**
 * JSONバリデーションAngular.jsディレクティブ。
 * @module ./scripts/directives/validate-json
 */
export default /* @ngInject */ function() {
	return {
		require: 'ngModel',
		scope: {},
		/**
		 * JSONのバリデーションを行う。
		 * @function link
		 * @param {Object} scope スコープ。
		 * @param {Object} elm 要素。
		 * @param {Object} attrs 引数。
		 * @param {Object} ctrl コントローラ。
		 */
		link: function(scope, elm, attrs, ctrl) {
			// 最初に呼ばれたタイミングだとパラメータが渡らない場合があるので、監視する
			scope.$watch(() => attrs.validateJson, (attr) => {
				if (attr && attr !== "false" && attr !== "FALSE" && attr !== "0") {
					ctrl.$validators.validateJson = validate;
				} else {
					delete ctrl.$validators.validateJson;
				}
			});

			/**
			 * バリデーションを実施する。
			 * @function validate
			 * @param {string} modelValue モデルの値。
			 * @param {string} viewValue ビューの値。
			 * @returns {boolean} バリデーションOKの場合true。
			 */
			function validate(modelValue, viewValue) {
				// JSONとして無効な値が設定されている場合NG
				if (ctrl.$isEmpty(modelValue)) {
					return true;
				}

				try {
					JSON.parse(modelValue);
				} catch (e) {
					return false;
				}
				return true;
			}
		}
	};
}<|endoftext|>


<commit_before>import Themeable from '~mixins/themeable'

export default {
  functional: true,

  mixins: [Themeable],

  props: {
    flat: Boolean,
    height: {
      type: String,
      default: 'auto'
    },
    img: String,
    hover: Boolean,
    raised: Boolean,
    tile: Boolean
  },

  render (h, { data, props, children, style }) {
    data.staticClass = data.staticClass ? `card ${data.staticClass}` : 'card'
    data.style = style || {}
    data.style.height = props.height

    if (props.horizontal) data.staticClass += ' card--horizontal'
    if (props.hover) data.staticClass += ' card--hover'
    if (props.raised) data.staticClass += ' card--raised'
    if (props.tile) data.staticClass += ' card--tile'
    if (props.flat) data.staticClass += ' card--flat'
    if (props.light) data.staticClass += ' theme--light'
    if (props.dark) data.staticClass += ' theme--dark'

    if (props.img) {
      data.style.background = `url(${props.img}) center center / cover no-repeat`
    }

    return h('div', data, children)
  }
}
<commit_msg>fix for #933<commit_after>import Themeable from '~mixins/themeable'

export default {
  functional: true,

  mixins: [Themeable],

  props: {
    flat: Boolean,
    height: {
      type: String,
      default: 'auto'
    },
    img: String,
    hover: Boolean,
    raised: Boolean,
    tile: Boolean
  },

  render (h, { data, props, children }) {
    data.staticClass = data.staticClass ? `card ${data.staticClass}` : 'card'
    data.style = data.style || {}
    data.style.height = props.height

    if (props.horizontal) data.staticClass += ' card--horizontal'
    if (props.hover) data.staticClass += ' card--hover'
    if (props.raised) data.staticClass += ' card--raised'
    if (props.tile) data.staticClass += ' card--tile'
    if (props.flat) data.staticClass += ' card--flat'
    if (props.light) data.staticClass += ' theme--light'
    if (props.dark) data.staticClass += ' theme--dark'

    if (props.img) {
      data.style.background = `url(${props.img}) center center / cover no-repeat`
    }

    return h('div', data, children)
  }
}
<|endoftext|>


<commit_before>
/* This file contains keyboard definitions and constants, plus some conversion
 * functions between dir-vectors and direction keys. */

const ROT = require('../../lib/rot.js');

const RG = {
    err: function(obj, fun, msg) {
        const formattedMsg = `[ERROR]: ${obj} ${fun} -> |${msg}|`;
        console.error(formattedMsg);
        throw new Error(formattedMsg);
    }
};

/* eslint-disable */
RG.VK_a = ROT.VK_A + 32;
RG.VK_b = ROT.VK_B + 32;
RG.VK_c = ROT.VK_C + 32;
RG.VK_d = ROT.VK_D + 32;
RG.VK_e = ROT.VK_E + 32;
RG.VK_f = ROT.VK_F + 32;
RG.VK_g = ROT.VK_G + 32;
RG.VK_h = ROT.VK_H + 32;
RG.VK_i = ROT.VK_I + 32;
RG.VK_j = ROT.VK_J + 32;
RG.VK_k = ROT.VK_K + 32;
RG.VK_l = ROT.VK_L + 32;
RG.VK_m = ROT.VK_M + 32;
RG.VK_n = ROT.VK_N + 32;
RG.VK_o = ROT.VK_O + 32;
RG.VK_p = ROT.VK_P + 32;
RG.VK_q = ROT.VK_Q + 32;
RG.VK_r = ROT.VK_R + 32;
RG.VK_s = ROT.VK_S + 32;
RG.VK_t = ROT.VK_T + 32;
RG.VK_u = ROT.VK_U + 32;
RG.VK_v = ROT.VK_V + 32;
RG.VK_w = ROT.VK_W + 32;
RG.VK_x = ROT.VK_X + 32;
RG.VK_y = ROT.VK_Y + 32;
RG.VK_z = ROT.VK_Z + 32;
/* eslint-enable */

RG.VK_COMMA = 44;
RG.VK_PERIOD = 46;
RG.VK_LT = 60;
RG.VK_GT = 62;

/* Lookup table object for movement and actions keys.*/
RG.KeyMap = {

    moveKeyMap: { },

    // Start from W, go clock wise on keyboard
    initMap: function() {
        this.moveKeyMap[RG.KEY.MOVE_N] = 0;
        this.moveKeyMap[RG.KEY.MOVE_NE] = 1;
        this.moveKeyMap[RG.KEY.MOVE_E] = 2;
        this.moveKeyMap[RG.KEY.MOVE_SE] = 3;
        this.moveKeyMap[RG.KEY.MOVE_S] = 4;
        this.moveKeyMap[RG.KEY.MOVE_SW] = 5;
        this.moveKeyMap[RG.KEY.MOVE_W] = 6;
        this.moveKeyMap[RG.KEY.MOVE_NW] = 7;

        this.moveKeyMap[ROT.VK_8] = 0;
        this.moveKeyMap[ROT.VK_9] = 1;
        this.moveKeyMap[ROT.VK_6] = 2;
        this.moveKeyMap[ROT.VK_3] = 3;
        this.moveKeyMap[ROT.VK_2] = 4;
        this.moveKeyMap[ROT.VK_1] = 5;
        this.moveKeyMap[ROT.VK_4] = 6;
        this.moveKeyMap[ROT.VK_7] = 7;
    },

    inMoveCodeMap: function(code) {
        return this.moveKeyMap.hasOwnProperty(code);
    },

    isRest: function(code) {return code === RG.VK_s || code === RG.VK_PERIOD;},
    isPickup: function(code) {return code === RG.KEY.PICKUP;},
    isUseStairs: function(code) {
        return code === RG.KEY.USE_STAIRS_DOWN ||
            code === RG.KEY.USE_STAIRS_UP;
    },
    isRunMode: function(code) {return code === RG.KEY.RUN;},
    isFightMode: function(code) {return code === RG.KEY.FIGHT;},
    isConfirmYes: function(code) {return code === RG.KEY.YES;},
    isNextItem: function(code) {return code === RG.KEY.NEXT_ITEM;},
    isToggleDoor: function(code) {return code === RG.KEY.DOOR;},
    isLook: function(code) {return code === RG.KEY.LOOK;},
    isUsePower: function(code) {return code === RG.KEY.POWER;},
    isTargetMode: function(code) {return code === RG.KEY.TARGET;},
    isNextTarget: function(code) {return code === RG.KEY.NEXT;},
    isChat: function(code) {return code === RG.KEY.CHAT;},
    isIssueOrder: function(code) {return code === RG.KEY.ORDER;},
    isSelect: function(code) {return code === RG.KEY.SELECT;},

    /* Based on keycode, computes and returns a new x,y pair. If code is
     * invalid, returns null. */
    getDiff: function(code, x, y) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            const diff = ROT.DIRS[8][this.moveKeyMap[code]];
            const newX = x + diff[0];
            const newY = y + diff[1];
            return [newX, newY];
        }
        else if (code === RG.VK_s) {
            return [x, y];
        }
        else {
            return null;
        }
    },

    /* Returns a direction vector for given keycode. */
    getDir: function(code) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            return ROT.DIRS[8][this.moveKeyMap[code]];
        }
        else if (this.isRest(code)) {
            return [0, 0];
        }
        return null;
    },

    /* Converts a direction vector to keycode. */
    dirToKeyCode: function(dXArg, dYArg) {
        // Normalize first to unit vector (-1,0 or 1)
        let dX = dXArg;
        let dY = dYArg;
        if (dX !== 0) {dX = dX / Math.abs(dX);}
        if (dY !== 0) {dY = dY / Math.abs(dY);}
        switch (dX) {
            case -1:
                switch (dY) {
                    case -1: return RG.KEY.MOVE_NW;
                    case 0: return RG.KEY.MOVE_W;
                    case 1: return RG.KEY.MOVE_SW;
                    default: RG.err('RG.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 0:
                switch (dY) {
                    case -1: return RG.KEY.MOVE_N;
                    case 0: return RG.KEY.REST;
                    case 1: return RG.KEY.MOVE_S;
                    default: RG.err('RG.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 1:
                switch (dY) {
                    case -1: return RG.KEY.MOVE_NE;
                    case 0: return RG.KEY.MOVE_E;
                    case 1: return RG.KEY.MOVE_SE;
                    default: RG.err('RG.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            default: RG.err('RG.KeyMap', 'dirToKeyCode',
                `Dir ${dX},${dY} not supported`);
        }
        return null;
    },

    keyCodeToCardinalDir: function(code) {
        switch (code) {
            case RG.KEY.MOVE_NW: return 'NW';
            case RG.KEY.MOVE_W: return 'W';
            case RG.KEY.MOVE_SW: return 'SW';
            case RG.KEY.MOVE_N: return 'N';
            case RG.KEY.REST: return 'REST';
            case RG.KEY.MOVE_S: return 'S';
            case RG.KEY.MOVE_NE: return 'NE';
            case RG.KEY.MOVE_E: return 'E';
            case RG.KEY.MOVE_SE: return 'SE';
            default: return '';
        }
    }

};

RG.menuIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y', 'z'
];

/* Given key code, returns the corresponding character in menu indices. */
RG.codeToMenuChar = code => {
    const index = RG.codeToIndex(code);
    return RG.menuIndices[index];
};

/* Convert a selection index into a keycode. For example, if user presses 'a',
 * this function should return keycode for a, ie RG.VK_a. */
RG.selectIndexToCode = indexChar => {
    const arrayIndex = RG.menuIndices.findIndex(val => val === indexChar);
    if (arrayIndex >= 0) {
        if (arrayIndex >= 0 && arrayIndex <= 9) {
            return ROT.VK_0 + arrayIndex;
        }
        else {
            const addToCode = arrayIndex - 10; // Offset in menuIndices
            return RG.VK_a + addToCode;
        }
    }
    RG.err('RG', 'selectIndexToCode',
        `Inv. select index |${indexChar}|`);
    return -1;

};

/* Converts the keycode into a selection index starting from 0. */
RG.codeToIndex = code => {
    if (code >= ROT.VK_0 && code <= ROT.VK_9) {
        return code - ROT.VK_0;
    }
    else if (code >= RG.VK_a && code <= RG.VK_z) {
        return code - RG.VK_a + 10;
    }
    return -1;
};

RG.KEY = {};

// Assign ROT keys to meaningful constants
RG.KEY.MOVE_N = ROT.VK_W + 32;
RG.KEY.MOVE_NE = ROT.VK_E + 32;
RG.KEY.MOVE_E = ROT.VK_D + 32;
RG.KEY.MOVE_SE = ROT.VK_C + 32;
RG.KEY.MOVE_S = ROT.VK_X + 32;
RG.KEY.MOVE_SW = ROT.VK_Z + 32;
RG.KEY.MOVE_W = ROT.VK_A + 32;
RG.KEY.MOVE_NW = ROT.VK_Q + 32;

RG.KEY.CHAT = ROT.VK_C;
RG.KEY.DOOR = ROT.VK_O + 32;
RG.KEY.FIGHT = ROT.VK_F + 32;
RG.KEY.LOOK = ROT.VK_L + 32;
RG.KEY.NEXT = RG.VK_n;
RG.KEY.NEXT_ITEM = ROT.VK_H + 32;
RG.KEY.ORDER = ROT.VK_O;
RG.KEY.PICKUP = RG.VK_COMMA;
RG.KEY.POWER = ROT.VK_P + 32;
RG.KEY.QUIT_MENU = RG.VK_q;
RG.KEY.REST = ROT.VK_S + 32;
RG.KEY.RUN = ROT.VK_R + 32;
RG.KEY.SELECT = RG.VK_s;
RG.KEY.TARGET = RG.VK_t;
RG.KEY.USE_STAIRS_DOWN = RG.VK_GT;
RG.KEY.USE_STAIRS_UP = RG.VK_LT;
RG.KEY.YES = ROT.VK_Y + 32;
RG.KeyMap.initMap();

RG.isValidKey = keyCode => {
    let found = false;
    Object.keys(RG.KEY).forEach(key => {
        found = found || RG.KEY[key] === keyCode;
    });
    found = found || RG.KeyMap.inMoveCodeMap(keyCode);
    return found;
};

module.exports = RG;
<commit_msg>All keys and key functions are now referenced via Keys.<commit_after>/* This file contains keyboard definitions and constants, plus some conversion
 * functions between dir-vectors and direction keys. */

const ROT = require('../../lib/rot.js');
const RG = require('./rg');

const Keys = {};

/* eslint-disable */
Keys.VK_a = ROT.VK_A + 32;
Keys.VK_b = ROT.VK_B + 32;
Keys.VK_c = ROT.VK_C + 32;
Keys.VK_d = ROT.VK_D + 32;
Keys.VK_e = ROT.VK_E + 32;
Keys.VK_f = ROT.VK_F + 32;
Keys.VK_g = ROT.VK_G + 32;
Keys.VK_h = ROT.VK_H + 32;
Keys.VK_i = ROT.VK_I + 32;
Keys.VK_j = ROT.VK_J + 32;
Keys.VK_k = ROT.VK_K + 32;
Keys.VK_l = ROT.VK_L + 32;
Keys.VK_m = ROT.VK_M + 32;
Keys.VK_n = ROT.VK_N + 32;
Keys.VK_o = ROT.VK_O + 32;
Keys.VK_p = ROT.VK_P + 32;
Keys.VK_q = ROT.VK_Q + 32;
Keys.VK_r = ROT.VK_R + 32;
Keys.VK_s = ROT.VK_S + 32;
Keys.VK_t = ROT.VK_T + 32;
Keys.VK_u = ROT.VK_U + 32;
Keys.VK_v = ROT.VK_V + 32;
Keys.VK_w = ROT.VK_W + 32;
Keys.VK_x = ROT.VK_X + 32;
Keys.VK_y = ROT.VK_Y + 32;
Keys.VK_z = ROT.VK_Z + 32;
/* eslint-enable */

Keys.VK_COMMA = 44;
Keys.VK_PERIOD = 46;
Keys.VK_LT = 60;
Keys.VK_GT = 62;

/* Lookup table object for movement and actions keys.*/
Keys.KeyMap = {

    moveKeyMap: { },

    // Start from W, go clock wise on keyboard
    initMap: function() {
        this.moveKeyMap[Keys.KEY.MOVE_N] = 0;
        this.moveKeyMap[Keys.KEY.MOVE_NE] = 1;
        this.moveKeyMap[Keys.KEY.MOVE_E] = 2;
        this.moveKeyMap[Keys.KEY.MOVE_SE] = 3;
        this.moveKeyMap[Keys.KEY.MOVE_S] = 4;
        this.moveKeyMap[Keys.KEY.MOVE_SW] = 5;
        this.moveKeyMap[Keys.KEY.MOVE_W] = 6;
        this.moveKeyMap[Keys.KEY.MOVE_NW] = 7;

        this.moveKeyMap[ROT.VK_8] = 0;
        this.moveKeyMap[ROT.VK_9] = 1;
        this.moveKeyMap[ROT.VK_6] = 2;
        this.moveKeyMap[ROT.VK_3] = 3;
        this.moveKeyMap[ROT.VK_2] = 4;
        this.moveKeyMap[ROT.VK_1] = 5;
        this.moveKeyMap[ROT.VK_4] = 6;
        this.moveKeyMap[ROT.VK_7] = 7;
    },

    inMoveCodeMap: function(code) {
        return this.moveKeyMap.hasOwnProperty(code);
    },

    isRest: function(code) {
        return code === Keys.VK_s || code === Keys.VK_PERIOD;
    },
    isPickup: function(code) {return code === Keys.KEY.PICKUP;},
    isUseStairs: function(code) {
        return code === Keys.KEY.USE_STAIRS_DOWN ||
            code === Keys.KEY.USE_STAIRS_UP;
    },
    isRunMode: function(code) {return code === Keys.KEY.RUN;},
    isFightMode: function(code) {return code === Keys.KEY.FIGHT;},
    isConfirmYes: function(code) {return code === Keys.KEY.YES;},
    isNextItem: function(code) {return code === Keys.KEY.NEXT_ITEM;},
    isToggleDoor: function(code) {return code === Keys.KEY.DOOR;},
    isLook: function(code) {return code === Keys.KEY.LOOK;},
    isUsePower: function(code) {return code === Keys.KEY.POWER;},
    isTargetMode: function(code) {return code === Keys.KEY.TARGET;},
    isNextTarget: function(code) {return code === Keys.KEY.NEXT;},
    isChat: function(code) {return code === Keys.KEY.CHAT;},
    isIssueOrder: function(code) {return code === Keys.KEY.ORDER;},
    isSelect: function(code) {return code === Keys.KEY.SELECT;},

    /* Based on keycode, computes and returns a new x,y pair. If code is
     * invalid, returns null. */
    getDiff: function(code, x, y) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            const diff = ROT.DIRS[8][this.moveKeyMap[code]];
            const newX = x + diff[0];
            const newY = y + diff[1];
            return [newX, newY];
        }
        else if (code === Keys.VK_s) {
            return [x, y];
        }
        else {
            return null;
        }
    },

    /* Returns a direction vector for given keycode. */
    getDir: function(code) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            return ROT.DIRS[8][this.moveKeyMap[code]];
        }
        else if (this.isRest(code)) {
            return [0, 0];
        }
        return null;
    },

    /* Converts a direction vector to keycode. */
    dirToKeyCode: function(dXArg, dYArg) {
        // Normalize first to unit vector (-1,0 or 1)
        let dX = dXArg;
        let dY = dYArg;
        if (dX !== 0) {dX = dX / Math.abs(dX);}
        if (dY !== 0) {dY = dY / Math.abs(dY);}
        switch (dX) {
            case -1:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_NW;
                    case 0: return Keys.KEY.MOVE_W;
                    case 1: return Keys.KEY.MOVE_SW;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 0:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_N;
                    case 0: return Keys.KEY.REST;
                    case 1: return Keys.KEY.MOVE_S;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 1:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_NE;
                    case 0: return Keys.KEY.MOVE_E;
                    case 1: return Keys.KEY.MOVE_SE;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                `Dir ${dX},${dY} not supported`);
        }
        return null;
    },

    keyCodeToCardinalDir: function(code) {
        switch (code) {
            case Keys.KEY.MOVE_NW: return 'NW';
            case Keys.KEY.MOVE_W: return 'W';
            case Keys.KEY.MOVE_SW: return 'SW';
            case Keys.KEY.MOVE_N: return 'N';
            case Keys.KEY.REST: return 'REST';
            case Keys.KEY.MOVE_S: return 'S';
            case Keys.KEY.MOVE_NE: return 'NE';
            case Keys.KEY.MOVE_E: return 'E';
            case Keys.KEY.MOVE_SE: return 'SE';
            default: return '';
        }
    }

};

Keys.menuIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y', 'z'
];

/* Given key code, returns the corresponding character in menu indices. */
Keys.codeToMenuChar = code => {
    const index = Keys.codeToIndex(code);
    return Keys.menuIndices[index];
};

/* Convert a selection index into a keycode. For example, if user presses 'a',
 * this function should return keycode for a, ie Keys.VK_a. */
Keys.selectIndexToCode = indexChar => {
    const arrayIndex = Keys.menuIndices.findIndex(val => val === indexChar);
    if (arrayIndex >= 0) {
        if (arrayIndex >= 0 && arrayIndex <= 9) {
            return ROT.VK_0 + arrayIndex;
        }
        else {
            const addToCode = arrayIndex - 10; // Offset in menuIndices
            return Keys.VK_a + addToCode;
        }
    }
    RG.err('RG', 'selectIndexToCode',
        `Inv. select index |${indexChar}|`);
    return -1;

};

/* Converts the keycode into a selection index starting from 0. */
Keys.codeToIndex = code => {
    if (code >= ROT.VK_0 && code <= ROT.VK_9) {
        return code - ROT.VK_0;
    }
    else if (code >= Keys.VK_a && code <= Keys.VK_z) {
        return code - Keys.VK_a + 10;
    }
    return -1;
};

/* Returns true if keyCode corresponds to a numeric key. */
Keys.isNumeric = keyCode => {
    return keyCode >= ROT.VK_0 && keyCode <= ROT.VK_9;
};

Keys.KEY = {};

// Assign ROT keys to meaningful constants
Keys.KEY.MOVE_N = ROT.VK_W + 32;
Keys.KEY.MOVE_NE = ROT.VK_E + 32;
Keys.KEY.MOVE_E = ROT.VK_D + 32;
Keys.KEY.MOVE_SE = ROT.VK_C + 32;
Keys.KEY.MOVE_S = ROT.VK_X + 32;
Keys.KEY.MOVE_SW = ROT.VK_Z + 32;
Keys.KEY.MOVE_W = ROT.VK_A + 32;
Keys.KEY.MOVE_NW = ROT.VK_Q + 32;

Keys.KEY.CHAT = ROT.VK_C;
Keys.KEY.DOOR = ROT.VK_O + 32;
Keys.KEY.FIGHT = ROT.VK_F + 32;
Keys.KEY.LOOK = ROT.VK_L + 32;
Keys.KEY.NEXT = Keys.VK_n;
Keys.KEY.NEXT_ITEM = ROT.VK_H + 32;
Keys.KEY.ORDER = ROT.VK_O;
Keys.KEY.PICKUP = Keys.VK_COMMA;
Keys.KEY.POWER = ROT.VK_P + 32;
Keys.KEY.QUIT_MENU = Keys.VK_q;
Keys.KEY.REST = ROT.VK_S + 32;
Keys.KEY.RUN = ROT.VK_R + 32;
Keys.KEY.SELECT = Keys.VK_s;
Keys.KEY.TARGET = Keys.VK_t;
Keys.KEY.USE_STAIRS_DOWN = Keys.VK_GT;
Keys.KEY.USE_STAIRS_UP = Keys.VK_LT;
Keys.KEY.YES = ROT.VK_Y + 32;
Keys.KeyMap.initMap();

// Used in the GUI only
Keys.GUI = {};
Keys.GUI.Inv = Keys.VK_i;
Keys.GUI.Map = Keys.VK_m;
Keys.GUI.Look = Keys.VK_l;
Keys.GUI.Use = Keys.VK_u;
Keys.GUI.OwMap = ROT.VK_M;
Keys.GUI.Help = ROT.VK_H;
Keys.GUI.CharInfo = ROT.VK_I;

Keys.isValidKey = keyCode => {
    let found = false;
    Object.keys(Keys.KEY).forEach(key => {
        found = found || Keys.KEY[key] === keyCode;
    });
    found = found || Keys.KeyMap.inMoveCodeMap(keyCode);
    return found;
};

module.exports = Keys;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Link } from 'react-router-dom';

import * as FetchAPI from '../fetch_data';

const parse = require('parse-diff');

/* DiffViewer loads a raw diff from Mozilla's hg-web and code coverage from
 * shiptit-uplift to show a diff with code coverage information for added
 * lines.
 */
export default class DiffViewerContainer extends Component {
  state = {
    appError: undefined,
    coverage: undefined,
    parsedDiff: []
  }

  componentDidMount() {
    const { changeset } = this.props;

    FetchAPI.getDiff(changeset).then(response =>
      response.text()
    ).then(text =>
      this.setState({ parsedDiff: parse(text) })
    ).catch(error =>
      this.setState({ appError: error })
    );

    FetchAPI.getChangesetCoverage(changeset).then(response =>
      response.text()
    ).then(text =>
      this.setState({ coverage: JSON.parse(text) })
    ).catch(error =>
      this.setState({ appError: error })
    );
  }

  render() {
    const { changeset } = this.props;
    const { appError, coverage, parsedDiff } = this.state;
    return (
      <DiffViewer
        appError={appError}
        changeset={changeset}
        coverage={coverage}
        parsedDiff={parsedDiff} />
    );
  }
}

const DiffViewer = ({ appError, changeset, coverage, parsedDiff }) => (

    <Link className="return-home" to="/">Return to main page</Link>
    <DiffViewerMeta appError={appError} changeset={changeset} />
    <CoverageMeta coverage={coverage} parsedDiff={parsedDiff} />

    {parsedDiff.map(
      (diffBlock, index) =>
        // We only push down the subset of code coverage data
        // applicable to a file
        (
          <DiffFile
            key={index}
            id={index}
            diffBlock={diffBlock}
            coverage={coverage} />
        )
    )}

);

const DiffViewerMeta = ({ appError, changeset }) => {
  const hgRev = `${FetchAPI.hgHost}/mozilla-central/rev/${changeset}`;
  const ccovUrl = `${FetchAPI.ccovBackend}/coverage/changeset/${changeset}`;

  return (

Link to Hg diff ({changeset})

Link to Code coverage backend

{appError}

  );
};

const NetCoverage = ({ addedLines, coveredLines, netGain }) => (

    {`New lines coverage change: ${netGain}% / `}
    {`Added lines: ${addedLines} / `}
    {`Covered lines: ${coveredLines}`}

);

const CoverageMeta = ({ coverage, parsedDiff }) => {
  if (!coverage || coverage.error || !coverage.diffs) {
    let errorMessage;
    if (!coverage) {
      errorMessage = "We're waiting for coverage data from the backend.";
    } else if (coverage.error) {
      errorMessage = coverage.error;
    } else if (!coverage.diffs) {
      errorMessage = 'This change does not have NEW LINES.';
    }
    return (

{errorMessage}

    );
  }

  const addedLines = parsedDiff.reduce((sum, file) => (
    sum + file.additions), 0);
  const coveredLines = coverage.diffs.reduce((sum, file) => (
    sum + file.changes.reduce((acumm, lineCov) => (
      (lineCov.coverage === 'Y') ? acumm + 1 : acumm), 0)
  ), 0);
  const netGain = (addedLines !== 0) ? coveredLines / addedLines : 0;

  return (

        <NetCoverage
          addedLines={addedLines}
          coveredLines={coveredLines}
          netGain={netGain} />

          {(coverage) ?
            `Current coverage: ${coverage.overall_cur}` :
            'No coverage data available.'}

          {(coverage) ?
            `Build changeset: ${coverage.build_changeset}` : ''}

  );
};

/* A DiffLine contains all diff changes for a specific file */
const DiffFile = ({ coverage, diffBlock }) => {
  // We try to see if the file modified shows up in the code
  // coverage data we have for this diff
  let coverageInfo;
  if (coverage) {
    coverageInfo = (coverage.diffs) ?
      coverage.diffs.find(info => info.name === diffBlock.from) : undefined;
  }

  return (

{diffBlock.from}

      {diffBlock.chunks.map((block, index) => (
        <DiffBlock
          key={index}
          block={block}
          coverageInfo={coverageInfo} />
      ))}

  );
};

/* A DiffBlock is *one* of the blocks changed for a specific file */
const DiffBlock = ({ block, coverageInfo }) => (

{block.content}

        {block.changes.map((change, index) => (
          <DiffLine
            key={index}
            id={index}
            change={change}
            coverageInfo={coverageInfo} />
         ))}

);

/* A DiffLine contains metadata about a line in a DiffBlock */
const DiffLine = ({ change, coverageInfo, id }) => {
  // Information about the line itself
  const c = change;
  // Added, deleted or unchanged line
  const changeType = change.type;
  // CSS tr and td classes
  let rowClass = 'nolinechange';
  const rowId = id;
  // Cell contents
  let [oldLineNumber, newLineNumber] = ['', ''];

  if (changeType === 'add') {
    // Added line - <blank> | <new line number>
    if (coverageInfo) {
      const { coverage } = coverageInfo.changes.find(lineCovInfo =>
        (lineCovInfo.new_line === c.ln));

      if (coverage === 'Y') {
        rowClass = 'hit';
      } else {
        rowClass = 'miss'; // Let's start assuming a miss
      }
    }
    newLineNumber = c.ln;
  } else if (changeType === 'del') {
    // Removed line - <old line number> | <blank>
    oldLineNumber = c.ln;
  } else {
    // Unchanged line - <old line number> | <blank>
    oldLineNumber = c.ln1;
    if (oldLineNumber !== c.ln2) {
      newLineNumber = c.ln2;
    }
  }

  return (

      {oldLineNumber}
      {newLineNumber}

{c.content}

  );
};
<commit_msg>fix: Handle gracefully diffs that have empty code coverage<commit_after>import React, { Component } from 'react';
import { Link } from 'react-router-dom';

import * as FetchAPI from '../fetch_data';

const parse = require('parse-diff');

/* DiffViewer loads a raw diff from Mozilla's hg-web and code coverage from
 * shiptit-uplift to show a diff with code coverage information for added
 * lines.
 */
export default class DiffViewerContainer extends Component {
  state = {
    appError: undefined,
    coverage: undefined,
    parsedDiff: []
  }

  componentDidMount() {
    const { changeset } = this.props;

    FetchAPI.getDiff(changeset).then(response =>
      response.text()
    ).then(text =>
      this.setState({ parsedDiff: parse(text) })
    ).catch(error =>
      this.setState({ appError: error })
    );

    FetchAPI.getChangesetCoverage(changeset).then(response =>
      response.text()
    ).then(text =>
      this.setState({ coverage: JSON.parse(text) })
    ).catch(error =>
      this.setState({ appError: error })
    );
  }

  render() {
    const { changeset } = this.props;
    const { appError, coverage, parsedDiff } = this.state;
    return (
      <DiffViewer
        appError={appError}
        changeset={changeset}
        coverage={coverage}
        parsedDiff={parsedDiff} />
    );
  }
}

const DiffViewer = ({ appError, changeset, coverage, parsedDiff }) => (

    <Link className="return-home" to="/">Return to main page</Link>
    <DiffViewerMeta appError={appError} changeset={changeset} />
    <CoverageMeta coverage={coverage} parsedDiff={parsedDiff} />

    {parsedDiff.map(
      (diffBlock, index) =>
        // We only push down the subset of code coverage data
        // applicable to a file
        (
          <DiffFile
            key={index}
            id={index}
            diffBlock={diffBlock}
            coverage={coverage} />
        )
    )}

);

const DiffViewerMeta = ({ appError, changeset }) => {
  const hgRev = `${FetchAPI.hgHost}/mozilla-central/rev/${changeset}`;
  const ccovUrl = `${FetchAPI.ccovBackend}/coverage/changeset/${changeset}`;

  return (

Link to Hg diff ({changeset})

Link to Code coverage backend

{appError}

  );
};

const NetCoverage = ({ addedLines, coveredLines, netGain }) => (

    {`New lines coverage change: ${netGain}% / `}
    {`Added lines: ${addedLines} / `}
    {`Covered lines: ${coveredLines}`}

);

const CoverageMeta = ({ coverage, parsedDiff }) => {
  let errorMessage;
  if (!coverage || coverage.error) {
    if (!coverage) {
      errorMessage = "We're waiting for coverage data from the backend.";
    } else if (coverage.error) {
      errorMessage = coverage.error;
    }
    return (

{errorMessage}

    );
  }

  const addedLines = parsedDiff.reduce((sum, file) => (
    sum + file.additions), 0);
  const coveredLines = (coverage.diffs.length !== 0) ?
    coverage.diffs.reduce((sum, file) => (
      ('changes' in file) ?
        sum + file.changes.reduce((acumm, lineCov) => (
            (lineCov.coverage === 'Y') ? acumm + 1 : acumm), 0) :
        sum), 0) : 0;

  let netGain = ((addedLines !== 0) ?
    (coveredLines / addedLines) * 100 : 0);
  if (netGain.length > 1) {
    netGain = netGain.toPrecision(3);
  }

  if (coverage.diffs.length === 0) {
    errorMessage = 'This change does not have NEW LINES.';
  }

  return (

        <NetCoverage
          addedLines={addedLines}
          coveredLines={coveredLines}
          netGain={netGain} />

          {(coverage) ?
            `Current coverage: ${coverage.overall_cur}` :
            'No coverage data available.'}

          {(coverage) ?
            `Build changeset: ${coverage.build_changeset}` : ''}

{errorMessage}

  );
};

/* A DiffLine contains all diff changes for a specific file */
const DiffFile = ({ coverage, diffBlock }) => {
  // We try to see if the file modified shows up in the code
  // coverage data we have for this diff
  let coverageInfo;
  if (coverage) {
    coverageInfo = (coverage.diffs) ?
      coverage.diffs.find(info => info.name === diffBlock.from) : undefined;
  }

  return (

{diffBlock.from}

      {diffBlock.chunks.map((block, index) => (
        <DiffBlock
          key={index}
          block={block}
          coverageInfo={coverageInfo} />
      ))}

  );
};

/* A DiffBlock is *one* of the blocks changed for a specific file */
const DiffBlock = ({ block, coverageInfo }) => (

{block.content}

        {block.changes.map((change, index) => (
          <DiffLine
            key={index}
            id={index}
            change={change}
            coverageInfo={coverageInfo} />
         ))}

);

/* A DiffLine contains metadata about a line in a DiffBlock */
const DiffLine = ({ change, coverageInfo, id }) => {
  // Information about the line itself
  const c = change;
  // Added, deleted or unchanged line
  const changeType = change.type;
  // CSS tr and td classes
  let rowClass = 'nolinechange';
  const rowId = id;
  // Cell contents
  let [oldLineNumber, newLineNumber] = ['', ''];

  if (changeType === 'add') {
    // Added line - <blank> | <new line number>
    if (coverageInfo) {
      const { coverage } = coverageInfo.changes.find(lineCovInfo =>
        (lineCovInfo.new_line === c.ln));

      if (coverage === 'Y') {
        rowClass = 'hit';
      } else {
        rowClass = 'miss'; // Let's start assuming a miss
      }
    }
    newLineNumber = c.ln;
  } else if (changeType === 'del') {
    // Removed line - <old line number> | <blank>
    oldLineNumber = c.ln;
  } else {
    // Unchanged line - <old line number> | <blank>
    oldLineNumber = c.ln1;
    if (oldLineNumber !== c.ln2) {
      newLineNumber = c.ln2;
    }
  }

  return (

      {oldLineNumber}
      {newLineNumber}

{c.content}

  );
};
<|endoftext|>


<commit_before>/**
 * Form
 */
'use strict';

(document => {
    /**
     * Check field
     */
    function check(div) {
        if (div.querySelector(':invalid')) {
            div.setAttribute('data-invalid', '');
        } else {
            div.removeAttribute('data-invalid');
        }
    }

    document.addEventListener('DOMContentLoaded', () => {
        document.querySelectorAll('form').forEach(form => {
            form.querySelectorAll('div[data-attr] input, div[data-attr] select, div[data-attr] textarea').forEach(input => {
                const call = ev => check(ev.target.closest('div[data-attr]'));
                input.addEventListener('change', call);
                input.addEventListener('input', call);
            });
            form.querySelector('input[type=submit').addEventListener('click', () => form.querySelectorAll('div[data-attr]').forEach(check));
        });
    });
})(document);
<commit_msg>Temp. solution to allow autocomplete for login<commit_after>/**
 * Form
 */
'use strict';

(document => {
    /**
     * Check field
     */
    function check(div) {
        if (div.querySelector(':invalid')) {
            div.setAttribute('data-invalid', '');
        } else {
            div.removeAttribute('data-invalid');
        }
    }

    document.addEventListener('DOMContentLoaded', () => {
        // Allow autocomplete for login
        document.querySelector('html[data-url="/account/login"] input[type=password]').removeAttribute('autocomplete');

        // Set validity to wrapper div element
        document.querySelectorAll('form').forEach(form => {
            form.querySelectorAll('div[data-attr] input, div[data-attr] select, div[data-attr] textarea').forEach(input => {
                const call = ev => check(ev.target.closest('div[data-attr]'));
                input.addEventListener('change', call);
                input.addEventListener('input', call);
            });
            form.querySelector('input[type=submit').addEventListener('click', () => form.querySelectorAll('div[data-attr]').forEach(check));
        });
    });
})(document);
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Asynchronous eval workaround for lack of eval in Chrome Apps. Do not add
// workaround in Cordova Chrome Apps.
if ( ! (window.cordova && window.chrome) ) {
  TemplateUtil.compile = function() {
    return function() {
      return this.name_ + " wasn't required.  Models must be arequired()'ed for Templates to be compiled in Packaged Apps.";
    };
  };

  var __EVAL_CALLBACKS__ = {};
  var aeval = (function() {
    var nextID = 0;

    var future = afuture();
    if ( ! document.body )
      window.addEventListener('load', future.set);
    else
      future.set();

    return function(src) {
      return aseq(
          future.get,
          function(ret) {
            var id = 'c' + (nextID++);

            var newjs = ['__EVAL_CALLBACKS__["' + id + '"](' + src + ');'];
            var blob  = new Blob(newjs, {type: 'text/javascript'});
            var url   = window.URL.createObjectURL(blob);

            // TODO: best values?
            // url.defer = ?;
            // url.async = ?;

            __EVAL_CALLBACKS__[id] = function(data) {
              delete __EVAL_CALLBACKS__[id];
              ret && ret.call(this, data);
            };

            var script = document.createElement('script');
            script.src = url;
            script.onload = function() {
              this.remove();
              window.URL.revokeObjectURL(url);
              //        document.body.removeChild(this);
            };
            document.body.appendChild(script);
          });
    };
  })();

  var TEMPLATE_FUNCTIONS = [];

  var aevalTemplate = function(t, model) {
    var doEval_ = function(t) {
      // Parse result: [isSimple, maybeCode]: [true, null] or [false, codeString].
      var parseResult = TemplateCompiler.parseString(t.template);

      // Simple case, just a string literal
      if ( parseResult[0] )
        return aconstant(ConstantTemplate(t.language === 'css' ?
            X.foam.grammars.CSS3.create().parser.parseString(t.template).toString() :
            t.template));

      var code = TemplateUtil.HEADER + parseResult[1] + TemplateUtil.FOOTERS[t.language];

      var args = ['opt_out'];
      if ( t.args ) {
        for ( var i = 0 ; i < t.args.length ; i++ ) {
          args.push(t.args[i].name);
        }
      }
      return aeval('function(' + args.join(',') + '){' + code + '}');
    };
    var doEval = function(t) {
      try {
        return doEval_(t);
      } catch (err) {
        console.log('Template Error: ', err);
        console.log(code);
        return aconstant(function() {return 'TemplateError: Check console.';});
      }
    };

    var i = TEMPLATE_FUNCTIONS.length;
    TEMPLATE_FUNCTIONS[i] = '';
    return aseq(
        t.futureTemplate,
        function(ret, t) {
          // Sometimes futureTemplate is called before the global Template model
          // is available. In such cases, "t" will be a map describing a
          // Template object that still needs to be instantiated.
          if ( typeof t.model_ === 'string' ) t = Template.create(t);

          doEval(t)(ret);
        },
        function(ret, f) {
          TEMPLATE_FUNCTIONS[i] = f;
          ret(f);
        });
  };
}
<commit_msg>TemplateUtil/Templates/ChromeEval: More elegant solution to returning-uninitialized-template problem: Re-order mm4Method's models to remove a template initialization hack and eliminate possibility of futureTemplate producing an uninitialized template<commit_after>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Asynchronous eval workaround for lack of eval in Chrome Apps. Do not add
// workaround in Cordova Chrome Apps.
if ( ! (window.cordova && window.chrome) ) {
  TemplateUtil.compile = function() {
    return function() {
      return this.name_ + " wasn't required.  Models must be arequired()'ed for Templates to be compiled in Packaged Apps.";
    };
  };

  var __EVAL_CALLBACKS__ = {};
  var aeval = (function() {
    var nextID = 0;

    var future = afuture();
    if ( ! document.body )
      window.addEventListener('load', future.set);
    else
      future.set();

    return function(src) {
      return aseq(
          future.get,
          function(ret) {
            var id = 'c' + (nextID++);

            var newjs = ['__EVAL_CALLBACKS__["' + id + '"](' + src + ');'];
            var blob  = new Blob(newjs, {type: 'text/javascript'});
            var url   = window.URL.createObjectURL(blob);

            // TODO: best values?
            // url.defer = ?;
            // url.async = ?;

            __EVAL_CALLBACKS__[id] = function(data) {
              delete __EVAL_CALLBACKS__[id];
              ret && ret.call(this, data);
            };

            var script = document.createElement('script');
            script.src = url;
            script.onload = function() {
              this.remove();
              window.URL.revokeObjectURL(url);
              //        document.body.removeChild(this);
            };
            document.body.appendChild(script);
          });
    };
  })();

  var TEMPLATE_FUNCTIONS = [];

  var aevalTemplate = function(t, model) {
    var doEval_ = function(t) {
      // Parse result: [isSimple, maybeCode]: [true, null] or [false, codeString].
      var parseResult = TemplateCompiler.parseString(t.template);

      // Simple case, just a string literal
      if ( parseResult[0] )
        return aconstant(ConstantTemplate(t.language === 'css' ?
            X.foam.grammars.CSS3.create().parser.parseString(t.template).toString() :
            t.template));

      var code = TemplateUtil.HEADER + parseResult[1] + TemplateUtil.FOOTERS[t.language];

      var args = ['opt_out'];
      if ( t.args ) {
        for ( var i = 0 ; i < t.args.length ; i++ ) {
          args.push(t.args[i].name);
        }
      }
      return aeval('function(' + args.join(',') + '){' + code + '}');
    };
    var doEval = function(t) {
      try {
        return doEval_(t);
      } catch (err) {
        console.log('Template Error: ', err);
        console.log(code);
        return aconstant(function() {return 'TemplateError: Check console.';});
      }
    };

    var i = TEMPLATE_FUNCTIONS.length;
    TEMPLATE_FUNCTIONS[i] = '';
    return aseq(
        t.futureTemplate,
        function(ret, t) { doEval(t)(ret); },
        function(ret, f) {
          TEMPLATE_FUNCTIONS[i] = f;
          ret(f);
        });
  };
}
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react'

const Step = ({ children, stepComponent: StepComponent, ...props }) => (
  <StepComponent {...props}>
    {children}
  </StepComponent>
)

Step.propTypes = {
  step: PropTypes.number,
  isValid: PropTypes.bool,
  stepComponent: PropTypes.node.isRequired
}

export default Step
<commit_msg>fix warning for type of stepComponent<commit_after>import React, { PropTypes } from 'react'

const Step = ({ children, stepComponent: StepComponent, ...props }) => (
  <StepComponent {...props}>
    {children}
  </StepComponent>
)

Step.propTypes = {
  step: PropTypes.number,
  isValid: PropTypes.bool,
  stepComponent: PropTypes.any.isRequired
}

export default Step
<|endoftext|>


<commit_before>import { h, Component } from 'preact';
import style from './style';

import System from '../system';
import BatteryLevel from '../battery-level';
import DoNotTrack from '../do-not-track';
import IpAddresses from '../ip-addresses';
import Location from '../location';
import Motion from '../motion';
import Network from '../network';
import Devices from '../devices';
import GetUserMedia from '../get-user-media';

import adapter from 'webrtc-adapter';
window.adapter = adapter;

export default class Home extends Component {
	render() {
		return (

				<System/>
				<BatteryLevel/>
				<Network/>
				<Devices/>
				<DoNotTrack/>
				<IpAddresses/>
				<Motion/>
				<Location/>
				<Location map="map"/>
				<GetUserMedia/>

		);
	}
}
<commit_msg>add vibration<commit_after>import { h, Component } from 'preact';
import style from './style';

import System from '../system';
import BatteryLevel from '../battery-level';
import DoNotTrack from '../do-not-track';
import IpAddresses from '../ip-addresses';
import Location from '../location';
import Motion from '../motion';
import Network from '../network';
import Devices from '../devices';
import GetUserMedia from '../get-user-media';
import Vibrate from '../vibrate';

import adapter from 'webrtc-adapter';
window.adapter = adapter;

export default class Home extends Component {
	render() {
		return (

				<System/>
				<BatteryLevel/>
				<Network/>
				<Devices/>
				<DoNotTrack/>
				<IpAddresses/>
				<Motion/>
				<Location/>
				<Location map="map"/>
				<GetUserMedia/>
				<Vibrate/>

		);
	}
}
<|endoftext|>


<commit_before>'use strict';

/**
 * This is a wrapper to build cli commands when an app has a cli.yml file
 */

module.exports = function(kbox) {

  // Node
  var fs = require('fs');
  var path = require('path');

  // Npm
  var _ = require('lodash');

  kbox.whenAppRegistered(function(app) {

    // Check whether we should load these task or not
    var loadTasks = (app.config.pluginconfig.cli === 'on') ? true : false;

    // Load the tasks
    if (loadTasks) {

      // Cli tasks yml file
      var tasksFile = path.join(app.root, 'cli.yml');

      /*
       * Return the task defaults
       */
      var taskDefaults = function() {
        return {
          services: ['cli'],
          entrypoint: ['/bin/sh', '-c'],
          description: 'Run a command against a container'
        };
      };

      /*
       * Return a run object
       */
      var getRun = function(options) {
        var cliFile = path.join(app.root, 'kalabox-cli.yml');
        return {
          compose: app.composeCore.concat([cliFile]),
          project: app.name,
          opts: {
            mode: 'attach',
            services: [options.service],
            entrypoint: options.entrypoint,
            cmd: options.cmd
          }
        };
      };

      // Check for our tasks and then generate tasks if we have a
      // file
      if (fs.existsSync(tasksFile)) {

        var tasks = kbox.util.yaml.toJson(tasksFile);

        _.forEach(tasks, function(data, cmd) {

          // Merge in default options
          var options = _.merge(taskDefaults(), data);

          // Build the command task
          kbox.tasks.add(function(task) {
            task.path = [app.name, cmd];
            task.category = 'appCmd';
            task.kind = 'delegate';
            task.description = options.description;
            task.func = function() {

              // Shift off our first cmd arg if its also the entrypoint
              // or the name of the command
              // @todo: this implies that our entrypoint should be in the path
              // and not constructed absolutely
              var payload = kbox.core.deps.get('argv').payload;
              if (options.entrypoint === payload[0] || options.stripfirst) {
                payload.shift();
              }

              // Set the payload to be the command
              options.cmd = payload;

              // If we have pre cmd opts then unshift them
              if (options.precmdopts) {
                options.cmd.unshift(options.precmdopts);
              }

              // If we have posrt cmd opts then unshift them
              if (options.postcmdopts) {
                options.cmd.push(options.postcmdopts);
              }

              // Get teh run definition objecti
              var runDef = getRun(options);

              // RUN IT!
              return kbox.engine.run(runDef);

            };
          });

        });

      }
    }

  });

};
<commit_msg>This causes duplicate kalabox-cli.ymls to be references which docker compose 1.6 complains about now<commit_after>'use strict';

/**
 * This is a wrapper to build cli commands when an app has a cli.yml file
 */

module.exports = function(kbox) {

  // Node
  var fs = require('fs');
  var path = require('path');

  // Npm
  var _ = require('lodash');

  kbox.whenAppRegistered(function(app) {

    // Check whether we should load these task or not
    var loadTasks = (app.config.pluginconfig.cli === 'on') ? true : false;

    // Load the tasks
    if (loadTasks) {

      // Cli tasks yml file
      var tasksFile = path.join(app.root, 'cli.yml');

      /*
       * Return the task defaults
       */
      var taskDefaults = function() {
        return {
          services: ['cli'],
          entrypoint: ['/bin/sh', '-c'],
          description: 'Run a command against a container'
        };
      };

      /*
       * Return a run object
       */
      var getRun = function(options) {
        return {
          compose: app.composeCore,
          project: app.name,
          opts: {
            mode: 'attach',
            services: [options.service],
            entrypoint: options.entrypoint,
            cmd: options.cmd
          }
        };
      };

      // Check for our tasks and then generate tasks if we have a
      // file
      if (fs.existsSync(tasksFile)) {

        var tasks = kbox.util.yaml.toJson(tasksFile);

        _.forEach(tasks, function(data, cmd) {

          // Merge in default options
          var options = _.merge(taskDefaults(), data);

          // Build the command task
          kbox.tasks.add(function(task) {
            task.path = [app.name, cmd];
            task.category = 'appCmd';
            task.kind = 'delegate';
            task.description = options.description;
            task.func = function() {

              // Shift off our first cmd arg if its also the entrypoint
              // or the name of the command
              // @todo: this implies that our entrypoint should be in the path
              // and not constructed absolutely
              var payload = kbox.core.deps.get('argv').payload;
              if (options.entrypoint === payload[0] || options.stripfirst) {
                payload.shift();
              }

              // Set the payload to be the command
              options.cmd = payload;

              // If we have pre cmd opts then unshift them
              if (options.precmdopts) {
                options.cmd.unshift(options.precmdopts);
              }

              // If we have posrt cmd opts then unshift them
              if (options.postcmdopts) {
                options.cmd.push(options.postcmdopts);
              }

              // Get teh run definition objecti
              var runDef = getRun(options);

              // RUN IT!
              return kbox.engine.run(runDef);

            };
          });

        });

      }
    }

  });

};
<|endoftext|>


<commit_before>import fs from "fs-promise";
import path from "path";

export default async function(opts) {
	let {basedir=process.cwd()} = opts;
	let pkgfile = path.resolve(basedir, "package.json");
	let pkg = JSON.parse(await fs.readFile(pkgfile, "utf-8"));

	return {
		...opts,
		...pkg.autorelease,
		basedir,
		package: pkg,
		packageFile: pkgfile
	};
}
<commit_msg>feat: add .autoreleaserc file for options<commit_after>import fs from "fs-promise";
import {resolve} from "path";

export default async function(opts) {
	let {basedir=process.cwd()} = opts;
	let pkgfile = resolve(basedir, "package.json");
	let pkg = JSON.parse(await fs.readFile(pkgfile, "utf-8"));

	let rc;
	try {
		rc = await fs.readFile(resolve(basedir, ".autoreleaserc"), "utf-8");
	} catch(e) {
		if (e.code !== "ENOENT") throw e;
	}

	if (rc) rc = JSON.parse(rc);

	return {
		...opts,
		...pkg.autorelease,
		...rc,
		basedir,
		package: pkg,
		packageFile: pkgfile
	};
}
<|endoftext|>