<commit_before>/*
Copyright 2014 Weswit s.r.l.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
define(["./Constants","./Utils"],function(Constants,Utils) {

  var tmpGeometry = new THREE.CubeGeometry(2,4,2);
  var tmpMaterial = new THREE.MeshLambertMaterial( { color: 0xdddddd } );

  var materials = {};
  materials[Constants.OWN] = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
  materials[Constants.OTHER] = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );

  var mtls = {};
  mtls[Constants.OWN] = "obj/dart.obj.mtl";
  mtls[Constants.OTHER] = "obj/dartb.obj.mtl";

  var waiting = {};
  waiting[Constants.OWN] = [];
  waiting[Constants.OTHER] = [];
  var clonable = {};
  function loadClonable(type) {
    Utils.loadObj("obj/dart.obj", mtls[type], function (object) {
      clonable[type] = object;
      for(var i=0; i<waiting[type].length; i++) {
        waiting[type][i].convertDart();
      }
      waiting[type] = null;
    });
  }
  loadClonable(Constants.OWN);
  loadClonable(Constants.OTHER);

  var NICK_OFFSET = {
      x: function(size) {
        return -size/2;
      },
      y: -30,
      z: 30
  };
  var FONT_SIZE = 12;

  //assumes  NICK_OFFSET.y is < 0
  var SWITCH_NICK_POSITION = -(Constants.MAX_SIZE.y + NICK_OFFSET.y - FONT_SIZE);

  var CAMERA_OFFSET = {
      x: 0,
      y: 0,
      z: 250
  };

  var hitSound = Utils.loadSound("hit"); // https://www.youtube.com/watch?v=Zt_L-J6xcN0
  var throwSound = Utils.loadSound("throw"); // http://soundbible.com/963-Arrow.html

  //Dart obj is 13.598 units, we want it 96
  var expectedSize = Constants.DART_ORIGINAL_SIZE;
  var SCALE_TO = expectedSize/13.598;

  var Dart = function(key,type,field,showInfo,audio) {
    this.field = field;

    this.dart = null;
    this.dinamics =  new THREE.Vector3( 0, 0, 0 );
    this.startPos =  new THREE.Vector3( 0, 0, 0 );

    this.status = "";

    this.text = null;
    this.nick = null;
    this.showNickFlag = showInfo;
    this.audio = audio;

    this.timestamp = 0;

    this.key = key;
    this.type = type;

    this.planted = false;
    this.plantedTime = null;

    this.initDart();
  };

  Dart.prototype = {
      /**
       * @private
       */
      initDart: function() {
        if (!clonable[this.type]) {
          //wait
          this.dart = new THREE.Mesh(tmpGeometry,tmpMaterial);
          this.dart.castShadow = true;
          waiting[this.type].push(this);
        } else {
          this.createDart();
        }

        this.reset();

        this.field.addObject(this.dart);
      },

      /**
       * @private
       */
      createDart: function() {
        this.dart = clonable[this.type].clone();
        this.dart.scale.set(SCALE_TO,SCALE_TO,SCALE_TO);
      },

      /**
       * @private
       */
      convertDart: function() {
        var tmp = this.dart;
        this.createDart();

        //text already correctly positioned
        this.dart.position = tmp.position;
        this.dart.quaternion = tmp.quaternion;

        this.field.removeObject(tmp);
        this.field.addObject(this.dart);
      },

      changeType: function(newType) {
        if (newType == this.type) {
          return;
        }
        this.type = newType;

        this.convertDart();

        if (this.text) {
          this.text.material = materials[this.type];
        }
      },

      clear: function() {
        this.field.removeObject(this.dart);
        this.dart = null;
        this.showNick(false);
      },

      setNick: function(nick) {
        this.nick = nick;
        this.showNick(this.showNickFlag);
      },

      setStatus: function(status) {
        this.status = status;
      },

      enableAudio: function(enable) {
        this.audio = enable;
      },

      /**
       * @private
       */
      showNick: function(show) {
        if (this.text != null) {
          this.field.removeObject(this.text);
          this.text = null;
        }

        this.showNickFlag = show;

        if (this.nick == null || this.nick == "" || !show) {
          return;
        }

        var text3d = new THREE.TextGeometry( this.nick, {
              size: FONT_SIZE,
              height: 0,
              curveSegments: 0,

              font: "droid sans",
              weight: "bold"
            });
        text3d.computeBoundingBox();

        this.text = new THREE.Mesh(text3d, materials[this.type]);

        this.nickOffsetX = NICK_OFFSET["x"](text3d.boundingBox.max.x);

        this.text.position.x = this.dart.position.x+this.nickOffsetX;
        this.text.position.y = this.dart.position.y+NICK_OFFSET["y"];
        this.text.position.z = this.dart.position.z+NICK_OFFSET["z"];
        this.field.addObject(this.text);
      },

      getKey: function() {
        return this.key;
      },
      getDart: function() {
        return this.dart;
      },
      getDinamics: function() {
        return this.dinamics;
      },
      getNick: function() {
        return this.nick;
      },
      getStatus: function() {
        return this.status;
      },
      isFlying: function() {
        return this.dinamics.z != 0;
      },
      isPlanted: function() {
        return this.planted;
      },
      getPlantedDelta: function() {
        if (this.plantedTime) {
          return new Date().getTime() - this.plantedTime;
        }
        return 0;
      },

      attachCamera: function(attach) {
        this.camera = attach;
        this.field.enableOrbit(!attach);
      },

      //Rotation

      setRotation: function(x,y,z) {
        this.setRot("x",x);
        this.setRot("y",y);
        this.setRot("z",z);
      },

      setRot: function(axis,val) {
        this.dart.rotation[axis] = val;
      },

      //speed

      setSpeed: function(vx,vy,vz) {
        this.dinamics.x = vx;
        this.dinamics.y = vy;
        this.dinamics.z = vz;

        if (vz !== 0) {
          this.doSound(throwSound);

          this.timestamp = new Date().getTime();
          this.fixStartPosition();
          this.calculate();

        }
      },

      //Sound

      doSound: function(sound) {
        if (sound && this.audio) {
          sound.play();
        }
      },

      //Position

      /**
       * @private
       */
      fixStartPosition: function(x,y,z) {
        this.startPos.x = this.dart.position.x;
        this.startPos.y = this.dart.position.y;
        this.startPos.z = this.dart.position.z;
      },

      setPosition: function(x,y,z) {
        this.setPos("x",x);
        this.setPos("y",y);
        this.setPos("z",z);

        if (this.camera && this.isFlying()) {
          this.repositionCamera();
        }
      },

      repositionCamera: function() {
        var x = this.dart.position.x;
        var y = this.dart.position.y;
        var z = this.dart.position.z;

        var px = x+CAMERA_OFFSET.x;
        var py = y+CAMERA_OFFSET.y;
        var pz = z+CAMERA_OFFSET.z;

        this.field.moveCameraToward(px,py,pz);
        this.field.pointCamera(x,y,z);
      },

      /**
       * @private
       */
      setPos: function(axis,value) {
        if (value ==  this.dart.position[axis]) {
          return;
        }

        if (value <= -Constants.MAX_SIZE[axis]) {
          this.doSound(hitSound);
        } else if (axis != "z" && value >= Constants.MAX_SIZE[axis]) {
          this.doSound(hitSound);
        }

        this.dart.position[axis] = value;
        if (this.text) {
          if (axis == "x") {
            this.text.position[axis] = value+this.nickOffsetX;
          } else if (axis == "y" && value < SWITCH_NICK_POSITION){
            this.text.position[axis] = value-NICK_OFFSET[axis];
          } else {
            this.text.position[axis] = value+NICK_OFFSET[axis];
          }
        }

        this.field.render();
      },

      //calculus

      /**
       * @private
       */
      getFinalTimeIfOverflow: function(axis,value) {

        if (value > Constants.MAX_SIZE[axis] || value < -Constants.MAX_SIZE[axis]) {
          var endValue =  value > Constants.MAX_SIZE[axis] ? Constants.MAX_SIZE[axis] : -Constants.MAX_SIZE[axis];

          if (axis == "y") {
            return this.calculateTimestampY(endValue);
          } else {
            return this.calculateTimestamp(axis,endValue);
          }
        }

        return null;
      },

      /**
       * @private
       */
      calculateTimestamp: function(axis,value) {
        return Math.abs((value-this.startPos[axis])/this.dinamics[axis]);
      },

      /**
       * @private
       */
      calculateAxisPos: function(axis,tNow) {
        return this.startPos[axis] + this.dinamics[axis]*tNow;
      },

      /**
       * @private
       */
      calculateYPosition: function(tNow) {
        //s = v*t + (1/2)at^2
        var units = Constants.HALF_ACCELERATION*Math.pow(tNow,2);
        return this.calculateAxisPos("y",tNow) - units;
      },

      /**
       * @private
       */
      calculateTimestampY: function(value) {

        //s =  v*t + (1/2)at^2

        var c = -(value-this.startPos["y"]);
        var a = -Constants.HALF_ACCELERATION;
        var b = this.dinamics["y"];

        var d = Math.pow(b,2) - 4*a*c;

        if (c<0) {
          return (-b + Math.sqrt(d))/(2*a);
        } else if(c>0) {
          return (-b - Math.sqrt(d))/(2*a);
        }
        return 0;

      },

      calculate: function() {
        if (!this.isFlying()) {
          return;
        }

        var tNow = new Date().getTime() - this.timestamp;

        var x = this.calculateAxisPos("x",tNow);
        var y = this.calculateYPosition(tNow);
        var z = this.calculateAxisPos("z",tNow);

        var isLanded = this.calculateLanding(x,y,z);
        if (!isLanded) {
          this.setPosition(x,y,z);
          this.calculateRotation(x,y,z,tNow);
        } else if(this.camera) {
          this.field.enableOrbit(true);
        }

      },

      calculateRotation: function(x,y,z,tNow) {
        var currentSpeedY = this.dinamics.y + -Constants.ACCELERATION*tNow;

        var x = this.getRotationRadians(currentSpeedY,this.dinamics.z) + Math.PI/2;
        var y = this.getRotationRadians(this.dinamics.x,this.dinamics.z);

        this.setRotation(x,0,y);
      },

      getRotationRadians: function(v1,v2) {
        var up = v1 >= 0;
        var v1IsBigger = Math.abs(v1) > Math.abs(v2);
        var rot = Math.atan(v1IsBigger ? v1/v2 : v2/v1);

        if (v1IsBigger) {
          return -rot;
        } else if (up) {
          return rot + Math.PI/2;
        } else {
          return rot - Math.PI/2;
        }

      },

      calculateLanding: function(x,y,z) {
        var endXt = this.getFinalTimeIfOverflow("x",x);
        var endYt = this.getFinalTimeIfOverflow("y",y);
        var endZt = this.getFinalTimeIfOverflow("z",z);

        if (endXt !== null || endYt !== null ||  endZt !== null) {
          var tEnd = endXt;
          tEnd = tEnd === null || endYt !== null && endYt < tEnd ? endYt : tEnd;
          tEnd = tEnd === null || endZt !== null && endZt < tEnd ? endZt : tEnd;
          x = this.calculateAxisPos("x",tEnd);
          y = this.calculateYPosition(tEnd);
          z = this.calculateAxisPos("z",tEnd);

          this.setPosition(x,y,z);
          this.calculateRotation(x,y,z,tEnd);
          this.setSpeed(0,0,0);

          /*
          console.log("Start "+this.startPos.x+"|"+this.startPos.y+"|"+this.startPos.z);
          console.log("Speed "+this.dinamics.x+"|"+this.dinamics.y+"|"+this.dinamics.z);
          console.log(tEnd);
          console.log("End "+x+"|"+y+"|"+z);
          */

          this.planted = true;
          this.plantedTime = new Date().getTime();

          return true;
        }
        return false;
      },

      reset: function() {
        this.setPosition(0,Constants.INITIAL_CAMERA_POS_Y,Constants.MAX_SIZE.z);
        this.setRotation(Math.PI/2,0,0);
        this.planted = false;
        this.plantedTime = null;
        if (this.camera) {
          this.attachCamera(false);
          this.field.resetCamera();
        }

      }
  };

  return Dart;
});<commit_msg>change hit sound hook<commit_after>/*
Copyright 2014 Weswit s.r.l.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
define(["./Constants","./Utils"],function(Constants,Utils) {

  var tmpGeometry = new THREE.CubeGeometry(2,4,2);
  var tmpMaterial = new THREE.MeshLambertMaterial( { color: 0xdddddd } );

  var materials = {};
  materials[Constants.OWN] = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
  materials[Constants.OTHER] = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );

  var mtls = {};
  mtls[Constants.OWN] = "obj/dart.obj.mtl";
  mtls[Constants.OTHER] = "obj/dartb.obj.mtl";

  var waiting = {};
  waiting[Constants.OWN] = [];
  waiting[Constants.OTHER] = [];
  var clonable = {};
  function loadClonable(type) {
    Utils.loadObj("obj/dart.obj", mtls[type], function (object) {
      clonable[type] = object;
      for(var i=0; i<waiting[type].length; i++) {
        waiting[type][i].convertDart();
      }
      waiting[type] = null;
    });
  }
  loadClonable(Constants.OWN);
  loadClonable(Constants.OTHER);

  var NICK_OFFSET = {
      x: function(size) {
        return -size/2;
      },
      y: -30,
      z: 30
  };
  var FONT_SIZE = 12;

  //assumes  NICK_OFFSET.y is < 0
  var SWITCH_NICK_POSITION = -(Constants.MAX_SIZE.y + NICK_OFFSET.y - FONT_SIZE);

  var CAMERA_OFFSET = {
      x: 0,
      y: 0,
      z: 250
  };

  var hitSound = Utils.loadSound("hit"); // https://www.youtube.com/watch?v=Zt_L-J6xcN0
  var throwSound = Utils.loadSound("throw"); // http://soundbible.com/963-Arrow.html

  //Dart obj is 13.598 units, we want it 96
  var expectedSize = Constants.DART_ORIGINAL_SIZE;
  var SCALE_TO = expectedSize/13.598;

  var Dart = function(key,type,field,showInfo,audio) {
    this.field = field;

    this.dart = null;
    this.dinamics =  new THREE.Vector3( 0, 0, 0 );
    this.startPos =  new THREE.Vector3( 0, 0, 0 );

    this.status = "";

    this.text = null;
    this.nick = null;
    this.showNickFlag = showInfo;
    this.audio = audio;

    this.timestamp = 0;

    this.key = key;
    this.type = type;

    this.planted = false;
    this.plantedTime = null;

    this.initDart();
  };

  Dart.prototype = {
      /**
       * @private
       */
      initDart: function() {
        if (!clonable[this.type]) {
          //wait
          this.dart = new THREE.Mesh(tmpGeometry,tmpMaterial);
          this.dart.castShadow = true;
          waiting[this.type].push(this);
        } else {
          this.createDart();
        }

        this.reset();

        this.field.addObject(this.dart);
      },

      /**
       * @private
       */
      createDart: function() {
        this.dart = clonable[this.type].clone();
        this.dart.scale.set(SCALE_TO,SCALE_TO,SCALE_TO);
      },

      /**
       * @private
       */
      convertDart: function() {
        var tmp = this.dart;
        this.createDart();

        //text already correctly positioned
        this.dart.position = tmp.position;
        this.dart.quaternion = tmp.quaternion;

        this.field.removeObject(tmp);
        this.field.addObject(this.dart);
      },

      changeType: function(newType) {
        if (newType == this.type) {
          return;
        }
        this.type = newType;

        this.convertDart();

        if (this.text) {
          this.text.material = materials[this.type];
        }
      },

      clear: function() {
        this.field.removeObject(this.dart);
        this.dart = null;
        this.showNick(false);
      },

      setNick: function(nick) {
        this.nick = nick;
        this.showNick(this.showNickFlag);
      },

      setStatus: function(status) {
        this.status = status;
      },

      enableAudio: function(enable) {
        this.audio = enable;
      },

      /**
       * @private
       */
      showNick: function(show) {
        if (this.text != null) {
          this.field.removeObject(this.text);
          this.text = null;
        }

        this.showNickFlag = show;

        if (this.nick == null || this.nick == "" || !show) {
          return;
        }

        var text3d = new THREE.TextGeometry( this.nick, {
              size: FONT_SIZE,
              height: 0,
              curveSegments: 0,

              font: "droid sans",
              weight: "bold"
            });
        text3d.computeBoundingBox();

        this.text = new THREE.Mesh(text3d, materials[this.type]);

        this.nickOffsetX = NICK_OFFSET["x"](text3d.boundingBox.max.x);

        this.text.position.x = this.dart.position.x+this.nickOffsetX;
        this.text.position.y = this.dart.position.y+NICK_OFFSET["y"];
        this.text.position.z = this.dart.position.z+NICK_OFFSET["z"];
        this.field.addObject(this.text);
      },

      getKey: function() {
        return this.key;
      },
      getDart: function() {
        return this.dart;
      },
      getDinamics: function() {
        return this.dinamics;
      },
      getNick: function() {
        return this.nick;
      },
      getStatus: function() {
        return this.status;
      },
      isFlying: function() {
        return this.dinamics.z != 0;
      },
      isPlanted: function() {
        return this.planted;
      },
      getPlantedDelta: function() {
        if (this.plantedTime) {
          return new Date().getTime() - this.plantedTime;
        }
        return 0;
      },

      attachCamera: function(attach) {
        this.camera = attach;
        this.field.enableOrbit(!attach);
      },

      //Rotation

      setRotation: function(x,y,z) {
        this.setRot("x",x);
        this.setRot("y",y);
        this.setRot("z",z);
      },

      setRot: function(axis,val) {
        this.dart.rotation[axis] = val;
      },

      //speed

      setSpeed: function(vx,vy,vz) {
        this.dinamics.x = vx;
        this.dinamics.y = vy;

        var currZ = this.dinamics.z;
        this.dinamics.z = vz;

        if (vz !== 0) {
          this.doSound(throwSound);

          this.timestamp = new Date().getTime();
          this.fixStartPosition();
          this.calculate();

        } else if (currZ != 0) {
          this.doSound(hitSound);
        }
      },

      //Sound

      doSound: function(sound) {
        if (sound && this.audio) {
          sound.play();
        }
      },

      //Position

      /**
       * @private
       */
      fixStartPosition: function(x,y,z) {
        this.startPos.x = this.dart.position.x;
        this.startPos.y = this.dart.position.y;
        this.startPos.z = this.dart.position.z;
      },

      setPosition: function(x,y,z) {
        this.setPos("x",x);
        this.setPos("y",y);
        this.setPos("z",z);

        if (this.camera && this.isFlying()) {
          this.repositionCamera();
        }
      },

      repositionCamera: function() {
        var x = this.dart.position.x;
        var y = this.dart.position.y;
        var z = this.dart.position.z;

        var px = x+CAMERA_OFFSET.x;
        var py = y+CAMERA_OFFSET.y;
        var pz = z+CAMERA_OFFSET.z;

        this.field.moveCameraToward(px,py,pz);
        this.field.pointCamera(x,y,z);
      },

      /**
       * @private
       */
      setPos: function(axis,value) {
        if (value ==  this.dart.position[axis]) {
          return;
        }

        this.dart.position[axis] = value;
        if (this.text) {
          if (axis == "x") {
            this.text.position[axis] = value+this.nickOffsetX;
          } else if (axis == "y" && value < SWITCH_NICK_POSITION){
            this.text.position[axis] = value-NICK_OFFSET[axis];
          } else {
            this.text.position[axis] = value+NICK_OFFSET[axis];
          }
        }

        this.field.render();
      },

      //calculus

      /**
       * @private
       */
      getFinalTimeIfOverflow: function(axis,value) {

        if (value > Constants.MAX_SIZE[axis] || value < -Constants.MAX_SIZE[axis]) {
          var endValue =  value > Constants.MAX_SIZE[axis] ? Constants.MAX_SIZE[axis] : -Constants.MAX_SIZE[axis];

          if (axis == "y") {
            return this.calculateTimestampY(endValue);
          } else {
            return this.calculateTimestamp(axis,endValue);
          }
        }

        return null;
      },

      /**
       * @private
       */
      calculateTimestamp: function(axis,value) {
        return Math.abs((value-this.startPos[axis])/this.dinamics[axis]);
      },

      /**
       * @private
       */
      calculateAxisPos: function(axis,tNow) {
        return this.startPos[axis] + this.dinamics[axis]*tNow;
      },

      /**
       * @private
       */
      calculateYPosition: function(tNow) {
        //s = v*t + (1/2)at^2
        var units = Constants.HALF_ACCELERATION*Math.pow(tNow,2);
        return this.calculateAxisPos("y",tNow) - units;
      },

      /**
       * @private
       */
      calculateTimestampY: function(value) {

        //s =  v*t + (1/2)at^2

        var c = -(value-this.startPos["y"]);
        var a = -Constants.HALF_ACCELERATION;
        var b = this.dinamics["y"];

        var d = Math.pow(b,2) - 4*a*c;

        if (c<0) {
          return (-b + Math.sqrt(d))/(2*a);
        } else if(c>0) {
          return (-b - Math.sqrt(d))/(2*a);
        }
        return 0;

      },

      calculate: function() {
        if (!this.isFlying()) {
          return;
        }

        var tNow = new Date().getTime() - this.timestamp;

        var x = this.calculateAxisPos("x",tNow);
        var y = this.calculateYPosition(tNow);
        var z = this.calculateAxisPos("z",tNow);

        var isLanded = this.calculateLanding(x,y,z);
        if (!isLanded) {
          this.setPosition(x,y,z);
          this.calculateRotation(x,y,z,tNow);
        } else if(this.camera) {
          this.field.enableOrbit(true);
        }

      },

      calculateRotation: function(x,y,z,tNow) {
        var currentSpeedY = this.dinamics.y + -Constants.ACCELERATION*tNow;

        var x = this.getRotationRadians(currentSpeedY,this.dinamics.z) + Math.PI/2;
        var y = this.getRotationRadians(this.dinamics.x,this.dinamics.z);

        this.setRotation(x,0,y);
      },

      getRotationRadians: function(v1,v2) {
        var up = v1 >= 0;
        var v1IsBigger = Math.abs(v1) > Math.abs(v2);
        var rot = Math.atan(v1IsBigger ? v1/v2 : v2/v1);

        if (v1IsBigger) {
          return -rot;
        } else if (up) {
          return rot + Math.PI/2;
        } else {
          return rot - Math.PI/2;
        }

      },

      calculateLanding: function(x,y,z) {
        var endXt = this.getFinalTimeIfOverflow("x",x);
        var endYt = this.getFinalTimeIfOverflow("y",y);
        var endZt = this.getFinalTimeIfOverflow("z",z);

        if (endXt !== null || endYt !== null ||  endZt !== null) {
          var tEnd = endXt;
          tEnd = tEnd === null || endYt !== null && endYt < tEnd ? endYt : tEnd;
          tEnd = tEnd === null || endZt !== null && endZt < tEnd ? endZt : tEnd;
          x = this.calculateAxisPos("x",tEnd);
          y = this.calculateYPosition(tEnd);
          z = this.calculateAxisPos("z",tEnd);

          this.setPosition(x,y,z);
          this.calculateRotation(x,y,z,tEnd);
          this.setSpeed(0,0,0);

          /*
          console.log("Start "+this.startPos.x+"|"+this.startPos.y+"|"+this.startPos.z);
          console.log("Speed "+this.dinamics.x+"|"+this.dinamics.y+"|"+this.dinamics.z);
          console.log(tEnd);
          console.log("End "+x+"|"+y+"|"+z);
          */

          this.planted = true;
          this.plantedTime = new Date().getTime();

          return true;
        }
        return false;
      },

      reset: function() {
        this.setPosition(0,Constants.INITIAL_CAMERA_POS_Y,Constants.MAX_SIZE.z);
        this.setRotation(Math.PI/2,0,0);
        this.planted = false;
        this.plantedTime = null;
        if (this.camera) {
          this.attachCamera(false);
          this.field.resetCamera();
        }

      }
  };

  return Dart;
});<|endoftext|>


<commit_before>/**
 * 创建对象（精灵）池
 * @param {Int} 对象池大小
 * @return {Object} 对象（精灵）池
 */
function ObjectPool() {
  let objectPool = [];
  return {
    // 初始化对象池
    init(size = 100) {
      while (size--) {
        objectPool.push({});
      }
      return objectPool;
    },
    // 创建新的对象
    create() {
      if (objectPool.length) {
        return objectPool.shift();
      } else {
        objectPool.push({});
      }
    },
    // 销毁整个对象池
    destory() {
      objectPool = [];
    },
    // 回收一个对象
    recover(obj) {
      objectPool.push(obj);
    },
    // 获得大小
    size() {
      return objectPool.length();
    }
  }
}

/**
 * 生成一个 15 * 15 的二维数组
 */
function makeMatrix() {
    // 初始化矩阵
  let matrix = [];
  let counter = 15;
  while (counter--) {
    matrix.push((() => {
      let arr = [];
      arr.length = 15;
      return arr;
    })());
  }
  return matrix;
}
/**
 * 将所有棋子的位置，转换成二维数组
 * @param {Array} list
 */

function toMatrix(list) {
  // console.log(list);
  // 生成一个空白的二维数组
  let matrix = makeMatrix();
  // 将对象填充进去
  list.forEach((onePiece) => {
    matrix[onePiece.$view.$model.a][onePiece.$view.$model.b] = onePiece;
  });
  // console.log(matrix);
  return matrix;
}

/**
 * 横向是否有连续五颗棋子
 * @param {Array} matrix
 * @param {Object} lastPiece
 * @return {Boolean} 是否存在胜利
 */
function isVerticalSuccess(matrix, lastPiece) {
  const horizon = lastPiece.$view.$model.b;
  let black = 0;
  let white = 0;
  let left = lastPiece.$view.$model.a - 5;
  let right = lastPiece.$view.$model.a + 5;
  left < 0 ? left = 0 : left = left;
  right > 14 ? right = 14 : right = right;
  while (left < right - 4) {
    if (matrix[left][horizon]) {
      if (matrix[left][horizon].$view.$model.type === 'black') {
        white = 0;
        black++;
      } else if (matrix[left][horizon].$view.$model.type === 'white') {
        black = 0;
        white++;
      }
    } else {
      white = 0;
      black = 0;
    }
    // 往右移动
    left++;
  }
  return white >= 5 || black >= 5;
}

/**
 * 纵向是否有连续五颗棋子
 * @param {Array} matrix
 * @param {Object} lastPiece
 * @return {Boolean} 是否存在胜利
 */
function isHorizontalSuccess(matrix, lastPiece) {
  const vertical = lastPiece.$view.$model.a;
  let black = 0;
  let white = 0;
  let up = lastPiece.$view.$model.b - 5;
  let down = lastPiece.$view.$model.b + 5;
  up < 0 ? up = 0 : up = up;
  down > 14 ? down = 14 : down = down;
  while (up < down - 4) {
    if (matrix[vertical][up]) {
      if (matrix[vertical][up].$view.$model.type === 'black') {
        white = 0;
        black++;
      } else if (matrix[vertical][up].$view.$model.type === 'white') {
        black = 0;
        white++;
      }
    } else {
      white = 0;
      black = 0;
    }
    // 往右移动
    up++;
  }
  return white >= 5 || black >= 5;
}

/**
 * 左上到右下是否有连续五颗棋子
 * @param {Array} matrix
 * @param {Object} lastPiece
 * @return {Boolean} 是否存在胜利
 */
function isLeftUpToRightBottomSuccess(matrix, lastPiece) {
  const isABigerThanB = lastPiece.$view.$model.a > lastPiece.$view.$model.b;
  let black = 0;
  let white = 0;
  let xMin = lastPiece.$view.$model.a - 5;
  let xMax = lastPiece.$view.$model.a + 5;
  let yMin = lastPiece.$view.$model.b - 5;
  let yMax = lastPiece.$view.$model.b + 5;
  console.log('a', lastPiece.$view.$model.a, 'b', lastPiece.$view.$model.b);
  while (xMin < 0 || yMin < 0) {
    xMin++;
    yMin++;
  }
  while (xMax > 14 || yMax > 14) {
    xMax--;
    yMax--;
  }
  while (xMin < xMax && yMin < yMax) {
    console.log('xMin', xMin, 'yMin', yMin, 'xMax', xMax, 'yMax', yMax, 'black', black, 'white', white);
    if (matrix[xMin][yMin]) {
      if (matrix[xMin][yMin].$view.$model.type === 'black') {
        white = 0;
        black++;
        if (black >= 5) return true;
      } else if (matrix[xMin][yMin].$view.$model.type === 'white') {
        black = 0;
        white++;
        if (white >= 5) return true;
      }
    } else {
      white = 0;
      black = 0;
    }
    console.log('xMin', xMin, 'yMin', yMin, 'xMax', xMax, 'yMax', yMax, 'black', black, 'white', white);

    xMin++;
    yMin++;
  };
  return white >= 5 || black >= 5;
}

/**
 * 左上到右下是否有连续五颗棋子
 * @param {Array} matrix
 * @param {Object} lastPiece
 * @return {Boolean} 是否存在胜利
 */
function isRightUpToLeftBottomSuccess(matrix, lastPiece) {
  let sum = 0;
  return false;
}

/**
 * 判断当前是否胜利
 * @param {Object} gobangData 棋盘数据
 * @return {Boolean} 是否胜利
 */
function judgeSuccess(pieceList) {
  const list = pieceList.$view.$model.pieceList;
  if (isArray(list) && list.length >= 1) {
    // 生成棋盘矩阵
    const matrix = toMatrix(list);
    // 最后一枚棋子位置
    const lastPiece = list[list.length - 1];
    // 横向是否有连续五个棋子
    const vs = isVerticalSuccess(matrix, lastPiece);
    // 纵向是否有连续五颗棋子
    const hs = isHorizontalSuccess(matrix, lastPiece);
    // 左上到右下是否有连续五颗棋子
    const urrbs = isLeftUpToRightBottomSuccess(matrix, lastPiece);
    // 左上到右下是否有连续五颗棋子
    const rulbs = isRightUpToLeftBottomSuccess(matrix, lastPiece);
    if (vs || hs || urrbs || rulbs) {
      return true;
    }
  }
  return false;
}

/**
 * 封装requestAnimationFrame
 */
const run = (function () {
  return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function (callback) {
      window.setTimeout(callback, 1000 / 60);
    };
})();

/**
 * 闭包缓存纯函数的结果
 * @param {Function} fn 需要缓存的函数
 * @return {Function} 修改后的函数
 */
function cached(fn) {
  const cache = Object.create(null);
  return ((...argv) => {
    const key = Array.prototype.join.call(argv, '_');
    const hit = cache[key];
    // eslint-disable-next-line
    return hit || (cache[key] = Reflect.apply(fn, null, argv));
  });
}

/**
 * 格式工厂，生成判断的函数
 * @param {String} type
 * @return {Function} 判断函数
 */
function typeFactory(type) {
  return (obj) => {
    return Object.prototype.toString.call(obj) === `[object ${type}]`;
  }
}

/**
 * 判断数据是否为Array
 * @param {*} obj
 * @return {Boolean} 是否为Array
 */
const isArray = typeFactory('Array');

/**
 * 判断数据是否为Object
 * @param {*} obj
 * @return {Boolean} 是否为Object
 */
const isObject = typeFactory('Object');

/**
 * 判断数据是否为Function
 * @param {*} obj
 * @return {Boolean} 是否为Function
 */
const isFunction = typeFactory('Function');

/**
 * 判断数据是否为Number
 * @param {*} obj
 * @return {Boolean} 是否为Number
 */
const isNumber = typeFactory('Number');

/**
 * 判断数据是否为RegExp
 * @param {*} obj
 * @return {Boolean} 是否为RegExp
 */
const isRegExp = typeFactory('RegExp');

/**
 * 判断数据是否为String
 * @param {*} obj
 * @return {Boolean} 是否为String
 */
const isString = typeFactory('String');

/**
 * 判断数据是否为Boolean
 * @param {*} obj
 * @return {Boolean} 是否为Boolean
 */
const isBoolean = typeFactory('Boolean');

/**
 * 判断数据是否为Undefined
 * @param {*} obj
 * @return {Boolean} 是否为Undefined
 */
const isUndefined = typeFactory('Undefined');

/**
 * 判断数据是否为Null
 * @param {*} obj
 * @return {Boolean} 是否为Null
 */
const isNull = typeFactory('Null');

/**
 * 判断数据是否为Symbol
 * @param {*} obj
 * @return {Boolean} 是否为Symbol
 */
const isSymbol = typeFactory('Symbol');

/**
 * 判断数据是否为Map
 * @param {*} obj
 * @return {Boolean} 是否为Map
 */
const isMap = typeFactory('Map');

/**
 * 判断数据是否为Set
 * @param {*} obj
 * @return {Boolean} 是否为Set
 */
const isSet = typeFactory('Set');

/**
 * 判断数据是否为WeakMap
 * @param {*} obj
 * @return {Boolean} 是否为WeakMap
 */
const isWeakMap = typeFactory('WeakMap');

/**
 * 判断数据是否为WeakSet
 * @param {*} obj
 * @return {Boolean} 是否为WeakSet
 */
const isWeakSet = typeFactory('WeakSet');

/**
 * 通过点击的位置，判断落子的位置
 * @param {Object} clickPoint {x,y}
 * @param {Int} areaWidth 棋盘的Width
 * @param {Int} areaHeight 棋盘的Height
 */
const getPieceLocation = cached((x, y, areaWidth, areaHeight, gap) => {
  const widthStep = (areaWidth - gap * 2) / 15
  const heightStep = (areaHeight - gap * 2) / 15
  console.log(widthStep, heightStep);
  const a = Math.floor((x - gap) / widthStep);
  const b = Math.floor((y - gap) / heightStep);
  console.log({ x, y, a, b});
  return {
    x: (a + 0.5 ) * widthStep + gap,
    y: (b + 0.5 ) * heightStep + gap,
    a,
    b
  };
});

/**
 * 棋子是否已经存在棋盘中
 * @param {Object} pieceList
 * @param {Object} point
 */
function notInChessboard(pieceList, point) {
  const list = pieceList.$view.$model.pieceList;
  if (isArray(list)) {
    let isNotInChessboard = true;
    list.forEach((onePiece) => {
      if (onePiece.$view.$model.a === point.a && onePiece.$view.$model.b === point.b) {
        isNotInChessboard = false;
      }
    });
    return isNotInChessboard;
  }
  throw 'pieceList不是数组';
}

export {
  run,
  cached,
  getPieceLocation,
  notInChessboard,
  ObjectPool,
  judgeSuccess,
  typeFactory,
  isArray,
  isObject,
  isFunction,
  isNumber,
  isRegExp,
  isString,
  isBoolean,
  isUndefined,
  isNull,
  isSymbol,
  isMap,
  isSet,
  isWeakMap,
  isWeakSet,
};
<commit_msg>增加右上到左下的判断<commit_after>/**
 * 创建对象（精灵）池
 * @param {Int} 对象池大小
 * @return {Object} 对象（精灵）池
 */
function ObjectPool() {
  let objectPool = [];
  return {
    // 初始化对象池
    init(size = 100) {
      while (size--) {
        objectPool.push({});
      }
      return objectPool;
    },
    // 创建新的对象
    create() {
      if (objectPool.length) {
        return objectPool.shift();
      } else {
        objectPool.push({});
      }
    },
    // 销毁整个对象池
    destory() {
      objectPool = [];
    },
    // 回收一个对象
    recover(obj) {
      objectPool.push(obj);
    },
    // 获得大小
    size() {
      return objectPool.length();
    }
  }
}

/**
 * 生成一个 15 * 15 的二维数组
 */
function makeMatrix() {
    // 初始化矩阵
  let matrix = [];
  let counter = 15;
  while (counter--) {
    matrix.push((() => {
      let arr = [];
      arr.length = 15;
      return arr;
    })());
  }
  return matrix;
}
/**
 * 将所有棋子的位置，转换成二维数组
 * @param {Array} list
 */

function toMatrix(list) {
  // 生成一个空白的二维数组
  let matrix = makeMatrix();
  // 将对象填充进去
  list.forEach((onePiece) => {
    matrix[onePiece.$view.$model.a][onePiece.$view.$model.b] = onePiece;
  });
  return matrix;
}

/**
 * 横向是否有连续五颗棋子
 * @param {Array} matrix
 * @param {Object} lastPiece
 * @return {Boolean} 是否存在胜利
 */
function isVerticalSuccess(matrix, lastPiece) {
  const horizon = lastPiece.$view.$model.b;
  let black = 0;
  let white = 0;
  let left = lastPiece.$view.$model.a - 5;
  let right = lastPiece.$view.$model.a + 5;
  left < 0 ? left = 0 : left = left;
  right > 14 ? right = 14 : right = right;
  while (left < right - 4) {
    if (matrix[left][horizon]) {
      if (matrix[left][horizon].$view.$model.type === 'black') {
        white = 0;
        black++;
      } else if (matrix[left][horizon].$view.$model.type === 'white') {
        black = 0;
        white++;
      }
    } else {
      white = 0;
      black = 0;
    }
    // 往右移动
    left++;
  }
  return white >= 5 || black >= 5;
}

/**
 * 纵向是否有连续五颗棋子
 * @param {Array} matrix
 * @param {Object} lastPiece
 * @return {Boolean} 是否存在胜利
 */
function isHorizontalSuccess(matrix, lastPiece) {
  const vertical = lastPiece.$view.$model.a;
  let black = 0;
  let white = 0;
  let up = lastPiece.$view.$model.b - 5;
  let down = lastPiece.$view.$model.b + 5;
  up < 0 ? up = 0 : up = up;
  down > 14 ? down = 14 : down = down;
  while (up < down - 4) {
    if (matrix[vertical][up]) {
      if (matrix[vertical][up].$view.$model.type === 'black') {
        white = 0;
        black++;
      } else if (matrix[vertical][up].$view.$model.type === 'white') {
        black = 0;
        white++;
      }
    } else {
      white = 0;
      black = 0;
    }
    // 往右移动
    up++;
  }
  return white >= 5 || black >= 5;
}

/**
 * 左上到右下是否有连续五颗棋子
 * @param {Array} matrix
 * @param {Object} lastPiece
 * @return {Boolean} 是否存在胜利
 */
function isLeftUpToRightBottomSuccess(matrix, lastPiece) {
  let black = 0;
  let white = 0;
  let xMin = lastPiece.$view.$model.a - 5;
  let xMax = lastPiece.$view.$model.a + 5;
  let yMin = lastPiece.$view.$model.b - 5;
  let yMax = lastPiece.$view.$model.b + 5;
  while (xMin < 0 || yMin < 0) {
    xMin++;
    yMin++;
  }
  while (xMax > 14 || yMax > 14) {
    xMax--;
    yMax--;
  }
  while (xMin < xMax && yMin < yMax) {
    if (matrix[xMin][yMin]) {
      if (matrix[xMin][yMin].$view.$model.type === 'black') {
        white = 0;
        black++;
        if (black >= 5) return true;
      } else if (matrix[xMin][yMin].$view.$model.type === 'white') {
        black = 0;
        white++;
        if (white >= 5) return true;
      }
    } else {
      white = 0;
      black = 0;
    }

    xMin++;
    yMin++;
  };
  return white >= 5 || black >= 5;
}

/**
 * 右到左下是否有连续五颗棋子
 * @param {Array} matrix
 * @param {Object} lastPiece
 * @return {Boolean} 是否存在胜利
 */
function isRightUpToLeftBottomSuccess(matrix, lastPiece) {
  let black = 0;
  let white = 0;
  let xMin = lastPiece.$view.$model.a - 5;
  let xMax = lastPiece.$view.$model.a + 5;
  let yMin = lastPiece.$view.$model.b - 5;
  let yMax = lastPiece.$view.$model.b + 5;
  while (xMin < 0 || yMin < 0) {
    xMin++;
    yMin++;
  }
  while (xMax > 14 || yMax > 14) {
    xMax--;
    yMax--;
  }
  while (xMin <= xMax && yMin <= yMax) {
    if (matrix[xMax][yMin]) {
      if (matrix[xMax][yMin].$view.$model.type === 'black') {
        white = 0;
        black++;
        if (black >= 5) return true;
      } else if (matrix[xMax][yMin].$view.$model.type === 'white') {
        black = 0;
        white++;
        if (white >= 5) return true;
      }
    } else {
      white = 0;
      black = 0;
    }

    xMax--;
    yMin++;
  };
  return white >= 5 || black >= 5;
}

/**
 * 判断当前是否胜利
 * @param {Object} gobangData 棋盘数据
 * @return {Boolean} 是否胜利
 */
function judgeSuccess(pieceList) {
  const list = pieceList.$view.$model.pieceList;
  if (isArray(list) && list.length >= 1) {
    // 生成棋盘矩阵
    const matrix = toMatrix(list);
    // 最后一枚棋子位置
    const lastPiece = list[list.length - 1];
    // 横向是否有连续五个棋子
    const vs = isVerticalSuccess(matrix, lastPiece);
    // 纵向是否有连续五颗棋子
    const hs = isHorizontalSuccess(matrix, lastPiece);
    // 左上到右下是否有连续五颗棋子
    const urrbs = isLeftUpToRightBottomSuccess(matrix, lastPiece);
    // 左上到右下是否有连续五颗棋子
    const rulbs = isRightUpToLeftBottomSuccess(matrix, lastPiece);
    if (vs || hs || urrbs || rulbs) {
      return true;
    }
  }
  return false;
}

/**
 * 封装requestAnimationFrame
 */
const run = (function () {
  return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function (callback) {
      window.setTimeout(callback, 1000 / 60);
    };
})();

/**
 * 闭包缓存纯函数的结果
 * @param {Function} fn 需要缓存的函数
 * @return {Function} 修改后的函数
 */
function cached(fn) {
  const cache = Object.create(null);
  return ((...argv) => {
    const key = Array.prototype.join.call(argv, '_');
    const hit = cache[key];
    // eslint-disable-next-line
    return hit || (cache[key] = Reflect.apply(fn, null, argv));
  });
}

/**
 * 格式工厂，生成判断的函数
 * @param {String} type
 * @return {Function} 判断函数
 */
function typeFactory(type) {
  return (obj) => {
    return Object.prototype.toString.call(obj) === `[object ${type}]`;
  }
}

/**
 * 判断数据是否为Array
 * @param {*} obj
 * @return {Boolean} 是否为Array
 */
const isArray = typeFactory('Array');

/**
 * 判断数据是否为Object
 * @param {*} obj
 * @return {Boolean} 是否为Object
 */
const isObject = typeFactory('Object');

/**
 * 判断数据是否为Function
 * @param {*} obj
 * @return {Boolean} 是否为Function
 */
const isFunction = typeFactory('Function');

/**
 * 判断数据是否为Number
 * @param {*} obj
 * @return {Boolean} 是否为Number
 */
const isNumber = typeFactory('Number');

/**
 * 判断数据是否为RegExp
 * @param {*} obj
 * @return {Boolean} 是否为RegExp
 */
const isRegExp = typeFactory('RegExp');

/**
 * 判断数据是否为String
 * @param {*} obj
 * @return {Boolean} 是否为String
 */
const isString = typeFactory('String');

/**
 * 判断数据是否为Boolean
 * @param {*} obj
 * @return {Boolean} 是否为Boolean
 */
const isBoolean = typeFactory('Boolean');

/**
 * 判断数据是否为Undefined
 * @param {*} obj
 * @return {Boolean} 是否为Undefined
 */
const isUndefined = typeFactory('Undefined');

/**
 * 判断数据是否为Null
 * @param {*} obj
 * @return {Boolean} 是否为Null
 */
const isNull = typeFactory('Null');

/**
 * 判断数据是否为Symbol
 * @param {*} obj
 * @return {Boolean} 是否为Symbol
 */
const isSymbol = typeFactory('Symbol');

/**
 * 判断数据是否为Map
 * @param {*} obj
 * @return {Boolean} 是否为Map
 */
const isMap = typeFactory('Map');

/**
 * 判断数据是否为Set
 * @param {*} obj
 * @return {Boolean} 是否为Set
 */
const isSet = typeFactory('Set');

/**
 * 判断数据是否为WeakMap
 * @param {*} obj
 * @return {Boolean} 是否为WeakMap
 */
const isWeakMap = typeFactory('WeakMap');

/**
 * 判断数据是否为WeakSet
 * @param {*} obj
 * @return {Boolean} 是否为WeakSet
 */
const isWeakSet = typeFactory('WeakSet');

/**
 * 通过点击的位置，判断落子的位置
 * @param {Object} clickPoint {x,y}
 * @param {Int} areaWidth 棋盘的Width
 * @param {Int} areaHeight 棋盘的Height
 */
const getPieceLocation = cached((x, y, areaWidth, areaHeight, gap) => {
  const widthStep = (areaWidth - gap * 2) / 15
  const heightStep = (areaHeight - gap * 2) / 15
  const a = Math.floor((x - gap) / widthStep);
  const b = Math.floor((y - gap) / heightStep);
  return {
    x: (a + 0.5 ) * widthStep + gap,
    y: (b + 0.5 ) * heightStep + gap,
    a,
    b
  };
});

/**
 * 棋子是否已经存在棋盘中
 * @param {Object} pieceList
 * @param {Object} point
 */
function notInChessboard(pieceList, point) {
  const list = pieceList.$view.$model.pieceList;
  if (isArray(list)) {
    let isNotInChessboard = true;
    list.forEach((onePiece) => {
      if (onePiece.$view.$model.a === point.a && onePiece.$view.$model.b === point.b) {
        isNotInChessboard = false;
      }
    });
    return isNotInChessboard;
  }
  throw 'pieceList不是数组';
}

export {
  run,
  cached,
  getPieceLocation,
  notInChessboard,
  ObjectPool,
  judgeSuccess,
  typeFactory,
  isArray,
  isObject,
  isFunction,
  isNumber,
  isRegExp,
  isString,
  isBoolean,
  isUndefined,
  isNull,
  isSymbol,
  isMap,
  isSet,
  isWeakMap,
  isWeakSet,
};
<|endoftext|>


<commit_before>//----------------------------------------------------------------------
//
// This source file is part of the Meta:Magical project.
//
// Copyright (C) 2015-2016 Quildreen Motta.
// Licensed under the MIT licence.
//
// See LICENCE for licence information.
// See CONTRIBUTORS for the list of contributors to the project.
//
//----------------------------------------------------------------------

const yaml = require('js-yaml');

const COMPUTED = true; /* computed properties */

function isDocComment(comment) {
  return comment.type === 'CommentBlock'
  &&     /^~\s*$/m.test(comment.value);
}

function last(xs) {
  return xs[xs.length - 1];
}

function getDocComment(node) {
  const comment = last(node.leadingComments || []);

  return comment && isDocComment(comment) ?   comment.value
  :      /* otherwise */                      null;
}

function parseDoc(doc) {
  const parts = doc.replace(/^~\s*$/m, '')
                 .replace(/^\s*\*\s/gm, '')
                 .split(/\n\s*-{3,}\s*\n/);

  let meta = yaml.safeLoad(parts[1] || '') || {};
  meta.documentation = parts[0] || '';
  return meta;
}

function pairs(object) {
  return Object.keys(object).map(key => [key, object[key]]);
}

function raise(error) {
  throw error;
}

function isString(value) {
  return typeof value === 'string';
}

function isBoolean(value) {
  return typeof value === 'boolean';
}

function isNumber(value) {
  return typeof value === 'number';
}

function isObject(value) {
  return Object(value) === value;
}

module.exports = function({ types: t }) {
  function metaSymbol() {
    return t.callExpression(
      t.memberExpression(
        t.identifier('Symbol'),
        t.identifier('for')
      ),
      [t.stringLiteral('@@meta:magical')]
    );
  }

  function objectToExpression(object) {
    return t.objectExpression(
      pairs(object).map(pairToProperty)
    );
  }

  function pairToProperty([key, value]) {
    return t.objectProperty(
      t.stringLiteral(key),
      valueToLiteral(value)
    );
  }

  function valueToLiteral(value) {
    return Array.isArray(value) ?  t.arrayExpression(value.map(valueToLiteral))
    :      isString(value)      ?  t.stringLiteral(value)
    :      isBoolean(value)     ?  t.booleanLiteral(value)
    :      isNumber(value)      ?  t.numericLiteral(value)
    :      isObject(value)      ?  objectToExpression(value)
    :      /* otherwise */         raise(new TypeError('Type of property not supported: ' + value));
  }

  function assignMeta(path, node, binding) {
    const doc = getDocComment(node);
    if (doc) {
      const meta = parseDoc(doc);
      path.insertAfter(
        t.expressionStatement(
          t.assignmentExpression(
            '=',
            t.memberExpression(binding, metaSymbol(), COMPUTED),
            objectToExpression(meta)
          )
        )
      );
    }
  }

  return {
    visitor: {
      FunctionDeclaration(path, state) {
        assignMeta(path, path.node, t.identifier(path.node.id.name));
      },

      VariableDeclaration(path, state) {
        if (path.node.declarations.length === 1) {
          const declarator = path.node.declarations[0];
          assignMeta(path, path.node, t.identifier(declarator.id.name));
        }
      }
    }
  };
};
<commit_msg>feat: Supports annotations on assignments & object properties<commit_after>//----------------------------------------------------------------------
//
// This source file is part of the Meta:Magical project.
//
// Copyright (C) 2015-2016 Quildreen Motta.
// Licensed under the MIT licence.
//
// See LICENCE for licence information.
// See CONTRIBUTORS for the list of contributors to the project.
//
//----------------------------------------------------------------------

const yaml = require('js-yaml');

const COMPUTED = true; /* computed properties */

function isDocComment(comment) {
  return comment.type === 'CommentBlock'
  &&     /^~\s*$/m.test(comment.value);
}

function last(xs) {
  return xs[xs.length - 1];
}

function getDocComment(node) {
  const comment = last(node.leadingComments || []);

  return comment && isDocComment(comment) ?   comment.value
  :      /* otherwise */                      null;
}

function parseDoc(doc) {
  const parts = doc.replace(/^~\s*$/m, '')
                 .replace(/^\s*\*\s/gm, '')
                 .split(/\n\s*-{3,}\s*\n/);

  let meta = yaml.safeLoad(parts[1] || '') || {};
  meta.documentation = parts[0] || '';
  return meta;
}

function pairs(object) {
  return Object.keys(object).map(key => [key, object[key]]);
}

function raise(error) {
  throw error;
}

function isString(value) {
  return typeof value === 'string';
}

function isBoolean(value) {
  return typeof value === 'boolean';
}

function isNumber(value) {
  return typeof value === 'number';
}

function isObject(value) {
  return Object(value) === value;
}

module.exports = function({ types: t }) {
  function metaSymbol() {
    return t.callExpression(
      t.memberExpression(
        t.identifier('Symbol'),
        t.identifier('for')
      ),
      [t.stringLiteral('@@meta:magical')]
    );
  }

  function inferName(id, computed) {
    return t.isIdentifier(id) && !computed?  { name: id.name }
    :      t.isMemberExpression(id)?         inferName(id.property, id.computed)
    :      /* otherwise */                   {};
  }

  function objectToExpression(object) {
    return t.objectExpression(
      pairs(object).map(pairToProperty)
    );
  }

  function pairToProperty([key, value]) {
    return t.objectProperty(
      t.stringLiteral(key),
      valueToLiteral(value)
    );
  }

  function valueToLiteral(value) {
    return Array.isArray(value) ?  t.arrayExpression(value.map(valueToLiteral))
    :      isString(value)      ?  t.stringLiteral(value)
    :      isBoolean(value)     ?  t.booleanLiteral(value)
    :      isNumber(value)      ?  t.numericLiteral(value)
    :      isObject(value)      ?  objectToExpression(value)
    :      /* otherwise */         raise(new TypeError('Type of property not supported: ' + value));
  }

  function setMeta(lvalue, meta) {
    return t.assignmentExpression(
      '=',
      t.memberExpression(lvalue, metaSymbol(), COMPUTED),
      objectToExpression(meta)
    );
  }

  function assignMeta(binding, doc, path, additionalMeta) { // path, node, binding) {
    if (doc) {
      const meta = Object.assign(parseDoc(doc), additionalMeta || {});
      path.insertAfter(t.expressionStatement(setMeta(binding, meta)));
    }
  }

  function wrapRValue(expr, meta, path, bindingName) {
    const id = path.scope.generateUidIdentifier(bindingName || "ref");
    path.scope.push({ id });

    return t.sequenceExpression([
      t.assignmentExpression('=', id, expr),
      setMeta(id, meta),
      id
    ]);
  }

  const visitor = {
    FunctionDeclaration(path, state) {
      assignMeta(path.node.id, getDocComment(path.node), path, {
        name: path.node.id.name
      });
    },

    VariableDeclaration(path, state) {
      if (path.node.declarations.length === 1) {
        const declarator = path.node.declarations[0];
        assignMeta(declarator.id, getDocComment(path.node), path, inferName(declarator.id));
      }
    },

    ExpressionStatement(path, state) {
      const expr = path.node.expression;
      if (t.isAssignmentExpression(expr)) {
        const doc = getDocComment(path.node);
        if (doc) {
          const inferredMeta = inferName(expr.left);
          const meta = Object.assign(parseDoc(doc), inferredMeta);

          expr.right = wrapRValue(expr.right, meta, path, inferredMeta.name);
        }
      }
    },

    ObjectProperty(path, state) {
      const doc = getDocComment(path.node);
      if (doc) {
        const inferredMeta = inferName(path.node.key, path.node.computed);
        const meta = Object.assign(parseDoc(doc), inferredMeta);

        path.node.value = wrapRValue(path.node.value, meta, path, inferredMeta.name);
      }
    },

    ObjectMethod(path, state) {
      const inferredMeta = inferName(path.node.key, path.node.computed);
      const fn = t.functionExpression(
        path.node.computed? null : path.node.key,
        path.node.params,
        path.node.body,
        path.node.generator,
        path.node.async
      );

      // Babel will invoke ObjectProperty on this newly created node
      // This is a problem :<
      path.replaceWith(
        t.objectProperty(path.node.key, fn, path.node.computed)
      );
    }
  };

  return { visitor };
};
<|endoftext|>


<commit_before>var app = (function ($, L, document) {
    function init() {
        articleList.init();
        projectMarkers.init();
        var map = L.mapbox.map('map', 'sharkins.map-uwias8cf', {maxZoom:12});
        map.on('ready', function() {
            var miniMap = new L.Control.MiniMap(L.mapbox.tileLayer(
                'sharkins.hc52c67l'), {position: 'bottomleft'})
                .addTo(map);
        });

        $(document)
            .hide()
            .ajaxStart(function() {
                $('#ajaxloader').show();
            })
            .ajaxStop(function() {
                $('#ajaxloader').hide();
            });

        $('#showSidebar').click(function(e) {
            $('#sidebar').toggleClass('active');
            e.stopPropagation();
        });

        $(document).on('articleMarkerClick', function(e, id) {
            articleList.scrollToArticle(id);
        });

        $(document).on('articleDeactivated', function(e, id) {
            articleMarkers.deactivateMarker(id);
        });

        $(document).on('articleActivated', function(e, id) {
            articleMarkers.activateMarker(id);
            $('#sidebar').toggleClass('active');
        });

        googleNewsSearch.fetchData().then(processRSSXML).then(function() {
            articleList.sortByDate();
            map.addLayer(articleMarkers.getMarkerLayer());
        });

        $("input[name='radio']").on("change", function () {
            if (this.id == 'tab-articles') {
                map.addLayer(articleMarkers.getMarkerLayer());
                map.removeLayer(projectMarkers.getMarkerLayer());
            }
            else if (this.id == 'tab-projects') {
                map.removeLayer(articleMarkers.getMarkerLayer());
                map.addLayer(projectMarkers.getMarkerLayer());
            }
            else if (this.id == 'tab-earthquakes') {
                map.removeLayer(articleMarkers.getMarkerLayer());
                map.removeLayer(projectMarkers.getMarkerLayer());
            }
        });
    }

    function processRSSXML(xml) {
            $(xml)
                .find('item')
                    .each(function(index) {
                        var xmlitem = $(this);
                        if (xmlitem.children('geo\\:long').length > 0) {
                            var lat = parseFloat(xmlitem
                                                 .children('geo\\:lat').text());
                            var lon = parseFloat(xmlitem
                                                 .children('geo\\:long').text());

                            articleMarkers.addMarker(index, lat, lon);
                            articleList.addListItem(index, xmlitem);
                        }
                     });
    }

    var googleNewsSearch = (function () {
        function buildRSSUrl() {
            function buildQueryString(data) {
                var params = [];
                for (var d in data) {
                    params.push(d + '=' + data[d]);
                }
                return params.join('&');
            }
            var googleUrl = 'https://news.google.com/news/feeds';
            var googleParams = {
                gl: 'ca',
                q: 'peru+mining+protests',
                um: 1,
                ie: 'UTF-8',
                output: 'rss',
                num: 40
            };
            var encodedGoogleUrl = encodeURIComponent(googleUrl + '?' +
                                                      buildQueryString(googleParams));
            var geonamesUrl = 'http://api.geonames.org/rssToGeoRSS';
            var geonamesParams = {
                feedUrl: encodedGoogleUrl,
                username: 'sharkinsgis',
                feedLanguage: 'en', country: 'pe',
                addUngeocodedItems: 'false'
            };
            geonamesUrl = geonamesUrl + '?' + buildQueryString(geonamesParams);
            return geonamesUrl;
        }

        function fetchData() {
            return $.ajax(buildRSSUrl());
        }

        return {
            fetchData: fetchData
        };
    })();

    var projectMarkers = (function () {
        var _list = $('<ul/>', {'id': 'projects'});
        function init() {
            $('#projects').append(_list);
        }

        var projectURL = "http://geocatmin.ingemmet.gob.pe/arcgis/rest/services/SERV_CARTERA_PROYECTOS_MINEROS/MapServer/0";
        var _markers = new L.MarkerClusterGroup(
                {spiderfyDistanceMultiplier:1, showCoverageOnHover:false}
        );

        function addListItem(geojson) {
            var listItem = $('<li/>', {
                html: geojson.properties.EMPRESA,
                'id': geojson.properties.OBJECTID
            });
            _list.append(listItem);
        }

        var clusteredFeatureLayer = new L.esri.clusteredFeatureLayer(
            projectURL, {
                cluster: _markers,
                onEachMarker: addListItem
        });

        function getMarkerLayer() {
            return clusteredFeatureLayer;
        }

        return {
            init: init,
            getMarkerLayer: getMarkerLayer
        };
    })();

    var articleMarkers = (function () {
        var _markerMap = {};
        var _markerList = [];
        var _defaultIcon = new L.Icon.Default();
        var _activeIcon = new L.Icon.Default({iconUrl:
                                             './images/marker-icon-red.png'});
        var _markers = new L.MarkerClusterGroup(
                {spiderfyDistanceMultiplier:1, showCoverageOnHover:false}
        );

        function addMarker(index, lat, lon) {
            var latlng = new L.LatLng(lat, lon);
            var marker = L.marker(latlng, {id: index});
            marker.on('click', function() {
                this.setIcon(_activeIcon);
                $(document).trigger('articleMarkerClick', [this.options.id]);
            });
            _markerList.push(marker);
            _markerMap[index] = marker;
        }

        function getMarkerLayer() {
            var layers = _markers.getLayers();
            if (layers.length === 0) {
                _markers.addLayers(_markerList);
            }
            return _markers;
        }

        function getMarkerList() {
            return _markerList;
        }

        function activateMarker(id) {
            var activeMarker = _markerMap[id];
            _markers.zoomToShowLayer(activeMarker, function(){
                activeMarker.setIcon(_activeIcon);
            });
        }

        function deactivateMarker(id) {
            var previousActiveMarker = _markerMap[id];
            if (previousActiveMarker) {
                previousActiveMarker.setIcon(_defaultIcon);
            }
        }

        return {
            addMarker: addMarker,
            getMarkerLayer: getMarkerLayer,
            getMarkerList: getMarkerList,
            activateMarker: activateMarker,
            deactivateMarker: deactivateMarker
        };
    })();

    var articleList = (function () {
        var _list = $('<ul/>', {'id': 'articlelist'});
        function init() {
            $('#articles').append(_list);
        }

        function addListItem(index, xmlitem) {
            var titleComponents = xmlitem
                                    .children('title')
                                    .text()
                                    .split(' - ');
            var articleTitle = titleComponents[0];
            var articleOrigin = titleComponents[1];
            var pubdate = new Date(xmlitem.children('pubDate').text());
            var articleOriginUrl = xmlitem.children('link').text();
            var articleSpan = $('<span/>', {
                html: articleTitle
            });
            var listItem = $('<li/>', {
                html: articleSpan,
                'id': index,
                'datetime': pubdate
            });
            var itemLink = $('<a/>', {
                html: articleOrigin,
                'href': articleOriginUrl,
                'target': '_blank'
            });
            listItem.on('click', articleClick);
            listItem.append(itemLink);
            _list.append(listItem);
        }

        function sortByDate() {
            var listItems = $('#articlelist li');
            var list = _list;
            if(listItems.length && list.length) {
                listItems.sort(function(a,b) {
                    return new Date($(a).attr('datetime')) -
                        new Date($(b).attr('datetime'));
                }).each(function() {
                    list.prepend(this);
                });
            }
        }

        function deactivatePreviousArticle() {
            var previousActiveLi = $('li.active');
            $(document).trigger('articleDeactivated',
                                [previousActiveLi.attr('id')]);
            previousActiveLi.removeClass('active');
        }

        function scrollToArticle(id) {
            deactivatePreviousArticle();
            var articles = $('#articles');
            var activeLi = $('#articlelist' + ' #' + id);
            activeLi.addClass('active');
            articles.scrollTop(8);
            articles.animate({
                duration: 'slow',
                scrollTop: activeLi.position().top
            });
        }

        function articleClick(e) {
            deactivatePreviousArticle();
            var activeLi = $(this);
            activeLi.addClass('active');
            $(document).trigger('articleActivated', [activeLi.attr('id')]);
        }

        return {
            init: init,
            addListItem: addListItem,
            sortByDate: sortByDate,
            scrollToArticle: scrollToArticle
        };
    })();

    return {
        init: init,
        processRSSXML: processRSSXML,
        articleList: articleList,
        articleMarkers: articleMarkers
    };
})($, L, this.document);
<commit_msg>Refactor for markers<commit_after>var app = (function ($, L, document) {
    function init() {
        articleList.init();
        articleMarkers = new ArticleMarkers();
        projectList.init();
        projectMarkers = new ProjectMarkers();
        var map = L.mapbox.map('map', 'sharkins.map-uwias8cf', {maxZoom:12});
        map.on('ready', function() {
            var miniMap = new L.Control.MiniMap(L.mapbox.tileLayer(
                'sharkins.hc52c67l'), {position: 'bottomleft'})
                .addTo(map);
        });

        $(document)
            .hide()
            .ajaxStart(function() {
                $('#ajaxloader').show();
            })
            .ajaxStop(function() {
                $('#ajaxloader').hide();
            });

        $('#showSidebar').click(function(e) {
            $('#sidebar').toggleClass('active');
            e.stopPropagation();
        });

        $(document).on('articleMarkerClick', function(e, id) {
            articleList.scrollToArticle(id);
        });

        $(document).on('projectMarkerClick', function(e, id) {
        });

        $(document).on('articleDeactivated', function(e, id) {
            articleMarkers.deactivateMarker(id);
        });

        $(document).on('articleActivated', function(e, id) {
            articleMarkers.activateMarker(id);
            $('#sidebar').toggleClass('active');
        });

        googleNewsSearch.fetchData().then(processRSSXML).then(function() {
            articleList.sortByDate();
            map.addLayer(articleMarkers.getMarkerLayer());
        });

        $("input[name='radio']").on("change", function () {
            if (this.id == 'tab-articles') {
                map.addLayer(articleMarkers.getMarkerLayer());
                map.removeLayer(projectMarkers.getMarkerLayer());
            }
            else if (this.id == 'tab-projects') {
                map.removeLayer(articleMarkers.getMarkerLayer());
                map.addLayer(projectMarkers.getMarkerLayer());
            }
            else if (this.id == 'tab-earthquakes') {
                map.removeLayer(articleMarkers.getMarkerLayer());
                map.removeLayer(projectMarkers.getMarkerLayer());
            }
        });
    }

    function processRSSXML(xml) {
            $(xml)
                .find('item')
                    .each(function(index) {
                        var xmlitem = $(this);
                        if (xmlitem.children('geo\\:long').length > 0) {
                            var lat = parseFloat(xmlitem
                                                 .children('geo\\:lat').text());
                            var lon = parseFloat(xmlitem
                                                 .children('geo\\:long').text());

                            articleMarkers.addMarker(index, lat, lon);
                            articleList.addListItem(index, xmlitem);
                        }
                     });
    }

    var googleNewsSearch = (function () {
        function buildRSSUrl() {
            function buildQueryString(data) {
                var params = [];
                for (var d in data) {
                    params.push(d + '=' + data[d]);
                }
                return params.join('&');
            }
            var googleUrl = 'https://news.google.com/news/feeds';
            var googleParams = {
                gl: 'ca',
                q: 'peru+mining',
                um: 1,
                ie: 'UTF-8',
                output: 'rss',
                num: 40
            };
            var encodedGoogleUrl = encodeURIComponent(googleUrl + '?' +
                                                      buildQueryString(googleParams));
            var geonamesUrl = 'http://api.geonames.org/rssToGeoRSS';
            var geonamesParams = {
                feedUrl: encodedGoogleUrl,
                username: 'sharkinsgis',
                feedLanguage: 'en', country: 'pe',
                addUngeocodedItems: 'false'
            };
            geonamesUrl = geonamesUrl + '?' + buildQueryString(geonamesParams);
            return geonamesUrl;
        }

        function fetchData() {
            return $.ajax(buildRSSUrl());
        }

        return {
            fetchData: fetchData
        };
    })();

    function Markers () {
        this._markerMap = {};
        this._markerList = [];
        this._defaultIcon = new L.Icon.Default();
        this._activeIcon = new L.Icon.Default({iconUrl:
                                             './images/marker-icon-red.png'});
        this._markers = new L.MarkerClusterGroup(
                {spiderfyDistanceMultiplier:1, showCoverageOnHover:false}
        );
    }

    Markers.prototype = {
        getMarkerLayer: function () {
            var layers = this._markers.getLayers();
            if (layers.length === 0) {
                this._markers.addLayers(this._markerList);
            }
            return this._markers;
        },

        getMarkerList: function () {
            return this._markerList;
        },

        activateMarker: function (id) {
            var activeMarker = this._markerMap[id];
            var activeIcon = this._activeIcon;
            this._markers.zoomToShowLayer(activeMarker, function(){
                activeMarker.setIcon(activeIcon);
            });
        },

        deactivateMarker: function (id) {
            var previousActiveMarker = this._markerMap[id];
            if (previousActiveMarker) {
                previousActiveMarker.setIcon(this._defaultIcon);
            }
        }
    };

    function ProjectMarkers() {
        Markers.call(this);
        //this._list = $('<ul/>', {'id': 'projects'});
        //$('#projects').append(this._list);
        var projectURL = "http://geocatmin.ingemmet.gob.pe/arcgis/rest/services/SERV_CARTERA_PROYECTOS_MINEROS/MapServer/0";
        var that = this;
        this._clusteredFeatureLayer = new L.esri.clusteredFeatureLayer(
            projectURL, {
                cluster: that._markers,
                onEachMarker: function (geojson, marker) {
                    //var listItem = $('<li/>', {
                        //html: geojson.properties.EMPRESA,
                        //'id': geojson.properties.OBJECTID
                    //});
                    //that._list.append(listItem);
                    projectList.addListItem(geojson);
                    var activeIcon = that._activeIcon;
                    marker.on('click', function() {
                        this.setIcon(activeIcon);
                        $(document).trigger('projectMarkerClick',
                                            [geojson.properties.OBJECTID]);
                    });
                    that._markerList.push(marker);
                    that._markerMap[geojson.properties.OBJECTID] = marker;
                }
            });
    }
    ProjectMarkers.prototype = Object.create(Markers.prototype);
    ProjectMarkers.prototype.getMarkerLayer = function () {
        return this._clusteredFeatureLayer;
    };

    function ArticleMarkers() {
        Markers.call(this);
    }
    ArticleMarkers.prototype = Object.create(Markers.prototype);
    ArticleMarkers.prototype.addMarker = function (index, lat, lon) {
        var latlng = new L.LatLng(lat, lon);
        var marker = L.marker(latlng, {id: index});
        var activeIcon = this._activeIcon;
        marker.on('click', function() {
            this.setIcon(activeIcon);
            $(document).trigger('articleMarkerClick', [this.options.id]);
        });
        this._markerList.push(marker);
        this._markerMap[index] = marker;
    };

    var projectList = (function () {
        var _list = $('<ul/>', {'id': 'projectlist'});
        function init() {
            $('#projects').append(_list);
        }

        function addListItem(geojson) {
            var listItem = $('<li/>', {
                html: geojson.properties.EMPRESA,
                'id': geojson.properties.OBJECTID
            });
            listItem.on('click', click);
            _list.append(listItem);
        }

        function deactivatePrevious() {
            var previousActiveLi = $('#projectlist li.active');
            $(document).trigger('projectDeactivated',
                                [previousActiveLi.attr('id')]);
            previousActiveLi.removeClass('active');
        }

        function scrollTo(id) {
            deactivatePrevious();
            var div = $('#projects');
            var activeLi = $('#projectlist' + ' #' + id);
            activeLi.addClass('active');
            div.scrollTop(8);
            div.animate({
                duration: 'slow',
                scrollTop: activeLi.position().top
            });
        }

        function click(e) {
            deactivatePrevious();
            var activeLi = $(this);
            activeLi.addClass('active');
            $(document).trigger('projectActivated', [activeLi.attr('id')]);
        }

        return {
            init: init,
            addListItem: addListItem,
            scrollTo: scrollTo
        };
    })();

    var articleList = (function () {
        var _list = $('<ul/>', {'id': 'articlelist'});
        function init() {
            $('#articles').append(_list);
        }

        function addListItem(index, xmlitem) {
            var titleComponents = xmlitem
                                    .children('title')
                                    .text()
                                    .split(' - ');
            var articleTitle = titleComponents[0];
            var articleOrigin = titleComponents[1];
            var pubdate = new Date(xmlitem.children('pubDate').text());
            var articleOriginUrl = xmlitem.children('link').text();
            var articleSpan = $('<span/>', {
                html: articleTitle
            });
            var listItem = $('<li/>', {
                html: articleSpan,
                'id': index,
                'datetime': pubdate
            });
            var itemLink = $('<a/>', {
                html: articleOrigin,
                'href': articleOriginUrl,
                'target': '_blank'
            });
            listItem.on('click', articleClick);
            listItem.append(itemLink);
            _list.append(listItem);
        }

        function sortByDate() {
            var listItems = $('#articlelist li');
            var list = _list;
            if(listItems.length && list.length) {
                listItems.sort(function(a,b) {
                    return new Date($(a).attr('datetime')) -
                        new Date($(b).attr('datetime'));
                }).each(function() {
                    list.prepend(this);
                });
            }
        }

        function deactivatePreviousArticle() {
            var previousActiveLi = $('li.active');
            $(document).trigger('articleDeactivated',
                                [previousActiveLi.attr('id')]);
            previousActiveLi.removeClass('active');
        }

        function scrollToArticle(id) {
            deactivatePreviousArticle();
            var articles = $('#articles');
            var activeLi = $('#articlelist' + ' #' + id);
            activeLi.addClass('active');
            articles.scrollTop(8);
            articles.animate({
                duration: 'slow',
                scrollTop: activeLi.position().top
            });
        }

        function articleClick(e) {
            deactivatePreviousArticle();
            var activeLi = $(this);
            activeLi.addClass('active');
            $(document).trigger('articleActivated', [activeLi.attr('id')]);
        }

        return {
            init: init,
            addListItem: addListItem,
            sortByDate: sortByDate,
            scrollToArticle: scrollToArticle
        };
    })();

    return {
        init: init,
        processRSSXML: processRSSXML,
        articleList: articleList
   };
})($, L, this.document);
<|endoftext|>


<commit_before>function setupGhostApi({siteUrl = window.location.origin}) {
    const apiPath = 'members/api';

    function endpointFor({type, resource}) {
        if (type === 'members') {
            return `${siteUrl.replace(/\/$/, '')}/${apiPath}/${resource}/`;
        }
    }

    function makeRequest({url, method, headers = {}, credentials, body}) {
        const options = {
            method,
            headers,
            credentials,
            body
        };
        return fetch(url, options);
    }
    const api = {};

    api.site = {
        read() {
            const url = endpointFor({type: 'members', resource: 'site'});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch site data');
                }
            });
        }
    };

    api.member = {
        identity() {
            const url = endpointFor({type: 'members', resource: 'session'});
            return makeRequest({
                url,
                credentials: 'same-origin'
            }).then(function (res) {
                if (!res.ok) {
                    return null;
                }
                return res.text();
            });
        },

        sessionData() {
            const url = endpointFor({type: 'members', resource: 'member'});
            return makeRequest({
                url,
                credentials: 'same-origin'
            }).then(function (res) {
                if (!res.ok) {
                    return null;
                }
                return res.json();
            });
        },

        update({name, subscribed}) {
            const url = endpointFor({type: 'members', resource: 'member'});
            return makeRequest({
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                credentials: 'same-origin',
                body: JSON.stringify({
                    name,
                    subscribed
                })
            }).then(function (res) {
                if (!res.ok) {
                    return null;
                }
                return res.json();
            });
        },

        sendMagicLink({email, emailType, labels, name, oldEmail}) {
            const url = endpointFor({type: 'members', resource: 'send-magic-link'});
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    name,
                    email,
                    oldEmail,
                    emailType,
                    labels,
                    requestSrc: 'portal'
                })
            }).then(function (res) {
                if (res.ok) {
                    return 'Success';
                } else {
                    throw new Error('Failed to send magic link email');
                }
            });
        },

        signout() {
            const url = endpointFor({type: 'members', resource: 'session'});
            return makeRequest({
                url,
                method: 'DELETE'
            }).then(function (res) {
                if (res.ok) {
                    window.location.replace(siteUrl);
                    return 'Success';
                } else {
                    throw new Error('Failed to signout');
                }
            });
        },

        async checkoutPlan({plan, cancelUrl, successUrl, email: customerEmail, name, metadata = {}} = {}) {
            const siteUrlObj = new URL(siteUrl);
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'create-stripe-checkout-session'});
            if (!successUrl) {
                const checkoutSuccessUrl = new URL(siteUrl);
                checkoutSuccessUrl.searchParams.set('stripe', 'success');
                successUrl = checkoutSuccessUrl.href;
            }

            if (!cancelUrl) {
                const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);
                checkoutCancelUrl.searchParams.set('stripe', 'cancel');
                cancelUrl = checkoutCancelUrl.href;
            }
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    plan: plan,
                    identity: identity,
                    metadata: {
                        name,
                        requestSrc: 'portal',
                        ...metadata
                    },
                    successUrl,
                    cancelUrl,
                    customerEmail: customerEmail
                })
            }).then(function (res) {
                if (!res.ok) {
                    throw new Error('Could not create stripe checkout session');
                }
                return res.json();
            }).then(function (result) {
                const stripe = window.Stripe(result.publicKey);
                return stripe.redirectToCheckout({
                    sessionId: result.sessionId
                });
            }).then(function (result) {
                if (result.error) {
                    throw new Error(result.error.message);
                }
            }).catch(function (err) {
                throw err;
            });
        },

        async editBilling({successUrl, cancelUrl, subscriptionId} = {}) {
            const siteUrlObj = new URL(siteUrl);
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'create-stripe-update-session'});
            if (!successUrl) {
                const checkoutSuccessUrl = new URL(siteUrl);
                checkoutSuccessUrl.searchParams.set('stripe', 'billing-update-success');
                successUrl = checkoutSuccessUrl.href;
            }

            if (!cancelUrl) {
                const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);
                checkoutCancelUrl.searchParams.set('stripe', 'billing-update-cancel');
                cancelUrl = checkoutCancelUrl.href;
            }
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    identity: identity,
                    subscription_id: subscriptionId,
                    successUrl,
                    cancelUrl
                })
            }).then(function (res) {
                if (!res.ok) {
                    throw new Error('Could not create stripe checkout session');
                }
                return res.json();
            }).then(function (result) {
                const stripe = window.Stripe(result.publicKey);
                return stripe.redirectToCheckout({
                    sessionId: result.sessionId
                });
            }).then(function (result) {
                if (result.error) {
                    throw new Error(result.error.message);
                }
            }).catch(function (err) {
                throw err;
            });
        },

        async updateSubscription({subscriptionId, planName, planId, smartCancel, cancelAtPeriodEnd, cancellationReason}) {
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'subscriptions'}) + subscriptionId + '/';
            return makeRequest({
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    smart_cancel: smartCancel,
                    cancel_at_period_end: cancelAtPeriodEnd,
                    cancellation_reason: cancellationReason,
                    identity: identity,
                    planName,
                    planId
                })
            });
        }
    };

    api.init = async () => {
        const {site} = await api.site.read();
        const member = await api.member.sessionData();
        return {site, member};
    };

    return api;
}

export default setupGhostApi;
<commit_msg>Updated api to use price id instead of plan name<commit_after>function setupGhostApi({siteUrl = window.location.origin}) {
    const apiPath = 'members/api';

    function endpointFor({type, resource}) {
        if (type === 'members') {
            return `${siteUrl.replace(/\/$/, '')}/${apiPath}/${resource}/`;
        }
    }

    function makeRequest({url, method, headers = {}, credentials, body}) {
        const options = {
            method,
            headers,
            credentials,
            body
        };
        return fetch(url, options);
    }
    const api = {};

    api.site = {
        read() {
            const url = endpointFor({type: 'members', resource: 'site'});
            return makeRequest({
                url,
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (res) {
                if (res.ok) {
                    return res.json();
                } else {
                    throw new Error('Failed to fetch site data');
                }
            });
        }
    };

    api.member = {
        identity() {
            const url = endpointFor({type: 'members', resource: 'session'});
            return makeRequest({
                url,
                credentials: 'same-origin'
            }).then(function (res) {
                if (!res.ok) {
                    return null;
                }
                return res.text();
            });
        },

        sessionData() {
            const url = endpointFor({type: 'members', resource: 'member'});
            return makeRequest({
                url,
                credentials: 'same-origin'
            }).then(function (res) {
                if (!res.ok) {
                    return null;
                }
                return res.json();
            });
        },

        update({name, subscribed}) {
            const url = endpointFor({type: 'members', resource: 'member'});
            return makeRequest({
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                credentials: 'same-origin',
                body: JSON.stringify({
                    name,
                    subscribed
                })
            }).then(function (res) {
                if (!res.ok) {
                    return null;
                }
                return res.json();
            });
        },

        sendMagicLink({email, emailType, labels, name, oldEmail}) {
            const url = endpointFor({type: 'members', resource: 'send-magic-link'});
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    name,
                    email,
                    oldEmail,
                    emailType,
                    labels,
                    requestSrc: 'portal'
                })
            }).then(function (res) {
                if (res.ok) {
                    return 'Success';
                } else {
                    throw new Error('Failed to send magic link email');
                }
            });
        },

        signout() {
            const url = endpointFor({type: 'members', resource: 'session'});
            return makeRequest({
                url,
                method: 'DELETE'
            }).then(function (res) {
                if (res.ok) {
                    window.location.replace(siteUrl);
                    return 'Success';
                } else {
                    throw new Error('Failed to signout');
                }
            });
        },

        async checkoutPlan({plan, cancelUrl, successUrl, email: customerEmail, name, metadata = {}} = {}) {
            const siteUrlObj = new URL(siteUrl);
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'create-stripe-checkout-session'});
            if (!successUrl) {
                const checkoutSuccessUrl = new URL(siteUrl);
                checkoutSuccessUrl.searchParams.set('stripe', 'success');
                successUrl = checkoutSuccessUrl.href;
            }

            if (!cancelUrl) {
                const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);
                checkoutCancelUrl.searchParams.set('stripe', 'cancel');
                cancelUrl = checkoutCancelUrl.href;
            }
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    priceId: plan,
                    identity: identity,
                    metadata: {
                        name,
                        requestSrc: 'portal',
                        ...metadata
                    },
                    successUrl,
                    cancelUrl,
                    customerEmail: customerEmail
                })
            }).then(function (res) {
                if (!res.ok) {
                    throw new Error('Could not create stripe checkout session');
                }
                return res.json();
            }).then(function (result) {
                const stripe = window.Stripe(result.publicKey);
                return stripe.redirectToCheckout({
                    sessionId: result.sessionId
                });
            }).then(function (result) {
                if (result.error) {
                    throw new Error(result.error.message);
                }
            }).catch(function (err) {
                throw err;
            });
        },

        async editBilling({successUrl, cancelUrl, subscriptionId} = {}) {
            const siteUrlObj = new URL(siteUrl);
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'create-stripe-update-session'});
            if (!successUrl) {
                const checkoutSuccessUrl = new URL(siteUrl);
                checkoutSuccessUrl.searchParams.set('stripe', 'billing-update-success');
                successUrl = checkoutSuccessUrl.href;
            }

            if (!cancelUrl) {
                const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);
                checkoutCancelUrl.searchParams.set('stripe', 'billing-update-cancel');
                cancelUrl = checkoutCancelUrl.href;
            }
            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    identity: identity,
                    subscription_id: subscriptionId,
                    successUrl,
                    cancelUrl
                })
            }).then(function (res) {
                if (!res.ok) {
                    throw new Error('Could not create stripe checkout session');
                }
                return res.json();
            }).then(function (result) {
                const stripe = window.Stripe(result.publicKey);
                return stripe.redirectToCheckout({
                    sessionId: result.sessionId
                });
            }).then(function (result) {
                if (result.error) {
                    throw new Error(result.error.message);
                }
            }).catch(function (err) {
                throw err;
            });
        },

        async updateSubscription({subscriptionId, planName, planId, smartCancel, cancelAtPeriodEnd, cancellationReason}) {
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'subscriptions'}) + subscriptionId + '/';
            return makeRequest({
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    smart_cancel: smartCancel,
                    cancel_at_period_end: cancelAtPeriodEnd,
                    cancellation_reason: cancellationReason,
                    identity: identity,
                    priceId: planId
                })
            });
        }
    };

    api.init = async () => {
        const {site} = await api.site.read();
        const member = await api.member.sessionData();
        return {site, member};
    };

    return api;
}

export default setupGhostApi;
<|endoftext|>