<commit_before>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2013 melonJS
 * http://www.melonjs.org
 *
 */
(function(window) {

	/**
	 * A singleton object representing the device capabilities and specific events
	 * @namespace me.device
	 * @memberOf me
	 */
	me.device = (function() {

		// defines object for holding public information/functionality.
		var obj = {};
		// private properties
		var accelInitialized = false;
		var deviceOrientationInitialized = false;
		var devicePixelRatio = null;

		/**
		 * check the device capapbilities
		 * @ignore
		 */
		obj._check = function() {

			// detect audio capabilities (should be moved here too)
			me.audio.detectCapabilities();

			// future proofing (MS) feature detection
			navigator.pointerEnabled = navigator.pointerEnabled || navigator.msPointerEnabled;
			navigator.maxTouchPoints = navigator.maxTouchPoints || navigator.msMaxTouchPoints || 0;
			window.gesture = window.gesture || window.MSGesture;

			// detect touch capabilities
			me.device.touch = ('createTouch' in document) || ('ontouchstart' in window) ||
							  (navigator.isCocoonJS) || (navigator.maxTouchPoints > 0);

			// detect platform
			me.device.isMobile = me.device.ua.match(/Android|iPhone|iPad|iPod|BlackBerry|Windows Phone|Mobi/i);

			// accelerometer detection
			me.device.hasAccelerometer = (
				(typeof (window.DeviceMotionEvent) !== 'undefined') || (
					(typeof (window.Windows) !== 'undefined') &&
					(typeof (Windows.Devices.Sensors.Accelerometer) === 'function')
				)
			);

			if (window.DeviceOrientationEvent) {
				me.device.hasDeviceOrientation = true;
			}

			try {
				obj.localStorage = (window.localStorage !== null && typeof window.localStorage !== 'undefined');
			} catch (e) {
				// the above generates an exception when cookies are blocked
				obj.localStorage = false;
			}
		};

		// ----- PUBLIC Properties & Functions -----

		// Browser capabilities

		/**
		 * Browser User Agent
		 * @type Boolean
         * @readonly
		 * @name ua
		 * @memberOf me.device
		 */
		obj.ua = navigator.userAgent;
		/**
		 * Browser Audio capabilities
		 * @type Boolean
         * @readonly
		 * @name sound
		 * @memberOf me.device
		 */
		obj.sound = false;
		/**
		 * Browser Local Storage capabilities

		 * (this flag will be set to false if cookies are blocked)
		 * @type Boolean
         * @readonly
		 * @name localStorage
		 * @memberOf me.device
		 */
		obj.localStorage = false;
		/**
		 * Browser accelerometer capabilities
		 * @type Boolean
         * @readonly
		 * @name hasAccelerometer
		 * @memberOf me.device
		 */
		obj.hasAccelerometer = false;

		/**
		 * Browser device orientation
		 * @type Boolean
         * @readonly
		 * @name hasDeviceOrientation
		 * @memberOf me.device
		 */
		obj.hasDeviceOrientation = false;

		/**
		 * Browser Base64 decoding capability
		 * @type Boolean
         * @readonly
		 * @name nativeBase64
		 * @memberOf me.device
		 */
		obj.nativeBase64 = (typeof(window.atob) === 'function');

		/**
		 * Touch capabilities
		 * @type Boolean
         * @readonly
		 * @name touch
		 * @memberOf me.device
		 */
		obj.touch = false;

		/**
		 * equals to true if a mobile device

		 * (Android | iPhone | iPad | iPod | BlackBerry | Windows Phone)
		 * @type Boolean
         * @readonly
		 * @name isMobile
		 * @memberOf me.device
		 */
		obj.isMobile = false;

        /**
         * The device current orientation status.

         *   0 : default orientation

         *  90 : 90 degrees clockwise from default

         * -90 : 90 degrees anti-clockwise from default

         * 180 : 180 degrees from default
         * @type Number
         * @readonly
         * @name orientation
         * @memberOf me.device
         */
        obj.orientation = 0;

		/**
		 * contains the g-force acceleration along the x-axis.
		 * @public
		 * @type Number
         * @readonly
		 * @name accelerationX
		 * @memberOf me.device
		 */
		obj.accelerationX = 0;

		/**
		 * contains the g-force acceleration along the y-axis.
		 * @public
		 * @type Number
         * @readonly
		 * @name accelerationY
		 * @memberOf me.device
		 */
		obj.accelerationY = 0;

		/**
		 * contains the g-force acceleration along the z-axis.
		 * @public
		 * @type Number
         * @readonly
		 * @name accelerationZ
		 * @memberOf me.device
		 */
		obj.accelerationZ = 0;

		/**
		 * Device orientation Gamma property. Gives angle on tilting a portrait held phone left or right
		 * @public
		 * @type Number
         * @readonly
		 * @name gamma
		 * @memberOf me.device
		 */
		obj.gamma = 0;

		/**
		 * Device orientation Beta property. Gives angle on tilting a portrait held phone forward or backward
		 * @public
		 * @type Number
         * @readonly
		 * @name beta
		 * @memberOf me.device
		 */
		obj.beta = 0;

		/**
		 * Device orientation Alpha property. Gives angle based on the rotation of the phone around its z axis.
		 * The z-axis is perpendicular to the phone, facing out from the center of the screen.
		 * @public
		 * @type Number
         * @readonly
		 * @name alpha
		 * @memberOf me.device
		 */
		obj.alpha = 0;

		/**
		 * return the device pixel ratio
		 * @name getPixelRatio
		 * @memberOf me.device
		 * @function
		 */
		obj.getPixelRatio = function() {

			if (devicePixelRatio===null) {
				var _context = me.video.getScreenContext();
				var _devicePixelRatio = window.devicePixelRatio || 1,
					_backingStoreRatio = _context.webkitBackingStorePixelRatio ||
					_context.mozBackingStorePixelRatio ||
					_context.msBackingStorePixelRatio ||
					_context.oBackingStorePixelRatio ||
					_context.backingStorePixelRatio || 1;
				devicePixelRatio = _devicePixelRatio / _backingStoreRatio;
			}
			return devicePixelRatio;
		};

		/**
		 * return the device storage
		 * @name getStorage
		 * @memberOf me.device
		 * @function
		 * @param {String} [type="local"]
		 * @return me.save object
		 */
		obj.getStorage = function(type) {

			type = type || "local";

			switch (type) {
				case "local" :
					return me.save;

				default :
					break;
			}
			throw "melonJS : storage type " + type + " not supported";
		};

		/**
		 * event management (Accelerometer)
		 * http://www.mobilexweb.com/samples/ball.html
		 * http://www.mobilexweb.com/blog/safari-ios-accelerometer-websockets-html5
		 * @ignore
		 */
		function onDeviceMotion(e) {
			if (e.reading) {
				// For Windows 8 devices
				obj.accelerationX = e.reading.accelerationX;
				obj.accelerationY = e.reading.accelerationY;
				obj.accelerationZ = e.reading.accelerationZ;
			} else {
				// Accelerometer information
				obj.accelerationX = e.accelerationIncludingGravity.x;
				obj.accelerationY = e.accelerationIncludingGravity.y;
				obj.accelerationZ = e.accelerationIncludingGravity.z;
			}
		}

		function onDeviceRotate(e) {
			obj.gamma = e.gamma;
			obj.beta = e.beta;
			obj.alpha = e.alpha;
		}

		/**
		 * watch Accelerator event
		 * @name watchAccelerometer
		 * @memberOf me.device
		 * @public
		 * @function
		 * @return {boolean} false if not supported by the device
		 */
		obj.watchAccelerometer = function () {
			if (me.device.hasAccelerometer) {
				if (!accelInitialized) {
					if (typeof Windows === 'undefined') {
						// add a listener for the devicemotion event
						window.addEventListener('devicemotion', onDeviceMotion, false);
					} else {
						// On Windows 8 Device
						var accelerometer = Windows.Devices.Sensors.Accelerometer.getDefault();
						if (accelerometer) {
							// Capture event at regular intervals
							var minInterval = accelerometer.minimumReportInterval;
							var Interval = minInterval >= 16 ? minInterval : 25;
							accelerometer.reportInterval = Interval;

							accelerometer.addEventListener('readingchanged', onDeviceMotion, false);
						}
					}
					accelInitialized = true;
				}
				return true;
			}
			return false;
		};

		/**
		 * unwatch Accelerometor event
		 * @name unwatchAccelerometer
		 * @memberOf me.device
		 * @public
		 * @function
		 */
		obj.unwatchAccelerometer = function() {
			if (accelInitialized) {
				if (typeof Windows === 'undefined') {
					// add a listener for the mouse
					window.removeEventListener('devicemotion', onDeviceMotion, false);
				} else {
					// On Windows 8 Devices
					var accelerometer = Windows.Device.Sensors.Accelerometer.getDefault();

					accelerometer.removeEventListener('readingchanged', onDeviceMotion, false);
				}
				accelInitialized = false;
			}
		};

		/**
		 * watch the device orientation event
		 * @name watchDeviceOrientation
		 * @memberOf me.device
		 * @public
		 * @function
		 * @return {boolean} false if not supported by the device
		 */
		obj.watchDeviceOrientation = function() {
			if(me.device.hasDeviceOrientation && !deviceOrientationInitialized) {
				window.addEventListener('deviceorientation', onDeviceRotate, false);
				deviceOrientationInitialized = true;
			}
			return false;
		};

		/**
		 * unwatch Device orientation event
		 * @name unwatchDeviceOrientation
		 * @memberOf me.device
		 * @public
		 * @function
		 */
		obj.unwatchDeviceOrientation = function() {
			if(deviceOrientationInitialized) {
				window.removeEventListener('deviceorientation', onDeviceRotate, false);
				deviceOrientationInitialized = false;
			}
		};

		return obj;
	})();
})(window);
<commit_msg>changed to double bang to ensure we are getting a truthy/falsey return<commit_after>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2013 melonJS
 * http://www.melonjs.org
 *
 */
(function(window) {

	/**
	 * A singleton object representing the device capabilities and specific events
	 * @namespace me.device
	 * @memberOf me
	 */
	me.device = (function() {

		// defines object for holding public information/functionality.
		var obj = {};
		// private properties
		var accelInitialized = false;
		var deviceOrientationInitialized = false;
		var devicePixelRatio = null;

		/**
		 * check the device capapbilities
		 * @ignore
		 */
		obj._check = function() {

			// detect audio capabilities (should be moved here too)
			me.audio.detectCapabilities();

			// future proofing (MS) feature detection
			navigator.pointerEnabled = navigator.pointerEnabled || navigator.msPointerEnabled;
			navigator.maxTouchPoints = navigator.maxTouchPoints || navigator.msMaxTouchPoints || 0;
			window.gesture = window.gesture || window.MSGesture;

			// detect touch capabilities
			me.device.touch = ('createTouch' in document) || ('ontouchstart' in window) ||
							  (navigator.isCocoonJS) || (navigator.maxTouchPoints > 0);

			// detect platform
			me.device.isMobile = me.device.ua.match(/Android|iPhone|iPad|iPod|BlackBerry|Windows Phone|Mobi/i);

			// accelerometer detection
			me.device.hasAccelerometer = (
				(typeof (window.DeviceMotionEvent) !== 'undefined') || (
					(typeof (window.Windows) !== 'undefined') &&
					(typeof (Windows.Devices.Sensors.Accelerometer) === 'function')
				)
			);

			if (window.DeviceOrientationEvent) {
				me.device.hasDeviceOrientation = true;
			}

			try {
				obj.localStorage = !!window.localStorage;
			} catch (e) {
				// the above generates an exception when cookies are blocked
				obj.localStorage = false;
			}
		};

		// ----- PUBLIC Properties & Functions -----

		// Browser capabilities

		/**
		 * Browser User Agent
		 * @type Boolean
         * @readonly
		 * @name ua
		 * @memberOf me.device
		 */
		obj.ua = navigator.userAgent;
		/**
		 * Browser Audio capabilities
		 * @type Boolean
         * @readonly
		 * @name sound
		 * @memberOf me.device
		 */
		obj.sound = false;
		/**
		 * Browser Local Storage capabilities

		 * (this flag will be set to false if cookies are blocked)
		 * @type Boolean
         * @readonly
		 * @name localStorage
		 * @memberOf me.device
		 */
		obj.localStorage = false;
		/**
		 * Browser accelerometer capabilities
		 * @type Boolean
         * @readonly
		 * @name hasAccelerometer
		 * @memberOf me.device
		 */
		obj.hasAccelerometer = false;

		/**
		 * Browser device orientation
		 * @type Boolean
         * @readonly
		 * @name hasDeviceOrientation
		 * @memberOf me.device
		 */
		obj.hasDeviceOrientation = false;

		/**
		 * Browser Base64 decoding capability
		 * @type Boolean
         * @readonly
		 * @name nativeBase64
		 * @memberOf me.device
		 */
		obj.nativeBase64 = (typeof(window.atob) === 'function');

		/**
		 * Touch capabilities
		 * @type Boolean
         * @readonly
		 * @name touch
		 * @memberOf me.device
		 */
		obj.touch = false;

		/**
		 * equals to true if a mobile device

		 * (Android | iPhone | iPad | iPod | BlackBerry | Windows Phone)
		 * @type Boolean
         * @readonly
		 * @name isMobile
		 * @memberOf me.device
		 */
		obj.isMobile = false;

        /**
         * The device current orientation status.

         *   0 : default orientation

         *  90 : 90 degrees clockwise from default

         * -90 : 90 degrees anti-clockwise from default

         * 180 : 180 degrees from default
         * @type Number
         * @readonly
         * @name orientation
         * @memberOf me.device
         */
        obj.orientation = 0;

		/**
		 * contains the g-force acceleration along the x-axis.
		 * @public
		 * @type Number
         * @readonly
		 * @name accelerationX
		 * @memberOf me.device
		 */
		obj.accelerationX = 0;

		/**
		 * contains the g-force acceleration along the y-axis.
		 * @public
		 * @type Number
         * @readonly
		 * @name accelerationY
		 * @memberOf me.device
		 */
		obj.accelerationY = 0;

		/**
		 * contains the g-force acceleration along the z-axis.
		 * @public
		 * @type Number
         * @readonly
		 * @name accelerationZ
		 * @memberOf me.device
		 */
		obj.accelerationZ = 0;

		/**
		 * Device orientation Gamma property. Gives angle on tilting a portrait held phone left or right
		 * @public
		 * @type Number
         * @readonly
		 * @name gamma
		 * @memberOf me.device
		 */
		obj.gamma = 0;

		/**
		 * Device orientation Beta property. Gives angle on tilting a portrait held phone forward or backward
		 * @public
		 * @type Number
         * @readonly
		 * @name beta
		 * @memberOf me.device
		 */
		obj.beta = 0;

		/**
		 * Device orientation Alpha property. Gives angle based on the rotation of the phone around its z axis.
		 * The z-axis is perpendicular to the phone, facing out from the center of the screen.
		 * @public
		 * @type Number
         * @readonly
		 * @name alpha
		 * @memberOf me.device
		 */
		obj.alpha = 0;

		/**
		 * return the device pixel ratio
		 * @name getPixelRatio
		 * @memberOf me.device
		 * @function
		 */
		obj.getPixelRatio = function() {

			if (devicePixelRatio===null) {
				var _context = me.video.getScreenContext();
				var _devicePixelRatio = window.devicePixelRatio || 1,
					_backingStoreRatio = _context.webkitBackingStorePixelRatio ||
					_context.mozBackingStorePixelRatio ||
					_context.msBackingStorePixelRatio ||
					_context.oBackingStorePixelRatio ||
					_context.backingStorePixelRatio || 1;
				devicePixelRatio = _devicePixelRatio / _backingStoreRatio;
			}
			return devicePixelRatio;
		};

		/**
		 * return the device storage
		 * @name getStorage
		 * @memberOf me.device
		 * @function
		 * @param {String} [type="local"]
		 * @return me.save object
		 */
		obj.getStorage = function(type) {

			type = type || "local";

			switch (type) {
				case "local" :
					return me.save;

				default :
					break;
			}
			throw "melonJS : storage type " + type + " not supported";
		};

		/**
		 * event management (Accelerometer)
		 * http://www.mobilexweb.com/samples/ball.html
		 * http://www.mobilexweb.com/blog/safari-ios-accelerometer-websockets-html5
		 * @ignore
		 */
		function onDeviceMotion(e) {
			if (e.reading) {
				// For Windows 8 devices
				obj.accelerationX = e.reading.accelerationX;
				obj.accelerationY = e.reading.accelerationY;
				obj.accelerationZ = e.reading.accelerationZ;
			} else {
				// Accelerometer information
				obj.accelerationX = e.accelerationIncludingGravity.x;
				obj.accelerationY = e.accelerationIncludingGravity.y;
				obj.accelerationZ = e.accelerationIncludingGravity.z;
			}
		}

		function onDeviceRotate(e) {
			obj.gamma = e.gamma;
			obj.beta = e.beta;
			obj.alpha = e.alpha;
		}

		/**
		 * watch Accelerator event
		 * @name watchAccelerometer
		 * @memberOf me.device
		 * @public
		 * @function
		 * @return {boolean} false if not supported by the device
		 */
		obj.watchAccelerometer = function () {
			if (me.device.hasAccelerometer) {
				if (!accelInitialized) {
					if (typeof Windows === 'undefined') {
						// add a listener for the devicemotion event
						window.addEventListener('devicemotion', onDeviceMotion, false);
					} else {
						// On Windows 8 Device
						var accelerometer = Windows.Devices.Sensors.Accelerometer.getDefault();
						if (accelerometer) {
							// Capture event at regular intervals
							var minInterval = accelerometer.minimumReportInterval;
							var Interval = minInterval >= 16 ? minInterval : 25;
							accelerometer.reportInterval = Interval;

							accelerometer.addEventListener('readingchanged', onDeviceMotion, false);
						}
					}
					accelInitialized = true;
				}
				return true;
			}
			return false;
		};

		/**
		 * unwatch Accelerometor event
		 * @name unwatchAccelerometer
		 * @memberOf me.device
		 * @public
		 * @function
		 */
		obj.unwatchAccelerometer = function() {
			if (accelInitialized) {
				if (typeof Windows === 'undefined') {
					// add a listener for the mouse
					window.removeEventListener('devicemotion', onDeviceMotion, false);
				} else {
					// On Windows 8 Devices
					var accelerometer = Windows.Device.Sensors.Accelerometer.getDefault();

					accelerometer.removeEventListener('readingchanged', onDeviceMotion, false);
				}
				accelInitialized = false;
			}
		};

		/**
		 * watch the device orientation event
		 * @name watchDeviceOrientation
		 * @memberOf me.device
		 * @public
		 * @function
		 * @return {boolean} false if not supported by the device
		 */
		obj.watchDeviceOrientation = function() {
			if(me.device.hasDeviceOrientation && !deviceOrientationInitialized) {
				window.addEventListener('deviceorientation', onDeviceRotate, false);
				deviceOrientationInitialized = true;
			}
			return false;
		};

		/**
		 * unwatch Device orientation event
		 * @name unwatchDeviceOrientation
		 * @memberOf me.device
		 * @public
		 * @function
		 */
		obj.unwatchDeviceOrientation = function() {
			if(deviceOrientationInitialized) {
				window.removeEventListener('deviceorientation', onDeviceRotate, false);
				deviceOrientationInitialized = false;
			}
		};

		return obj;
	})();
})(window);
<|endoftext|>


<commit_before>define([ 'jquery', 'underscore', 'widgy.backbone', 'widgy.contents',
    'text!./node.html',
    'text!./preview.html',
    'text!./drop_target.html'
    ], function($, _, Backbone, contents,
      node_view_template,
      node_preview_view_template,
      drop_target_view_template
      ) {

  /**
   * Nodes provide structure in the tree.  Nodes only hold data that deals with
   * structure.  Any other data lives in it's content.
   *
   * A node will have two properties: `children` and `content`.  `children` is
   * a NodeCollection which is basically just a list of child nodes. `content`
   * is model containing all non-structure information about a node.  The
   * actual Model Class that defines the content property depends on the
   * content type.  See `widgy.contents.js` for more information.
   */
  var Node = Backbone.Model.extend({
    urlRoot: '/admin/widgy/node/',

    initialize: function() {
      Backbone.Model.prototype.initialize.apply(this, arguments);

      _.bindAll(this,
        'instantiateContent',
        'checkDidReposition'
        );

      this
        .on('change', this.checkDidReposition)
        .on('change:content', this.loadContent);

      // same as content.  We need to actually instantiate the NodeCollection
      // and set it as a property, not an attribute.
      var children = this.get('children');
      this.children = new NodeCollection(children);
      this.unset('children');
    },

    checkDidReposition: function() {
      if ( this.hasChanged('parent_id') ||
          (this.hasChanged('right_id') && this.id !== this.get('right_id')) )
      {
        this.trigger('reposition', this, this.get('parent_id'), this.get('right_id'));
      }
    },

    checkIsContentLoaded: function() {
      if ( this.content && this.content instanceof contents.Content ) {
        this.trigger('load:content', this.content);
      } else {
        this.loadContent(this, this.get('content'));
      }
    },

    loadContent: function(model, content) {
      if ( content )
      {
        // content gets set because it is in the JSON for the node.  We need to
        // unset it as it is not an attribute, but a property.  We also need to
        // instantiate it as a real Content Model.
        this.unset('content');

        // This is asynchronous because of requirejs.
        contents.getModel(content.__class__, _.bind(this.instantiateContent, this, content));
      }
    },

    instantiateContent: function(content, model_class) {
      this.content = new model_class(content);

      this.trigger('load:content', this.content);
    },

    toJSON: function() {
      var json = Backbone.Model.prototype.toJSON.apply(this, arguments);
      if ( this.content )
        json.content = this.content.toJSON();
      return json;
    }

  });

  /**
   * NodeCollections provide the children interface for nodes and also an
   * interface to NodeViews for how to handle child NodeViews.
   */
  var NodeCollection = Backbone.Collection.extend({
    model: Node
  });

  /**
   * Provides an interface for a draggable NodeView.  See NodeView for more
   * specific Node functionality related definition.  NodeViewBase is exposed
   * for subclassing by a NodePreviewView and NodeView.
   *
   * TODO: Does NodePreviewView do anything that NodeViewBase doesn't?
   */
  var NodeViewBase = Backbone.View.extend({
    tagName: 'li',
    className: 'node',

    events: {
      'mousedown .drag_handle': 'startDrag'
    },

    initialize: function(options) {
      Backbone.View.prototype.initialize.apply(this, arguments);

      _.bindAll(this,
        'startDrag',
        'followMouse',
        'stopDrag',
        'reposition',
        'addDropTargets',
        'clearDropTargets'
      );

      this.model
        .on('remove', this.close)
        .on('destroy', this.close)
        .on('reposition', this.reposition);

      this.app = options.app;
      this.app.node_view_list.push(this);

      this.drop_targets_list = new Backbone.ViewList;
    },

    /**
     * `startDrag`, `stopDrag`, `followMouse`, and `reposition` all deal with a
     * NodeView itself being dragged around.
     */
    startDrag: function(event) {
      // only on a left click.
      if ( event.button !== 0 )
        return;

      event.preventDefault();
      event.stopPropagation();

      this.app.startDrag(this);

      // Store the mouse offset in this container for followMouse to
      // use.
      this.offsetY = event.offsetY;
      this.offsetX = event.offsetX;

      // follow mouse really quick, just in case they haven't moved their mouse
      // yet.
      this.followMouse(event);

      this.$el.addClass('being_dragged');
    },

    stopDrag: function() {
      this.$el.css({
        position: 'static'
      });

      this.$el.removeClass('being_dragged');
    },

    followMouse: function(event) {
      this.$el.css({
        position: 'absolute',
        top: event.pageY - this.offsetY,
        left: event.pageX - this.offsetX
      });
    },

    /**
     * If the node has been put into a different parent, we need to update the
     * collection.  That parent will be listening for adding and it will handle
     * the positioning.  Otherwise, this node is still in the right parent and
     * it just needs to be positioned in the parent.
     *
     * This is a callback to the reposition event on the Node model.
     *
     * When a Node is removed from the Collection, it closes this, but we need
     * to clean up our bindings.
     */
    reposition: function(model, parent_id, right_id) {
      var parent_view = this.app.node_view_list.findById(parent_id);

      // This line is a little confusing.  For a model, the
      // `collection` property is its parent collection, for a view,
      // the `collection` is a child.  If a model has the same
      // `collection` as a view, that means the view is the parent
      // of the model.
      if ( model.collection !== parent_view.collection ) {
        model.collection.remove(model);
        this.model.off('reposition', this.reposition);
        parent_view.collection.add(model);
      } else {
        parent_view.position(this);
      }
    },

    /**
     * `addDropTargets` and `clearDropTargets` are required as an API for the
     * AppView. See NodeView for the actual implementation.
     */
    addDropTargets: function() {},
    clearDropTargets: function() {}
  });

  /**
   * The NodeView provides an interface to the node.  It will also create a
   * ContentView for the node's content.  Additionally, it will create child
   * NodeViews for all of the nodes in children.
   *
   * Properties:
   *
   * -  `this.model` is the node.
   * -  `this.collection` is the node's children (Also available in
   *    `this.model.children`.
   * -  `this.app` is the instance of AppView
   */
  var NodeView = NodeViewBase.extend({
    template: node_view_template,

    events: function() {
      return _.extend({}, NodeViewBase.prototype.events , {
        'click .delete': 'delete'
      });
    },

    initialize: function() {
      NodeViewBase.prototype.initialize.apply(this, arguments);

      _.bindAll(this,
        'addAll',
        'addOne',
        'position',
        'createDropTarget',
        'dropChildView',
        'renderContent'
        );

      this.model
        .on('load:content', this.renderContent);

      this.collection = this.model.children;

      this.collection
        .on('reset', this.addAll)
        .on('add', this.addOne);

      this.list = new Backbone.ViewList;
    },

    addAll: function() {
      this.collection.each(this.addOne);
    },

    addOne: function(node) {
      var node_view = new NodeView({
        model: node,
        app: this.app
      });

      this.position(node_view.render());
      this.list.push(node_view);
    },

    'delete': function(event) {
      event.stopPropagation();
      this.model.destroy();
    },

    /**
     * `addDropTargets`, `createDropTarget`, `clearDropTargets`, `position`,
     * and `dropChildView` all deal with a possible child NodeView being
     * dragged.  It is confusing that these methods are on the same class that
     * the methods dealing with being dragged around are on, but that's the
     * nature of the beast with recursive nodes.
     */
    addDropTargets: function(view) {
      var $children = this.$children,
          that = this,
          mine = this.list.contains(view);

      // do nothing if it's me or I'm not accepting children.
      if ( this === view ||
          (! mine && this.model.content && !this.model.content.get('accepting_children')) )
      {
        return;
      }

      $children.prepend(this.createDropTarget().el);
      $children.children('.node').each(function(index, elem) {
        var drop_target = that.createDropTarget().$el.insertAfter(elem);

        if ( mine && view.el == elem )
          drop_target.hide();
      });
    },

    createDropTarget: function() {
      var drop_target = new DropTargetView;
      drop_target.on('dropped', this.dropChildView);
      this.drop_targets_list.push(drop_target);

      return drop_target.render();
    },

    clearDropTargets: function() {
      this.drop_targets_list.closeAll();
    },

    position: function(child_node_view) {
      var child_node = child_node_view.model;

      if ( child_node.get('right_id') ) {
        var right_id = child_node.get('right_id'),
          right_view = this.app.node_view_list.findById(right_id);
          right_view.$el.before(child_node_view.el);
      } else {
        this.$children.append(child_node_view.el);
      }
    },

    /**
     * This is the method that is called when the NodeView that is being
     * dragged is dropped on one of my drop targets.
     */
    dropChildView: function(view) {
      var $children = this.$children;
      var index = view.$el.index() / 2;

      // We need to stop the drag before finding the right node.
      // `this.app.stopDrag` will clear all of the drop targets, so we need to
      // remove them before we can get elements by index.
      var dragged_view = this.app.stopDrag();

      var right_id = null;

      // If index is the length of $children.children there is no right element
      // and we want it set to null.  Otherwise there is a right and we need
      // its id.
      //
      // ($children.children() refers to DOM elements.)
      if ( index !== $children.children().length ) {
        var right_el = $children.children().eq(index)[0],
            right_view = this.app.node_view_list.findByEl(right_el);

        right_id = right_view.model.id;
      }

      // Dragged into my own drop target.
      if ( dragged_view === right_view )
        return;

      // pessimistic save (for now).
      dragged_view.model.save({
        parent_id: this.model.id,
        right_id: right_id
      }, {wait: true});
    },

    render: function() {
      Backbone.View.prototype.render.apply(this, arguments);

      this.$children = this.$el.children('.node_children');
      this.$content = this.$el.children('.content');

      // TODO: this could be like a document.ready sorta?
      this.model.checkIsContentLoaded();

      // TODO: investigate possible problems with this.
      this.addAll();

      return this;
    },

    renderContent: function(content) {
      var view_class = content.getViewClass();

      content_view = new view_class({
        model: content
      });

      this.$content.html(content_view.render().el);
    }
  });

  var NodePreviewView = NodeViewBase.extend({
    template: node_preview_view_template
  });

  var DropTargetView = Backbone.View.extend({
    className: 'node_drop_target',

    template: drop_target_view_template,

    triggers: {
      'mouseup': 'dropped'
    },

    events: {
      'mouseenter': 'activate',
      'mouseleave': 'deactivate'
    },

    activate: function(event) {
      this.$el.addClass('active');
    },

    deactivate: function(event) {
      this.$el.removeClass('active');
    }
  });

  return {
    DropTargetView: DropTargetView,
    Node: Node,
    NodeCollection: NodeCollection,
    NodeView: NodeView,
    NodePreviewView: NodePreviewView,
    NodeViewBase: NodeViewBase
  };

});
<commit_msg>fix drop target offset<commit_after>define([ 'jquery', 'underscore', 'widgy.backbone', 'widgy.contents',
    'text!./node.html',
    'text!./preview.html',
    'text!./drop_target.html'
    ], function($, _, Backbone, contents,
      node_view_template,
      node_preview_view_template,
      drop_target_view_template
      ) {

  /**
   * Nodes provide structure in the tree.  Nodes only hold data that deals with
   * structure.  Any other data lives in it's content.
   *
   * A node will have two properties: `children` and `content`.  `children` is
   * a NodeCollection which is basically just a list of child nodes. `content`
   * is model containing all non-structure information about a node.  The
   * actual Model Class that defines the content property depends on the
   * content type.  See `widgy.contents.js` for more information.
   */
  var Node = Backbone.Model.extend({
    urlRoot: '/admin/widgy/node/',

    initialize: function() {
      Backbone.Model.prototype.initialize.apply(this, arguments);

      _.bindAll(this,
        'instantiateContent',
        'checkDidReposition'
        );

      this
        .on('change', this.checkDidReposition)
        .on('change:content', this.loadContent);

      // same as content.  We need to actually instantiate the NodeCollection
      // and set it as a property, not an attribute.
      var children = this.get('children');
      this.children = new NodeCollection(children);
      this.unset('children');
    },

    checkDidReposition: function() {
      if ( this.hasChanged('parent_id') ||
          (this.hasChanged('right_id') && this.id !== this.get('right_id')) )
      {
        this.trigger('reposition', this, this.get('parent_id'), this.get('right_id'));
      }
    },

    checkIsContentLoaded: function() {
      if ( this.content && this.content instanceof contents.Content ) {
        this.trigger('load:content', this.content);
      } else {
        this.loadContent(this, this.get('content'));
      }
    },

    loadContent: function(model, content) {
      if ( content )
      {
        // content gets set because it is in the JSON for the node.  We need to
        // unset it as it is not an attribute, but a property.  We also need to
        // instantiate it as a real Content Model.
        this.unset('content');

        // This is asynchronous because of requirejs.
        contents.getModel(content.__class__, _.bind(this.instantiateContent, this, content));
      }
    },

    instantiateContent: function(content, model_class) {
      this.content = new model_class(content);

      this.trigger('load:content', this.content);
    },

    toJSON: function() {
      var json = Backbone.Model.prototype.toJSON.apply(this, arguments);
      if ( this.content )
        json.content = this.content.toJSON();
      return json;
    }

  });

  /**
   * NodeCollections provide the children interface for nodes and also an
   * interface to NodeViews for how to handle child NodeViews.
   */
  var NodeCollection = Backbone.Collection.extend({
    model: Node
  });

  /**
   * Provides an interface for a draggable NodeView.  See NodeView for more
   * specific Node functionality related definition.  NodeViewBase is exposed
   * for subclassing by a NodePreviewView and NodeView.
   *
   * TODO: Does NodePreviewView do anything that NodeViewBase doesn't?
   */
  var NodeViewBase = Backbone.View.extend({
    tagName: 'li',
    className: 'node',

    events: {
      'mousedown .drag_handle': 'startDrag'
    },

    initialize: function(options) {
      Backbone.View.prototype.initialize.apply(this, arguments);

      _.bindAll(this,
        'startDrag',
        'followMouse',
        'stopDrag',
        'reposition',
        'addDropTargets',
        'clearDropTargets'
      );

      this.model
        .on('remove', this.close)
        .on('destroy', this.close)
        .on('reposition', this.reposition);

      this.app = options.app;
      this.app.node_view_list.push(this);

      this.drop_targets_list = new Backbone.ViewList;
    },

    /**
     * `startDrag`, `stopDrag`, `followMouse`, and `reposition` all deal with a
     * NodeView itself being dragged around.
     */
    startDrag: function(event) {
      // only on a left click.
      if ( event.button !== 0 )
        return;

      event.preventDefault();
      event.stopPropagation();

      this.app.startDrag(this);

      // Store the mouse offset in this container for followMouse to
      // use.
      // The +80 is due to the 'div#content.offsetTop'
      this.offsetY = event.offsetY + 80;
      this.offsetX = event.offsetX;

      // follow mouse really quick, just in case they haven't moved their mouse
      // yet.
      this.followMouse(event);

      this.$el.addClass('being_dragged');
    },

    stopDrag: function() {
      this.$el.css({
        position: 'static'
      });

      this.$el.removeClass('being_dragged');
    },

    followMouse: function(event) {
      this.$el.css({
        position: 'absolute',
        top: event.pageY - this.offsetY,
        left: event.pageX - this.offsetX
      });
    },

    /**
     * If the node has been put into a different parent, we need to update the
     * collection.  That parent will be listening for adding and it will handle
     * the positioning.  Otherwise, this node is still in the right parent and
     * it just needs to be positioned in the parent.
     *
     * This is a callback to the reposition event on the Node model.
     *
     * When a Node is removed from the Collection, it closes this, but we need
     * to clean up our bindings.
     */
    reposition: function(model, parent_id, right_id) {
      var parent_view = this.app.node_view_list.findById(parent_id);

      // This line is a little confusing.  For a model, the
      // `collection` property is its parent collection, for a view,
      // the `collection` is a child.  If a model has the same
      // `collection` as a view, that means the view is the parent
      // of the model.
      if ( model.collection !== parent_view.collection ) {
        model.collection.remove(model);
        this.model.off('reposition', this.reposition);
        parent_view.collection.add(model);
      } else {
        parent_view.position(this);
      }
    },

    /**
     * `addDropTargets` and `clearDropTargets` are required as an API for the
     * AppView. See NodeView for the actual implementation.
     */
    addDropTargets: function() {},
    clearDropTargets: function() {}
  });

  /**
   * The NodeView provides an interface to the node.  It will also create a
   * ContentView for the node's content.  Additionally, it will create child
   * NodeViews for all of the nodes in children.
   *
   * Properties:
   *
   * -  `this.model` is the node.
   * -  `this.collection` is the node's children (Also available in
   *    `this.model.children`.
   * -  `this.app` is the instance of AppView
   */
  var NodeView = NodeViewBase.extend({
    template: node_view_template,

    events: function() {
      return _.extend({}, NodeViewBase.prototype.events , {
        'click .delete': 'delete'
      });
    },

    initialize: function() {
      NodeViewBase.prototype.initialize.apply(this, arguments);

      _.bindAll(this,
        'addAll',
        'addOne',
        'position',
        'createDropTarget',
        'dropChildView',
        'renderContent'
        );

      this.model
        .on('load:content', this.renderContent);

      this.collection = this.model.children;

      this.collection
        .on('reset', this.addAll)
        .on('add', this.addOne);

      this.list = new Backbone.ViewList;
    },

    addAll: function() {
      this.collection.each(this.addOne);
    },

    addOne: function(node) {
      var node_view = new NodeView({
        model: node,
        app: this.app
      });

      this.position(node_view.render());
      this.list.push(node_view);
    },

    'delete': function(event) {
      event.stopPropagation();
      this.model.destroy();
    },

    /**
     * `addDropTargets`, `createDropTarget`, `clearDropTargets`, `position`,
     * and `dropChildView` all deal with a possible child NodeView being
     * dragged.  It is confusing that these methods are on the same class that
     * the methods dealing with being dragged around are on, but that's the
     * nature of the beast with recursive nodes.
     */
    addDropTargets: function(view) {
      var $children = this.$children,
          that = this,
          mine = this.list.contains(view);

      // do nothing if it's me or I'm not accepting children.
      if ( this === view ||
          (! mine && this.model.content && !this.model.content.get('accepting_children')) )
      {
        return;
      }

      $children.prepend(this.createDropTarget().el);
      $children.children('.node').each(function(index, elem) {
        var drop_target = that.createDropTarget().$el.insertAfter(elem);

        if ( mine && view.el == elem )
          drop_target.hide();
      });
    },

    createDropTarget: function() {
      var drop_target = new DropTargetView;
      drop_target.on('dropped', this.dropChildView);
      this.drop_targets_list.push(drop_target);

      return drop_target.render();
    },

    clearDropTargets: function() {
      this.drop_targets_list.closeAll();
    },

    position: function(child_node_view) {
      var child_node = child_node_view.model;

      if ( child_node.get('right_id') ) {
        var right_id = child_node.get('right_id'),
          right_view = this.app.node_view_list.findById(right_id);
          right_view.$el.before(child_node_view.el);
      } else {
        this.$children.append(child_node_view.el);
      }
    },

    /**
     * This is the method that is called when the NodeView that is being
     * dragged is dropped on one of my drop targets.
     */
    dropChildView: function(view) {
      var $children = this.$children;
      var index = view.$el.index() / 2;

      // We need to stop the drag before finding the right node.
      // `this.app.stopDrag` will clear all of the drop targets, so we need to
      // remove them before we can get elements by index.
      var dragged_view = this.app.stopDrag();

      var right_id = null;

      // If index is the length of $children.children there is no right element
      // and we want it set to null.  Otherwise there is a right and we need
      // its id.
      //
      // ($children.children() refers to DOM elements.)
      if ( index !== $children.children().length ) {
        var right_el = $children.children().eq(index)[0],
            right_view = this.app.node_view_list.findByEl(right_el);

        right_id = right_view.model.id;
      }

      // Dragged into my own drop target.
      if ( dragged_view === right_view )
        return;

      // pessimistic save (for now).
      dragged_view.model.save({
        parent_id: this.model.id,
        right_id: right_id
      }, {wait: true});
    },

    render: function() {
      Backbone.View.prototype.render.apply(this, arguments);

      this.$children = this.$el.children('.node_children');
      this.$content = this.$el.children('.content');

      // TODO: this could be like a document.ready sorta?
      this.model.checkIsContentLoaded();

      // TODO: investigate possible problems with this.
      this.addAll();

      return this;
    },

    renderContent: function(content) {
      var view_class = content.getViewClass();

      content_view = new view_class({
        model: content
      });

      this.$content.html(content_view.render().el);
    }
  });

  var NodePreviewView = NodeViewBase.extend({
    template: node_preview_view_template
  });

  var DropTargetView = Backbone.View.extend({
    className: 'node_drop_target',

    template: drop_target_view_template,

    triggers: {
      'mouseup': 'dropped'
    },

    events: {
      'mouseenter': 'activate',
      'mouseleave': 'deactivate'
    },

    activate: function(event) {
      this.$el.addClass('active');
    },

    deactivate: function(event) {
      this.$el.removeClass('active');
    }
  });

  return {
    DropTargetView: DropTargetView,
    Node: Node,
    NodeCollection: NodeCollection,
    NodeView: NodeView,
    NodePreviewView: NodePreviewView,
    NodeViewBase: NodeViewBase
  };

});
<|endoftext|>


<commit_before><commit_msg>Update ogario.v4.js<commit_after><|endoftext|>


<commit_before>import Ember from 'ember';
import Route from 'ember-route';
import service from 'ember-service/inject';
import get from 'ember-metal/get';
import { isEmpty } from 'ember-utils';
import { parseHuddles } from 'ember-on-fhir/models/huddle';

const { RSVP } = Ember;

export default Route.extend({
  store: service(),
  ajax: service(),

  queryParams: {
    sortBy: { refreshModel: true },
    sortDescending: { refreshModel: true },
    assessment: { refreshModel: true },
    huddleId: { refreshModel: true },
    groupId: { refreshModel: true },
    name: { refreshModel: true }
  },

  huddle: null,

  beforeModel(transition) {
    let huddleId = get(transition, 'queryParams.huddleId');
    if (huddleId) {
      return this.get('ajax').request(`/Group/${huddleId}`).then((response) => {
        this.set('huddle', parseHuddles(response));
      });
    }

    this.set('huddle', null);
  },

  model(params) {
    let hash = {
      patients: this.get('store').query('patient', patientParams(params, this.get('huddle'))),
      huddles: this.get('ajax').request('/Group', { data: { code: 'http://interventionengine.org/fhir/cs/huddle|HUDDLE' } }).then((response) => parseHuddles(response.entry || []))
    };

    if (params.groupId) {
      hash.group = this.get('store').find('group', params.groupId);
    }

    return new RSVP.Promise((resolve) => {
      RSVP.hash(hash).then((response) => {
        let patientIds = response.patients.mapBy('id');
        let riskParams = {
          method: `http://interventionengine.org/risk-assessments|${methodFromAssessment(params.assessment)}`,
          _tag: 'http://interventionengine.org/tags/|MOST_RECENT',
          'subject:Patient': patientIds.join(',')
        };

        this.get('store').query('risk-assessment', riskParams).then((risks) => {
          resolve(Object.assign({ risks }, response));
        });
      });
    });
  }
});

function patientParams(params, huddle) {
  let patientParams = { _count: 0 };
  let { sortBy, sortDescending } = params;

  if (!isEmpty(sortBy)) {
    patientParams._sort = `${sortDescending === 'true' ? '-' : ''}${sortBy}`;
  }

  let { groupId } = params;
  if (!isEmpty(groupId)) {
    Object.assign(patientParams, {
      _query: 'group',
      groupId
    });
  }

  if (huddle) {
    let patientIds = huddle.get('patients').mapBy('patientId');
    patientParams._id = patientIds.join(',');
  }

  let { name } = params;
  if (name) {
    patientParams.name = name;
  }

  return patientParams;
}

// TODO: find a better way to fetch this value
function methodFromAssessment(assessment) {
  if (assessment === 'Catastrophic Health Event') {
    return 'MultiFactor';
  }
}
<commit_msg>Updated print patients risk assessment request to include count of patients (#97)<commit_after>import Ember from 'ember';
import Route from 'ember-route';
import service from 'ember-service/inject';
import get from 'ember-metal/get';
import { isEmpty } from 'ember-utils';
import { parseHuddles } from 'ember-on-fhir/models/huddle';

const { RSVP } = Ember;

export default Route.extend({
  store: service(),
  ajax: service(),

  queryParams: {
    sortBy: { refreshModel: true },
    sortDescending: { refreshModel: true },
    assessment: { refreshModel: true },
    huddleId: { refreshModel: true },
    groupId: { refreshModel: true },
    name: { refreshModel: true }
  },

  huddle: null,

  beforeModel(transition) {
    let huddleId = get(transition, 'queryParams.huddleId');
    if (huddleId) {
      return this.get('ajax').request(`/Group/${huddleId}`).then((response) => {
        this.set('huddle', parseHuddles(response));
      });
    }

    this.set('huddle', null);
  },

  model(params) {
    let hash = {
      patients: this.get('store').query('patient', patientParams(params, this.get('huddle'))),
      huddles: this.get('ajax').request('/Group', { data: { code: 'http://interventionengine.org/fhir/cs/huddle|HUDDLE' } }).then((response) => parseHuddles(response.entry || []))
    };

    if (params.groupId) {
      hash.group = this.get('store').find('group', params.groupId);
    }

    return new RSVP.Promise((resolve) => {
      RSVP.hash(hash).then((response) => {
        let patientIds = response.patients.mapBy('id');
        let riskParams = {
          _count: patientIds.length,
          method: `http://interventionengine.org/risk-assessments|${methodFromAssessment(params.assessment)}`,
          _tag: 'http://interventionengine.org/tags/|MOST_RECENT',
          'subject:Patient': patientIds.join(',')
        };

        this.get('store').query('risk-assessment', riskParams).then((risks) => {
          resolve(Object.assign({ risks }, response));
        });
      });
    });
  }
});

function patientParams(params, huddle) {
  let patientParams = { _count: 0 };
  let { sortBy, sortDescending } = params;

  if (!isEmpty(sortBy)) {
    patientParams._sort = `${sortDescending === 'true' ? '-' : ''}${sortBy}`;
  }

  let { groupId } = params;
  if (!isEmpty(groupId)) {
    Object.assign(patientParams, {
      _query: 'group',
      groupId
    });
  }

  if (huddle) {
    let patientIds = huddle.get('patients').mapBy('patientId');
    patientParams._id = patientIds.join(',');
  }

  let { name } = params;
  if (name) {
    patientParams.name = name;
  }

  return patientParams;
}

// TODO: find a better way to fetch this value
function methodFromAssessment(assessment) {
  if (assessment === 'Catastrophic Health Event') {
    return 'MultiFactor';
  }
}
<|endoftext|>


<commit_before>import { cloneDeep, get, merge } from 'lodash';

import themable from './themable';

function registerTheme(theme) {
  const _theme = theme || ThemeManager.globalTheme;

  if ((this || {}).themes) this.themes[_theme] = this.themes[_theme] || {};
  ThemeManager.themeVariables[_theme] = ThemeManager.themeVariables[_theme] || {};
}

function registerProperties(stylesObj) {
  if (!stylesObj) return {};

  const properties = {};
  for (const key in stylesObj) {
    properties[key] = {
      configurable: true,
      get: () => this.getPropertyFromTheme(this.currentTheme, key),
    };
  }

  Object.defineProperties(stylesObj, properties);
  merge(this.styles, stylesObj);

  return stylesObj;
}

function isVariable(key) {
  return typeof key === 'string' && key[0] === '$';
}

function fixVariableName(variableName) {
  return isVariable(variableName) ? variableName.slice(1) : variableName;
}

function fixVariableNames(variablesObject) {
  if (!variablesObject) return {};

  let fixedVariables = {};
  for (const key in variablesObject) fixedVariables[fixVariableName(key)] = variablesObject[key];

  return fixedVariables;
}

function addStyle(theme, stylesObj) {
  const _theme = theme || ThemeManager.globalTheme;

  registerTheme.bind(this)(_theme);
  return merge(this.themes[_theme], stylesObj);
}

function addThemeVariables(theme, variables) {
  const _theme = theme || ThemeManager.globalTheme;
  if (!_theme) return;

  registerTheme(_theme);
  Object.assign(ThemeManager.themeVariables[_theme], fixVariableNames(variables));
}

function parseStyle(theme, style) {
  if (isVariable(style)) return ThemeManager.getVariable(theme, style);
  if (typeof style !== 'object') return style;

  const _theme = theme || ThemeManager.globalTheme;
  const parsedStyle = cloneDeep(style);
  for (const key in parsedStyle) parsedStyle[key] = parseStyle(_theme, parsedStyle[key]);
  return parsedStyle;
}

export default class ThemeManager {
  static globalTheme = 'default';
  static themeVariables = {};
  static config = {
    styleSheetReference: null, // { StyleSheet } from 'react-native' or a custom one like EStyleSheet
    fallbackToGlobalTheme: true,
  };

  constructor() {
    this.styles = {};
    this.themes = {};
    this.localThemeVariables = {};
    this.currentTheme = ThemeManager.globalTheme;

    registerTheme.bind(this)(ThemeManager.globalTheme);
  }

  static config({ styleSheetReference }) {
    if (styleSheetReference) this.config.styleSheetReference = styleSheetReference;
  }

  static addVariables(themes = [], variables = {}) {
    let _themes;
    let _variables;

    // allow to not specify first argument (will consider variables as global ones)
    if (!Array.isArray(arguments[0]) && typeof arguments[0] === 'object') {
      _themes = [ThemeManager.globalTheme];
      _variables = arguments[0] || {};
    } else {
      _themes = (Array.isArray(themes) ? themes : [themes]).filter(Boolean);
      _variables = variables || {};
    }

    // add variables for each specified theme
    _themes.map((theme) => addThemeVariables(theme, _variables));
  }

  static getVariable(theme, variableName) {
    const _variableName = variableName[0] === '$' ? variableName.slice(1) : variableName;

    const value = (theme && get(this.themeVariables[theme], _variableName))
      || (ThemeManager.config.fallbackToGlobalTheme && get(this.themeVariables[ThemeManager.globalTheme], _variableName));

    return value === undefined ? `$${_variableName}` : value;
  }

  create(themes = [], styleObj) {
    let _themes;
    let _styleObj;

    // allow to not specify first argument (considering theme as the global one)
    if (!Array.isArray(arguments[0]) && typeof arguments[0] === 'object') {
      _themes = [ThemeManager.globalTheme];
      _styleObj = arguments[0];
    } else {
      _themes = Array.isArray(themes) ? themes : [themes];
      _styleObj = styleObj || {};
    }

    // create styles for each specified theme
    _themes.map((theme) => addStyle.bind(this)(theme, _styleObj));

    registerProperties.bind(this)(_styleObj);
    return this.styles;
  }

  getPropertyFromTheme(theme, propertyName) {
    let _theme;
    let value;

    if (get(this.themes[theme], propertyName) !== undefined) {
      _theme = theme;
      value = get(this.themes[theme], propertyName);
    }

    else if (ThemeManager.config.fallbackToGlobalTheme
      && get(this.themes[ThemeManager.globalTheme], propertyName) !== undefined) {
      _theme = ThemeManager.globalTheme;
      value = get(this.themes[ThemeManager.globalTheme], propertyName);
    }

    return parseStyle(_theme, value);
  }

  setTheme(theme) {
    const _theme = theme || ThemeManager.globalTheme;
    if (this.currentTheme === _theme) return _theme;

    this.currentTheme = _theme;
    this.styles = this.getStyles(this.currentTheme); // ThemeManager.config.styleSheetReference.create(styles);

    return this.currentTheme;
  }

  getStyles(theme) {
    const _theme = theme || this.currentTheme;
    const _globalStyles = (ThemeManager.config.fallbackToGlobalTheme && this.themes[ThemeManager.globalTheme]) || {};

    this.setTheme(_theme);
    return parseStyle(_theme, merge({}, _globalStyles, this.themes[_theme]));
  }

  attach(component) {
    console.warn('DEPRECATED: themeManager.attach(Component). Use themable(Component) instead.')
    return themable(component);
  }
};
<commit_msg>Renamed themeManager.currentTheme to themeManager.theme<commit_after>import { cloneDeep, get, merge } from 'lodash';

import themable from './themable';

function registerTheme(theme) {
  const _theme = theme || ThemeManager.globalTheme;

  if ((this || {}).themes) this.themes[_theme] = this.themes[_theme] || {};
  ThemeManager.themeVariables[_theme] = ThemeManager.themeVariables[_theme] || {};
}

function registerProperties(stylesObj) {
  if (!stylesObj) return {};

  const properties = {};
  for (const key in stylesObj) {
    properties[key] = {
      configurable: true,
      get: () => this.getPropertyFromTheme(this.theme, key),
    };
  }

  Object.defineProperties(stylesObj, properties);
  merge(this.styles, stylesObj);

  return stylesObj;
}

function isVariable(key) {
  return typeof key === 'string' && key[0] === '$';
}

function fixVariableName(variableName) {
  return isVariable(variableName) ? variableName.slice(1) : variableName;
}

function fixVariableNames(variablesObject) {
  if (!variablesObject) return {};

  let fixedVariables = {};
  for (const key in variablesObject) fixedVariables[fixVariableName(key)] = variablesObject[key];

  return fixedVariables;
}

function addStyle(theme, stylesObj) {
  const _theme = theme || ThemeManager.globalTheme;

  registerTheme.bind(this)(_theme);
  return merge(this.themes[_theme], stylesObj);
}

function addThemeVariables(theme, variables) {
  const _theme = theme || ThemeManager.globalTheme;
  if (!_theme) return;

  registerTheme(_theme);
  Object.assign(ThemeManager.themeVariables[_theme], fixVariableNames(variables));
}

function parseStyle(theme, style) {
  if (isVariable(style)) return ThemeManager.getVariable(theme, style);
  if (typeof style !== 'object') return style;

  const _theme = theme || ThemeManager.globalTheme;
  const parsedStyle = cloneDeep(style);
  for (const key in parsedStyle) parsedStyle[key] = parseStyle(_theme, parsedStyle[key]);
  return parsedStyle;
}

export default class ThemeManager {
  static globalTheme = 'default';
  static themeVariables = {};
  static config = {
    styleSheetReference: null, // { StyleSheet } from 'react-native' or a custom one like EStyleSheet
    fallbackToGlobalTheme: true,
  };

  constructor() {
    this.styles = {};
    this.themes = {};
    this.localThemeVariables = {};
    this.theme = ThemeManager.globalTheme;

    registerTheme.bind(this)(ThemeManager.globalTheme);
  }

  static config({ styleSheetReference }) {
    if (styleSheetReference) this.config.styleSheetReference = styleSheetReference;
  }

  static addVariables(themes = [], variables = {}) {
    let _themes;
    let _variables;

    // allow to not specify first argument (will consider variables as global ones)
    if (!Array.isArray(arguments[0]) && typeof arguments[0] === 'object') {
      _themes = [ThemeManager.globalTheme];
      _variables = arguments[0] || {};
    } else {
      _themes = (Array.isArray(themes) ? themes : [themes]).filter(Boolean);
      _variables = variables || {};
    }

    // add variables for each specified theme
    _themes.map((theme) => addThemeVariables(theme, _variables));
  }

  static getVariable(theme, variableName) {
    const _variableName = variableName[0] === '$' ? variableName.slice(1) : variableName;

    const value = (theme && get(this.themeVariables[theme], _variableName))
      || (ThemeManager.config.fallbackToGlobalTheme && get(this.themeVariables[ThemeManager.globalTheme], _variableName));

    return value === undefined ? `$${_variableName}` : value;
  }

  create(themes = [], styleObj) {
    let _themes;
    let _styleObj;

    // allow to not specify first argument (considering theme as the global one)
    if (!Array.isArray(arguments[0]) && typeof arguments[0] === 'object') {
      _themes = [ThemeManager.globalTheme];
      _styleObj = arguments[0];
    } else {
      _themes = Array.isArray(themes) ? themes : [themes];
      _styleObj = styleObj || {};
    }

    // create styles for each specified theme
    _themes.map((theme) => addStyle.bind(this)(theme, _styleObj));

    registerProperties.bind(this)(_styleObj);
    return this.styles;
  }

  getPropertyFromTheme(theme, propertyName) {
    let _theme;
    let value;

    if (get(this.themes[theme], propertyName) !== undefined) {
      _theme = theme;
      value = get(this.themes[theme], propertyName);
    }

    else if (ThemeManager.config.fallbackToGlobalTheme
      && get(this.themes[ThemeManager.globalTheme], propertyName) !== undefined) {
      _theme = ThemeManager.globalTheme;
      value = get(this.themes[ThemeManager.globalTheme], propertyName);
    }

    return parseStyle(_theme, value);
  }

  setTheme(theme) {
    const _theme = theme || ThemeManager.globalTheme;
    if (this.theme === _theme) return _theme;

    this.theme = _theme;
    this.styles = this.getStyles(this.theme); // ThemeManager.config.styleSheetReference.create(styles);

    return this.theme;
  }

  getStyles(theme) {
    const _theme = theme || this.theme;
    const _globalStyles = (ThemeManager.config.fallbackToGlobalTheme && this.themes[ThemeManager.globalTheme]) || {};

    this.setTheme(_theme);
    return parseStyle(_theme, merge({}, _globalStyles, this.themes[_theme]));
  }

  attach(component) {
    console.warn('DEPRECATED: themeManager.attach(Component). Use themable(Component) instead.')
    return themable(component);
  }
};
<|endoftext|>


<commit_before>(function () {

    var modules = {};

    window.require = function (name) {
        var m = modules[name];
        if (!m) {
            throw Error('Cannot find module ' + JSON.stringify(m));
        }
        if (m.state === 'unloaded') {
            m.state = 'beingloaded';
            m.instance = m.init(m.instance, window.require);
            m.state = 'loaded';
        }
        return m.instance;
    };

    window.defModule = function (name, init) {
        if (modules[name]) {
            throw Error('Redefinition of module ' + JSON.stringify(name));
        }
        modules[name] = {state: 'unloaded', init: init, instance: {}};
    };

})();
<commit_msg>fake-require.js: renamed "instance" -> "exports"<commit_after>(function () {

    var modules = {};

    window.require = function (name) {
        var m = modules[name];
        if (!m) {
            throw Error('Cannot find module ' + JSON.stringify(m));
        }
        if (m.state === 'unloaded') {
            m.state = 'beingloaded';
            m.exports = m.init(m.exports, window.require);
            m.state = 'loaded';
        }
        return m.exports;
    };

    window.defModule = function (name, init) {
        if (modules[name]) {
            throw Error('Redefinition of module ' + JSON.stringify(name));
        }
        modules[name] = {state: 'unloaded', init: init, exports: {}};
    };

})();
<|endoftext|>


<commit_before>/**
 * Create a map with markers using Google's Maps JavaScript API v3
 * Author: Paul Molluzzo - paul@molluzzo.com
 * ©2015 MIT License
 */

(function(ns, google){

  'use strict';

  var marker;

  /**
   * Check if a Marker has a valid lat/long
   * @param  {object} markerObj This is an individual marker from the array supplied in the createMap() function
   * @return {boolean}          Returns true/false depending on validity of the MarkerObj lat/long
   */
  function validateLatLong(markerObj) {
    if (typeof markerObj.lat !== 'undefined' && typeof markerObj.lat === 'number' && typeof markerObj.long !== 'undefined' && typeof markerObj.long === 'number') {
      return true;
    }

    return false;
  }

  /**
   * Check if a Marker has valid content for an info window
   * @param  {object} markerObj This is an individual marker from the array supplied in the createMap() function
   * @return {boolean}          Returns true/false depending on validity of the MarkerObj infoContent
   */
  function validateInfoContent(markerObj) {
    if (typeof markerObj.infoContent !== 'undefined' && typeof markerObj.infoContent === 'string')
      return true;

    return false;
  }

  /**
   * initialize the Map Plugin
   * @param  {string}   mapElementId The HTML element that will hold the map. Must be an id not a class.
   * @param  {integer}  startingLat  The latitude used to center the map if no markers are listed.
   * @param  {integer}  startingLong The longitude used to center the map if no markers are listed.
   * @param  {array}    markersArr   An array of markers to add to the map. Each marker is an object with the following key/values:
   *         For each marker Object
   *                    lat         {integer}   The latitude for the marker (required)
   *                    long        {integer}   The longitude for the marker (required)
   *                    infoContent {string}    The HTML content of the info window shown when clicking a marker
   *                    maxWidth    {integer}   The max width of the info window in px. Defaults to 400.
   * @param  {boolean}  showNumber  If true, will number the markers placed on the map.
   */
  ns.createMap = function(mapElementId, startingLat, startingLong, markersArr, showNumber) {

    // default the number param to false
    var willShow = showNumber || false;

    // initialize the map
    var map = new google.maps.Map(document.getElementById(mapElementId), {
      zoom: 4,
      center: new google.maps.LatLng(startingLat,startingLong)
    });

    // take the array of requested markers provided, create Google Maps LatLng objects, fit the map, add a marker to the map
    if (markersArr.length > 0) {

      var markerCount = 1;

      var bounds = new google.maps.LatLngBounds(),
          markerList = [];

      // iterate over the array, check the data, create a marker
      for (var i = 0; i < markersArr.length; i++) {

        // check that lat and long are there and are numbers
        if (validateLatLong(markersArr[i])) {

          // add lat/long to markerList as google.maps.LatLng
          markerList.push(new google.maps.LatLng(markersArr[i].lat,markersArr[i].long));

          // set the center of the map to that new marker
          map.setCenter(new google.maps.LatLng(markersArr[i].lat,markersArr[i].long));

          // expand the map to fit the whole list of added markers
          bounds.extend(markerList[markerCount-1]);

          // add the marker to the map
          marker = new google.maps.Marker({
            position: new google.maps.LatLng(markersArr[i].lat,markersArr[i].long),
            map: map,
            icon: ''
          });

          // if the showNumber param is true, number the marker
          if (willShow) {
            marker.icon = 'http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld='+markerCount+'|FF0000|000000';
          }

          // add an info window if there is something in the info window key
          if (validateInfoContent(markersArr[i])) {

            // set marker HTML to the infoContent
            marker.html = markersArr[i].infoContent;
            marker.infoWidth = markersArr[i].maxWidth || 400;

            // create an info window
            var infowindow = new google.maps.InfoWindow({
                content: markersArr[i].infoContent,
                maxWidth: marker.infoWidth
            });

            //add event listener to show info window
            google.maps.event.addListener(marker, 'click', function() {
              infowindow.setContent(this.html);
              infowindow.maxWidth = this.infoWidth;
              infowindow.open(map, this);
            });
          }

          // increment added marker count
          markerCount++;
        }
      }

      // ensure that the map isn't too small when there are few markers that are very close together
      if (bounds.getNorthEast().equals(bounds.getSouthWest())) {
         var extendPoint1 = new google.maps.LatLng(bounds.getNorthEast().lat() + 0.01, bounds.getNorthEast().lng() + 0.01);
         var extendPoint2 = new google.maps.LatLng(bounds.getNorthEast().lat() - 0.01, bounds.getNorthEast().lng() - 0.01);
         bounds.extend(extendPoint1);
         bounds.extend(extendPoint2);
      }

      // fit the map to show all the markers
      map.fitBounds(bounds);
      }

      console.log(map.center);
  };
}(this.mapPlugin = this.mapPlugin || {}, google));
<commit_msg>Remove unnecessary console log<commit_after>/**
 * Create a map with markers using Google's Maps JavaScript API v3
 * Author: Paul Molluzzo - paul@molluzzo.com
 * ©2015 MIT License
 */

(function(ns, google){

  'use strict';

  var marker;

  /**
   * Check if a Marker has a valid lat/long
   * @param  {object} markerObj This is an individual marker from the array supplied in the createMap() function
   * @return {boolean}          Returns true/false depending on validity of the MarkerObj lat/long
   */
  function validateLatLong(markerObj) {
    if (typeof markerObj.lat !== 'undefined' && typeof markerObj.lat === 'number' && typeof markerObj.long !== 'undefined' && typeof markerObj.long === 'number') {
      return true;
    }

    return false;
  }

  /**
   * Check if a Marker has valid content for an info window
   * @param  {object} markerObj This is an individual marker from the array supplied in the createMap() function
   * @return {boolean}          Returns true/false depending on validity of the MarkerObj infoContent
   */
  function validateInfoContent(markerObj) {
    if (typeof markerObj.infoContent !== 'undefined' && typeof markerObj.infoContent === 'string')
      return true;

    return false;
  }

  /**
   * initialize the Map Plugin
   * @param  {string}   mapElementId The HTML element that will hold the map. Must be an id not a class.
   * @param  {integer}  startingLat  The latitude used to center the map if no markers are listed.
   * @param  {integer}  startingLong The longitude used to center the map if no markers are listed.
   * @param  {array}    markersArr   An array of markers to add to the map. Each marker is an object with the following key/values:
   *         For each marker Object
   *                    lat         {integer}   The latitude for the marker (required)
   *                    long        {integer}   The longitude for the marker (required)
   *                    infoContent {string}    The HTML content of the info window shown when clicking a marker
   *                    maxWidth    {integer}   The max width of the info window in px. Defaults to 400.
   * @param  {boolean}  showNumber  If true, will number the markers placed on the map.
   */
  ns.createMap = function(mapElementId, startingLat, startingLong, markersArr, showNumber) {

    // default the number param to false
    var willShow = showNumber || false;

    // initialize the map
    var map = new google.maps.Map(document.getElementById(mapElementId), {
      zoom: 4,
      center: new google.maps.LatLng(startingLat,startingLong)
    });

    // take the array of requested markers provided, create Google Maps LatLng objects, fit the map, add a marker to the map
    if (markersArr.length > 0) {

      var markerCount = 1;

      var bounds = new google.maps.LatLngBounds(),
          markerList = [];

      // iterate over the array, check the data, create a marker
      for (var i = 0; i < markersArr.length; i++) {

        // check that lat and long are there and are numbers
        if (validateLatLong(markersArr[i])) {

          // add lat/long to markerList as google.maps.LatLng
          markerList.push(new google.maps.LatLng(markersArr[i].lat,markersArr[i].long));

          // set the center of the map to that new marker
          map.setCenter(new google.maps.LatLng(markersArr[i].lat,markersArr[i].long));

          // expand the map to fit the whole list of added markers
          bounds.extend(markerList[markerCount-1]);

          // add the marker to the map
          marker = new google.maps.Marker({
            position: new google.maps.LatLng(markersArr[i].lat,markersArr[i].long),
            map: map,
            icon: ''
          });

          // if the showNumber param is true, number the marker
          if (willShow) {
            marker.icon = 'http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld='+markerCount+'|FF0000|000000';
          }

          // add an info window if there is something in the info window key
          if (validateInfoContent(markersArr[i])) {

            // set marker HTML to the infoContent
            marker.html = markersArr[i].infoContent;
            marker.infoWidth = markersArr[i].maxWidth || 400;

            // create an info window
            var infowindow = new google.maps.InfoWindow({
                content: markersArr[i].infoContent,
                maxWidth: marker.infoWidth
            });

            //add event listener to show info window
            google.maps.event.addListener(marker, 'click', function() {
              infowindow.setContent(this.html);
              infowindow.maxWidth = this.infoWidth;
              infowindow.open(map, this);
            });
          }

          // increment added marker count
          markerCount++;
        }
      }

      // ensure that the map isn't too small when there are few markers that are very close together
      if (bounds.getNorthEast().equals(bounds.getSouthWest())) {
         var extendPoint1 = new google.maps.LatLng(bounds.getNorthEast().lat() + 0.01, bounds.getNorthEast().lng() + 0.01);
         var extendPoint2 = new google.maps.LatLng(bounds.getNorthEast().lat() - 0.01, bounds.getNorthEast().lng() - 0.01);
         bounds.extend(extendPoint1);
         bounds.extend(extendPoint2);
      }

      // fit the map to show all the markers
      map.fitBounds(bounds);
      }
  };
}(this.mapPlugin = this.mapPlugin || {}, google));
<|endoftext|>


<commit_before>'use strict';

var App = require('../app');
var Backbone = require('Backbone');
var Marionette = require('backbone.marionette');

var navigationItem = App.request('addNavigationItem', {
  name: 'Friends',
  icon: 'user',
  url: '/friends'
});

var controller = {
  showFriends: function () {
    var FriendsView = require('./views/FriendsView');
    navigationItem.select();
    var model = new Backbone.Model();
    App.mainRegion.show(new FriendsView({
      model: model
    }));
  }
};

App.addInitializer(function () {
  new Marionette.AppRouter({
    controller: controller,
    appRoutes: {
      'friends': 'showFriends'
    }
  });
});
<commit_msg>Removed unnecessary backbone dependency<commit_after>'use strict';

var App = require('../app');
var Marionette = require('backbone.marionette');

var navigationItem = App.request('addNavigationItem', {
  name: 'Friends',
  icon: 'user',
  url: '/friends'
});

var controller = {
  showFriends: function () {
    var FriendsView = require('./views/FriendsView');
    navigationItem.select();
    var model = new Backbone.Model();
    App.mainRegion.show(new FriendsView({
      model: model
    }));
  }
};

App.addInitializer(function () {
  new Marionette.AppRouter({
    controller: controller,
    appRoutes: {
      'friends': 'showFriends'
    }
  });
});
<|endoftext|>


<commit_before>var utils = require('../utils');

/**
 * This object represents a tileset used by a Tilemap.
 * There can be multiple Tilesets in a map
 *
 * @class Tileset
 * @extends Texture
 * @constructor
 * @param game {Phaser.Game} Phaser game this belongs to.
 * @param key {string} The name of the tiledmap, this is usually the filename without the extension.
 * @param settings {Object} All the settings for the tileset
 * @param settings.tilewidth {Number} The width of a single tile in the set
 * @param settings.tileheight {Number} The height of a single tile in the set
 * @param [settings.firstgid=1] {Number} The id of the first tile in the set, defaults to 1
 * @param [settings.spacing=0] {Number} The spacing around tiles in the tileset (in pixels)
 * @param [settings.margin=0] {Number} The margin around a tile in the tileset (in pixels)
 * @param [settings.tileoffset] {Object} The offset to apply to a tile rendered from this tileset
 * @param [settings.tileoffset.x=0] {Number} The X offset to apply to the tile
 * @param [settings.tileoffset.y=0] {Number} The Y offset to apply to the tile
 * @param [settings.properties] {Object} User-defined, custom properties that apply to the tileset
 * @param [settings.tileproperties] {Object} User-defined, custom properties that apply to tiles in the tileset.
 *          The keys of this object should the tile id of the properties
 * @param [settings.tiles] {Object} Extra metadata about specific tiles
 * @param [settings.imagewidth] {Number} An override for the image width
 * @param [settings.imageheight] {Number} An override for the image height
 */
//TODO: Support external tilesets (TSX files) via the "source" attribute
//see: https://github.com/bjorn/tiled/wiki/TMX-Map-Format#tileset
function Tileset(game, key, settings) {
    var txkey = utils.cacheKey(key, 'tileset', settings.name),
        tx = PIXI.BaseTextureCache[txkey],
        ids,
        ttxkey,
        ttx,
        tileTextures;

    // if no main texture, check if multi-image tileset
    if (!tx && settings.tiles) {
        // need to sort because order matters here, and can't guarantee that the object's keys will be ordered.
        // We need a custom comparator because .sort() is lexagraphic, not numeric.
        ids = Object.keys(settings.tiles).sort(function (a, b) { return parseInt(a, 10) - parseInt(b, 10); });

        for (var i = 0; i < ids.length; ++i) {
            if (settings.tiles[ids[i]].image) {
                // this is a multi-image tileset
                tileTextures = tileTextures || [];

                ttxkey = utils.cacheKey(key, 'tileset_image_' + ids[i], settings.name);
                ttx = PIXI.TextureCache[ttxkey];

                if (!ttx) {
                    console.warn(
                        'Tileset "' + settings.name + '" unable to find texture cached by key "' +
                        ttxkey + '", using blank texture.'
                    );
                    ttx = PIXI.Texture.emptyTexture;
                }

                tileTextures.push(ttx);
            }
        }
    }

    // if no main texture, and we didn't find any image tiles then warn about blank tileset
    if (!tx && !tileTextures) {
        console.warn(
            'Tileset "' + settings.name + '" unable to find texture cached by key "' +
            txkey +  '", using blank texture.'
        );
    }

    PIXI.Texture.call(this, tx || PIXI.Texture.emptyTexture.baseTexture);

    this.game = game;

    this.multiImage = !!tileTextures;

    //Tiled Editor properties

    /**
     * The first tileId in the tileset
     *
     * @property firstgid
     * @type Number
     */
    this.firstgid = settings.firstgid || 1;

    /**
     * The name of the tileset
     *
     * @property name
     * @type String
     */
    this.name = settings.name;

    /**
     * The width of a tile in the tileset
     *
     * @property tileWidth
     * @type Number
     */
    this.tileWidth = settings.tilewidth;

    /**
     * The height of a tile in the tileset
     *
     * @property tileHeight
     * @type Number
     */
    this.tileHeight = settings.tileheight;

    /**
     * The spacing around a tile in the tileset
     *
     * @property spacing
     * @type Number
     */
    this.spacing = settings.spacing || 0;

    /**
     * The margin around a tile in the tileset
     *
     * @property margin
     * @type Number
     */
    this.margin = settings.margin || 0;

    /**
     * The offset of tile positions when rendered
     *
     * @property tileoffset
     * @type Phaser.Point
     */
    this.tileoffset = new Phaser.Point(
        settings.tileoffset ? settings.tileoffset.x : 0,
        settings.tileoffset ? settings.tileoffset.y : 0
    );

    //TODO: Support for "terraintypes," "image"
    //see: https://github.com/bjorn/tiled/wiki/TMX-Map-Format#tileset

    //Custom/Optional properties

    /**
     * The number of tiles calculated based on size, margin, and spacing
     *
     * @property numTiles
     * @type Vector
     */
    this.numTiles = this.multiImage ? tileTextures.length : new Phaser.Point(
        Phaser.Math.floor((this.baseTexture.width - this.margin) / (this.tileWidth - this.spacing)),
        Phaser.Math.floor((this.baseTexture.height - this.margin) / (this.tileHeight - this.spacing))
    );

    /**
     * The last tileId in the tileset
     *
     * @property lastgid
     * @type Number
     */
    this.lastgid = this.firstgid + (this.multiImage ? tileTextures.length : ((this.numTiles.x * this.numTiles.y) || 1)) - 1;

    /**
     * The properties of the tileset
     *
     * @property properties
     * @type Object
     */
    this.properties = utils.parseTiledProperties(settings.properties);

    /**
     * The properties of the tiles in the tileset
     *
     * @property tileproperties
     * @type Object
     */
    this.tileproperties = {};

    // massage tile tileproperties
    for (var k in settings.tileproperties) {
        this.tileproperties[k] = utils.parseTiledProperties(settings.tileproperties[k]);
    }

    /**
     * The size of the tileset
     *
     * @property size
     * @type Vector
     */
    this.size = this.multiImage ? new Phaser.Point(0, 0) : new Phaser.Point(
        settings.imagewidth || this.baseTexture.width,
        settings.imageheight || this.baseTexture.height
    );

    /**
     * The texture instances for each tile in the set
     *
     * @property textures
     * @type Array
     */
    this.textures = this.multiImage ? tileTextures : [];

    // generate tile textures for single image tileset
    if (!this.multiImage) {
        for(var t = 0, tl = this.lastgid - this.firstgid + 1; t < tl; ++t) {
            // convert the tileId to x,y coords of the tile in the Texture
            var y = Phaser.Math.floor(t / this.numTiles.x),
                x = (t - (y * this.numTiles.x));

            // get location in pixels
            x = (x * this.tileWidth) + (x * this.spacing) + this.margin;
            y = (y * this.tileHeight) + (y * this.spacing) + this.margin;

            this.textures.push(
                new PIXI.Texture(
                    this.baseTexture,
                    new Phaser.Rectangle(x, y, this.tileWidth, this.tileHeight)
                )
            );
        }
    }

    /**
     * The animation data for tile animations in the set
     *
     * @property tileanimations
     * @type Object
     */
    this.tileanimations = {};

    // parse extra information about the tiles
    for (var p in settings.tiles) {
        if (settings.tiles[p].animation) {
            this.tileanimations[p] = {
                rate: 1000 / settings.tiles[p].animation[0].duration,
                data: new Phaser.FrameData()
            };

            for (var a = 0; a < settings.tiles[p].animation.length; ++a) {
                var frame = this.textures[settings.tiles[p].animation[a].tileid].frame;

                this.tileanimations[p].data.addFrame(
                    new Phaser.Frame(a, frame.x, frame.y, frame.width, frame.height)
                );
            }
        }

        // image - url
        // objectgroup - collision data
    }
}

Tileset.prototype = Object.create(PIXI.Texture.prototype);
Tileset.prototype.constructor = Tileset;

module.exports = Tileset;

/**
 * Gets the tile properties for a tile based on it's ID
 *
 * @method getTileProperties
 * @param tileId {Number} The id of the tile to get the properties for
 * @return {Object} The properties of the tile
 */
Tileset.prototype.getTileProperties = function (tileId) {
    if (!tileId) {
        return null;
    }

    var flags = Tileset.FLAGS,
        flippedX = tileId & flags.FLIPPED_HORZ,
        flippedY = tileId & flags.FLIPPED_VERT,
        flippedAD = tileId & flags.FLIPPED_ANTI_DIAG;

    tileId = (tileId & ~Tileset.FLAGS.ALL) - this.firstgid;

    // if less than 0, then this id isn't in this tileset
    if (tileId < 0) {
        return null;
    }

    var props = this.tileproperties[tileId] ?
        //get this value
        this.tileproperties[tileId] :
        //set this id to default values and cache
        this.tileproperties[tileId] = {
            collides: false
        };

    props.flippedX = flippedX;
    props.flippedY = flippedY;
    props.flippedAD = flippedAD;

    return props;
};

/**
 * Gets the tile animations for a tile based on it's ID
 *
 * @method getTileProperties
 * @param tileId {Number} The id of the tile to get the animation frames for
 * @return {Phaser.FrameData} The frame data of the tile
 */
Tileset.prototype.getTileAnimations = function (tileId) {
    if (!tileId) {
        return null;
    }

    tileId = (tileId & ~Tileset.FLAGS.ALL) - this.firstgid;

    // if less than 0, then this id isn't in this tileset
    if (tileId < 0) {
        return null;
    }

    return this.tileanimations[tileId];
};

/**
 * Gets the tile texture for a tile based on it's ID
 *
 * @method getTileTexture
 * @param tileId {Number} The id of the tile to get the texture for
 * @return {Texture} The texture for the tile
 */
Tileset.prototype.getTileTexture = function (tileId) {
    if (!tileId) {
        return null;
    }

    // get the internal ID of the tile in this set (0 indexed)
    tileId = (tileId & ~Tileset.FLAGS.ALL) - this.firstgid;

    // if less than 0, then this id isn't in this tileset
    if (tileId < 0) {
        return null;
    }

    return this.textures[tileId];
};

/**
 * Returns whether or not this tileset contains the given tile guid
 *
 * @method contains
 * @param tileId {Number} The ID of the tile to check
 * @return {Boolean}
 */
Tileset.prototype.contains = function (tileId) {
    if (!tileId) {
        return false;
    }

    tileId &= ~Tileset.FLAGS.ALL;

    return (tileId >= this.firstgid && tileId <= this.lastgid);
};

Tileset.prototype.destroy = function () {
    destroyTexture(this);

    // destroy sub tile textures
    for (var i = 0; i < this.textures.length; ++i) {
        destroyTexture(this.textures[i]);
    }

    this.tileoffset = null;
    this.numTiles = null;
    this.properties = null;
    this.tileproperties = null;
    this.size = null;
    this.textures = null;
    this.tileanimations = null;
};

/**
 * Tileset GID flags, these flags are set on a tile's ID to give it a special property
 *
 * @property FLAGS
 * @static
 */
Tileset.FLAGS = {
    FLIPPED_HORZ: 0x80000000,
    FLIPPED_VERT: 0x40000000,
    FLIPPED_ANTI_DIAG: 0x20000000
};

var mask = 0;
for(var f in Tileset.FLAGS) {
    mask |= Tileset.FLAGS[f];
}

Tileset.FLAGS.ALL = mask;

function destroyTexture(texture) {
    texture.destroy();

    texture.baseTexture = null;
    texture.frame = null;
    texture.trim = null;
    texture.crop = null;
    texture._uvs = null;
}
<commit_msg>Lazy create tile textures/animations<commit_after>var utils = require('../utils');

/**
 * This object represents a tileset used by a Tilemap.
 * There can be multiple Tilesets in a map
 *
 * @class Tileset
 * @extends Texture
 * @constructor
 * @param game {Phaser.Game} Phaser game this belongs to.
 * @param key {string} The name of the tiledmap, this is usually the filename without the extension.
 * @param settings {Object} All the settings for the tileset
 * @param settings.tilewidth {Number} The width of a single tile in the set
 * @param settings.tileheight {Number} The height of a single tile in the set
 * @param [settings.firstgid=1] {Number} The id of the first tile in the set, defaults to 1
 * @param [settings.spacing=0] {Number} The spacing around tiles in the tileset (in pixels)
 * @param [settings.margin=0] {Number} The margin around a tile in the tileset (in pixels)
 * @param [settings.tileoffset] {Object} The offset to apply to a tile rendered from this tileset
 * @param [settings.tileoffset.x=0] {Number} The X offset to apply to the tile
 * @param [settings.tileoffset.y=0] {Number} The Y offset to apply to the tile
 * @param [settings.properties] {Object} User-defined, custom properties that apply to the tileset
 * @param [settings.tileproperties] {Object} User-defined, custom properties that apply to tiles in the tileset.
 *          The keys of this object should the tile id of the properties
 * @param [settings.tiles] {Object} Extra metadata about specific tiles
 * @param [settings.imagewidth] {Number} An override for the image width
 * @param [settings.imageheight] {Number} An override for the image height
 */
//TODO: Support external tilesets (TSX files) via the "source" attribute
//see: https://github.com/bjorn/tiled/wiki/TMX-Map-Format#tileset
function Tileset(game, key, settings) {
    var txkey = utils.cacheKey(key, 'tileset', settings.name),
        tx = PIXI.BaseTextureCache[txkey],
        ids,
        ttxkey,
        ttx,
        tileTextures = {},
        numTileTextures = 0;

    // if no main texture, check if multi-image tileset
    if (!tx && settings.tiles) {
        // need to sort because order matters here, and can't guarantee that the object's keys will be ordered.
        // We need a custom comparator because .sort() is lexagraphic, not numeric.
        ids = Object.keys(settings.tiles).sort(function (a, b) { return parseInt(a, 10) - parseInt(b, 10); });

        for (var i = 0; i < ids.length; ++i) {
            if (settings.tiles[ids[i]].image) {
                ttxkey = utils.cacheKey(key, 'tileset_image_' + ids[i], settings.name);
                ttx = PIXI.TextureCache[ttxkey];

                if (!ttx) {
                    console.warn(
                        'Tileset "' + settings.name + '" unable to find texture cached by key "' +
                        ttxkey + '", using blank texture.'
                    );
                    ttx = PIXI.Texture.emptyTexture;
                }

                tileTextures[numTileTextures++] = ttx;
            }
        }
    }

    // if no main texture, and we didn't find any image tiles then warn about blank tileset
    if (!tx && numTileTextures === 0) {
        console.warn(
            'Tileset "' + settings.name + '" unable to find texture cached by key "' +
            txkey +  '", using blank texture.'
        );
    }

    PIXI.Texture.call(this, tx || PIXI.Texture.emptyTexture.baseTexture);

    this.game = game;

    this.multiImage = numTileTextures > 0;

    //Tiled Editor properties

    /**
     * The first tileId in the tileset
     *
     * @property firstgid
     * @type Number
     */
    this.firstgid = settings.firstgid || 1;

    /**
     * The name of the tileset
     *
     * @property name
     * @type String
     */
    this.name = settings.name;

    /**
     * The width of a tile in the tileset
     *
     * @property tileWidth
     * @type Number
     */
    this.tileWidth = settings.tilewidth;

    /**
     * The height of a tile in the tileset
     *
     * @property tileHeight
     * @type Number
     */
    this.tileHeight = settings.tileheight;

    /**
     * The spacing around a tile in the tileset
     *
     * @property spacing
     * @type Number
     */
    this.spacing = settings.spacing || 0;

    /**
     * The margin around a tile in the tileset
     *
     * @property margin
     * @type Number
     */
    this.margin = settings.margin || 0;

    /**
     * The offset of tile positions when rendered
     *
     * @property tileoffset
     * @type Phaser.Point
     */
    this.tileoffset = new Phaser.Point(
        settings.tileoffset ? settings.tileoffset.x : 0,
        settings.tileoffset ? settings.tileoffset.y : 0
    );

    //TODO: Support for "terraintypes," "image"
    //see: https://github.com/bjorn/tiled/wiki/TMX-Map-Format#tileset

    //Custom/Optional properties

    /**
     * The number of tiles calculated based on size, margin, and spacing
     *
     * @property numTiles
     * @type Vector
     */
    this.numTiles = this.multiImage ? numTileTextures : new Phaser.Point(
        Phaser.Math.floor((this.baseTexture.width - this.margin) / (this.tileWidth - this.spacing)),
        Phaser.Math.floor((this.baseTexture.height - this.margin) / (this.tileHeight - this.spacing))
    );

    /**
     * The last tileId in the tileset
     *
     * @property lastgid
     * @type Number
     */
    this.lastgid = this.firstgid + (this.multiImage ? numTileTextures : ((this.numTiles.x * this.numTiles.y) || 1)) - 1;

    /**
     * The properties of the tileset
     *
     * @property properties
     * @type Object
     */
    this.properties = utils.parseTiledProperties(settings.properties);

    /**
     * The properties of the tiles in the tileset
     *
     * @property tileproperties
     * @type Object
     */
    this.tileproperties = {};

    // massage tile tileproperties
    for (var k in settings.tileproperties) {
        this.tileproperties[k] = utils.parseTiledProperties(settings.tileproperties[k]);
    }

    /**
     * The size of the tileset
     *
     * @property size
     * @type Vector
     */
    this.size = this.multiImage ? new Phaser.Point(0, 0) : new Phaser.Point(
        settings.imagewidth || this.baseTexture.width,
        settings.imageheight || this.baseTexture.height
    );

    /**
     * The texture instances for each tile in the set
     *
     * @property textures
     * @type Array
     */
    this.textures = this.multiImage ? tileTextures : {};

    /**
     * The animation data for tile animations in the set
     *
     * @property tileanimations
     * @type Object
     */
    this.tileanimations = {};

    /**
     * Internal tiles config data.
     *
     * @property _tilesData
     * @type Object
     * @private
     */
    this._tilesData = settings.tiles || {};
}

Tileset.prototype = Object.create(PIXI.Texture.prototype);
Tileset.prototype.constructor = Tileset;

module.exports = Tileset;

/**
 * Gets the tile properties for a tile based on it's ID
 *
 * @method getTileProperties
 * @param tileId {Number} The id of the tile to get the properties for
 * @return {Object} The properties of the tile
 */
Tileset.prototype.getTileProperties = function (tileId) {
    if (!tileId) {
        return null;
    }

    var flags = Tileset.FLAGS,
        flippedX = tileId & flags.FLIPPED_HORZ,
        flippedY = tileId & flags.FLIPPED_VERT,
        flippedAD = tileId & flags.FLIPPED_ANTI_DIAG;

    tileId = (tileId & ~Tileset.FLAGS.ALL) - this.firstgid;

    // if less than 0, then this id isn't in this tileset
    if (tileId < 0) {
        return null;
    }

    var props = this.tileproperties[tileId] ?
        //get this value
        this.tileproperties[tileId] :
        //set this id to default values and cache
        this.tileproperties[tileId] = {
            collides: false
        };

    props.flippedX = flippedX;
    props.flippedY = flippedY;
    props.flippedAD = flippedAD;

    return props;
};

/**
 * Gets the tile animations for a tile based on it's ID
 *
 * @method getTileProperties
 * @param tileId {Number} The id of the tile to get the animation frames for
 * @return {Phaser.FrameData} The frame data of the tile
 */
Tileset.prototype.getTileAnimations = function (tileId) {
    if (!tileId) {
        return null;
    }

    tileId = (tileId & ~Tileset.FLAGS.ALL) - this.firstgid;

    // if less than 0, then this id isn't in this tileset
    if (tileId < 0) {
        return null;
    }

    // if we have already created the animation data
    if (this.tileanimations[tileId]) {
        return this.tileanimations[tileId];
    }
    else {
        if (this._tilesData[tileId] && this._tilesData[tileId].animation) {
            this.tileanimations[tileId] = {
                rate: 1000 / this._tilesData[tileId].animation[0].duration,
                data: new Phaser.FrameData()
            };

            for (var i = 0; i < this._tilesData[tileId].animation.length; ++i) {
                var frame = this.getTileTexture(this._tilesData[tileId].animation[i].tileid + this.firstgid).frame;

                this.tileanimations[tileId].data.addFrame(
                    new Phaser.Frame(i, frame.x, frame.y, frame.width, frame.height)
                );
            }
        }

        return null;
    }
};

/**
 * Gets the tile texture for a tile based on it's ID
 *
 * @method getTileTexture
 * @param tileId {Number} The id of the tile to get the texture for
 * @return {Texture} The texture for the tile
 */
Tileset.prototype.getTileTexture = function (tileId) {
    if (!tileId) {
        return null;
    }

    // get the internal ID of the tile in this set (0 indexed)
    tileId = (tileId & ~Tileset.FLAGS.ALL) - this.firstgid;

    // if less than 0, then this id isn't in this tileset
    if (tileId < 0) {
        return null;
    }

    // multi image set or we have cached the texture, just return it
    if (this.multiImage || this.textures[tileId]) {
        return this.textures[tileId];
    }
    // generate this tile's texture then cache it.
    else {
        // convert the tileId to x,y coords of the tile in the Texture
        var y = Phaser.Math.floor(tileId / this.numTiles.x),
            x = (tileId - (y * this.numTiles.x));

        // get location in pixels
        x = (x * this.tileWidth) + (x * this.spacing) + this.margin;
        y = (y * this.tileHeight) + (y * this.spacing) + this.margin;

        return this.textures[tileId] = new PIXI.Texture(
            this.baseTexture,
            new Phaser.Rectangle(x, y, this.tileWidth, this.tileHeight)
        );
    }
};

/**
 * Returns whether or not this tileset contains the given tile guid
 *
 * @method contains
 * @param tileId {Number} The ID of the tile to check
 * @return {Boolean}
 */
Tileset.prototype.contains = function (tileId) {
    if (!tileId) {
        return false;
    }

    tileId &= ~Tileset.FLAGS.ALL;

    return (tileId >= this.firstgid && tileId <= this.lastgid);
};

Tileset.prototype.destroy = function () {
    destroyTexture(this, false);

    // destroy tile textures
    for (var id in this.textures) {
        destroyTexture(this.textures[id]);
    }

    // destroy tile animations
    for (var jd in this.tileanimations) {
        this.tileanimations[jd]._frames = null;
        this.tileanimations[jd]._frameNames = null;
    }

    this.tileoffset = null;
    this.numTiles = null;
    this.properties = null;
    this.tileproperties = null;
    this.size = null;
    this.textures = null;
    this.tileanimations = null;
};

/**
 * Tileset GID flags, these flags are set on a tile's ID to give it a special property
 *
 * @property FLAGS
 * @static
 */
Tileset.FLAGS = {
    FLIPPED_HORZ: 0x80000000,
    FLIPPED_VERT: 0x40000000,
    FLIPPED_ANTI_DIAG: 0x20000000
};

var mask = 0;
for(var f in Tileset.FLAGS) {
    mask |= Tileset.FLAGS[f];
}

Tileset.FLAGS.ALL = mask;

function destroyTexture(texture, callDestroy) {
    if (callDestroy !== false) {
        texture.destroy();
    }

    texture.baseTexture = null;
    texture.frame = null;
    texture.trim = null;
    texture.crop = null;
    texture._uvs = null;
}
<|endoftext|>