<commit_before>'use strict';
//debugger;

/**
 * @file Character class. Part of the MurmuresJS project.
 * @author github.com/azziliz
 * @author github.com/thyshimrod
 * @license MIT
 */

/**
 * Characters are entities that live, move and act inside a [level]{@link murmures.Level}.
 *
 * Heroes are characters managed by the players.
 * They can be given [orders]{@link murmures.Order} on client side.
 * They can move from one level to another.
 * Mobs are characters managed by the AI.
 * The AI methods to control them are defined in the [game engine]{@link murmures.GameEngine} class.
 * They cannot change level.
 *
 * Three steps are mandatory to create a valid character:
 * 1. Load all character templates from mobs.json.
 * 2. Select templates that are needed for the current level.
 * 2. Use the "instantiate" method in the Character class to create new instances.
 * Step 1 is performed by the game engine during the server startup.
 * Step 2 and 3 are performed together by the server and the game engine when a level is loaded.
 *
 * @class
 */
murmures.Character = function () {
    /** @type {string} */
    this.guid = '';
    /** @type {murmures.Tile} */
    this.position = null;
    /** @type {string} */
    this.mobTemplate = '';
    /** @type {number} */
    this.hitPointsMax = 0 | 0;
    /** @type {Object.<string, boolean>} */
    this.onVisionCharacters={};
    /** @type {number} */
    this.hitPoints = 0 | 0;
    /** @type {number} */
    this.range = 0 | 0;
    /** @type {number} */
    this.defaultDamageValue = 0 | 0;
    /** @type {boolean} */
    this.canMove = false; // unused for now
    /** @type {boolean} */
    this.onVision = false; // hero is in sight
    /** @type {boolean} */
    this.charSpotted = false; // hero is known because seen at least once
    /** @type {number} */
    this.stateOrder = murmures.C.STATE_HERO_WAITING_FOR_ORDER;

};

murmures.Character.prototype = {

    /**
     * It is expected that, when the server calls this function,
     * the Tile object in parameter is already built.
     */
    build : function (tile, template) {
        this.guid = Math.random().toString();
        this.position = tile;
        this.mobTemplate = template;
        let ref = gameEngine.bodies[template];
        this.hitPointsMax = (ref.hitPointsMax || (this.isHero ? 20 : 10)) | 0; // by default, heroes start with 20 HP. Other mobs with 10. This can be changed in assets.json.
        this.hitPoints = this.hitPointsMax | 0;
        this.range = (ref.range || (this.isHero ? 3 : 2)) | 0; // by default, heroes start with a 3 tile range. Other mobs with 2. This can be changed in assets.json.
        this.defaultDamageValue = (ref.defaultDamageValue || (this.isHero ? 3 : 1)) | 0; // by default, heroes deal 3 damage per attack. Other mobs deal 1. This can be changed in assets.json.
        this.canMove = ref.canMove || false;
        this.stateOrder = murmures.C.STATE_HERO_WAITING_FOR_ORDER;
    },

    initialize : function (src) {
        this.guid = src.guid;
        this.synchronize(src);
    },

    synchronize : function (src) {
        if (typeof src === 'undefined') return;
        if (typeof src.position !== 'undefined') this.move(src.position.x, src.position.y); // TODO position=null when mob becomes invisible?
        if (typeof src.mobTemplate !== 'undefined') this.mobTemplate = src.mobTemplate;
        if (typeof src.hitPointsMax !== 'undefined') this.hitPointsMax = src.hitPointsMax;
        if (typeof src.hitPoints !== 'undefined') this.hitPoints = src.hitPoints;
        if (typeof src.range !== 'undefined') this.range = src.range;
        if (typeof src.defaultDamageValue !== 'undefined') this.defaultDamageValue = src.defaultDamageValue;
        if (typeof src.canMove !== 'undefined') this.canMove = src.canMove;
        if (typeof src.onVision !== 'undefined') this.onVision = src.onVision;
        if (typeof src.stateOrder !== 'undefined') this.stateOrder = src.stateOrder;
        if (typeof src.onVisionCharacters !== 'undefined') this.onVisionCharacters = src.onVisionCharacters;
    },

    clone : function () {
        return {
            guid: this.guid,
            position: this.position.coordinates,
            mobTemplate: this.mobTemplate,
            hitPointsMax: this.hitPointsMax,
            hitPoints: this.hitPoints,
            range: this.range,
            defaultDamageValue: this.defaultDamageValue,
            canMove: this.canMove,
            onVision: this.onVision,
            stateOrder : this.stateOrder,
            onVisionCharacters : this.onVisionCharacters,
        };
    },

    compare : function (beforeState) {
        let ret = {};
        if (this.guid !== beforeState.guid) throw 'Character changed guid. This souldn\'t be happening';
        if (this.position.x !== beforeState.position.x || this.position.y !== beforeState.position.y) ret.position = this.position.coordinates;
        if (this.mobTemplate !== beforeState.mobTemplate) ret.mobTemplate = this.mobTemplate;
        if (this.hitPointsMax !== beforeState.hitPointsMax) ret.hitPointsMax = this.hitPointsMax;
        if (this.hitPoints !== beforeState.hitPoints) ret.hitPoints = this.hitPoints;
        if (this.range !== beforeState.range) ret.range = this.range;
        if (this.defaultDamageValue !== beforeState.defaultDamageValue) ret.defaultDamageValue = this.defaultDamageValue;
        if (this.canMove !== beforeState.canMove) ret.canMove = this.canMove;
        if (this.stateOrder !== beforeState.stateOrder) ret.stateOrder = this.stateOrder;
        if (this.onVision !== beforeState.onVision) {
            ret.onVision = this.onVision;
            if (this.onVision) {
                // client discovers the mob for the first time --> send everything
                ret.position = this.position.coordinates;
                ret.mobTemplate = this.mobTemplate;
                ret.hitPointsMax = this.hitPointsMax;
                ret.hitPoints = this.hitPoints;
                ret.range = this.range;
                ret.defaultDamageValue = this.defaultDamageValue;
                ret.canMove = this.canMove;
            }
        }
        ret.onVisionCharacters = this.onVisionCharacters;
        for (let prop in ret) {
            // only returns ret if not empty
            ret.guid = this.guid;
            return ret;
        }
        // otherwise, no return = undefined
    },

    move : function (x, y) {
        this.position = gameEngine.level.tiles[y][x];
    },

    get isHero() {
        let ref = gameEngine.bodies[this.mobTemplate];
        return murmures.C.LAYERS[ref.layerId][0] === 'Hero';
    },

    setVision : function (tilesProcessed) {
        murmures.serverLog("hero position");
        let level = gameEngine.level;
        if (typeof tilesProcessed === 'undefined' || tilesProcessed === null) { tilesProcessed = []; }
        for (let xx=0; xx < level.width; xx++) {
            for (let yy=0; yy < level.height; yy++) {
                let toProceed = (tilesProcessed.indexOf(level.tiles[yy][xx])<0);
                if (toProceed) {
                    if (level.tiles[yy][xx].state === murmures.C.TILE_HIGHLIGHTED) {
                        level.tiles[yy][xx].state = murmures.C.TILE_FOG_OF_WAR;
                    }
                }
            }
        }
/*
        for (let itMob=0; itMob < gameEngine.level.mobs.length; itMob++) {
            gameEngine.level.mobs[itMob].onVision = false;
            gameEngine.level.mobs[itMob].onVisionCharacters[this.guid] = false;
        }
*/
        for (let i=0; i < 360; i++) {
            let x = Math.cos(i * 0.01745);
            let y = Math.sin(i * 0.01745);
            let ox = this.position.x + 0.5;
            let oy = this.position.y + 0.5;
            let j=0;
            let breakObstacle = false;
            while((j<murmures.C.DEFAULT_RANGE_FOV ) && (breakObstacle===false)){
                let oxx = 0;
                oxx = Math.floor(ox);
                let oyy = 0;
                oyy = Math.floor(oy);
                if ((oxx >= 0) && (oxx < level.width) && (oyy >= 0) && (oyy < level.height)) {
                    let toProceed = (tilesProcessed.indexOf(level.tiles[oyy][oxx])<0);

                    let groundLight = (level.tiles[oyy][oxx].groundId === "") ? true : !gameEngine.bodies[level.tiles[oyy][oxx].groundId].hasPhysics ? true : !!gameEngine.bodies[level.tiles[oyy][oxx].groundId].allowFlying;
                    let propLight = (level.tiles[oyy][oxx].propId === "") ? true : !gameEngine.bodies[level.tiles[oyy][oxx].propId].hasPhysics ? true : !!gameEngine.bodies[level.tiles[oyy][oxx].propId].allowFlying;
                    if ((!groundLight || !propLight) && (j > 0)) {
                        breakObstacle = true;
                    }
                    if(toProceed){
                      level.tiles[oyy][oxx].state = murmures.C.TILE_HIGHLIGHTED;
                      tilesProcessed.push(level.tiles[oyy][oxx]);
                      for (let itMob=0; itMob < gameEngine.level.mobs.length; itMob++) {
                          let mob = gameEngine.level.mobs[itMob];
                          if (mob.position.y == oyy && mob.position.x== oxx) {
                              mob.charSpotted = true;
                              mob.onVisionCharacters[this.guid]=true;
                              mob.onVision = true;
                          }
                      }
                    }

                    ox += x;
                    oy += y;
                }else{
                  breakObstacle = true;
                }
                j+=1;
            }
        }

        return tilesProcessed;
    }
};
<commit_msg>fix #138<commit_after>'use strict';
//debugger;

/**
 * @file Character class. Part of the MurmuresJS project.
 * @author github.com/azziliz
 * @author github.com/thyshimrod
 * @license MIT
 */

/**
 * Characters are entities that live, move and act inside a [level]{@link murmures.Level}.
 *
 * Heroes are characters managed by the players.
 * They can be given [orders]{@link murmures.Order} on client side.
 * They can move from one level to another.
 * Mobs are characters managed by the AI.
 * The AI methods to control them are defined in the [game engine]{@link murmures.GameEngine} class.
 * They cannot change level.
 *
 * Three steps are mandatory to create a valid character:
 * 1. Load all character templates from mobs.json.
 * 2. Select templates that are needed for the current level.
 * 2. Use the "instantiate" method in the Character class to create new instances.
 * Step 1 is performed by the game engine during the server startup.
 * Step 2 and 3 are performed together by the server and the game engine when a level is loaded.
 *
 * @class
 */
murmures.Character = function () {
    /** @type {string} */
    this.guid = '';
    /** @type {murmures.Tile} */
    this.position = null;
    /** @type {string} */
    this.mobTemplate = '';
    /** @type {number} */
    this.hitPointsMax = 0 | 0;
    /** @type {Object.<string, boolean>} */
    this.onVisionCharacters={};
    /** @type {number} */
    this.hitPoints = 0 | 0;
    /** @type {number} */
    this.range = 0 | 0;
    /** @type {number} */
    this.defaultDamageValue = 0 | 0;
    /** @type {boolean} */
    this.canMove = false; // unused for now
    /** @type {boolean} */
    this.onVision = false; // hero is in sight
    /** @type {boolean} */
    this.charSpotted = false; // hero is known because seen at least once
    /** @type {number} */
    this.stateOrder = murmures.C.STATE_HERO_WAITING_FOR_ORDER;

};

murmures.Character.prototype = {

    /**
     * It is expected that, when the server calls this function,
     * the Tile object in parameter is already built.
     */
    build : function (tile, template) {
        this.guid = Math.random().toString();
        this.position = tile;
        this.mobTemplate = template;
        let ref = gameEngine.bodies[template];
        this.hitPointsMax = (ref.hitPointsMax || (this.isHero ? 20 : 10)) | 0; // by default, heroes start with 20 HP. Other mobs with 10. This can be changed in assets.json.
        this.hitPoints = this.hitPointsMax | 0;
        this.range = (ref.range || (this.isHero ? 3 : 2)) | 0; // by default, heroes start with a 3 tile range. Other mobs with 2. This can be changed in assets.json.
        this.defaultDamageValue = (ref.defaultDamageValue || (this.isHero ? 3 : 1)) | 0; // by default, heroes deal 3 damage per attack. Other mobs deal 1. This can be changed in assets.json.
        this.canMove = ref.canMove || false;
        this.stateOrder = murmures.C.STATE_HERO_WAITING_FOR_ORDER;
    },

    initialize : function (src) {
        this.guid = src.guid;
        this.synchronize(src);
    },

    synchronize : function (src) {
        if (typeof src === 'undefined') return;
        if (typeof src.position !== 'undefined') this.move(src.position.x, src.position.y); // TODO position=null when mob becomes invisible?
        if (typeof src.mobTemplate !== 'undefined') this.mobTemplate = src.mobTemplate;
        if (typeof src.hitPointsMax !== 'undefined') this.hitPointsMax = src.hitPointsMax;
        if (typeof src.hitPoints !== 'undefined') this.hitPoints = src.hitPoints;
        if (typeof src.range !== 'undefined') this.range = src.range;
        if (typeof src.defaultDamageValue !== 'undefined') this.defaultDamageValue = src.defaultDamageValue;
        if (typeof src.canMove !== 'undefined') this.canMove = src.canMove;
        if (typeof src.onVision !== 'undefined') this.onVision = src.onVision;
        if (typeof src.stateOrder !== 'undefined') this.stateOrder = src.stateOrder;
        if (typeof src.onVisionCharacters !== 'undefined') this.onVisionCharacters = src.onVisionCharacters;
    },

    clone : function () {
        return {
            guid: this.guid,
            position: this.position.coordinates,
            mobTemplate: this.mobTemplate,
            hitPointsMax: this.hitPointsMax,
            hitPoints: this.hitPoints,
            range: this.range,
            defaultDamageValue: this.defaultDamageValue,
            canMove: this.canMove,
            onVision: this.onVision,
            stateOrder : this.stateOrder,
            onVisionCharacters : this.onVisionCharacters,
        };
    },

    compare : function (beforeState) {
        let ret = {};
        if (this.guid !== beforeState.guid) throw 'Character changed guid. This souldn\'t be happening';
        if (this.position.x !== beforeState.position.x || this.position.y !== beforeState.position.y) ret.position = this.position.coordinates;
        if (this.mobTemplate !== beforeState.mobTemplate) ret.mobTemplate = this.mobTemplate;
        if (this.hitPointsMax !== beforeState.hitPointsMax) ret.hitPointsMax = this.hitPointsMax;
        if (this.hitPoints !== beforeState.hitPoints) ret.hitPoints = this.hitPoints;
        if (this.range !== beforeState.range) ret.range = this.range;
        if (this.defaultDamageValue !== beforeState.defaultDamageValue) ret.defaultDamageValue = this.defaultDamageValue;
        if (this.canMove !== beforeState.canMove) ret.canMove = this.canMove;
        if (this.stateOrder !== beforeState.stateOrder) ret.stateOrder = this.stateOrder;
        if (this.onVision !== beforeState.onVision) {
            ret.onVision = this.onVision;
            if (this.onVision) {
                // client discovers the mob for the first time --> send everything
                ret.position = this.position.coordinates;
                ret.mobTemplate = this.mobTemplate;
                ret.hitPointsMax = this.hitPointsMax;
                ret.hitPoints = this.hitPoints;
                ret.range = this.range;
                ret.defaultDamageValue = this.defaultDamageValue;
                ret.canMove = this.canMove;
            }
        }

        for (let itMap in this.onVisionCharacters){
          if(this.onVisionCharacters[itMap] !== beforeState.onVisionCharacters[itMap]){
              ret.onVisionCharacters = this.onVisionCharacters;
              break;
          }
        }

        for (let prop in ret) {
            // only returns ret if not empty
            ret.guid = this.guid;
            return ret;
        }
        // otherwise, no return = undefined
    },

    move : function (x, y) {
        this.position = gameEngine.level.tiles[y][x];
    },

    get isHero() {
        let ref = gameEngine.bodies[this.mobTemplate];
        return murmures.C.LAYERS[ref.layerId][0] === 'Hero';
    },

    setVision : function (tilesProcessed) {
        murmures.serverLog("hero position");
        let level = gameEngine.level;
        if (typeof tilesProcessed === 'undefined' || tilesProcessed === null) { tilesProcessed = []; }
        for (let xx=0; xx < level.width; xx++) {
            for (let yy=0; yy < level.height; yy++) {
                let toProceed = (tilesProcessed.indexOf(level.tiles[yy][xx])<0);
                if (toProceed) {
                    if (level.tiles[yy][xx].state === murmures.C.TILE_HIGHLIGHTED) {
                        level.tiles[yy][xx].state = murmures.C.TILE_FOG_OF_WAR;
                    }
                }
            }
        }
/*
        for (let itMob=0; itMob < gameEngine.level.mobs.length; itMob++) {
            gameEngine.level.mobs[itMob].onVision = false;
            gameEngine.level.mobs[itMob].onVisionCharacters[this.guid] = false;
        }
*/
        for (let i=0; i < 360; i++) {
            let x = Math.cos(i * 0.01745);
            let y = Math.sin(i * 0.01745);
            let ox = this.position.x + 0.5;
            let oy = this.position.y + 0.5;
            let j=0;
            let breakObstacle = false;
            while((j<murmures.C.DEFAULT_RANGE_FOV ) && (breakObstacle===false)){
                let oxx = 0;
                oxx = Math.floor(ox);
                let oyy = 0;
                oyy = Math.floor(oy);
                if ((oxx >= 0) && (oxx < level.width) && (oyy >= 0) && (oyy < level.height)) {
                    let toProceed = (tilesProcessed.indexOf(level.tiles[oyy][oxx])<0);

                    let groundLight = (level.tiles[oyy][oxx].groundId === "") ? true : !gameEngine.bodies[level.tiles[oyy][oxx].groundId].hasPhysics ? true : !!gameEngine.bodies[level.tiles[oyy][oxx].groundId].allowFlying;
                    let propLight = (level.tiles[oyy][oxx].propId === "") ? true : !gameEngine.bodies[level.tiles[oyy][oxx].propId].hasPhysics ? true : !!gameEngine.bodies[level.tiles[oyy][oxx].propId].allowFlying;
                    if ((!groundLight || !propLight) && (j > 0)) {
                        breakObstacle = true;
                    }
                    if(toProceed){
                      level.tiles[oyy][oxx].state = murmures.C.TILE_HIGHLIGHTED;
                      tilesProcessed.push(level.tiles[oyy][oxx]);
                      for (let itMob=0; itMob < gameEngine.level.mobs.length; itMob++) {
                          let mob = gameEngine.level.mobs[itMob];
                          if (mob.position.y == oyy && mob.position.x== oxx) {
                              mob.charSpotted = true;
                              mob.onVisionCharacters[this.guid]=true;
                              mob.onVision = true;
                          }
                      }
                    }

                    ox += x;
                    oy += y;
                }else{
                  breakObstacle = true;
                }
                j+=1;
            }
        }

        return tilesProcessed;
    }
};
<|endoftext|>


<commit_before>import { editor, parsedYAMLDocument } from './editor';
import { isAbsoluteUrl, splitUrlIntoFilenameAndBasePath } from '../tools/helpers';
import { addError } from './errors';
import { getKeyAddressForNode } from './yaml-ast';

// Redux
import store from '../store';
import { ADD_FILE } from '../store/actions';

/**
 * Is the provided scalar value node an import?
 *
 * @param {YAML-AST node} node
 * @returns {Boolean}
 */
function isImportValue(node) {
  // Returns false if not in a node, or is a top-level node
  if (!node || !node.parent) return false;

  // Returns true if the node value is a scalar, and the key address for this value is 'import'
  // We don't want to return true if we're inside an import node but at the
  // sequence node level
  const address = getKeyAddressForNode(node);
  if (address === 'import' && node.kind === 0) return true;

  // All other cases, return false
  return false;
}

// Check the files array to see if a file of "key" property is open already
function isAlreadyOpened(key) {
  const files = store.getState().scene.files;
  let found = false;
  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    if (file.key && file.key === key) {
      found = true;
      break;
    }
  }
  return found;
}

function handleImportValue(node, doc, cursorPos) {
  let urlString = node.value;

  const startPosition = doc.posFromIndex(node.startPosition);
  const startCharCoords = editor.charCoords(startPosition);
  const el = document.querySelector('.editor-context-menu');
  el.style.display = 'block';
  el.style.left = `${startCharCoords.left}px`;
  el.style.top = `${startCharCoords.bottom}px`;

  // TODO: url strings that are passed in as globals

  // Attach the file's base path if it looks like a relative URL!
  // Relative paths must be resolved in relationship to current FILE,
  // not the current SCENE. Imagine a scene that imports an external file,
  // which then imports a file relative to it; attaching the scene's base
  // path will break it.
  if (isAbsoluteUrl(urlString) === false) {
    // Get the files's base path if present
    const scene = store.getState().scene;

    const activeFile = scene.files[scene.activeFileIndex];
    const basePath = activeFile.basePath || scene.originalBasePath;
    const intermediaryUrlResolver = new window.URL(urlString, basePath);
    urlString = intermediaryUrlResolver.href;
  }

  // if the urlString is already opened, don't open it again
  if (isAlreadyOpened(urlString) === true) return;

  // Next: fetch contents of this file
  window.fetch(urlString)
    .then((response) => {
      if (!response.ok) {
        const error = new Error(`HTTP error code ${response.status}: Could not open ${urlString}`);
        error.line = cursorPos.line;
        throw error;
      }

      // Assume text content of file; don't use this to open other
      // zips, or images etc
      return response.text();
    })
    .then((contents) => {
      // If successful, add it to the files array in state.
      const urlParts = splitUrlIntoFilenameAndBasePath(urlString);
      const file = {
        key: urlString,
        basePath: urlParts[0],
        filename: urlParts[1],
        contents,
        readOnly: true,
      };
      store.dispatch({
        type: ADD_FILE,
        file,
      });
    })
    .catch((error) => {
      // Error message is thrown as stringified JSON, parse it first
      addError({
        type: 'error',
        line: error.line,
        message: error.message,
      });
    });
}

// Let's work on finding scene imports.
// TODO: This is not a final API; this is just for testing purposes.
export function initContextSensitiveClickEvents() {
  // Currently scoped to admin-users only or local development environment.
  // Bail if neither is true
  if (window.location.hostname !== 'localhost' && store.getState().user.admin === false) {
    return;
  }

  const wrapper = editor.getWrapperElement();

  wrapper.addEventListener('mouseup', (event) => {
    const doc = editor.getDoc();

    // Bail if we were doing a selection and not a click
    if (doc.somethingSelected()) return;

    const cursorPos = doc.getCursor();
    const cursorIndex = doc.indexFromPos(cursorPos); // -> Number
    const node = parsedYAMLDocument.getNodeAtIndex(cursorIndex);
    const isImportBlock = isImportValue(node);

    if (isImportBlock) {
      handleImportValue(node, doc, cursorPos);
    } else {
      const el = document.querySelector('.editor-context-menu');
      el.style.display = 'none';
    }
  });
}
<commit_msg>Test import opening in context-sensitive menu<commit_after>import { editor, parsedYAMLDocument } from './editor';
import { isAbsoluteUrl, splitUrlIntoFilenameAndBasePath } from '../tools/helpers';
import { addError } from './errors';
import { getKeyAddressForNode } from './yaml-ast';

// Redux
import store from '../store';
import { ADD_FILE } from '../store/actions';

/**
 * Is the provided scalar value node an import?
 *
 * @param {YAML-AST node} node
 * @returns {Boolean}
 */
function isImportValue(node) {
  // Returns false if not in a node, or is a top-level node
  if (!node || !node.parent) return false;

  // Returns true if the node value is a scalar, and the key address for this value is 'import'
  // We don't want to return true if we're inside an import node but at the
  // sequence node level
  const address = getKeyAddressForNode(node);
  if (address === 'import' && node.kind === 0) return true;

  // All other cases, return false
  return false;
}

// Check the files array to see if a file of "key" property is open already
function isAlreadyOpened(key) {
  const files = store.getState().scene.files;
  let found = false;
  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    if (file.key && file.key === key) {
      found = true;
      break;
    }
  }
  return found;
}

let urlString;
let cursorPos;

function handleImportValue(node, doc, incomingCursorPos) {
  urlString = node.value;
  cursorPos = incomingCursorPos;

  const startPosition = doc.posFromIndex(node.startPosition);
  const startCharCoords = editor.charCoords(startPosition);
  const el = document.querySelector('.editor-context-menu');
  el.style.display = 'block';
  el.style.left = `${startCharCoords.left}px`;
  el.style.top = `${startCharCoords.bottom}px`;
}

function openLink(event) {
  // TODO: url strings that are passed in as globals

  // Attach the file's base path if it looks like a relative URL!
  // Relative paths must be resolved in relationship to current FILE,
  // not the current SCENE. Imagine a scene that imports an external file,
  // which then imports a file relative to it; attaching the scene's base
  // path will break it.
  if (isAbsoluteUrl(urlString) === false) {
    // Get the files's base path if present
    const scene = store.getState().scene;

    const activeFile = scene.files[scene.activeFileIndex];
    const basePath = activeFile.basePath || scene.originalBasePath;
    const intermediaryUrlResolver = new window.URL(urlString, basePath);
    urlString = intermediaryUrlResolver.href;
  }

  // if the urlString is already opened, don't open it again
  if (isAlreadyOpened(urlString) === true) return;

  // Next: fetch contents of this file
  window.fetch(urlString)
    .then((response) => {
      if (!response.ok) {
        const error = new Error(`HTTP error code ${response.status}: Could not open ${urlString}`);
        error.line = cursorPos.line;
        throw error;
      }

      // Assume text content of file; don't use this to open other
      // zips, or images etc
      return response.text();
    })
    .then((contents) => {
      // If successful, add it to the files array in state.
      const urlParts = splitUrlIntoFilenameAndBasePath(urlString);
      const file = {
        key: urlString,
        basePath: urlParts[0],
        filename: urlParts[1],
        contents,
        readOnly: true,
      };
      store.dispatch({
        type: ADD_FILE,
        file,
      });

      // finally (todo: don't do this here)
      const el = document.querySelector('.editor-context-menu');
      el.style.display = 'none';
    })
    .catch((error) => {
      // Error message is thrown as stringified JSON, parse it first
      addError({
        type: 'error',
        line: error.line,
        message: error.message,
      });
    });
}

// Let's work on finding scene imports.
// TODO: This is not a final API; this is just for testing purposes.
export function initContextSensitiveClickEvents() {
  // Currently scoped to admin-users only or local development environment.
  // Bail if neither is true
  if (window.location.hostname !== 'localhost' && store.getState().user.admin === false) {
    return;
  }

  const wrapper = editor.getWrapperElement();

  wrapper.addEventListener('mouseup', (event) => {
    const doc = editor.getDoc();

    // Bail if we were doing a selection and not a click
    if (doc.somethingSelected()) return;

    const cursorPos = doc.getCursor();
    const cursorIndex = doc.indexFromPos(cursorPos); // -> Number
    const node = parsedYAMLDocument.getNodeAtIndex(cursorIndex);
    const isImportBlock = isImportValue(node);

    if (isImportBlock) {
      handleImportValue(node, doc, cursorPos);
    } else {
      const el = document.querySelector('.editor-context-menu');
      el.style.display = 'none';
    }
  });

  // listener for links
  const contextLink = document.querySelector('.editor-context-menu li');
  contextLink.addEventListener('click', openLink, true);

  // Shut down the context menu on other interactions
  // todo: move elsewhere, react flow
  window.addEventListener('mousedown', (event) => {
    if (event.target.textContent !== 'Open in new tab (read-only)') {
      const el = document.querySelector('.editor-context-menu');
      el.style.display = 'none';
    }
  });

  editor.on('scroll', (event) => {
    const el = document.querySelector('.editor-context-menu');
    el.style.display = 'none';
  });
}
<|endoftext|>


<commit_before>// Page modules
var FastClick = require('fastclick')
var nav = require('./nav.js')
var socialShare = require('./social-share')
var analytics = require('./analytics')

nav.init()
analytics.init()
FastClick.attach(document.body)

// Load and process data
require.ensure(['./api', './get-api-data', './template-render', 'spin.js', 'lodash'], function (require) {
  var apiRoutes = require('./api')
  var getApiData = require('./get-api-data')
  var templating = require('./template-render')
  var Spinner = require('spin.js')
  var _ = require('lodash')

  // Spinner
  var spin = document.getElementById('spin')
  var loading = new Spinner().spin(spin)

  // Get API data using promise
  getApiData.data(apiRoutes.needs)
    .then(function (result) {
      var needsFromApi = result.data
      _.each(needsFromApi, function(need) {
        need.link = "mailto:givehelp@streetsupport.net?subject=" + need.serviceProviderId + " need: " + need.description
      })
      var theData = { 'needs': needsFromApi }
      var callback = function () {
        loading.stop()
        socialShare.init()
      }

      templating.renderTemplate('js-need-list-tpl', theData, 'js-need-list-output', callback)
    })
})
<commit_msg>Revert "point more info link to mailto"<commit_after>// Page modules
var FastClick = require('fastclick')
var nav = require('./nav.js')
var socialShare = require('./social-share')
var analytics = require('./analytics')

nav.init()
analytics.init()
FastClick.attach(document.body)

// Load and process data
require.ensure(['./api', './get-api-data', './template-render', 'spin.js', 'lodash'], function (require) {
  var apiRoutes = require('./api')
  var getApiData = require('./get-api-data')
  var templating = require('./template-render')
  var Spinner = require('spin.js')
  var _ = require('lodash')

  // Spinner
  var spin = document.getElementById('spin')
  var loading = new Spinner().spin(spin)

  // Get API data using promise
  getApiData.data(apiRoutes.needs)
    .then(function (result) {
      var needsFromApi = result.data
      _.each(needsFromApi, function(need) {
        need.link = "give-item-submit-details.html?needId=" + need.id
      })
      var theData = { 'needs': needsFromApi }
      var callback = function () {
        loading.stop()
        socialShare.init()
      }

      templating.renderTemplate('js-need-list-tpl', theData, 'js-need-list-output', callback)
    })
})
<|endoftext|>


<commit_before>import { combineReducers } from "redux";

import * as actions from "./actions";
import safeStorage from "../storage";

// リスト操作

const lastMode = safeStorage.get("lastMode", actions.listMode.track);
const lastAlbumMode = safeStorage.get("lastAlbumMode", actions.albumMode.collapsed);
const defaultListState = { mode: lastMode, albumMode: lastAlbumMode };

const listReducer = (state = defaultListState, action) => {
    switch (action.type) {
        case actions.CHANGE_LIST_MODE: {
            safeStorage.set("lastMode", action.mode);
            return { ...state, mode: action.mode };
        }
        case actions.CHANGE_ALBUM_MODE: {
            safeStorage.set("lastAlbumMode", action.mode);
            return { ...state, albumMode: action.mode };
        }
        default:
            return state;
    }
};

// 再生関連

const defaultPlayState = {
    currentTrack: null,
    playList: []
};

function pick(tracks) {
    const copied = [...tracks];
    const [picked] = copied.splice(Math.floor(Math.random() * copied.length), 1);
    return [picked, copied];
}

const playReducer = (state = { ...defaultPlayState }, action) => {
    switch (action.type) {
        case actions.PLAY: {
            return { ...state, currentTrack: action.track, playList: [] };
        }
        case actions.PLAY_ALL: {
            const [nextTrack, rest] = pick(action.tracks);
            return {
                ...state,
                currentTrack: nextTrack,
                playList: rest
            };
        }
        case actions.PLAY_NEXT: {
            const [nextTrack, rest] = pick(state.playList);
            if (nextTrack) {
                return {
                    ...state,
                    currentTrack: nextTrack,
                    playList: rest
                };
            } else {
                return state;
            }
        }
        case actions.CLEAR: {
            return { ...defaultPlayState };
        }
        default: {
            return state;
        }
    }
};

// カーソル移動

const pointerReducer = (state = { index: -1, tracks: [], active: false }, action) => {
    switch (action.type) {
        case actions.SWITCH_POINTER: {
            return { ...state, active: action.active };
        }
        case actions.MOVE_RESET: {
            return { ...state, active: false, index: -1 };
        }
        case actions.MOVE_DOWN: {
            return { ...state, active: true, index: Math.min(state.index + 1, state.tracks.length - 1) };
        }
        case actions.MOVE_UP: {
            return { ...state, active: true, index: Math.max(state.index - 1, 0) };
        }
        case actions.MOVE_TO: {
            return {
                ...state,
                active: true,
                index: Math.min(Math.max(action.index, 0), state.tracks.length - 1)
            };
        }
        case actions.SETUP_LIST: {
            return { ...state, tracks: action.tracks };
        }
        default:
            return state;
    }
};

// ジャンル一覧

const genresReducer = (state = { names: [] }, action) => {
    switch (action.type) {
        case actions.UPDATE_GENRES: {
            return { ...state, names: action.genreNames };
        }
        default: {
            return state;
        }
    }
};

export default combineReducers({
    list: listReducer,
    play: playReducer,
    pointer: pointerReducer,
    genre: genresReducer
});

<commit_msg>選択されていない状態からの移動を直感的に<commit_after>import { combineReducers } from "redux";

import * as actions from "./actions";
import safeStorage from "../storage";

// リスト操作

const lastMode = safeStorage.get("lastMode", actions.listMode.track);
const lastAlbumMode = safeStorage.get("lastAlbumMode", actions.albumMode.collapsed);
const defaultListState = { mode: lastMode, albumMode: lastAlbumMode };

const listReducer = (state = defaultListState, action) => {
    switch (action.type) {
        case actions.CHANGE_LIST_MODE: {
            safeStorage.set("lastMode", action.mode);
            return { ...state, mode: action.mode };
        }
        case actions.CHANGE_ALBUM_MODE: {
            safeStorage.set("lastAlbumMode", action.mode);
            return { ...state, albumMode: action.mode };
        }
        default:
            return state;
    }
};

// 再生関連

const defaultPlayState = {
    currentTrack: null,
    playList: []
};

function pick(tracks) {
    const copied = [...tracks];
    const [picked] = copied.splice(Math.floor(Math.random() * copied.length), 1);
    return [picked, copied];
}

const playReducer = (state = { ...defaultPlayState }, action) => {
    switch (action.type) {
        case actions.PLAY: {
            return { ...state, currentTrack: action.track, playList: [] };
        }
        case actions.PLAY_ALL: {
            const [nextTrack, rest] = pick(action.tracks);
            return {
                ...state,
                currentTrack: nextTrack,
                playList: rest
            };
        }
        case actions.PLAY_NEXT: {
            const [nextTrack, rest] = pick(state.playList);
            if (nextTrack) {
                return {
                    ...state,
                    currentTrack: nextTrack,
                    playList: rest
                };
            } else {
                return state;
            }
        }
        case actions.CLEAR: {
            return { ...defaultPlayState };
        }
        default: {
            return state;
        }
    }
};

// カーソル移動

const pointerReducer = (state = { index: 0, tracks: [], active: false }, action) => {
    switch (action.type) {
        case actions.SWITCH_POINTER: {
            return { ...state, active: action.active };
        }
        case actions.MOVE_RESET: {
            return { ...state, active: false, index: 0 };
        }
        case actions.MOVE_DOWN: {
            if (state.active) {
                return { ...state, index: Math.min(state.index + 1, state.tracks.length - 1) };
            }
            return { ...state, active: true };
        }
        case actions.MOVE_UP: {
            if (state.active) {
                return { ...state, index: Math.max(state.index - 1, 0) };
            }
            return { ...state, active: true };
        }
        case actions.MOVE_TO: {
            if (state.active) {
                return { ...state, index: Math.min(Math.max(action.index, 0), state.tracks.length - 1) };
            }
            return { ...state, active: true };
        }
        case actions.SETUP_LIST: {
            return { ...state, tracks: action.tracks };
        }
        default:
            return state;
    }
};

// ジャンル一覧

const genresReducer = (state = { names: [] }, action) => {
    switch (action.type) {
        case actions.UPDATE_GENRES: {
            return { ...state, names: action.genreNames };
        }
        default: {
            return state;
        }
    }
};

export default combineReducers({
    list: listReducer,
    play: playReducer,
    pointer: pointerReducer,
    genre: genresReducer
});

<|endoftext|>


<commit_before>// Routines for working with an account.
//
// You should not instantiate this class yourself, instead use Remote#account.
//
// Events:
//   wallet_clean	: True, iff the wallet has been updated.
//   wallet_dirty	: True, iff the wallet needs to be updated.
//   balance		: The current stamp balance.
//   balance_proposed
//

// var network = require("./network.js");

var EventEmitter       = require('events').EventEmitter;
var util               = require('util');
var extend             = require('extend');

var Amount             = require('./amount').Amount;
var UInt160            = require('./uint160').UInt160;
var TransactionManager = require('./transactionmanager').TransactionManager;

function Account(remote, account) {
  EventEmitter.call(this);

  var self = this;

  this._tx_manager = null;
  this._remote     = remote;
  this._account    = UInt160.from_json(account);
  this._account_id = this._account.to_json();
  this._subs       = 0;

  // Ledger entry object
  // Important: This must never be overwritten, only extend()-ed
  this._entry = { };

  function listener_added(type, listener) {
    if (~Account.subscribe_events.indexOf(type)) {
      if (!self._subs && self._remote._connected) {
        self._remote.request_subscribe()
        .accounts(self._account_id)
        .request();
      }
      self._subs += 1;
    }
  }

  function listener_removed(type, listener) {
    if (~Account.subscribe_events.indexOf(type)) {
      self._subs -= 1;
      if (!self._subs && self._remote._connected) {
        self._remote.request_unsubscribe()
        .accounts(self._account_id)
        .request();
      }
    }
  }

  this.on('newListener', listener_added);
  this.on('removeListener', listener_removed);

  function prepare_subscribe(request) {
    if (self._account.is_valid() && self._subs) {
      request.accounts(self._account_id);
    }
  }

  this._remote.on('prepare_subscribe', prepare_subscribe);

  function handle_transaction(transaction) {
    var changed = false;

    transaction.mmeta.each(function(an) {
      var isAccountRoot = an.entryType === 'AccountRoot'
      && an.fields.Account === self._account_id;
      if (isAccountRoot) {
        extend(self._entry, an.fieldsNew, an.fieldsFinal);
        changed = true;
      }
    });

    if (changed) {
      self.emit('entry', self._entry);
    }
  }

  this.on('transaction', handle_transaction);

  return this;
};

util.inherits(Account, EventEmitter);

/**
 * List of events that require a remote subscription to the account.
 */
Account.subscribe_events = [ 'transaction', 'entry' ];

Account.prototype.to_json = function () {
  return this._account.to_json();
};

/**
 * Whether the AccountId is valid.
 *
 * Note: This does not tell you whether the account exists in the ledger.
 */
Account.prototype.is_valid = function () {
  return this._account.is_valid();
};

Account.prototype.get_info = function(callback) {
  var callback = typeof callback === 'function' ? callback : function(){};
  this._remote.request_account_info(this._account_id, callback);
};

/**
 * Retrieve the current AccountRoot entry.
 *
 * To keep up-to-date with changes to the AccountRoot entry, subscribe to the
 * "entry" event.
 *
 * @param {function (err, entry)} callback Called with the result
 */
Account.prototype.entry = function (callback) {
  var self = this;
  var callback = typeof callback === 'function' ? callback : function(){};

  this.get_info(function account_info(err, info) {
    if (err) {
      callback(err);
    } else {
      extend(self._entry, info.account_data);
      self.emit('entry', self._entry);
      callback(null, info);
    }
  });

  return this;
};

Account.prototype.get_next_sequence = function(callback) {
  var callback = typeof callback === 'function' ? callback : function(){};

  this.get_info(function account_info(err, info) {
    if (err) {
      callback(err);
    } else {
      callback(null, info.account_data.Sequence);
    }
  });

  return this;
};

/**
 * Retrieve this account's Ripple trust lines.
 *
 * To keep up-to-date with changes to the AccountRoot entry, subscribe to the
 * "lines" event. (Not yet implemented.)
 *
 * @param {function (err, lines)} callback Called with the result
 */
Account.prototype.lines = function (callback) {
  var self = this;
  var callback = typeof callback === 'function' ? callback : function(){};

  function account_lines(err, lines) {
    if (err) {
      callback(err);
    } else {
      self._lines = e.lines;
      self.emit('lines', self._lines);
      callback(null, e);
    }
  }

  this._remote.request_account_lines(this._account_id, account_lines);

  return this;
};

/**
 * Notify object of a relevant transaction.
 *
 * This is only meant to be called by the Remote class. You should never have to
 * call this yourself.
 */
Account.prototype.notifyTx = function (message) {
  // Only trigger the event if the account object is actually
  // subscribed - this prevents some weird phantom events from
  // occurring.
  if (this._subs) {
    this.emit('transaction', message);
    try {
      var account = message.transaction.Account;
    } catch(exception) { }
    if (account === this._account_id) {
      this.emit('transaction-outbound', message);
    } else {
      this.emit('transaction-inbound', message);
    }
  }
};

Account.prototype.submit = function(tx) {
  if (!this._tx_manager) {
    this._tx_manager = new TransactionManager(this);
  }
  this._tx_manager.submit(tx);
};

exports.Account = Account;

// vim:sw=2:sts=2:ts=8:et
<commit_msg>Add transaction-inbound, transaction-outbound events<commit_after>// Routines for working with an account.
//
// You should not instantiate this class yourself, instead use Remote#account.
//
// Events:
//   wallet_clean	: True, iff the wallet has been updated.
//   wallet_dirty	: True, iff the wallet needs to be updated.
//   balance		: The current stamp balance.
//   balance_proposed
//

// var network = require("./network.js");

var EventEmitter       = require('events').EventEmitter;
var util               = require('util');
var extend             = require('extend');

var Amount             = require('./amount').Amount;
var UInt160            = require('./uint160').UInt160;
var TransactionManager = require('./transactionmanager').TransactionManager;

function Account(remote, account) {
  EventEmitter.call(this);

  var self = this;

  this._tx_manager = null;
  this._remote     = remote;
  this._account    = UInt160.from_json(account);
  this._account_id = this._account.to_json();
  this._subs       = 0;

  // Ledger entry object
  // Important: This must never be overwritten, only extend()-ed
  this._entry = { };

  function listener_added(type, listener) {
    if (~Account.subscribe_events.indexOf(type)) {
      if (!self._subs && self._remote._connected) {
        self._remote.request_subscribe()
        .accounts(self._account_id)
        .request();
      }
      self._subs += 1;
    }
  }

  function listener_removed(type, listener) {
    if (~Account.subscribe_events.indexOf(type)) {
      self._subs -= 1;
      if (!self._subs && self._remote._connected) {
        self._remote.request_unsubscribe()
        .accounts(self._account_id)
        .request();
      }
    }
  }

  this.on('newListener', listener_added);
  this.on('removeListener', listener_removed);

  function prepare_subscribe(request) {
    if (self._account.is_valid() && self._subs) {
      request.accounts(self._account_id);
    }
  }

  this._remote.on('prepare_subscribe', prepare_subscribe);

  function handle_transaction(transaction) {
    var changed = false;

    transaction.mmeta.each(function(an) {
      var isAccountRoot = an.entryType === 'AccountRoot'
      && an.fields.Account === self._account_id;
      if (isAccountRoot) {
        extend(self._entry, an.fieldsNew, an.fieldsFinal);
        changed = true;
      }
    });

    if (changed) {
      self.emit('entry', self._entry);
    }
  }

  this.on('transaction', handle_transaction);

  return this;
};

util.inherits(Account, EventEmitter);

/**
 * List of events that require a remote subscription to the account.
 */
Account.subscribe_events = [ 'transaction', 'entry' ];

Account.prototype.to_json = function () {
  return this._account.to_json();
};

/**
 * Whether the AccountId is valid.
 *
 * Note: This does not tell you whether the account exists in the ledger.
 */
Account.prototype.is_valid = function () {
  return this._account.is_valid();
};

Account.prototype.get_info = function(callback) {
  var callback = typeof callback === 'function' ? callback : function(){};
  this._remote.request_account_info(this._account_id, callback);
};

/**
 * Retrieve the current AccountRoot entry.
 *
 * To keep up-to-date with changes to the AccountRoot entry, subscribe to the
 * "entry" event.
 *
 * @param {function (err, entry)} callback Called with the result
 */
Account.prototype.entry = function (callback) {
  var self = this;
  var callback = typeof callback === 'function' ? callback : function(){};

  this.get_info(function account_info(err, info) {
    if (err) {
      callback(err);
    } else {
      extend(self._entry, info.account_data);
      self.emit('entry', self._entry);
      callback(null, info);
    }
  });

  return this;
};

Account.prototype.get_next_sequence = function(callback) {
  var callback = typeof callback === 'function' ? callback : function(){};

  this.get_info(function account_info(err, info) {
    if (err) {
      callback(err);
    } else {
      callback(null, info.account_data.Sequence);
    }
  });

  return this;
};

/**
 * Retrieve this account's Ripple trust lines.
 *
 * To keep up-to-date with changes to the AccountRoot entry, subscribe to the
 * "lines" event. (Not yet implemented.)
 *
 * @param {function (err, lines)} callback Called with the result
 */
Account.prototype.lines = function (callback) {
  var self = this;
  var callback = typeof callback === 'function' ? callback : function(){};

  function account_lines(err, lines) {
    if (err) {
      callback(err);
    } else {
      self._lines = e.lines;
      self.emit('lines', self._lines);
      callback(null, e);
    }
  }

  this._remote.request_account_lines(this._account_id, account_lines);

  return this;
};

/**
 * Notify object of a relevant transaction.
 *
 * This is only meant to be called by the Remote class. You should never have to
 * call this yourself.
 */
Account.prototype.notifyTx = function (message) {
  // Only trigger the event if the account object is actually
  // subscribed - this prevents some weird phantom events from
  // occurring.
  if (this._subs) {
    this.emit('transaction', message);
    if (!message.transaction.Account) return;
    var account = message.transaction.Account;
    if (account === this._account_id) {
      this.emit('transaction-outbound', message);
    } else {
      this.emit('transaction-inbound', message);
    }
  }
};

Account.prototype.submit = function(tx) {
  if (!this._tx_manager) {
    this._tx_manager = new TransactionManager(this);
  }
  this._tx_manager.submit(tx);
};

exports.Account = Account;

// vim:sw=2:sts=2:ts=8:et
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var util         = require('util');
var UInt160      = require('./uint160').UInt160;
var Currency     = require('./currency').Currency;
var RippleError  = require('./rippleerror').RippleError;
var Server       = require('./server').Server;

// Request events emitted:
//  'success' : Request successful.
//  'error'   : Request failed.
//  'remoteError'
//  'remoteUnexpected'
//  'remoteDisconnected'

/**
 * Request
 *
 * @param {Remote} remote
 * @param {String} command
 */

function Request(remote, command) {
  EventEmitter.call(this);

  this.remote = remote;
  this.requested = false;
  this.message = {
    command: command,
    id: void(0)
  };
};

util.inherits(Request, EventEmitter);

Request.prototype.broadcast = function() {
  this._broadcast = true;
  return this.request();
};

// Send the request to a remote.
Request.prototype.request = function(callback) {
  if (this.requested) {
    return this;
  }

  this.requested = true;

  this.on('error', function(){});
  this.emit('request', this.remote);

  if (this._broadcast) {
    this.remote._servers.forEach(function(server) {
      this.setServer(server);
      this.remote.request(this);
    }, this);
  } else {
    this.remote.request(this);
  }

  this.callback(callback);

  return this;
};

Request.prototype.callback = function(callback, successEvent, errorEvent) {
  var self = this;

  if (typeof callback !== 'function') {
    return this;
  }

  var called = false;

  function requestSuccess(message) {
    if (!called) {
      called = true;
      callback.call(self, null, message);
    }
  };

  function requestError(error) {
    if (!called) {
      called = true;

      if (!(error instanceof RippleError)) {
        error = new RippleError(error);
      }

      callback.call(self, error);
    }
  };

  this.once(successEvent || 'success', requestSuccess);
  this.once(errorEvent   || 'error'  , requestError);
  this.request();

  return this;
};

Request.prototype.timeout = function(duration, callback) {
  var self = this;

  function requested() {
    self.timeout(duration, callback);
  };

  if (!this.requested) {
    // Defer until requested
    return this.once('request', requested);
  }

  var emit = this.emit;
  var timed_out = false;

  var timeout = setTimeout(function() {
    timed_out = true;

    if (typeof callback === 'function') {
      callback();
    }

    emit.call(self, 'timeout');
  }, duration);

  this.emit = function() {
    if (!timed_out) {
      clearTimeout(timeout);
      emit.apply(self, arguments);
    }
  };

  return this;
};

Request.prototype.setServer = function(server) {
  var selected = null;

  switch (typeof server) {
    case 'object':
      selected = server;
      break;

    case 'string':
      // Find server by URL
      var servers = this.remote._servers;

      for (var i=0, s; (s=servers[i]); i++) {
        if (s._url === server) {
          selected = s;
          break;
        }
      }
      break;
  };

  if (selected instanceof Server) {
    this.server = selected;
  }

  return this;
};

Request.prototype.buildPath = function(build) {
  if (this.remote.local_signing) {
    throw new Error(
      '`build_path` is completely ignored when doing local signing as '
      + '`Paths` is a component of the signed blob. The `tx_blob` is signed,'
      + 'sealed and delivered, and the txn unmodified after' );
  }

  if (build) {
    this.message.build_path = true;
  } else {
    // ND: rippled currently intreprets the mere presence of `build_path` as the
    // value being `truthy`
    delete this.message.build_path;
  }

  return this;
};

Request.prototype.ledgerChoose = function(current) {
  if (current) {
    this.message.ledger_index = this.remote._ledger_current_index;
  } else {
    this.message.ledger_hash  = this.remote._ledger_hash;
  }

  return this;
};

// Set the ledger for a request.
// - ledger_entry
// - transaction_entry
Request.prototype.ledgerHash = function(hash) {
  this.message.ledger_hash = hash;
  return this;
};

// Set the ledger_index for a request.
// - ledger_entry
Request.prototype.ledgerIndex = function(ledger_index) {
  this.message.ledger_index = ledger_index;
  return this;
};

Request.prototype.ledgerSelect = function(ledger) {
  switch (ledger) {
    case 'current':
    case 'closed':
    case 'validated':
      this.message.ledger_index = ledger;
      break;

    default:
      if (isNaN(ledger)) {
        this.message.ledger_hash  = ledger;
      } else if ((ledger = Number(ledger))) {
        this.message.ledger_index = ledger;
      }
      break;
  }

  return this;
};

Request.prototype.accountRoot = function(account) {
  this.message.account_root  = UInt160.json_rewrite(account);
  return this;
};

Request.prototype.index = function(index) {
  this.message.index  = index;
  return this;
};

// Provide the information id an offer.
// --> account
// --> seq : sequence number of transaction creating offer (integer)
Request.prototype.offerId = function(account, sequence) {
  this.message.offer = {
    account:  UInt160.json_rewrite(account),
    seq:      sequence
  };
  return this;
};

// --> index : ledger entry index.
Request.prototype.offerIndex = function(index) {
  this.message.offer = index;
  return this;
};

Request.prototype.secret = function(secret) {
  if (secret) {
    this.message.secret = secret;
  }
  return this;
};

Request.prototype.txHash = function(hash) {
  this.message.tx_hash = hash;
  return this;
};

Request.prototype.txJson = function(json) {
  this.message.tx_json = json;
  return this;
};

Request.prototype.txBlob = function(json) {
  this.message.tx_blob = json;
  return this;
};

Request.prototype.rippleState = function(account, issuer, currency) {
  this.message.ripple_state = {
    currency : currency,
    accounts : [
      UInt160.json_rewrite(account),
      UInt160.json_rewrite(issuer)
    ]
  };
  return this;
};

Request.prototype.accounts = function(accounts, proposed) {
  if (!Array.isArray(accounts)) {
    accounts = [ accounts ];
  }

  // Process accounts parameters
  var processedAccounts = accounts.map(function(account) {
    return UInt160.json_rewrite(account);
  });

  if (proposed) {
    this.message.accounts_proposed = processedAccounts;
  } else {
    this.message.accounts = processedAccounts;
  }

  return this;
};

Request.prototype.addAccount = function(account, proposed) {
  var processedAccount = UInt160.json_rewrite(account);

  if (proposed) {
    this.message.accounts_proposed = (this.message.accounts_proposed || []).concat(processedAccount);
  } else {
    this.message.accounts = (this.message.accounts || []).concat(processedAccount);
  }

  return this;
};

Request.prototype.rtAccounts =
Request.prototype.accountsProposed = function(accounts) {
  return this.accounts(accounts, true);
};

Request.prototype.addAccountProposed = function(account) {
  return this.addAccount(account, true);
};

Request.prototype.books = function(books, snapshot) {
  // Reset list of books (this method overwrites the current list)
  this.message.books = [ ];

  for (var i=0, l=books.length; i<l; i++) {
    var book = books[i];
    this.addBook(book, snapshot);
  }

  return this;
};

Request.prototype.addBook = function(book, snapshot) {
  if (!Array.isArray(this.message.books)) {
    this.message.books = [ ];
  }

  var json = { };

  function processSide(side) {
    if (!book[side]) {
      throw new Error('Missing ' + side);
    }

    var obj = json[side] = {
      currency: Currency.json_rewrite(book[side].currency, {force_hex: true})
    };

    if (!Currency.from_json(obj.currency).is_native()) {
      obj.issuer = UInt160.json_rewrite(book[side].issuer);
    }
  }

  [ 'taker_gets', 'taker_pays' ].forEach(processSide);

  if (snapshot) {
    json.snapshot = true;
  }

  if (book.both) {
    json.both = true;
  }

  this.message.books.push(json);

  return this;
};

Request.prototype.addStream = function(stream) {
  var self = this;

  if (arguments.length > 1) {
    for (arg in arguments) {
      this.addStream(arguments[arg]);
    }
    return;
  }

  switch (stream) {
    case 'ledger':
    case 'server':
    case 'transactions':
    case 'transactions_proposed':
      this.message.streams = (this.message.streams || []).concat(stream);
  }

  return this;
};

exports.Request = Request;
<commit_msg>Complete Request.addStream<commit_after>var EventEmitter = require('events').EventEmitter;
var util         = require('util');
var UInt160      = require('./uint160').UInt160;
var Currency     = require('./currency').Currency;
var RippleError  = require('./rippleerror').RippleError;
var Server       = require('./server').Server;

// Request events emitted:
//  'success' : Request successful.
//  'error'   : Request failed.
//  'remoteError'
//  'remoteUnexpected'
//  'remoteDisconnected'

/**
 * Request
 *
 * @param {Remote} remote
 * @param {String} command
 */

function Request(remote, command) {
  EventEmitter.call(this);

  this.remote = remote;
  this.requested = false;
  this.message = {
    command: command,
    id: void(0)
  };
};

util.inherits(Request, EventEmitter);

Request.prototype.broadcast = function() {
  this._broadcast = true;
  return this.request();
};

// Send the request to a remote.
Request.prototype.request = function(callback) {
  if (this.requested) {
    return this;
  }

  this.requested = true;

  this.on('error', function(){});
  this.emit('request', this.remote);

  if (this._broadcast) {
    this.remote._servers.forEach(function(server) {
      this.setServer(server);
      this.remote.request(this);
    }, this);
  } else {
    this.remote.request(this);
  }

  this.callback(callback);

  return this;
};

Request.prototype.callback = function(callback, successEvent, errorEvent) {
  var self = this;

  if (typeof callback !== 'function') {
    return this;
  }

  var called = false;

  function requestSuccess(message) {
    if (!called) {
      called = true;
      callback.call(self, null, message);
    }
  };

  function requestError(error) {
    if (!called) {
      called = true;

      if (!(error instanceof RippleError)) {
        error = new RippleError(error);
      }

      callback.call(self, error);
    }
  };

  this.once(successEvent || 'success', requestSuccess);
  this.once(errorEvent   || 'error'  , requestError);
  this.request();

  return this;
};

Request.prototype.timeout = function(duration, callback) {
  var self = this;

  function requested() {
    self.timeout(duration, callback);
  };

  if (!this.requested) {
    // Defer until requested
    return this.once('request', requested);
  }

  var emit = this.emit;
  var timed_out = false;

  var timeout = setTimeout(function() {
    timed_out = true;

    if (typeof callback === 'function') {
      callback();
    }

    emit.call(self, 'timeout');
  }, duration);

  this.emit = function() {
    if (!timed_out) {
      clearTimeout(timeout);
      emit.apply(self, arguments);
    }
  };

  return this;
};

Request.prototype.setServer = function(server) {
  var selected = null;

  switch (typeof server) {
    case 'object':
      selected = server;
      break;

    case 'string':
      // Find server by URL
      var servers = this.remote._servers;

      for (var i=0, s; (s=servers[i]); i++) {
        if (s._url === server) {
          selected = s;
          break;
        }
      }
      break;
  };

  if (selected instanceof Server) {
    this.server = selected;
  }

  return this;
};

Request.prototype.buildPath = function(build) {
  if (this.remote.local_signing) {
    throw new Error(
      '`build_path` is completely ignored when doing local signing as '
      + '`Paths` is a component of the signed blob. The `tx_blob` is signed,'
      + 'sealed and delivered, and the txn unmodified after' );
  }

  if (build) {
    this.message.build_path = true;
  } else {
    // ND: rippled currently intreprets the mere presence of `build_path` as the
    // value being `truthy`
    delete this.message.build_path;
  }

  return this;
};

Request.prototype.ledgerChoose = function(current) {
  if (current) {
    this.message.ledger_index = this.remote._ledger_current_index;
  } else {
    this.message.ledger_hash  = this.remote._ledger_hash;
  }

  return this;
};

// Set the ledger for a request.
// - ledger_entry
// - transaction_entry
Request.prototype.ledgerHash = function(hash) {
  this.message.ledger_hash = hash;
  return this;
};

// Set the ledger_index for a request.
// - ledger_entry
Request.prototype.ledgerIndex = function(ledger_index) {
  this.message.ledger_index = ledger_index;
  return this;
};

Request.prototype.ledgerSelect = function(ledger) {
  switch (ledger) {
    case 'current':
    case 'closed':
    case 'validated':
      this.message.ledger_index = ledger;
      break;

    default:
      if (isNaN(ledger)) {
        this.message.ledger_hash  = ledger;
      } else if ((ledger = Number(ledger))) {
        this.message.ledger_index = ledger;
      }
      break;
  }

  return this;
};

Request.prototype.accountRoot = function(account) {
  this.message.account_root  = UInt160.json_rewrite(account);
  return this;
};

Request.prototype.index = function(index) {
  this.message.index  = index;
  return this;
};

// Provide the information id an offer.
// --> account
// --> seq : sequence number of transaction creating offer (integer)
Request.prototype.offerId = function(account, sequence) {
  this.message.offer = {
    account:  UInt160.json_rewrite(account),
    seq:      sequence
  };
  return this;
};

// --> index : ledger entry index.
Request.prototype.offerIndex = function(index) {
  this.message.offer = index;
  return this;
};

Request.prototype.secret = function(secret) {
  if (secret) {
    this.message.secret = secret;
  }
  return this;
};

Request.prototype.txHash = function(hash) {
  this.message.tx_hash = hash;
  return this;
};

Request.prototype.txJson = function(json) {
  this.message.tx_json = json;
  return this;
};

Request.prototype.txBlob = function(json) {
  this.message.tx_blob = json;
  return this;
};

Request.prototype.rippleState = function(account, issuer, currency) {
  this.message.ripple_state = {
    currency : currency,
    accounts : [
      UInt160.json_rewrite(account),
      UInt160.json_rewrite(issuer)
    ]
  };
  return this;
};

Request.prototype.setAccounts =
Request.prototype.accounts = function(accounts, proposed) {
  if (!Array.isArray(accounts)) {
    accounts = [ accounts ];
  }

  // Process accounts parameters
  var processedAccounts = accounts.map(function(account) {
    return UInt160.json_rewrite(account);
  });

  if (proposed) {
    this.message.accounts_proposed = processedAccounts;
  } else {
    this.message.accounts = processedAccounts;
  }

  return this;
};

Request.prototype.addAccount = function(account, proposed) {
  if (Array.isArray(account)) {
    account.forEach(this.addAccount, this);
    return this;
  }

  var processedAccount = UInt160.json_rewrite(account);

  if (proposed === true) {
    this.message.accounts_proposed = (this.message.accounts_proposed || []).concat(processedAccount);
  } else {
    this.message.accounts = (this.message.accounts || []).concat(processedAccount);
  }

  return this;
};

Request.prototype.setAccountsProposed =
Request.prototype.rtAccounts =
Request.prototype.accountsProposed = function(accounts) {
  return this.accounts(accounts, true);
};

Request.prototype.addAccountProposed = function(account) {
  if (Array.isArray(account)) {
    account.forEach(this.addAccountProposed, this);
    return this;
  }

  return this.addAccount(account, true);
};

Request.prototype.setBooks =
Request.prototype.books = function(books, snapshot) {
  // Reset list of books (this method overwrites the current list)
  this.message.books = [ ];

  for (var i=0, l=books.length; i<l; i++) {
    var book = books[i];
    this.addBook(book, snapshot);
  }

  return this;
};

Request.prototype.addBook = function(book, snapshot) {
  if (Array.isArray(book)) {
    book.forEach(this.addBook, this);
    return this;
  }

  var json = { };

  function processSide(side) {
    if (!book[side]) {
      throw new Error('Missing ' + side);
    }

    var obj = json[side] = {
      currency: Currency.json_rewrite(book[side].currency, { force_hex: true })
    };

    if (!Currency.from_json(obj.currency).is_native()) {
      obj.issuer = UInt160.json_rewrite(book[side].issuer);
    }
  }

  [ 'taker_gets', 'taker_pays' ].forEach(processSide);

  if (typeof snapshot !== 'boolean') {
    json.snapshot = true;
  } else if (snapshot) {
    json.snapshot = true;
  } else {
    delete json.snapshot;
  }

  if (book.both) {
    json.both = true;
  }

  this.message.books = (this.message.books || []).concat(json);

  return this;
};

Request.prototype.addStream = function(stream, values) {
  var self = this;

  if (Array.isArray(values)) {
    switch (stream) {
      case 'accounts':
        this.addAccount(values);
        break;
      case 'accounts_proposed':
        this.addAccountProposed(values);
        break;
      case 'books':
        this.addBook(values);
        break;
    }
  } else if (arguments.length > 1) {
    for (arg in arguments) {
      this.addStream(arguments[arg]);
    }
    return;
  }

  if (!Array.isArray(this.message.streams)) {
    this.message.streams = [ ];
  }

  if (this.message.streams.indexOf(stream) === -1) {
    this.message.streams.push(stream);
  }

  return this;
};

exports.Request = Request;
<|endoftext|>


<commit_before>/*global SingleView, ImageShowcase, Header*/
"use strict";
var PA = PA || {}
PA.dispatcher = PA.dispatcher || _.extend({}, Backbone.Events)

PA.Router = Backbone.Router.extend({

    routes : {
        "" : "homeLoader",
        "projects" : "projects",
        "projects/:title" : "singleProject",
        "photography" : "photography",
        "photography/:title" : "singleAlbum",
        "film" : "film",
        "film/:title" : "singleFilm",
        "profile" : "profile",
        "profile/:title" : "profileSection",
        "contact" : "contact",
        "stream" : "stream"
    },

    projects : function() {

        $.get('/fixtures/projectFixture').done(function(d) {
            PA.projects = new PA.Projects(d)

            PA.app.header.filterBar.render()

            PA.coverImages = new PA.Covers( PA.projects.pluck('coverImage') )
            PA.coverShowcase = new PA.ImageShowcase({
                cover : true,
                collection : PA.coverImages,
                path : 'projects'
            })

            PA.app.page.render({
                view : PA.coverShowcase,
                pageClass : 'projects',
                section : 'Projects'
            })

            PA.starInit()

        })
    },

    singleProject : function(title) {

        if (PA.projects.length) {
            // Projects are loaded

            var project = PA.projects.findWhere({ url : title })

            PA.singleProject = new PA.ProjectViewer({
                model : project
            })

            PA.app.page.render({
                view : PA.singleProject,
                pageClass : 'project-single',
                section : project.get('title')
            })

        } else {
            // Projects haven't loaded yet because
            // A) navigate to direct URL
            // B) navigate from a different page section

            $.get('/fixtures/projectFixture').done(function(d) {
                var project = new PA.Project( _.findWhere(d, {url : title}) )

                PA.singleProject = new PA.ProjectViewer({
                    model : project
                })

                PA.app.page.render({
                    view : PA.singleProject,
                    pageClass : 'project-single',
                    section : project.get('title')
                })
            })
        }

    },

    photography : function() {
        $.get('/fixtures/photographyFixture').done(function(d) {
            PA.albums = new PA.PhotoAlbums(d)

            PA.coverImages = new PA.Covers( PA.albums.pluck('coverImage') )
            PA.coverShowcase = new PA.ImageShowcase({
                cover : true,
                collection : PA.coverImages,
                path : 'photography'
            })

            PA.app.page.render({
                view : PA.coverShowcase,
                pageClass : 'photography',
                section : 'Photography'
            })

            PA.coverShowcase.firstLoad()
        })
    },

    singleAlbum : function(title) {

        if (PA.albums.length) {
            // Photo Galleries are loaded

            var photoAlbumModel = PA.albums.findWhere({ url : title })

            PA.singleAlbumView = new PA.SingleAlbumView({
                model : photoAlbumModel
            })

            PA.app.page.render({
                view : PA.singleAlbumView,
                pageClass : 'photography',
                section : photoAlbumModel.get('title')
            })

        } else {
            // Photo Galleries haven't loaded yet because
            // A) navigate to direct URL
            // B) navigate from a different page section

            $.get('/fixtures/photographyFixture').done(function(d) {
                var photoAlbumModel = new PA.PhotoAlbum( _.findWhere(d, {url : title}) )

                PA.singleAlbumView = new PA.SingleAlbumView({
                    model : photoAlbumModel
                })

                PA.app.page.render({
                    view : PA.singleAlbumView,
                    pageClass : 'photography',
                    section : photoAlbumModel.get('title')
                })
            })
        }

    },

    film : function() {
        $.get('/fixtures/filmFixture').done( function(d) {
            PA.films = new PA.Films(d)
            PA.filmLanding = new PA.FilmThumbLayout({
                collection : PA.films
            })

            PA.app.page.render({
                view : PA.filmLanding,
                pageClass : 'film',
                section : 'Film Home'
            })

        } )
    },

    singleFilm : function(title) {

        if (PA.films.length) {
            // Films are loaded

            var filmModel = PA.films.findWhere({ url : title })

            PA.singleFilmView = new PA.SingleFilmView({
                model : filmModel
            })

            PA.app.page.render({
                view : PA.singleFilmView,
                pageClass : 'film',
                section : filmModel.get('title')
            })
        } else {
            // Films haven't loaded yet

            $.get('/fixtures/filmFixture').done( function(d) {
                var filmModel = new PA.PhotoAlbum( _.findWhere(d, { url : title }) )
                PA.singleFilmView = new PA.SingleFilmView({
                    model : filmModel
                })

                PA.app.page.render({
                    view : PA.singleFilmView,
                    pageClass : 'film',
                    section : filmModel.get('title')
                })
            })
        }
    },

    profile : function() {

        PA.profilePages = new Backbone.Collection()
        var add = function(d) { PA.profilePages.add(d) }
        $.when( $.get('/fixtures/awardsFixture'),
                $.get('/fixtures/bioFixture'),
                $.get('/fixtures/paAuthorFixture'),
                $.get('/fixtures/paPhotosFixture'),
                $.get('/fixtures/pressFixture')
        ).done( function(){
            _.each(arguments, function(el){
                PA.profilePages.add(el[0])
                PA.groupedProfilePages = PA.profilePages.groupBy('type')
            })
        })
        PA.profileView = new PA.ProfileViewer({
            el : '#profileViewer',
            collection : PA.profilePages
        })

        PA.app.page.render({
            view : PA.profileView,
            pageClass : 'profile',
            section : 'Profile Home',
        })
    },

    contact : function() {
        $('.page').append('contact')
    },

    stream : function() {
        $('.page').append('stream')
    }

})
<commit_msg>added json extension to requests<commit_after>/*global SingleView, ImageShowcase, Header*/
"use strict";
var PA = PA || {}
PA.dispatcher = PA.dispatcher || _.extend({}, Backbone.Events)

PA.Router = Backbone.Router.extend({

    routes : {
        "" : "homeLoader",
        "projects" : "projects",
        "projects/:title" : "singleProject",
        "photography" : "photography",
        "photography/:title" : "singleAlbum",
        "film" : "film",
        "film/:title" : "singleFilm",
        "profile" : "profile",
        "profile/:title" : "profileSection",
        "contact" : "contact",
        "stream" : "stream"
    },

    projects : function() {

        $.get('/fixtures/projectFixture.json').done(function(d) {
            PA.projects = new PA.Projects(d)

            PA.app.header.filterBar.render()

            PA.coverImages = new PA.Covers( PA.projects.pluck('coverImage') )
            PA.coverShowcase = new PA.ImageShowcase({
                cover : true,
                collection : PA.coverImages,
                path : 'projects'
            })

            PA.app.page.render({
                view : PA.coverShowcase,
                pageClass : 'projects',
                section : 'Projects'
            })

            PA.starInit()

        })
    },

    singleProject : function(title) {

        if (PA.projects.length) {
            // Projects are loaded

            var project = PA.projects.findWhere({ url : title })

            PA.singleProject = new PA.ProjectViewer({
                model : project
            })

            PA.app.page.render({
                view : PA.singleProject,
                pageClass : 'project-single',
                section : project.get('title')
            })

        } else {
            // Projects haven't loaded yet because
            // A) navigate to direct URL
            // B) navigate from a different page section

            $.get('/fixtures/projectFixture.json').done(function(d) {
                var project = new PA.Project( _.findWhere(d, {url : title}) )

                PA.singleProject = new PA.ProjectViewer({
                    model : project
                })

                PA.app.page.render({
                    view : PA.singleProject,
                    pageClass : 'project-single',
                    section : project.get('title')
                })
            })
        }

    },

    photography : function() {
        $.get('/fixtures/photographyFixture.json').done(function(d) {
            PA.albums = new PA.PhotoAlbums(d)

            PA.coverImages = new PA.Covers( PA.albums.pluck('coverImage') )
            PA.coverShowcase = new PA.ImageShowcase({
                cover : true,
                collection : PA.coverImages,
                path : 'photography'
            })

            PA.app.page.render({
                view : PA.coverShowcase,
                pageClass : 'photography',
                section : 'Photography'
            })

            PA.coverShowcase.firstLoad()
        })
    },

    singleAlbum : function(title) {

        if (PA.albums.length) {
            // Photo Galleries are loaded

            var photoAlbumModel = PA.albums.findWhere({ url : title })

            PA.singleAlbumView = new PA.SingleAlbumView({
                model : photoAlbumModel
            })

            PA.app.page.render({
                view : PA.singleAlbumView,
                pageClass : 'photography',
                section : photoAlbumModel.get('title')
            })

        } else {
            // Photo Galleries haven't loaded yet because
            // A) navigate to direct URL
            // B) navigate from a different page section

            $.get('/fixtures/photographyFixture.json').done(function(d) {
                var photoAlbumModel = new PA.PhotoAlbum( _.findWhere(d, {url : title}) )

                PA.singleAlbumView = new PA.SingleAlbumView({
                    model : photoAlbumModel
                })

                PA.app.page.render({
                    view : PA.singleAlbumView,
                    pageClass : 'photography',
                    section : photoAlbumModel.get('title')
                })
            })
        }

    },

    film : function() {
        $.get('/fixtures/filmFixture.json').done( function(d) {
            PA.films = new PA.Films(d)
            PA.filmLanding = new PA.FilmThumbLayout({
                collection : PA.films
            })

            PA.app.page.render({
                view : PA.filmLanding,
                pageClass : 'film',
                section : 'Film Home'
            })

        } )
    },

    singleFilm : function(title) {

        if (PA.films.length) {
            // Films are loaded

            var filmModel = PA.films.findWhere({ url : title })

            PA.singleFilmView = new PA.SingleFilmView({
                model : filmModel
            })

            PA.app.page.render({
                view : PA.singleFilmView,
                pageClass : 'film',
                section : filmModel.get('title')
            })
        } else {
            // Films haven't loaded yet

            $.get('/fixtures/filmFixture.json').done( function(d) {
                var filmModel = new PA.PhotoAlbum( _.findWhere(d, { url : title }) )
                PA.singleFilmView = new PA.SingleFilmView({
                    model : filmModel
                })

                PA.app.page.render({
                    view : PA.singleFilmView,
                    pageClass : 'film',
                    section : filmModel.get('title')
                })
            })
        }
    },

    profile : function() {

        PA.profilePages = new Backbone.Collection()
        var add = function(d) { PA.profilePages.add(d) }
        $.when( $.get('/fixtures/awardsFixture.json'),
                $.get('/fixtures/bioFixture.json'),
                $.get('/fixtures/paAuthorFixture.json'),
                $.get('/fixtures/paPhotosFixture.json'),
                $.get('/fixtures/pressFixture.json')
        ).done( function(){
            _.each(arguments, function(el){
                PA.profilePages.add(el[0])
                PA.groupedProfilePages = PA.profilePages.groupBy('type')
            })
        })
        PA.profileView = new PA.ProfileViewer({
            el : '#profileViewer',
            collection : PA.profilePages
        })

        PA.app.page.render({
            view : PA.profileView,
            pageClass : 'profile',
            section : 'Profile Home',
        })
    },

    contact : function() {
        $('.page').append('contact')
    },

    stream : function() {
        $('.page').append('stream')
    }

})
<|endoftext|>