<commit_before>'use strict';

define(['legion/class'], function(Class) {
  var Environment = Class.extend(
  /** @lends Environment */
  {

    /**
     * The class name, 'Environment'
     * @type {String}
     * @default 'Environment'
     * @readonly
     */
    className: 'Environment',

    /**
     * Width of the environment in pixels.
     * @type {Number}
     * @default 0
     */
    width: 0,

    /**
     * Height of the environment in pixels.
     * @type {Number}
     * @default 0
     */
    height: 0,

    /**
     * Map of entities in the environment with the entity IDs as the keys.
     * @type {object}
     * @default null
     */
    entities: null,

    /**
     * A solid background color for the environment.
     * @type {Number}
     * @default 0x000000
     */
    backgroundColor: 0x000000,

    /**
     * Initialize the environment
     * @constructs Environment
     * @param  {object} properties - object of properties to mixin
     * @extends Class
     * @classdesc Environments are the game world or levels.
     */
    init: function(properties) {
      //Why was this here?
      //properties = typeof properties !== 'undefined' ? properties : {};
      this.entities = {};
      //this.entityMap = {};
      this.parent(properties);
    },

    /**
     * Add an entity to the environment and bind the game to the entity.
     *
     * @param  {Entity} entity - The entity to add to the environment.
     */
    addEntity: function(entity) {
      entity._bindGame(this.game);
      this.entities[entity.id] = entity;
    },

    /**
     * Remove an entity from the environment by passing in the entity or
     * it's ID.
     *
     * @param  {Entity|Number|String} entityID - the ID of the entity to remove.
     * @return {Entity} - Return the removed entity.
     */
    removeEntity: function(entityID) {
      // If the entity is passed in get its id
      if (entityID.id !== undefined) {
        entityID = entityID.id;
      }

      var entity = this.entities[entityID];

      this.entities[entityID] = undefined;

      return entity;
    },

    /**
     * forEachEntity calls a function for each entity in the environment.  The
     * function is passed an entity.
     *
     * @param  {function} func - A function to execute on each entity.
     * @example
     *
     * // Log each entity ID
     * env.forEachEntity(function(entity) {
     * 	console.log(entity.id);
     * });
     */
    forEachEntity: function(func) {
      for (var id in this.entities) {
        func(this.entities[id]);
      }
    },

    /**
     * _update() is called once each frame and calls _update() for all the
     * entites inside it.
     * @private
     */
    _update: function() {
      this.forEachEntity(function(entity) {
        entity._update();
      });
    },

    /**
     * Binds a game to the environment and then binds the game to any entities
     * already inside the environment.
     *
     * @param  {Game} game - the game to bind
     * @private
     */
    _bindGame: function(game) {
      this.parent(game);
      this.forEachEntity(function(entity) {
        entity._bindGame(game);
      });
    },

    /**
     * Returns a serializable object representing the state of the syncable
     * entities inside the environment.
     *
     * @return {object[]} - an array of entity representations
     * @private
     */
    _getSyncMessage: function() {
      var message = [];
      this.forEachEntity(function(entity) {
        if (entity.sync) {
          message.push(entity.serialize());
        }
      });
      return message;
    },

    /**
     * _sync() takes a remote message (if this is the server, from clients, and
     * if this is a client, from the server) and updates the environment with
     * the properties in the message.
     *
     * @param  {object[]} entities - an array of serialized entities.
     * @private
     */
    _sync: function(entities) {
      for (var i = 0; i < entities.length; i++) {
        var entity = entities[i];
        //If the entity is new to the client/server.
        if (this.entities[entity.id] === undefined) {
          this.addEntity(new legion._classes[entity.className](entity));
          //this.entities.push(entity);
        } else {
          // Don't override local copy with servers if it's the player's entity.
          if (!this.game.clientID || this.game.clientID !== entity.clientID) {
            this.entities[entity.id].mixin(entity);
          }
        }
      }
    }
  });

  // On node return an environment without rendering
  if (legion.isNode) {
    return Environment;
  } else {

    // Return an environment with rendering
    return Environment.extend({

      // PIXI stage
      stage: null,

      init: function(properties) {
        //properties = typeof properties !== 'undefined' ? properties : {};
        this.parent(properties);

        this.stage = new PIXI.Stage(this.backgroundColor);
      },

      addEntity: function(entity) {
        this.parent(entity);

        if ('displayObject' in entity) {
          this.stage.addChild(entity.displayObject);
        }
      },

      removeEntity: function(entityID) {
        var entity = this.parent(entityID);

        if ('displayObject' in entity) {
          this.stage.removeChild(entity.displayObject);
        }

        return entity;
      },

      _render: function() {
        legion._renderer.render(this.stage);
      }

    });
  }
});
<commit_msg>Adding check back to env.forEachEntity<commit_after>'use strict';

define(['legion/class'], function(Class) {
  var Environment = Class.extend(
  /** @lends Environment */
  {

    /**
     * The class name, 'Environment'
     * @type {String}
     * @default 'Environment'
     * @readonly
     */
    className: 'Environment',

    /**
     * Width of the environment in pixels.
     * @type {Number}
     * @default 0
     */
    width: 0,

    /**
     * Height of the environment in pixels.
     * @type {Number}
     * @default 0
     */
    height: 0,

    /**
     * Map of entities in the environment with the entity IDs as the keys.
     * @type {object}
     * @default null
     */
    entities: null,

    /**
     * A solid background color for the environment.
     * @type {Number}
     * @default 0x000000
     */
    backgroundColor: 0x000000,

    /**
     * Initialize the environment
     * @constructs Environment
     * @param  {object} properties - object of properties to mixin
     * @extends Class
     * @classdesc Environments are the game world or levels.
     */
    init: function(properties) {
      //Why was this here?
      //properties = typeof properties !== 'undefined' ? properties : {};
      this.entities = {};
      //this.entityMap = {};
      this.parent(properties);
    },

    /**
     * Add an entity to the environment and bind the game to the entity.
     *
     * @param  {Entity} entity - The entity to add to the environment.
     */
    addEntity: function(entity) {
      entity._bindGame(this.game);
      this.entities[entity.id] = entity;
    },

    /**
     * Remove an entity from the environment by passing in the entity or
     * it's ID.
     *
     * @param  {Entity|Number|String} entityID - the ID of the entity to remove.
     * @return {Entity} - Return the removed entity.
     */
    removeEntity: function(entityID) {
      // If the entity is passed in get its id
      if (entityID.id !== undefined) {
        entityID = entityID.id;
      }

      var entity = this.entities[entityID];

      this.entities[entityID] = undefined;

      return entity;
    },

    /**
     * forEachEntity calls a function for each entity in the environment.  The
     * function is passed an entity.
     *
     * @param  {function} func - A function to execute on each entity.
     * @example
     *
     * // Log each entity ID
     * env.forEachEntity(function(entity) {
     * 	console.log(entity.id);
     * });
     */
    forEachEntity: function(func) {
      for (var id in this.entities) {
        if (this.entities.hasOwnProperty(id) &&
          this.entities[id] !== undefined) {
          func(this.entities[id]);
        }
      }
    },

    /**
     * _update() is called once each frame and calls _update() for all the
     * entites inside it.
     * @private
     */
    _update: function() {
      this.forEachEntity(function(entity) {
        entity._update();
      });
    },

    /**
     * Binds a game to the environment and then binds the game to any entities
     * already inside the environment.
     *
     * @param  {Game} game - the game to bind
     * @private
     */
    _bindGame: function(game) {
      this.parent(game);
      this.forEachEntity(function(entity) {
        entity._bindGame(game);
      });
    },

    /**
     * Returns a serializable object representing the state of the syncable
     * entities inside the environment.
     *
     * @return {object[]} - an array of entity representations
     * @private
     */
    _getSyncMessage: function() {
      var message = [];
      this.forEachEntity(function(entity) {
        if (entity.sync) {
          message.push(entity.serialize());
        }
      });
      return message;
    },

    /**
     * _sync() takes a remote message (if this is the server, from clients, and
     * if this is a client, from the server) and updates the environment with
     * the properties in the message.
     *
     * @param  {object[]} entities - an array of serialized entities.
     * @private
     */
    _sync: function(entities) {
      for (var i = 0; i < entities.length; i++) {
        var entity = entities[i];
        //If the entity is new to the client/server.
        if (this.entities[entity.id] === undefined) {
          this.addEntity(new legion._classes[entity.className](entity));
          //this.entities.push(entity);
        } else {
          // Don't override local copy with servers if it's the player's entity.
          if (!this.game.clientID || this.game.clientID !== entity.clientID) {
            this.entities[entity.id].mixin(entity);
          }
        }
      }
    }
  });

  // On node return an environment without rendering
  if (legion.isNode) {
    return Environment;
  } else {

    // Return an environment with rendering
    return Environment.extend({

      // PIXI stage
      stage: null,

      init: function(properties) {
        //properties = typeof properties !== 'undefined' ? properties : {};
        this.parent(properties);

        this.stage = new PIXI.Stage(this.backgroundColor);
      },

      addEntity: function(entity) {
        this.parent(entity);

        if ('displayObject' in entity) {
          this.stage.addChild(entity.displayObject);
        }
      },

      removeEntity: function(entityID) {
        var entity = this.parent(entityID);

        if ('displayObject' in entity) {
          this.stage.removeChild(entity.displayObject);
        }

        return entity;
      },

      _render: function() {
        legion._renderer.render(this.stage);
      }

    });
  }
});
<|endoftext|>


<commit_before>var _NODE_TYPE = require('./nodeType'),
    _id        = 0,
    _toString  = Object.prototype.toString,
    _indexOf   = Array.prototype.indexOf,
    _slice     = Array.prototype.slice,
    _isTruthy  = function(arg) { return !!arg; };

var _ = {

    noop: function() {},

    now: Date.now || function() { return new Date().getTime(); },

    uniqueId: function() {
        return _id++;
    },

    exists: function(obj) {
        return obj !== null && obj !== undefined;
    },

    parseInt: function(num) {
        return parseInt(num, 10);
    },

    coerceToNum: function(val) {
        return _.isNumber(val) ? (val || 0) : // Its a number! || 0 to avoid NaN (as NaN's a number)
               _.isString(val) ? (_.parseInt(val) || 0) : // Avoid NaN again
               0; // Default to zero
    },

    toPx: function(num) {
        return num + 'px';
    },

    isElement: function(obj) {
        return !!(obj && obj.nodeType === _NODE_TYPE.ELEMENT);
    },

    // NOTE: In older browsers, this will be overwritten below
    isArray: Array.isArray,

    isArrayLike: function(obj) {
        if (!_.exists(obj)) {
            return false;
        }
        if (_.isArray(obj)) {
            return true;
        }
        if (_.isString(obj)) {
            return false;
        }
        if (_.isNodeList(obj)) {
            return true;
        }
        if (_.isArguments(obj)) {
            return true;
        }
        if (_.isNumber(obj.length) && ('0' in obj)) {
            return true;
        }
        return false;
    },

    // NodeList check. For our purposes, a NodeList and an HTMLCollection are the same.
    // NOTE: This fails in IE8 on DispStaticNodeList objects.
    // There is no reasonable way (that I know of) to detect a DispStaticNodeList
    // object in IE8 (go ahead, try Googling it...  I dare you).
    isNodeList: function(obj) {
        return !!(obj && (obj instanceof NodeList || obj instanceof HTMLCollection));
    },

    // Window check
    isWindow: function(obj) {
        return !!(obj && obj === obj.window);
    },

    // Supports IE8 via obj.callee (see http://stackoverflow.com/a/10645766/467582)
    isArguments: function(obj) {
        return !!(obj && (_toString.call(obj) === '[object Arguments]' || obj.callee));
    },

    // Flatten that also checks if value is a NodeList
    flatten: function(arr) {
        var result = [];

        var idx = 0,
            len = arr.length,
            value;
        for (; idx < len; idx++) {
            value = arr[idx];

            if (_.isArrayLike(value)) {
                result = result.concat(_.flatten(value));
            } else {
                result.push(value);
            }
        }

        return result;
    },

    // Concat flat for a single array of arrays
    concatFlat: (function(concat) {

        return function(nestedArrays) {
            return concat.apply([], nestedArrays);
        };

    }([].concat)),

    // No-context every; strip each()
    every: function(arr, iterator) {
        if (!_.exists(arr)) { return true; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (!iterator(value, idx)) { return false; }
        }

        return true;
    },

    // Faster extend; strip each()
    extend: function() {
        var args = arguments,
            obj  = args[0],
            idx  = 1,
            len  = args.length;

        if (!obj) { return obj; }

        for (; idx < len; idx++) {
            var source = args[idx];
            if (source) {
                for (var prop in source) {
                    obj[prop] = source[prop];
                }
            }
        }

        return obj;
    },

    // Standard map
    map: function(arr, iterator) {
        var results = [];
        if (!arr) { return results; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            results.push(iterator(arr[idx], idx));
        }

        return results;
    },

    // Array-preserving map
    // http://jsperf.com/push-map-vs-index-replacement-map
    fastmap: function(arr, iterator) {
        if (!arr) { return []; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            arr[idx] = iterator(arr[idx], idx);
        }

        return arr;
    },

    filter: function(arr, iterator) {
        var results = [];
        if (!arr || !arr.length) { return results; }
        iterator = iterator || _isTruthy;

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (iterator(arr[idx], idx)) {
                results.push(arr[idx]);
            }
        }

        return results;
    },

    any: function(arr, iterator) {
        var result = false;
        if (!arr || !arr.length) { return result; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (result || (result = iterator(arr[idx], idx))) { break; }
        }

        return !!result;
    },

    typecast: function(val) {
        var r;
        if (val === null || val === 'null') {
            r = null;
        } else if (val === 'true') {
            r = true;
        } else if (val === 'false') {
            r = false;
        } else if (val === undefined || val === 'undefined') {
            r = undefined;
        } else if (val === '' || isNaN(val)) {
            // isNaN('') returns false
            r = val;
        } else {
            // parseFloat(null || '') returns NaN
            r = parseFloat(val);
        }
        return r;
    },

    toArray: function(obj) {
        if (!obj) {
            return [];
        }
        if (_.isArray(obj)) {
            return _slice.call(obj);
        }

        var arr,
            len = +obj.length,
            idx = 0;

        if (obj.length === +obj.length) {
            arr = new Array(obj.length);
            for (; idx < len; idx++) {
                arr[idx] = obj[idx];
            }
            return arr;
        }

        for (var key in obj) {
            arr.push(obj[key]);
        }
        return arr;
    },

    makeArray: function(arg) {
        if (arg === null || arg === undefined) {
            return [];
        }
        if (arg.slice === _slice) {
            return arg.slice();
        }
        if (_.isArrayLike(arg)) {
            return _slice.call(arg);
        }
        return [ arg ];
    },

    // Doesn't a very simple case of array to object.
    // Takes the value and sets it as the key and the value.
    object: function(arr) {
        var obj = {},
            len = arr.length,
            idx = 0;
        for (; idx < len; idx++) {
            obj[arr[idx]] = arr[idx];
        }
        return obj;
    },

    indexOf: function(arr, item) {
        if (arr.indexOf === _indexOf) {
            return arr.indexOf(item);
        }
        return _indexOf.call(arr, item);
    },

    each: function(obj, iterator) {
        if (!obj || !iterator) { return; }

        // Array-like
        if (obj.length !== undefined) {
            var idx = 0, length = obj.length;
            for (; idx < length; idx++) {
                if (iterator(obj[idx], idx) === false) {
                    break;
                }
            }
        }
        // Plain object
        else {
            for (var prop in obj) {
                if (iterator(obj[prop], prop) === false) {
                    break;
                }
            }
        }

        return obj;
    },

    hasSize: function(obj) {
        var name;
        for (name in obj) { return false; }
        return true;
    },

    isObject: function(obj) {
        return obj === Object(obj);
    },

    // Breaks even on arrays with 3 items. 3 or more
    // items starts saving space
    splt: function(str, delimiter) {
        return str.split(delimiter || '|');
    }

};

// Add some isType methods (only if they do NOT already exist): isArray, isFunction, isString, isNumber, isDate, isRegExp.
var types = _.splt('Array|Function|String|Number|Date|RegExp'),
    idx = types.length,
    generateCheck = function(name) {
        return function(obj) {
            return !!obj && _toString.call(obj) === '[object ' + name + ']';
        };
    },
    name;
while (idx--) {
    name = types[idx];
    _['is' + name] = _['is' + name] || generateCheck(name);
}

// Optimize `isFunction` if appropriate.
if (typeof (/./) !== 'function') {
    _.isFunction = function(obj) {
        return typeof obj === 'function';
    };
}

// Optimize `isString` if appropriate.
if (typeof ('') === 'string') {
    _.isString = function(obj) {
        return typeof obj === 'string';
    };
}

module.exports = _;
<commit_msg>Revert "Use _.isArrayLike() instead of _.isArray() in _.flatten()"<commit_after>var _NODE_TYPE = require('./nodeType'),
    _id        = 0,
    _toString  = Object.prototype.toString,
    _indexOf   = Array.prototype.indexOf,
    _slice     = Array.prototype.slice,
    _isTruthy  = function(arg) { return !!arg; };

var _ = {

    noop: function() {},

    now: Date.now || function() { return new Date().getTime(); },

    uniqueId: function() {
        return _id++;
    },

    exists: function(obj) {
        return obj !== null && obj !== undefined;
    },

    parseInt: function(num) {
        return parseInt(num, 10);
    },

    coerceToNum: function(val) {
        return _.isNumber(val) ? (val || 0) : // Its a number! || 0 to avoid NaN (as NaN's a number)
               _.isString(val) ? (_.parseInt(val) || 0) : // Avoid NaN again
               0; // Default to zero
    },

    toPx: function(num) {
        return num + 'px';
    },

    isElement: function(obj) {
        return !!(obj && obj.nodeType === _NODE_TYPE.ELEMENT);
    },

    // NOTE: In older browsers, this will be overwritten below
    isArray: Array.isArray,

    isArrayLike: function(obj) {
        if (!_.exists(obj)) {
            return false;
        }
        if (_.isArray(obj)) {
            return true;
        }
        if (_.isString(obj)) {
            return false;
        }
        if (_.isNodeList(obj)) {
            return true;
        }
        if (_.isArguments(obj)) {
            return true;
        }
        if (_.isNumber(obj.length) && ('0' in obj)) {
            return true;
        }
        return false;
    },

    // NodeList check. For our purposes, a NodeList and an HTMLCollection are the same.
    // NOTE: This fails in IE8 on DispStaticNodeList objects.
    // There is no reasonable way (that I know of) to detect a DispStaticNodeList
    // object in IE8 (go ahead, try Googling it...  I dare you).
    isNodeList: function(obj) {
        return !!(obj && (obj instanceof NodeList || obj instanceof HTMLCollection));
    },

    // Window check
    isWindow: function(obj) {
        return !!(obj && obj === obj.window);
    },

    // Supports IE8 via obj.callee (see http://stackoverflow.com/a/10645766/467582)
    isArguments: function(obj) {
        return !!(obj && (_toString.call(obj) === '[object Arguments]' || obj.callee));
    },

    // Flatten that also checks if value is a NodeList
    flatten: function(arr) {
        var result = [];

        var idx = 0,
            len = arr.length,
            value;
        for (; idx < len; idx++) {
            value = arr[idx];

            if (_.isArray(value) || _.isNodeList(value)) {
                result = result.concat(_.flatten(value));
            } else {
                result.push(value);
            }
        }

        return result;
    },

    // Concat flat for a single array of arrays
    concatFlat: (function(concat) {

        return function(nestedArrays) {
            return concat.apply([], nestedArrays);
        };

    }([].concat)),

    // No-context every; strip each()
    every: function(arr, iterator) {
        if (!_.exists(arr)) { return true; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (!iterator(value, idx)) { return false; }
        }

        return true;
    },

    // Faster extend; strip each()
    extend: function() {
        var args = arguments,
            obj  = args[0],
            idx  = 1,
            len  = args.length;

        if (!obj) { return obj; }

        for (; idx < len; idx++) {
            var source = args[idx];
            if (source) {
                for (var prop in source) {
                    obj[prop] = source[prop];
                }
            }
        }

        return obj;
    },

    // Standard map
    map: function(arr, iterator) {
        var results = [];
        if (!arr) { return results; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            results.push(iterator(arr[idx], idx));
        }

        return results;
    },

    // Array-preserving map
    // http://jsperf.com/push-map-vs-index-replacement-map
    fastmap: function(arr, iterator) {
        if (!arr) { return []; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            arr[idx] = iterator(arr[idx], idx);
        }

        return arr;
    },

    filter: function(arr, iterator) {
        var results = [];
        if (!arr || !arr.length) { return results; }
        iterator = iterator || _isTruthy;

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (iterator(arr[idx], idx)) {
                results.push(arr[idx]);
            }
        }

        return results;
    },

    any: function(arr, iterator) {
        var result = false;
        if (!arr || !arr.length) { return result; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (result || (result = iterator(arr[idx], idx))) { break; }
        }

        return !!result;
    },

    typecast: function(val) {
        var r;
        if (val === null || val === 'null') {
            r = null;
        } else if (val === 'true') {
            r = true;
        } else if (val === 'false') {
            r = false;
        } else if (val === undefined || val === 'undefined') {
            r = undefined;
        } else if (val === '' || isNaN(val)) {
            // isNaN('') returns false
            r = val;
        } else {
            // parseFloat(null || '') returns NaN
            r = parseFloat(val);
        }
        return r;
    },

    toArray: function(obj) {
        if (!obj) {
            return [];
        }
        if (_.isArray(obj)) {
            return _slice.call(obj);
        }

        var arr,
            len = +obj.length,
            idx = 0;

        if (obj.length === +obj.length) {
            arr = new Array(obj.length);
            for (; idx < len; idx++) {
                arr[idx] = obj[idx];
            }
            return arr;
        }

        for (var key in obj) {
            arr.push(obj[key]);
        }
        return arr;
    },

    makeArray: function(arg) {
        if (arg === null || arg === undefined) {
            return [];
        }
        if (arg.slice === _slice) {
            return arg.slice();
        }
        if (_.isArrayLike(arg)) {
            return _slice.call(arg);
        }
        return [ arg ];
    },

    // Doesn't a very simple case of array to object.
    // Takes the value and sets it as the key and the value.
    object: function(arr) {
        var obj = {},
            len = arr.length,
            idx = 0;
        for (; idx < len; idx++) {
            obj[arr[idx]] = arr[idx];
        }
        return obj;
    },

    indexOf: function(arr, item) {
        if (arr.indexOf === _indexOf) {
            return arr.indexOf(item);
        }
        return _indexOf.call(arr, item);
    },

    each: function(obj, iterator) {
        if (!obj || !iterator) { return; }

        // Array-like
        if (obj.length !== undefined) {
            var idx = 0, length = obj.length;
            for (; idx < length; idx++) {
                if (iterator(obj[idx], idx) === false) {
                    break;
                }
            }
        }
        // Plain object
        else {
            for (var prop in obj) {
                if (iterator(obj[prop], prop) === false) {
                    break;
                }
            }
        }

        return obj;
    },

    hasSize: function(obj) {
        var name;
        for (name in obj) { return false; }
        return true;
    },

    isObject: function(obj) {
        return obj === Object(obj);
    },

    // Breaks even on arrays with 3 items. 3 or more
    // items starts saving space
    splt: function(str, delimiter) {
        return str.split(delimiter || '|');
    }

};

// Add some isType methods (only if they do NOT already exist): isArray, isFunction, isString, isNumber, isDate, isRegExp.
var types = _.splt('Array|Function|String|Number|Date|RegExp'),
    idx = types.length,
    generateCheck = function(name) {
        return function(obj) {
            return !!obj && _toString.call(obj) === '[object ' + name + ']';
        };
    },
    name;
while (idx--) {
    name = types[idx];
    _['is' + name] = _['is' + name] || generateCheck(name);
}

// Optimize `isFunction` if appropriate.
if (typeof (/./) !== 'function') {
    _.isFunction = function(obj) {
        return typeof obj === 'function';
    };
}

// Optimize `isString` if appropriate.
if (typeof ('') === 'string') {
    _.isString = function(obj) {
        return typeof obj === 'string';
    };
}

module.exports = _;
<|endoftext|>


<commit_before>(function (app_container) {

	var init = function() {

		// Initialize Phaser, and creates a game
		app_container.game = new Phaser.Game(960, 600, Phaser.AUTO, 'game_container');

		// add phaser states
		app_container.game.state.add('LudusSplash', app_container.LudusSplash);
		app_container.game.state.add('Preload', app_container.Preload);
		app_container.game.state.add('Menu', app_container.Menu);
		app_container.game.state.add('Gameplay', app_container.Gameplay);
		app_container.game.state.add('Gameover', app_container.Gameover);

		app_container.game.state.add('Credits', app_container.Credits);
		app_container.game.state.add('Howto', app_container.HowToPlay);
		app_container.game.state.add('StoryPresentation', app_container.StoryPresentation);

		//levels
		app_container.game.state.add('Level1', app_container.Level1);
		app_container.game.state.add('Level2', app_container.Level2);
		app_container.game.state.add('Level3', app_container.Level3);

		// start preload
		app_container.game.state.start('LudusSplash');
	};

	window.onload = function() {
		init();
	};

}(window.app_container = window.app_container || {}));<commit_msg>change phaser.auto to phaser.canvas<commit_after>(function (app_container) {

	var init = function() {

		// Initialize Phaser, and creates a game
		app_container.game = new Phaser.Game(960, 600, Phaser.CANVAS, 'game_container');

		// add phaser states
		app_container.game.state.add('LudusSplash', app_container.LudusSplash);
		app_container.game.state.add('Preload', app_container.Preload);
		app_container.game.state.add('Menu', app_container.Menu);
		app_container.game.state.add('Gameplay', app_container.Gameplay);
		app_container.game.state.add('Gameover', app_container.Gameover);

		app_container.game.state.add('Credits', app_container.Credits);
		app_container.game.state.add('Howto', app_container.HowToPlay);
		app_container.game.state.add('StoryPresentation', app_container.StoryPresentation);

		//levels
		app_container.game.state.add('Level1', app_container.Level1);
		app_container.game.state.add('Level2', app_container.Level2);
		app_container.game.state.add('Level3', app_container.Level3);

		// start preload
		app_container.game.state.start('LudusSplash');
	};

	window.onload = function() {
		init();
	};

}(window.app_container = window.app_container || {}));<|endoftext|>


<commit_before>exports.level = {
  "goalTreeString": "{\"branches\":{\"master\":{\"target\":\"C4\",\"id\":\"master\",\"remoteTrackingBranchID\":\"o/master\",\"localBranchesThatTrackThis\":null},\"o/master\":{\"target\":\"C3\",\"id\":\"o/master\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":[\"master\"]}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"},\"C2\":{\"parents\":[\"C1\"],\"id\":\"C2\"},\"C3\":{\"parents\":[\"C1\"],\"id\":\"C3\"},\"C4\":{\"parents\":[\"C2\",\"C3\"],\"id\":\"C4\"}},\"HEAD\":{\"target\":\"master\",\"id\":\"HEAD\"},\"originTree\":{\"branches\":{\"master\":{\"target\":\"C3\",\"id\":\"master\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":null}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"},\"C3\":{\"parents\":[\"C1\"],\"id\":\"C3\"}},\"HEAD\":{\"target\":\"master\",\"id\":\"HEAD\"}}}",
  "solutionCommand": "git pull",
  "startTree": "{\"branches\":{\"master\":{\"target\":\"C2\",\"id\":\"master\",\"remoteTrackingBranchID\":\"o/master\",\"localBranchesThatTrackThis\":null},\"o/master\":{\"target\":\"C1\",\"id\":\"o/master\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":[\"master\"]}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"},\"C2\":{\"parents\":[\"C1\"],\"id\":\"C2\"}},\"HEAD\":{\"target\":\"master\",\"id\":\"HEAD\"},\"originTree\":{\"branches\":{\"master\":{\"target\":\"C3\",\"id\":\"master\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":null}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"},\"C3\":{\"parents\":[\"C1\"],\"id\":\"C3\"}},\"HEAD\":{\"target\":\"master\",\"id\":\"HEAD\"}}}",
  "name": {
    "en_US": "Git Pullin'",
    "zh_CN": "Git Pullin'",
    "de_DE": "Git Pull"
  },
  "hint": {
    "en_US": "Just run git pull!",
    "zh_CN": "Just run git pull!",
    "de_DE": "Führe einfach git pull aus."
  },
  "startDialog": {
    "en_US": {
      "childViews": [
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "## Git Pull",
              "",
              "Now that we've seen how to fetch data from a remote repository with `git fetch`, let's update our work to reflect those changes!",
              "",
              "There are actually many ways to do this -- once you have new commits available locally, you can incorporate them as if they were just normal commits on other branches. This means you could execute commands like:",
              "",
              "* `git cherry-pick o/master`",
              "* `git rebase o/master`",
              "* `git merge o/master`",
              "* etc., etc.",
              "",
              "In fact, the workflow of *fetching* remote changes and then *merging* them is so common that git actually make a command that does both at once! That command is `git pull`."
            ]
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "Let's first see a `fetch` and a `merge` executed sequentially"
            ],
            "afterMarkdowns": [
              "Boom -- we downloaded `C3` with a `fetch` and then merged in that work with `git merge o/master`. Now our `master` branch reflects the new work from the remote (in this case, named `origin`)"
            ],
            "command": "git fetch; git merge o/master",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "What would happen if we used `git pull` instead?"
            ],
            "afterMarkdowns": [
              "The same thing! That should make it very clear that `git pull` is essentially shorthand for a `git fetch` followed by a merge of whatever branch was just fetched."
            ],
            "command": "git pull",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "We will explore the details of `git pull` later (including options and arguments), but for now let's try it out in the level.",
              "",
              "Remember -- you can actually solve this level with just `fetch` and `merge`, but it will cost you an extra command :P"
            ]
          }
        }
      ]
    },
    "zh_CN":{
      "childViews": [
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "## Git Pull",
              "",
              "现在我们已经知道了如何用`git fetch` 获取远端的变化, 现在我们学习如果将这些变化更新到我们的工作.",
              "",
              "其实有很多方法的 -- 只要我在本地有新的提交, 你可以像合并其它分支那样合并远端分支. 具体说就是你可以执行以下命令: ",
              "",
              "* `git cherry-pick o/master`",
              "* `git rebase o/master`",
              "* `git merge o/master`",
              "* etc, etc",
              "",
              "实际上, fetch / merge 变更是这样的普通, 以至于git 提供了一个实际两个功能的命令 -- `git pull`."
            ]
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "我们先顺序执行`fetch`,`merge` "
            ],
            "afterMarkdowns": [
              "我们用`fetch`下载了`C3`, 然后通过`git merge o/master`合并了这一提交. 现在我们的`master`分支映射到了远端的新工作"
            ],
            "command": "git fetch; git merge o/master",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "如果使用`git pull`呢?"
            ],
            "afterMarkdowns": [
              "同样的结果! 这清楚的说明了`git pull`就是git fetch再跟一个merge的缩写! "
            ],
            "command": "git pull",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "稍后我们会扫一下`git pull`的细节(选项和参数), 现在我们先完成作业.",
              "",
              "记住, 你可以用fetch/merge 通过本节, 但是这会增加你的命令.:P"
            ]
          }
        }
      ]
    },
    "de_DE": {
      "childViews": [
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "## Git Pull",
              "",
              "Jetzt, wo wir wissen wie wir mit `git fetch` Daten von einem entfernten Repository holen können, wollen wir unsere lokalen Daten aktualisieren, damit sie die Änderungen vom Server beinhalten.",
              "",
              "Tatsächlich gibt es eine Menge Wege dies zu erreichen -- sobald du die neuen Commits lokal verfügbar hast, kannst du sie integrieren so als wären es Commits von ganz normalen anderen Branches. Du kannst also:",
              "",
              "* `git cherry-pick o/master`",
              "* `git rebase o/master`",
              "* `git merge o/master`",
              "* usw. usf. ausfúhren.",
              "",
              "Der Ablauf, die Änderungen vom Server zu holen und dann in die eigene Arbeit zu mergen wird so häufig benötigt, dass Git einen Befehl kennt der beides auf einmal erledigt! Das ist `git pull`."
            ]
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "Schauen wir uns erst mal ein `fetch` gefolgt von `merge` an:"
            ],
            "afterMarkdowns": [
              "Bämm -- wir haben `C3` mit `fetch` heruntergeladen und dann in unseren Branch mit `git merge o/master` integriert. Nun bildet unser `master` dieselben Inhalte ab, wie sie auf dem entfernten Server (`origin`) liegen."
            ],
            "command": "git fetch; git merge o/master",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "Was passiert wohl, wenn wir stattdessen `git pull` benutzen?"
            ],
            "afterMarkdowns": [
              "Dasselbe in Pink. Das sollte recht deutlich machen, dass `git pull` nur eine Abkürzung für `git fetch` gefolgt von einem Merge des gerade aktualisierten Branches ist."
            ],
            "command": "git pull",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "Die Feinheiten von `git pull` werden wir uns später ansehen, für's Erste lass es uns in diesem Level ausprobieren.",
              "",
              "Vergiss nicht -- du kannst diesen Level auch mit `fetch` und `merge` lösen, aber das kostet dich einen Befehl extra. :P"
            ]
          }
        }
      ]
    }
  }
};
<commit_msg>update pull.js<commit_after>exports.level = {
  "goalTreeString": "{\"branches\":{\"master\":{\"target\":\"C4\",\"id\":\"master\",\"remoteTrackingBranchID\":\"o/master\",\"localBranchesThatTrackThis\":null},\"o/master\":{\"target\":\"C3\",\"id\":\"o/master\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":[\"master\"]}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"},\"C2\":{\"parents\":[\"C1\"],\"id\":\"C2\"},\"C3\":{\"parents\":[\"C1\"],\"id\":\"C3\"},\"C4\":{\"parents\":[\"C2\",\"C3\"],\"id\":\"C4\"}},\"HEAD\":{\"target\":\"master\",\"id\":\"HEAD\"},\"originTree\":{\"branches\":{\"master\":{\"target\":\"C3\",\"id\":\"master\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":null}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"},\"C3\":{\"parents\":[\"C1\"],\"id\":\"C3\"}},\"HEAD\":{\"target\":\"master\",\"id\":\"HEAD\"}}}",
  "solutionCommand": "git pull",
  "startTree": "{\"branches\":{\"master\":{\"target\":\"C2\",\"id\":\"master\",\"remoteTrackingBranchID\":\"o/master\",\"localBranchesThatTrackThis\":null},\"o/master\":{\"target\":\"C1\",\"id\":\"o/master\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":[\"master\"]}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"},\"C2\":{\"parents\":[\"C1\"],\"id\":\"C2\"}},\"HEAD\":{\"target\":\"master\",\"id\":\"HEAD\"},\"originTree\":{\"branches\":{\"master\":{\"target\":\"C3\",\"id\":\"master\",\"remoteTrackingBranchID\":null,\"localBranchesThatTrackThis\":null}},\"commits\":{\"C0\":{\"parents\":[],\"id\":\"C0\",\"rootCommit\":true},\"C1\":{\"parents\":[\"C0\"],\"id\":\"C1\"},\"C3\":{\"parents\":[\"C1\"],\"id\":\"C3\"}},\"HEAD\":{\"target\":\"master\",\"id\":\"HEAD\"}}}",
  "name": {
    "en_US": "Git Pullin'",
    "zh_CN": "Git Pullin'",
    "zh_TW": "Git Pullin'",
    "de_DE": "Git Pull"
  },
  "hint": {
    "en_US": "Just run git pull!",
    "zh_CN": "Just run git pull!",
    "zh_TW": "只要下 git pull 這個指令即可",
    "de_DE": "Führe einfach git pull aus."
  },
  "startDialog": {
    "en_US": {
      "childViews": [
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "## Git Pull",
              "",
              "Now that we've seen how to fetch data from a remote repository with `git fetch`, let's update our work to reflect those changes!",
              "",
              "There are actually many ways to do this -- once you have new commits available locally, you can incorporate them as if they were just normal commits on other branches. This means you could execute commands like:",
              "",
              "* `git cherry-pick o/master`",
              "* `git rebase o/master`",
              "* `git merge o/master`",
              "* etc., etc.",
              "",
              "In fact, the workflow of *fetching* remote changes and then *merging* them is so common that git actually make a command that does both at once! That command is `git pull`."
            ]
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "Let's first see a `fetch` and a `merge` executed sequentially"
            ],
            "afterMarkdowns": [
              "Boom -- we downloaded `C3` with a `fetch` and then merged in that work with `git merge o/master`. Now our `master` branch reflects the new work from the remote (in this case, named `origin`)"
            ],
            "command": "git fetch; git merge o/master",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "What would happen if we used `git pull` instead?"
            ],
            "afterMarkdowns": [
              "The same thing! That should make it very clear that `git pull` is essentially shorthand for a `git fetch` followed by a merge of whatever branch was just fetched."
            ],
            "command": "git pull",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "We will explore the details of `git pull` later (including options and arguments), but for now let's try it out in the level.",
              "",
              "Remember -- you can actually solve this level with just `fetch` and `merge`, but it will cost you an extra command :P"
            ]
          }
        }
      ]
    },
   "zh_TW": {
      "childViews": [
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "## Git Pull",
              "",
              "現在我們已經知道如何利用 `git fetch` 從 remotre repository 抓取資料，讓我們來看一下如何更新我們的資料!",
              "",
              "只要在你的 local repository 有新的 commit，便有很多方法可以做到這件事情，你可以把它們視為在其他 branch 上面的一般的 commit，這表示你可以執行像這樣子的命令:",
              "",
              "* `git cherry-pick o/master`",
              "* `git rebase o/master`",
              "* `git merge o/master`",
              "* 等等‧‧‧",
              "",
              "事實上，一次*下載 (fetch)* remote 的更新並且*合併 (merge)* 這些更新在 git 裡面是很常見的事情! 這個命令叫作 `git pull`。"
            ]
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "讓我們來看循序執行一個 `fetch` 和一個 `merge` 的樣子"
            ],
            "afterMarkdowns": [
              "看吧! 我們利用 `fetch` 下載了 `C3` 並且利用 `git merge o/master` 來更新資料，現在我們的 `master` branch 跟 remote repository 的新的工作進度同步了 (在這個例子中，remote repository 叫作 `origin`)"
            ],
            "command": "git fetch; git merge o/master",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "如果用 `git pull` 會發生什麼事情?"
            ],
            "afterMarkdowns": [
              "一樣! 很明顯地，`git pull` 其實就是 git fetch 跟 git merge 的循序執行的結果，而且 merge 的 branch 就是 fetch 所更新的 branch。"
            ],
            "command": "git pull",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "我們會解釋 `git pull` 的細節 (包括 options 以及 arguments), 但現在先讓我們在這個關卡試試看！",
              "",
              "記住喔，你可以利用循序執行的方式來執行 `fetch` 以及 `merge` 來完成這個關卡，但是相對於 `git pull`，你就得多打一個指令。:P"
            ]
          }
        }
      ]
    },
    "zh_CN":{
      "childViews": [
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "## Git Pull",
              "",
              "现在我们已经知道了如何用`git fetch` 获取远端的变化, 现在我们学习如果将这些变化更新到我们的工作.",
              "",
              "其实有很多方法的 -- 只要我在本地有新的提交, 你可以像合并其它分支那样合并远端分支. 具体说就是你可以执行以下命令: ",
              "",
              "* `git cherry-pick o/master`",
              "* `git rebase o/master`",
              "* `git merge o/master`",
              "* etc, etc",
              "",
              "实际上, fetch / merge 变更是这样的普通, 以至于git 提供了一个实际两个功能的命令 -- `git pull`."
            ]
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "我们先顺序执行`fetch`,`merge` "
            ],
            "afterMarkdowns": [
              "我们用`fetch`下载了`C3`, 然后通过`git merge o/master`合并了这一提交. 现在我们的`master`分支映射到了远端的新工作"
            ],
            "command": "git fetch; git merge o/master",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "如果使用`git pull`呢?"
            ],
            "afterMarkdowns": [
              "同样的结果! 这清楚的说明了`git pull`就是git fetch再跟一个merge的缩写! "
            ],
            "command": "git pull",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "稍后我们会扫一下`git pull`的细节(选项和参数), 现在我们先完成作业.",
              "",
              "记住, 你可以用fetch/merge 通过本节, 但是这会增加你的命令.:P"
            ]
          }
        }
      ]
    },
    "de_DE": {
      "childViews": [
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "## Git Pull",
              "",
              "Jetzt, wo wir wissen wie wir mit `git fetch` Daten von einem entfernten Repository holen können, wollen wir unsere lokalen Daten aktualisieren, damit sie die Änderungen vom Server beinhalten.",
              "",
              "Tatsächlich gibt es eine Menge Wege dies zu erreichen -- sobald du die neuen Commits lokal verfügbar hast, kannst du sie integrieren so als wären es Commits von ganz normalen anderen Branches. Du kannst also:",
              "",
              "* `git cherry-pick o/master`",
              "* `git rebase o/master`",
              "* `git merge o/master`",
              "* usw. usf. ausfúhren.",
              "",
              "Der Ablauf, die Änderungen vom Server zu holen und dann in die eigene Arbeit zu mergen wird so häufig benötigt, dass Git einen Befehl kennt der beides auf einmal erledigt! Das ist `git pull`."
            ]
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "Schauen wir uns erst mal ein `fetch` gefolgt von `merge` an:"
            ],
            "afterMarkdowns": [
              "Bämm -- wir haben `C3` mit `fetch` heruntergeladen und dann in unseren Branch mit `git merge o/master` integriert. Nun bildet unser `master` dieselben Inhalte ab, wie sie auf dem entfernten Server (`origin`) liegen."
            ],
            "command": "git fetch; git merge o/master",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "GitDemonstrationView",
          "options": {
            "beforeMarkdowns": [
              "Was passiert wohl, wenn wir stattdessen `git pull` benutzen?"
            ],
            "afterMarkdowns": [
              "Dasselbe in Pink. Das sollte recht deutlich machen, dass `git pull` nur eine Abkürzung für `git fetch` gefolgt von einem Merge des gerade aktualisierten Branches ist."
            ],
            "command": "git pull",
            "beforeCommand": "git clone; git commit; git fakeTeamwork"
          }
        },
        {
          "type": "ModalAlert",
          "options": {
            "markdowns": [
              "Die Feinheiten von `git pull` werden wir uns später ansehen, für's Erste lass es uns in diesem Level ausprobieren.",
              "",
              "Vergiss nicht -- du kannst diesen Level auch mit `fetch` und `merge` lösen, aber das kostet dich einen Befehl extra. :P"
            ]
          }
        }
      ]
    }
  }
};
<|endoftext|>


<commit_before>/**
 * If you want to provide a custom regexp, this is the configuration to use.
 * -- For historical reasons, all regexps are processed as if they have the global flag set.
 * -- Use the nonExhaustiveModeMaxMatchCount property to match a limited number of matches.
 * Note: any additional keys/props are permitted, and will be returned as-is!
 * @typedef {Object} CustomParseShape
 * @property {RegExp} pattern
 * @property {number} [nonExhaustiveModeMaxMatchCount] Enables "non-exhaustive mode", where you can limit how many matches are found. -- Must be a positive integer or Infinity matches are permitted
 * @property {Function} [renderText] arbitrary function to rewrite the matched string into something else
 * @property {Function} [onPress]
 * @property {Function} [onLongPress]
 */
/**
 * Class to encapsulate the business logic of converting text into matches & props
 */
class TextExtraction {
  /**
   * @param {String} text - Text to be parsed
   * @param {CustomParseShape[]} patterns - Patterns to be used when parsed,
   *                                 any extra attributes, will be returned from parse()
   */
  constructor(text, patterns) {
    this.text = text;
    this.patterns = patterns || [];
  }

  /**
   * Returns parts of the text with their own props
   * @public
   * @return {Object[]} - props for all the parts of the text
   */
  parse() {
    let parsedTexts = [{ children: this.text }];
    this.patterns.forEach((pattern) => {
      let newParts = [];

      const tmp = pattern.nonExhaustiveModeMaxMatchCount || 0;
      const numberOfMatchesPermitted = Math.min(
        Math.max(Number.isInteger(tmp) ? tmp : 0, 0) ||
          Number.POSITIVE_INFINITY,
        Number.POSITIVE_INFINITY,
      );

      let currentMatches = 0;

      parsedTexts.forEach((parsedText) => {
        // Only allow for now one parsing
        if (parsedText._matched) {
          newParts.push(parsedText);

          return;
        }

        let parts = [];
        let textLeft = parsedText.children;
        let indexOfMatchedString = 0;

        /** @type {RegExpExecArray} */
        let matches;
        // Global RegExps are stateful, this makes it start at 0 if reused
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec
        pattern.pattern.lastIndex = 0;
        while (textLeft && (matches = pattern.pattern.exec(textLeft))) {
          let previousText = textLeft.substr(0, matches.index);
          indexOfMatchedString = matches.index;

          if (++currentMatches > numberOfMatchesPermitted) {
            // Abort if we've exhausted our number of matches
            parts.push({ children: textLeft });
            break;
          }

          parts.push({ children: previousText });

          parts.push(
            this.getMatchedPart(
              pattern,
              matches[0],
              matches,
              indexOfMatchedString,
            ),
          );

          textLeft = textLeft.substr(matches.index + matches[0].length);
          indexOfMatchedString += matches[0].length - 1;
          // Global RegExps are stateful, this makes it operate on the "remainder" of the string
          pattern.pattern.lastIndex = 0;
        }

        parts.push({ children: textLeft });

        newParts.push(...parts);
      });

      parsedTexts = newParts;
    });

    // Remove _matched key.
    parsedTexts.forEach((parsedText) => delete parsedText._matched);

    return parsedTexts.filter((t) => !!t.children);
  }

  // private

  /**
   * @protected
   * @param {ParseShape} matchedPattern - pattern configuration of the pattern used to match the text
   * @param {String} text - Text matching the pattern
   * @param {String[]} matches - Result of the RegExp.exec
   * @param {Integer} index - Index of the matched string in the whole string
   * @return {Object} props for the matched text
   */
  getMatchedPart(matchedPattern, text, matches, index) {
    let props = {};

    Object.keys(matchedPattern).forEach((key) => {
      if (
        key === 'pattern' ||
        key === 'renderText' ||
        key === 'nonExhaustiveModeMaxMatchCount'
      ) {
        return;
      }

      if (typeof matchedPattern[key] === 'function') {
        // Support onPress / onLongPress functions
        props[key] = () => matchedPattern[key](text, index);
      } else {
        // Set a prop with an arbitrary name to the value in the match-config
        props[key] = matchedPattern[key];
      }
    });

    let children = text;
    if (
      matchedPattern.renderText &&
      typeof matchedPattern.renderText === 'function'
    ) {
      children = matchedPattern.renderText(text, matches);
    }

    return {
      ...props,
      children: children,
      _matched: true,
    };
  }
}

export default TextExtraction;
<commit_msg>nonExhaustiveModeMatchCount -- don't duplicate unmatched text<commit_after>/**
 * If you want to provide a custom regexp, this is the configuration to use.
 * -- For historical reasons, all regexps are processed as if they have the global flag set.
 * -- Use the nonExhaustiveModeMaxMatchCount property to match a limited number of matches.
 * Note: any additional keys/props are permitted, and will be returned as-is!
 * @typedef {Object} CustomParseShape
 * @property {RegExp} pattern
 * @property {number} [nonExhaustiveModeMaxMatchCount] Enables "non-exhaustive mode", where you can limit how many matches are found. -- Must be a positive integer or Infinity matches are permitted
 * @property {Function} [renderText] arbitrary function to rewrite the matched string into something else
 * @property {Function} [onPress]
 * @property {Function} [onLongPress]
 */
/**
 * Class to encapsulate the business logic of converting text into matches & props
 */
class TextExtraction {
  /**
   * @param {String} text - Text to be parsed
   * @param {CustomParseShape[]} patterns - Patterns to be used when parsed,
   *                                 any extra attributes, will be returned from parse()
   */
  constructor(text, patterns) {
    this.text = text;
    this.patterns = patterns || [];
  }

  /**
   * Returns parts of the text with their own props
   * @public
   * @return {Object[]} - props for all the parts of the text
   */
  parse() {
    let parsedTexts = [{ children: this.text }];
    this.patterns.forEach((pattern) => {
      let newParts = [];

      const tmp = pattern.nonExhaustiveModeMaxMatchCount || 0;
      const numberOfMatchesPermitted = Math.min(
        Math.max(Number.isInteger(tmp) ? tmp : 0, 0) ||
          Number.POSITIVE_INFINITY,
        Number.POSITIVE_INFINITY,
      );

      let currentMatches = 0;

      parsedTexts.forEach((parsedText) => {
        // Only allow for now one parsing
        if (parsedText._matched) {
          newParts.push(parsedText);
          return;
        }

        let parts = [];
        let textLeft = parsedText.children;
        let indexOfMatchedString = 0;

        /** @type {RegExpExecArray} */
        let matches;
        // Global RegExps are stateful, this makes it start at 0 if reused
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec
        pattern.pattern.lastIndex = 0;
        while (textLeft && (matches = pattern.pattern.exec(textLeft))) {
          let previousText = textLeft.substr(0, matches.index);
          indexOfMatchedString = matches.index;

          if (++currentMatches > numberOfMatchesPermitted) {
            // Abort if we've exhausted our number of matches
            break;
          }

          parts.push({ children: previousText });

          parts.push(
            this.getMatchedPart(
              pattern,
              matches[0],
              matches,
              indexOfMatchedString,
            ),
          );

          textLeft = textLeft.substr(matches.index + matches[0].length);
          indexOfMatchedString += matches[0].length - 1;
          // Global RegExps are stateful, this makes it operate on the "remainder" of the string
          pattern.pattern.lastIndex = 0;
        }

        parts.push({ children: textLeft });

        newParts.push(...parts);
      });

      parsedTexts = newParts;
    });

    // Remove _matched key.
    parsedTexts.forEach((parsedText) => delete parsedText._matched);

    return parsedTexts.filter((t) => !!t.children);
  }

  // private

  /**
   * @protected
   * @param {ParseShape} matchedPattern - pattern configuration of the pattern used to match the text
   * @param {String} text - Text matching the pattern
   * @param {String[]} matches - Result of the RegExp.exec
   * @param {Integer} index - Index of the matched string in the whole string
   * @return {Object} props for the matched text
   */
  getMatchedPart(matchedPattern, text, matches, index) {
    let props = {};

    Object.keys(matchedPattern).forEach((key) => {
      if (
        key === 'pattern' ||
        key === 'renderText' ||
        key === 'nonExhaustiveModeMaxMatchCount'
      ) {
        return;
      }

      if (typeof matchedPattern[key] === 'function') {
        // Support onPress / onLongPress functions
        props[key] = () => matchedPattern[key](text, index);
      } else {
        // Set a prop with an arbitrary name to the value in the match-config
        props[key] = matchedPattern[key];
      }
    });

    let children = text;
    if (
      matchedPattern.renderText &&
      typeof matchedPattern.renderText === 'function'
    ) {
      children = matchedPattern.renderText(text, matches);
    }

    return {
      ...props,
      children: children,
      _matched: true,
    };
  }
}

export default TextExtraction;
<|endoftext|>


<commit_before>
// global ide object

var ide = new(function() {
  // == private members ==
  var codeEditor = null;
  // == public members ==
  this.dataViewer = null;
  this.map = null;

  // == private methods ==
  var init = function() {
    // load settings
    settings.load();
    // check for any get-parameters
    if (location.search != "") {
      var get = location.search.substring(1).split("&");
      for (var i=0; i<get.length; i++) {
        var kv = get[i].split("=");
        if (kv[0] == "q") // compressed query set in url
          settings.code["overpass"] = lzw_decode(Base64.decode(decodeURIComponent(kv[1])));
        if (kv[0] == "Q") // uncompressed query set in url
          settings.code["overpass"] = decodeURIComponent(kv[1]);
      }
      settings.save();
    }

    // init codemirror
    codeEditor = CodeMirror($("#editor")[0], {
      value: settings.code["overpass"],
      lineNumbers: true,
      mode: "xml",
      onChange: function(e) {
        settings.code["overpass"] = e.getValue();
        settings.save();
      },
    });
    ide.dataViewer = CodeMirror($("#data")[0], {
      value:'no data loaded yet',
      lineNumbers: true,
      readonly: true,
      mode: "javascript",
    });

    // init leaflet
    ide.map = new L.Map("map");
    var osmUrl="http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
    var osmAttrib="Map data © openstreetmap contributors";
    var osm = new L.TileLayer(osmUrl,{minZoom:4,maxZoom:18,attribution:osmAttrib});
    var pos = new L.LatLng(settings.coords_lat,settings.coords_lon);
    ide.map.setView(pos,settings.coords_zoom).addLayer(osm);
    L.control.scale().addTo(ide.map);
    if (settings.use_html5_coords) {
      // One-shot position request.
      try {
        navigator.geolocation.getCurrentPosition(function (position){
          var pos = new L.LatLng(position.coords.latitude,position.coords.longitude);
          ide.map.setView(pos,settings.coords_zoom);
        });
      } catch(e) {}
    }
    ide.map.on('moveend', function() {
      settings.coords_lat = ide.map.getCenter().lat;
      settings.coords_lon = ide.map.getCenter().lng;
      settings.coords_zoom = ide.map.getZoom();
      settings.save(); // save settings
    });

    // disabled buttons
    $("a.disabled").bind("click",function() { return false; });

    // tabs
    $("#dataviewer > div#data")[0].style.zIndex = -1001;
    $(".tabs a.button").bind("click",function(e) {
      if ($(e.target).hasClass("active")) {
        return;
      } else {
        $("#dataviewer > div#data")[0].style.zIndex = -1*$("#dataviewer > div#data")[0].style.zIndex;
        $(".tabs a.button").toggleClass("active");
      }
    });

    // wait spinner
    $("body").on({
      ajaxStart: function() {
        $(this).addClass("loading");
      },
      ajaxStop: function() {
        $(this).removeClass("loading");
      },
    });

    // keyboard event listener
    // todo: make function "onKeyPress()" out of this
    $("body").keypress(function(event) {
      if ((event.keyCode == 120 && event.which == 0) || // F9
          ((event.which == 13 || event.which == 10) && (event.ctrlKey || event.metaKey))) { // Ctrl+Enter
        ide.onRunClick(); // run query
        event.preventDefault();
      }
      // todo: more shortcuts
    });

    // leaflet extension
    var MyControl = L.Control.extend({
      options: {
        position:'topleft',
      },
      onAdd: function(map) {
        // create the control container with a particular class name
        var container = L.DomUtil.create('div', 'leaflet-control-buttons');
        var link = L.DomUtil.create('a', "leaflet-control-buttons-fitdata", container);
        link.href = '#';
        link.title = "fit zoom to data";
        L.DomEvent.addListener(link, 'click', function() {
          try {ide.map.fitBounds(ide.map.geojsonLayer.getBounds()); } catch (e) {}
        }, ide.map);
        var link = L.DomUtil.create('a', "leaflet-control-buttons-myloc", container);
        link.href = '#';
        link.title = "pan to current location";
        L.DomEvent.addListener(link, 'click', function() {
          // One-shot position request.
          try {
            navigator.geolocation.getCurrentPosition(function (position){
              var pos = new L.LatLng(position.coords.latitude,position.coords.longitude);
              ide.map.setView(pos,settings.coords_zoom);
            });
          } catch(e) {}
        }, ide.map);
        return container;
      },
    });
  }
  var overpassJSON2geoJSON = function(json) {
    // 2. sort elements
    var nodes = new Array();
    var ways  = new Array();
    var rels  = new Array();
    for (var i=0;i<json.elements.length;i++) {
      switch (json.elements[i].type) {
        case "node":
          nodes.push(json.elements[i]);
          break;
        case "way":
          ways.push(json.elements[i]);
          break;
        case "relation":
          rels.push(json.elements[i]);
          break;
        default:
          alert("???");
      }
    }

    // 3. some data processing (e.g. filter nodes only used for ways)
    var nids = new Object();
    var nodeids = new Array();
    for (var i=0;i<nodes.length;i++) {
      nids[nodes[i].id] = nodes[i];
      nodeids.push(nodes[i].id);
    }
    var poinids = new Array();
    for (var i=0;i<nodes.length;i++) {
      if (typeof nodes[i].tags != 'undefined')
        poinids.push(nodes[i].id);
    }
    var waynids = new Array();
    var wayids = new Array();
    for (var i=0;i<ways.length;i++) {
      wayids.push(ways[i].id);
      for (var j=0;j<ways[i].nodes.length;j++) {
        waynids.push(ways[i].nodes[j]);
        ways[i].nodes[j] = nids[ways[i].nodes[j]];
      }
    }
    var pois = new Array();
    for (var i=0;i<nodes.length;i++) {
      if ((waynids.indexOf(nodes[i].id) == -1) || // not related to any way
          (poinids.indexOf(nodes[i].id) != -1))   // or has tags
        pois.push(nodes[i]);
    }
    var relids = new Array();
    for (var i=0;i<rels.length;i++) {
      relids.push(rels[i].id);
      for (var j=0;j<rels[i].members.length;j++) {
        switch (rels[i].members[j].type) {
        case "node":
          n = nodeids.indexOf(rels[i].members[j].ref);
          if (n != -1) {
            if (typeof nodes[n].relations == "undefined")
              nodes[n].relations = new Array();
            nodes[n].relations.push({
              "rel" : rels[i].id,
              "role" : rels[i].members[j].role,
            });
          }
        break;
        case "way":
          w = wayids.indexOf(rels[i].members[j].ref);
          if (w != -1) {
            if (typeof ways[w].relations == "undefined")
              ways[w].relations = new Array();
            ways[w].relations.push({
              "rel" : rels[i].id,
              "role" : rels[i].members[j].role,
            });
          }
        break;
        default:
        }
      }
    }

    // 4. construct geojson
    var geojson = new Array();
    var geojsonnodes = {
      "type"     : "FeatureCollection",
      "features" : new Array()};
    for (i=0;i<pois.length;i++) {
      geojsonnodes.features.push({
        "type"       : "Feature",
        "properties" : {
          "tags" : pois[i].tags,
          "relations" : pois[i].relations,
        },
        "id"         : pois[i].id,
        "geometry"   : {
          "type" : "Point",
          "coordinates" : [pois[i].lon, pois[i].lat],
        }
      });
    }
    geojson.push(geojsonnodes);
    var geojsonways = {
      "type"     : "FeatureCollection",
      "features" : new Array()};
    for (var i=0;i<ways.length;i++) {
      ways[i].tainted = false;
      coords = new Array();
      for (j=0;j<ways[i].nodes.length;j++) {
        if (typeof ways[i].nodes[j] == "object")
          coords.push([ways[i].nodes[j].lon, ways[i].nodes[j].lat]);
        else
          ways[i].tainted = true;
      }

      var way_type = "LineString"; // default
      if (ways[i].nodes[0] == ways[i].nodes[ways[i].nodes.length-1]) {
        if (typeof ways[i].tags != "undefined")
          if ((typeof ways[i].tags["landuse"] != "undefined") ||
              (typeof ways[i].tags["building"] != "undefined") ||
              (typeof ways[i].tags["leisure"] != "undefined") ||
              (typeof ways[i].tags["area"] == "yes") ||
              ($.inArray(ways[i].tags["natural"], new Array("forest","wood","water"))) ||
              false) {
             way_type="Polygon";
             coords = [coords];
           }
      }
      geojsonways.features.push({
        "type"       : "Feature",
        "properties" : {
          "tainted" : ways[i].tainted,
          "tags" : ways[i].tags,
          "relations" : ways[i].relations,
        },
        "id"         : ways[i].id,
        "geometry"   : {
          "type" : way_type,
          "coordinates" : coords,
        }
      });
    }
    geojson.push(geojsonways);

    return geojson;
  }

  // == public methods ==

  // returns the current visible bbox as a bbox-query
  this.map2bbox = function(lang) {
    if (lang=="ql")
      return "("+this.map.getBounds().getSouthWest().lat+','+this.map.getBounds().getSouthWest().lng+','+this.map.getBounds().getNorthEast().lat+','+this.map.getBounds().getNorthEast().lng+")";
    else (lang=="xml")
      return '<bbox-query s="'+this.map.getBounds().getSouthWest().lat+'" w="'+this.map.getBounds().getSouthWest().lng+'" n="'+this.map.getBounds().getNorthEast().lat+'" e="'+this.map.getBounds().getNorthEast().lng+'"/>';
  }
  // returns the current visible map center as a coord-query
  this.map2coord = function(lang) {
    if (lang=="xml")
      return '<coord-query lat="'+this.map.getCenter().lat+'" lon="'+this.map.getCenter().lng+'"/>';
  }
  this.getQuery = function() {
    return codeEditor.getValue();
  }
  this.setQuery = function(query) {
    codeEditor.setValue(query);
  }

  this.switchTab = function(tab) {
    $("#navs .tabs a:contains('"+tab+"')").click();
  }

  // Event handlers
  this.onLoadClick = function() {
    var ex_html = "";
    for(var example in settings.saves)
      ex_html += '
<input type="radio" name="ex_list" value="'+example+'" />'+example+'
';
    $('

Select example to load:

'+ex_html+'

').dialog({
      modal:true,
      //height:250,
      buttons: {
        "Load" : function() {
          $("input",this).each(function(i,inp) {
            if (inp.checked)
              ide.setQuery(settings.saves[inp.value].overpass);
          });
          $(this).dialog("close");
        },
        "Cancel" : function() {$(this).dialog("close");}
      }
    });

  }
  this.onSaveClick = function() {
    $('

Select name

<input name="save" type="text" />

').dialog({
      modal:true,
      //height:150,
      buttons: {
        "Save" : function() {
          var name = $("input",this)[0].value;
          settings.saves[name] = {
            "overpass": ide.getQuery()
          };
          settings.save();
          $(this).dialog("close");
        },
        "Cancel": function() {$(this).dialog("close");}
      }
    });
  }
  this.onRunClick = function() {
    overpass.update_map();
  }
  this.onShareClick = function() {
    var baseurl=location.protocol+"//"+location.host+location.pathname;
    var shared_code = codeEditor.getValue();
    var share_link_uncompressed = baseurl+"?Q="+encodeURIComponent(shared_code);
    var share_link;
    if (shared_code.length <= 300) // todo: more options for this in the settings (auto / compressed / uncompressed)
      share_link = share_link_uncompressed;
    else {
      var share_link_compressed = baseurl+"?q="+encodeURIComponent(Base64.encode(lzw_encode(shared_code)));
      share_link = share_link_compressed;
    }
    var warning = '';
    if (share_link.length >= 2000)
      warning = '
Warning: This share-link is quite long. It may not work under certain circumstances (browsers, webservers).
';
    if (share_link.length >= 8000)
      warning = '
Warning: This share-link is very long. It is likely to fail under normal circumstances (browsers, webservers). Use with caution.
';
    //alert(share_link_uncompressed.length + " / " + share_link_compressed.length + " => " + ((share_link_uncompressed.length-share_link_compressed.length)/share_link_uncompressed.length * 100) + "%");
    $('

Copy this link to share the current code:

<textarea rows=4 style="width:100%" readonly>'+share_link+'</textarea>
'+warning+'
').dialog({
      modal:true,
      buttons: {
        "OK": function() {$(this).dialog("close");}
      }
    });
  }

  // == initializations ==
  // initialize on document ready
  $(document).ready(init);

})(); // end create ide object

<commit_msg>moving onKeyPress handler into separate function<commit_after>
// global ide object

var ide = new(function() {
  // == private members ==
  var codeEditor = null;
  // == public members ==
  this.dataViewer = null;
  this.map = null;

  // == private methods ==
  var init = function() {
    // load settings
    settings.load();
    // check for any get-parameters
    if (location.search != "") {
      var get = location.search.substring(1).split("&");
      for (var i=0; i<get.length; i++) {
        var kv = get[i].split("=");
        if (kv[0] == "q") // compressed query set in url
          settings.code["overpass"] = lzw_decode(Base64.decode(decodeURIComponent(kv[1])));
        if (kv[0] == "Q") // uncompressed query set in url
          settings.code["overpass"] = decodeURIComponent(kv[1]);
      }
      settings.save();
    }

    // init codemirror
    codeEditor = CodeMirror($("#editor")[0], {
      value: settings.code["overpass"],
      lineNumbers: true,
      mode: "xml",
      onChange: function(e) {
        settings.code["overpass"] = e.getValue();
        settings.save();
      },
    });
    ide.dataViewer = CodeMirror($("#data")[0], {
      value:'no data loaded yet',
      lineNumbers: true,
      readonly: true,
      mode: "javascript",
    });

    // init leaflet
    ide.map = new L.Map("map");
    var osmUrl="http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
    var osmAttrib="Map data © openstreetmap contributors";
    var osm = new L.TileLayer(osmUrl,{minZoom:4,maxZoom:18,attribution:osmAttrib});
    var pos = new L.LatLng(settings.coords_lat,settings.coords_lon);
    ide.map.setView(pos,settings.coords_zoom).addLayer(osm);
    L.control.scale().addTo(ide.map);
    if (settings.use_html5_coords) {
      // One-shot position request.
      try {
        navigator.geolocation.getCurrentPosition(function (position){
          var pos = new L.LatLng(position.coords.latitude,position.coords.longitude);
          ide.map.setView(pos,settings.coords_zoom);
        });
      } catch(e) {}
    }
    ide.map.on('moveend', function() {
      settings.coords_lat = ide.map.getCenter().lat;
      settings.coords_lon = ide.map.getCenter().lng;
      settings.coords_zoom = ide.map.getZoom();
      settings.save(); // save settings
    });

    // disabled buttons
    $("a.disabled").bind("click",function() { return false; });

    // tabs
    $("#dataviewer > div#data")[0].style.zIndex = -1001;
    $(".tabs a.button").bind("click",function(e) {
      if ($(e.target).hasClass("active")) {
        return;
      } else {
        $("#dataviewer > div#data")[0].style.zIndex = -1*$("#dataviewer > div#data")[0].style.zIndex;
        $(".tabs a.button").toggleClass("active");
      }
    });

    // wait spinner
    $("body").on({
      ajaxStart: function() {
        $(this).addClass("loading");
      },
      ajaxStop: function() {
        $(this).removeClass("loading");
      },
    });

    // keyboard event listener
    $("body").keypress(ide.onKeyPress);

    // leaflet extension
    var MyControl = L.Control.extend({
      options: {
        position:'topleft',
      },
      onAdd: function(map) {
        // create the control container with a particular class name
        var container = L.DomUtil.create('div', 'leaflet-control-buttons');
        var link = L.DomUtil.create('a', "leaflet-control-buttons-fitdata", container);
        link.href = '#';
        link.title = "fit zoom to data";
        L.DomEvent.addListener(link, 'click', function() {
          try {ide.map.fitBounds(ide.map.geojsonLayer.getBounds()); } catch (e) {}
        }, ide.map);
        var link = L.DomUtil.create('a', "leaflet-control-buttons-myloc", container);
        link.href = '#';
        link.title = "pan to current location";
        L.DomEvent.addListener(link, 'click', function() {
          // One-shot position request.
          try {
            navigator.geolocation.getCurrentPosition(function (position){
              var pos = new L.LatLng(position.coords.latitude,position.coords.longitude);
              ide.map.setView(pos,settings.coords_zoom);
            });
          } catch(e) {}
        }, ide.map);
        return container;
      },
    });
  }
  var overpassJSON2geoJSON = function(json) {
    // 2. sort elements
    var nodes = new Array();
    var ways  = new Array();
    var rels  = new Array();
    for (var i=0;i<json.elements.length;i++) {
      switch (json.elements[i].type) {
        case "node":
          nodes.push(json.elements[i]);
          break;
        case "way":
          ways.push(json.elements[i]);
          break;
        case "relation":
          rels.push(json.elements[i]);
          break;
        default:
          alert("???");
      }
    }

    // 3. some data processing (e.g. filter nodes only used for ways)
    var nids = new Object();
    var nodeids = new Array();
    for (var i=0;i<nodes.length;i++) {
      nids[nodes[i].id] = nodes[i];
      nodeids.push(nodes[i].id);
    }
    var poinids = new Array();
    for (var i=0;i<nodes.length;i++) {
      if (typeof nodes[i].tags != 'undefined')
        poinids.push(nodes[i].id);
    }
    var waynids = new Array();
    var wayids = new Array();
    for (var i=0;i<ways.length;i++) {
      wayids.push(ways[i].id);
      for (var j=0;j<ways[i].nodes.length;j++) {
        waynids.push(ways[i].nodes[j]);
        ways[i].nodes[j] = nids[ways[i].nodes[j]];
      }
    }
    var pois = new Array();
    for (var i=0;i<nodes.length;i++) {
      if ((waynids.indexOf(nodes[i].id) == -1) || // not related to any way
          (poinids.indexOf(nodes[i].id) != -1))   // or has tags
        pois.push(nodes[i]);
    }
    var relids = new Array();
    for (var i=0;i<rels.length;i++) {
      relids.push(rels[i].id);
      for (var j=0;j<rels[i].members.length;j++) {
        switch (rels[i].members[j].type) {
        case "node":
          n = nodeids.indexOf(rels[i].members[j].ref);
          if (n != -1) {
            if (typeof nodes[n].relations == "undefined")
              nodes[n].relations = new Array();
            nodes[n].relations.push({
              "rel" : rels[i].id,
              "role" : rels[i].members[j].role,
            });
          }
        break;
        case "way":
          w = wayids.indexOf(rels[i].members[j].ref);
          if (w != -1) {
            if (typeof ways[w].relations == "undefined")
              ways[w].relations = new Array();
            ways[w].relations.push({
              "rel" : rels[i].id,
              "role" : rels[i].members[j].role,
            });
          }
        break;
        default:
        }
      }
    }

    // 4. construct geojson
    var geojson = new Array();
    var geojsonnodes = {
      "type"     : "FeatureCollection",
      "features" : new Array()};
    for (i=0;i<pois.length;i++) {
      geojsonnodes.features.push({
        "type"       : "Feature",
        "properties" : {
          "tags" : pois[i].tags,
          "relations" : pois[i].relations,
        },
        "id"         : pois[i].id,
        "geometry"   : {
          "type" : "Point",
          "coordinates" : [pois[i].lon, pois[i].lat],
        }
      });
    }
    geojson.push(geojsonnodes);
    var geojsonways = {
      "type"     : "FeatureCollection",
      "features" : new Array()};
    for (var i=0;i<ways.length;i++) {
      ways[i].tainted = false;
      coords = new Array();
      for (j=0;j<ways[i].nodes.length;j++) {
        if (typeof ways[i].nodes[j] == "object")
          coords.push([ways[i].nodes[j].lon, ways[i].nodes[j].lat]);
        else
          ways[i].tainted = true;
      }

      var way_type = "LineString"; // default
      if (ways[i].nodes[0] == ways[i].nodes[ways[i].nodes.length-1]) {
        if (typeof ways[i].tags != "undefined")
          if ((typeof ways[i].tags["landuse"] != "undefined") ||
              (typeof ways[i].tags["building"] != "undefined") ||
              (typeof ways[i].tags["leisure"] != "undefined") ||
              (typeof ways[i].tags["area"] == "yes") ||
              ($.inArray(ways[i].tags["natural"], new Array("forest","wood","water"))) ||
              false) {
             way_type="Polygon";
             coords = [coords];
           }
      }
      geojsonways.features.push({
        "type"       : "Feature",
        "properties" : {
          "tainted" : ways[i].tainted,
          "tags" : ways[i].tags,
          "relations" : ways[i].relations,
        },
        "id"         : ways[i].id,
        "geometry"   : {
          "type" : way_type,
          "coordinates" : coords,
        }
      });
    }
    geojson.push(geojsonways);

    return geojson;
  }

  // == public methods ==

  // returns the current visible bbox as a bbox-query
  this.map2bbox = function(lang) {
    if (lang=="ql")
      return "("+this.map.getBounds().getSouthWest().lat+','+this.map.getBounds().getSouthWest().lng+','+this.map.getBounds().getNorthEast().lat+','+this.map.getBounds().getNorthEast().lng+")";
    else (lang=="xml")
      return '<bbox-query s="'+this.map.getBounds().getSouthWest().lat+'" w="'+this.map.getBounds().getSouthWest().lng+'" n="'+this.map.getBounds().getNorthEast().lat+'" e="'+this.map.getBounds().getNorthEast().lng+'"/>';
  }
  // returns the current visible map center as a coord-query
  this.map2coord = function(lang) {
    if (lang=="xml")
      return '<coord-query lat="'+this.map.getCenter().lat+'" lon="'+this.map.getCenter().lng+'"/>';
  }
  this.getQuery = function() {
    return codeEditor.getValue();
  }
  this.setQuery = function(query) {
    codeEditor.setValue(query);
  }

  this.switchTab = function(tab) {
    $("#navs .tabs a:contains('"+tab+"')").click();
  }

  // Event handlers
  this.onLoadClick = function() {
    var ex_html = "";
    for(var example in settings.saves)
      ex_html += '
<input type="radio" name="ex_list" value="'+example+'" />'+example+'
';
    $('

Select example to load:

'+ex_html+'

').dialog({
      modal:true,
      //height:250,
      buttons: {
        "Load" : function() {
          $("input",this).each(function(i,inp) {
            if (inp.checked)
              ide.setQuery(settings.saves[inp.value].overpass);
          });
          $(this).dialog("close");
        },
        "Cancel" : function() {$(this).dialog("close");}
      }
    });

  }
  this.onSaveClick = function() {
    $('

Select name

<input name="save" type="text" />

').dialog({
      modal:true,
      //height:150,
      buttons: {
        "Save" : function() {
          var name = $("input",this)[0].value;
          settings.saves[name] = {
            "overpass": ide.getQuery()
          };
          settings.save();
          $(this).dialog("close");
        },
        "Cancel": function() {$(this).dialog("close");}
      }
    });
  }
  this.onRunClick = function() {
    overpass.update_map();
  }
  this.onShareClick = function() {
    var baseurl=location.protocol+"//"+location.host+location.pathname;
    var shared_code = codeEditor.getValue();
    var share_link_uncompressed = baseurl+"?Q="+encodeURIComponent(shared_code);
    var share_link;
    if (shared_code.length <= 300) // todo: more options for this in the settings (auto / compressed / uncompressed)
      share_link = share_link_uncompressed;
    else {
      var share_link_compressed = baseurl+"?q="+encodeURIComponent(Base64.encode(lzw_encode(shared_code)));
      share_link = share_link_compressed;
    }
    var warning = '';
    if (share_link.length >= 2000)
      warning = '
Warning: This share-link is quite long. It may not work under certain circumstances (browsers, webservers).
';
    if (share_link.length >= 8000)
      warning = '
Warning: This share-link is very long. It is likely to fail under normal circumstances (browsers, webservers). Use with caution.
';
    //alert(share_link_uncompressed.length + " / " + share_link_compressed.length + " => " + ((share_link_uncompressed.length-share_link_compressed.length)/share_link_uncompressed.length * 100) + "%");
    $('

Copy this link to share the current code:

<textarea rows=4 style="width:100%" readonly>'+share_link+'</textarea>
'+warning+'
').dialog({
      modal:true,
      buttons: {
        "OK": function() {$(this).dialog("close");}
      }
    });
  }
  this.onKeyPress = function(event) {
    if ((event.keyCode == 120 && event.which == 0) || // F9
        ((event.which == 13 || event.which == 10) && (event.ctrlKey || event.metaKey))) { // Ctrl+Enter
      ide.onRunClick(); // run query
      event.preventDefault();
    }
    // todo: more shortcuts
  }

  // == initializations ==
  // initialize on document ready
  $(document).ready(init);

})(); // end create ide object

<|endoftext|>