<commit_before>'use strict'

const db = require('APP/db')
const Cart = db.model('cart')
const CartItem = db.model('cartItem')

module.exports = require('express').Router()
  // .get('/:sessionId',
  //   (req, res, next) =>
  //   Cart.findOrCreate({where: {sessionId: req.params.sessionId})
  //   //defaults?
  //     .spread((cart, created) => {
  //         return cart
  //       })
  //     .catch(next))

  .param('sessionId', (req, res, next, sessionId) =>
    Cart.scope('populated')
    .findOrCreate({where: {sessionId: req.params.sessionId}})
    .spread((cart, created) => cart)
    .then(cart =>
      req.requestedCart = cart
      next()
      return null
    })
    .catch(next))

  .get('/:sessionId', (req, res, next) =>
    res.json(req.requestedCart)
    .catch(next))

  .get('/:sessionId/subtotal', (req, res, next) =>
    req.requestedCart
    .then(cart => res.json(cartItems.reduce(
      (acc, cur) => acc.price * acc.quantity + cur.price * cur.quantity, 0
      ))
   .catch(next))

  .post('/:sessionId/:faceId', (req, res, next) =>
    CartItem.create({cartId: req.requestedCart.id, faceId: req.params.faceId, quantity: req.body.quantity, price: req.body.price})
    .then(res.json(requestedCart))
    .catch(next))

  .put('/:sessionId/:faceId/add', (req, res, next) =>
    CartItem.findOne({
      where: {
        cartId: req.requestedCart.id,
        faceId: req.params.faceId
      }
    })
    .then(item =>
      item.update(
        {quantity: item.quantity + 1}
        )
      )
    .then(() => next())
    .catch(next))

    .put('/:sessionId/:faceId/subtract', (req, res, next) =>
    CartItem.findOne({
      where: {
        cartId: req.requestedCart.id,
        faceId: req.params.faceId
      }
    })
    .then(item =>
      item.update(
        {quantity: item.quantity - 1}
        )
      )
    .then(() => next())
    .catch(next))

  .delete('/:sessionId/:faceId', (req, res, next) =>
    CartItem.destroy({
      where: {
        cartId: req.requestedCart.id,
        faceId: req.params.faceId
      }
    })
    .then(() => next())
    .catch(next))

<commit_msg>edited post a new item for a cart<commit_after>'use strict'

const db = require('APP/db')
const Cart = db.model('cart')
const CartItem = db.model('cartItem')

module.exports = require('express').Router()
  // .get('/:sessionId',
  //   (req, res, next) =>
  //   Cart.findOrCreate({where: {sessionId: req.params.sessionId})
  //   //defaults?
  //     .spread((cart, created) => {
  //         return cart
  //       })
  //     .catch(next))

  .param('sessionId', (req, res, next, sessionId) =>
    Cart.scope('populated')
    .findOrCreate({where: {sessionId: req.params.sessionId}})
    .spread((cart, created) => cart)
    .then(cart =>
      req.requestedCart = cart
      next()
      return null
    })
    .catch(next))

  .get('/:sessionId', (req, res, next) =>
    res.json(req.requestedCart)
    .catch(next))

  .get('/:sessionId/subtotal', (req, res, next) =>
    req.requestedCart
    .then(cart => res.json(cartItems.reduce(
      (acc, cur) => acc.price * acc.quantity + cur.price * cur.quantity, 0
      ))
   .catch(next))

  .post('/:sessionId', (req, res, next) =>
    CartItem.create({cartId: req.requestedCart.id, faceId: req.body.faceId, quantity: req.body.quantity, price: req.body.price})
    .then(res.json(requestedCart))
    .catch(next))

  .put('/:sessionId/:faceId/add', (req, res, next) =>
    CartItem.findOne({
      where: {
        cartId: req.requestedCart.id,
        faceId: req.params.faceId
      }
    })
    .then(item =>
      item.update(
        {quantity: item.quantity + 1}
        )
      )
    .then(() => next())
    .catch(next))

    .put('/:sessionId/:faceId/subtract', (req, res, next) =>
    CartItem.findOne({
      where: {
        cartId: req.requestedCart.id,
        faceId: req.params.faceId
      }
    })
    .then(item =>
      item.update(
        {quantity: item.quantity - 1}
        )
      )
    .then(() => next())
    .catch(next))

  .delete('/:sessionId/:faceId', (req, res, next) =>
    CartItem.destroy({
      where: {
        cartId: req.requestedCart.id,
        faceId: req.params.faceId
      }
    })
    .then(() => next())
    .catch(next))

<|endoftext|>


<commit_before>/*
 * This file is part of Invenio.
 * Copyright (C) 2013, 2014 CERN.
 *
 * Invenio is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * Invenio is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Invenio; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

require(['jquery'], function ($) {

  "use strict"; // jshint ;_;

  $(function() {
    /* ENABLE AUTOMATIC MENU LOADING
     * =============================
     *
     * Finds all elements with `data-menu="click"` attributes and loads menu with
     * AJAX call to url specified in attribute `data-menu-source`.
     *
     * Example:
     * --------
     *
     *

     *
     *
     *       AJAX menu
     *
     *
     *

     */

    $('[data-menu="click"]').each(function (index) {
      var that = this,
        $menu = $(this).parent(),
        $li = $menu.parent(),
        message_update = function(obj) {
          $(that).unbind('click');
          $.ajax({
            url: $(that).attr('data-menu-source')
          }).done(function (data) {
            $menu.parent().append(data);
          });
        };
      $(that).on('click', message_update);
      $li.addClass('dropdown');
      $menu.attr('data-toggle', "dropdown");
      $menu.dropdown();
    });

    /* ENABLE AUTOMATIC POPOVER REGISTRATION
     * =============================
     *
     * Finds all elements with `data-toggle="popover"` attributes and
     * registers the bootstrap popover element.
     * Attributes:
     *  data-content = text placed inside the popover
     *  data-original-title = title of popover
     *  data-html = whether the text should be rendered as HTML
     *  data-placement = left / right / top / bottom
     *
     * Clicking link/button with data-dismiss="popover" inside the popover
     * will close the popover.
     *
     * Example:
     * --------
     *
     * <a href="#"
     *  data-toggle="popover"
     *  data-html="true"
     *  data-original-title="Qua"
     *  data-content="Qua qua qua">
     *     open popover
     *
     */
    $('[data-toggle="popover"]').each(function(){
      var $this = $(this);
      var bHtml = Boolean($this.attr('data-html'));
      var placement = $this.attr('data-placement');

      if(! placement)
      {
        placement = 'bottom';
      }

      var $popover = $(this).popover({
        html: bHtml,
        placement: placement,
      });

      $popover.popover('show');
      $popover.popover('hide');

      // Register the closing button
      $popover.parent().delegate('[data-dismiss="popover"]', 'click', function() {
        $popover.popover('hide');
      });
    });

    /* ENABLE AUTOMATIC JEDITABLE REGISTRATION
     * =============================
     *
     * Finds all elements with 'data-editable' attribute and
     * registers the element to use jeditable plugin.
     * Some elements can be created after document initialization,
     * therefore this function is triggered by click
     * (which usually opens the edit box)
     *
     * Attributes:
     *  data-editable = type of edit: text / textarea / select
     *  jeditable-action = URL to which data should be sent
     *
     * Some attributes are mirroring jeditable attributes,
     * which can be found here:
     * http://www.appelsiini.net/projects/jeditable
     *
     *  jeditable-XXX sets the option XXX in jeditable constructor
     *
     * (Not all options are mapped though, add them if you need them)
     *
     * Example:
     * --------
     *
     *
{{tag.annotation}}

     */
    $(document).delegate('[data-editable]', 'click', function(){
    //$('[data-editable]').each(function(){
      var $this = $(this);

      $this.editable($this.attr('jeditable-action'), {
         type      : $this.attr('data-editable') || 'textarea',
         cancel    : $this.attr('jeditable-cancel') || '',
         submit    : $this.attr('jeditable-submit') || '',
         indicator : $this.attr('jeditable-indicator') || 'Saving...',
         tooltip   : $this.attr('jeditable-tooltip') || 'Click to edit...',
         id        : $this.attr('jeditable-post-property-id') || 'id',
         name      : $this.attr('jeditable-post-property-value') || 'value',
         cssclass  : $this.attr('jeditable-cssclass') || '',
         onblur    : $this.attr('jeditable-onblur') || 'cancel',
         placeholder: $this.attr('jeditable-placeholder') || '',
      });

      //Remove attribute to prevent executing again
      $this.removeAttr('data-editable');

      //Trigger click event on the underlying jeditable object
      $this.trigger('click');

    });
  });

  function bindModals(filter, focusOn) {
    // Support for AJAX loaded modal window.
    // Focuses on first input textbox after it loads the window.
    if (filter) {
      filter = '[data-toggle="modal"]' + filter;
    } else {
      filter = '[data-toggle="modal"]';
    }
    $(filter).click(function (e) {
        e.preventDefault();
        var href = $(this).attr('href');
        if (href.indexOf('#') === 0) {
            $(href).modal({show: true});
        } else {
            $.get(href, function(data) {
                $('
' + data + '
').modal();
            }).success(function() {
                if(focusOn) {
                    $(focusOn).focus()
                }
            });
        }
    });
  }

  $(document).ready(function() {
    bindModals();
  });
  window.bindModals = bindModals;

  $(document).on('hidden.bs.modal', function() {
      // delete any existing modal elements instead of just hiding them
      $('.modal').remove();
      $('.modal-backdrop').remove();
  });

  $('[rel=tooltip]').tooltip();
});
<commit_msg>deposit: refactoring bug fixes<commit_after>/*
 * This file is part of Invenio.
 * Copyright (C) 2013, 2014 CERN.
 *
 * Invenio is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * Invenio is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Invenio; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

require(['jquery'], function ($) {

  "use strict"; // jshint ;_;

  $(function() {
    /* ENABLE AUTOMATIC MENU LOADING
     * =============================
     *
     * Finds all elements with `data-menu="click"` attributes and loads menu with
     * AJAX call to url specified in attribute `data-menu-source`.
     *
     * Example:
     * --------
     *
     *

     *
     *
     *       AJAX menu
     *
     *
     *

     */

    $('[data-menu="click"]').each(function (index) {
      var that = this,
        $menu = $(this).parent(),
        $li = $menu.parent(),
        message_update = function(obj) {
          $(that).unbind('click');
          $.ajax({
            url: $(that).attr('data-menu-source')
          }).done(function (data) {
            $menu.parent().append(data);
          });
        };
      $(that).on('click', message_update);
      $li.addClass('dropdown');
      $menu.attr('data-toggle', "dropdown");
      $menu.dropdown();
    });

    /* ENABLE AUTOMATIC POPOVER REGISTRATION
     * =============================
     *
     * Finds all elements with `data-toggle="popover"` attributes and
     * registers the bootstrap popover element.
     * Attributes:
     *  data-content = text placed inside the popover
     *  data-original-title = title of popover
     *  data-html = whether the text should be rendered as HTML
     *  data-placement = left / right / top / bottom
     *
     * Clicking link/button with data-dismiss="popover" inside the popover
     * will close the popover.
     *
     * Example:
     * --------
     *
     * <a href="#"
     *  data-toggle="popover"
     *  data-html="true"
     *  data-original-title="Qua"
     *  data-content="Qua qua qua">
     *     open popover
     *
     */
    $('[data-toggle="popover"]').each(function(){
      var $this = $(this);
      var bHtml = Boolean($this.attr('data-html'));
      var placement = $this.attr('data-placement');

      if(! placement)
      {
        placement = 'bottom';
      }

      var $popover = $(this).popover({
        html: bHtml,
        placement: placement,
      });

      $popover.popover('show');
      $popover.popover('hide');

      // Register the closing button
      $popover.parent().delegate('[data-dismiss="popover"]', 'click', function() {
        $popover.popover('hide');
      });
    });

    /* ENABLE AUTOMATIC JEDITABLE REGISTRATION
     * =============================
     *
     * Finds all elements with 'data-editable' attribute and
     * registers the element to use jeditable plugin.
     * Some elements can be created after document initialization,
     * therefore this function is triggered by click
     * (which usually opens the edit box)
     *
     * Attributes:
     *  data-editable = type of edit: text / textarea / select
     *  jeditable-action = URL to which data should be sent
     *
     * Some attributes are mirroring jeditable attributes,
     * which can be found here:
     * http://www.appelsiini.net/projects/jeditable
     *
     *  jeditable-XXX sets the option XXX in jeditable constructor
     *
     * (Not all options are mapped though, add them if you need them)
     *
     * Example:
     * --------
     *
     *
{{tag.annotation}}

     */
    $(document).delegate('[data-editable]', 'click', function(){
    //$('[data-editable]').each(function(){
      var $this = $(this);

      $this.editable($this.attr('jeditable-action'), {
         type      : $this.attr('data-editable') || 'textarea',
         cancel    : $this.attr('jeditable-cancel') || '',
         submit    : $this.attr('jeditable-submit') || '',
         indicator : $this.attr('jeditable-indicator') || 'Saving...',
         tooltip   : $this.attr('jeditable-tooltip') || 'Click to edit...',
         id        : $this.attr('jeditable-post-property-id') || 'id',
         name      : $this.attr('jeditable-post-property-value') || 'value',
         cssclass  : $this.attr('jeditable-cssclass') || '',
         onblur    : $this.attr('jeditable-onblur') || 'cancel',
         placeholder: $this.attr('jeditable-placeholder') || '',
      });

      //Remove attribute to prevent executing again
      $this.removeAttr('data-editable');

      //Trigger click event on the underlying jeditable object
      $this.trigger('click');

    });
  });

  function bindModals(filter, focusOn) {
    // Support for AJAX loaded modal window.
    // Focuses on first input textbox after it loads the window.
    if (filter) {
      filter = '[data-toggle="modal"]' + filter;
    } else {
      filter = '[data-toggle="modal"][href]';
    }
    $(filter).click(function (e) {
        e.preventDefault();
        var href = $(this).attr('href');
        if (href.indexOf('#') === 0) {
            $(href).modal({show: true});
        } else {
            $.get(href, function(data) {
                $('
' + data + '
').modal();
            }).success(function() {
                if(focusOn) {
                    $(focusOn).focus()
                }
            });
        }
    });
  }

  $(document).ready(function() {
    bindModals();
  });
  window.bindModals = bindModals;

  $(document).on('hidden.bs.modal', function() {
        // delete any existing modal elements instead of just hiding them
        var href = $(this).attr('href');
        if (href !== undefined){
            $('.modal').remove();
            $('.modal-backdrop').remove();
        }
  });

  $('[rel=tooltip]').tooltip();
});
<|endoftext|>


<commit_before>// Copyright 2013-2015, University of Colorado Boulder

/**
 * Experimental Sphere
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

define( function( require ) {
  'use strict';

  var GLNode = require( 'MOBIUS/GLNode' );
  var inherit = require( 'PHET_CORE/inherit' );
  var mobius = require( 'MOBIUS/mobius' );
  var Vector3 = require( 'DOT/Vector3' );

  var Float32Array = window.Float32Array || window.Array;

  var Sphere = mobius.Sphere = function Sphere( gl, radius, latitudeStrips, longitudeStrips ) {
    GLNode.call( this );

    this.gl = gl;
    this.radius = radius;
    this.latitudeStrips = latitudeStrips || 10;
    this.longitudeStrips = longitudeStrips || 10;

    this.setupBuffers();
  };

  return inherit( GLNode, Sphere, {
    // TODO: should we allow buffer parameters to change?
    setupBuffers: function() {
      var gl = this.gl;
      this.positionBuffer = gl.createBuffer();
      this.normalBuffer = gl.createBuffer();
      this.indexBuffer = gl.createBuffer();
      this.textureBuffer = gl.createBuffer();

      var positionData = [];
      var normalData = [];
      var indexData = [];
      var textureData = [];

      var latitudeSamples = this.latitudeStrips + 1;
      var longitudeSamples = this.longitudeStrips;

      var thetaIndex;

      for ( var phiIndex = 0; phiIndex < latitudeSamples; phiIndex++ ) {
        var v = phiIndex / (latitudeSamples - 1.0);
        var phi = ( v - 0.5 ) * Math.PI;

        var cosPhi = Math.cos( phi );
        var sinPhi = Math.sin( phi );

        for ( thetaIndex = 0; thetaIndex <= longitudeSamples; thetaIndex++ ) {
          var u = thetaIndex / longitudeSamples;
          var theta = u * 2 * Math.PI;

          var cosTheta = Math.cos( theta );
          var sinTheta = Math.sin( theta );

          var x = sinTheta * cosPhi;
          var y = sinTheta * sinPhi;
          var z = cosTheta;

          positionData.push( x * this.radius );
          positionData.push( y * this.radius );
          positionData.push( z * this.radius );

          normalData.push( x );
          normalData.push( y );
          normalData.push( z );

          textureData.push( u );
          textureData.push( v );
        }

        if ( phiIndex > 0 ) {
          var baseA = (phiIndex - 1) * (longitudeSamples + 1);
          var baseB = phiIndex * (longitudeSamples + 1);

          for ( thetaIndex = 1; thetaIndex <= longitudeSamples; thetaIndex++ ) {
            // upper-left triangle
            indexData.push( baseA + thetaIndex - 1 );
            indexData.push( baseB + thetaIndex - 1 );
            indexData.push( baseB + thetaIndex );

            // lower-right triangle
            indexData.push( baseA + thetaIndex - 1 );
            indexData.push( baseB + thetaIndex );
            indexData.push( baseA + thetaIndex );
          }
        }
      }

      gl.bindBuffer( gl.ARRAY_BUFFER, this.normalBuffer );
      gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( normalData ), gl.STATIC_DRAW );
      this.normalBuffer.itemSize = 3;
      this.normalBuffer.numItems = normalData.length / 3;

      gl.bindBuffer( gl.ARRAY_BUFFER, this.textureBuffer );
      gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( textureData ), gl.STATIC_DRAW );
      this.textureBuffer.itemSize = 2;
      this.textureBuffer.numItems = textureData.length / 2;

      gl.bindBuffer( gl.ARRAY_BUFFER, this.positionBuffer );
      gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( positionData ), gl.STATIC_DRAW );
      this.positionBuffer.itemSize = 3;
      this.positionBuffer.numItems = positionData.length / 3;

      gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer );
      gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indexData ), gl.STREAM_DRAW );
      this.indexBuffer.itemSize = 3;
      this.indexBuffer.numItems = indexData.length;
    },

    renderSelf: function( args ) {
      var gl = this.gl;

      gl.bindBuffer( gl.ARRAY_BUFFER, this.positionBuffer );
      gl.vertexAttribPointer( args.positionAttribute, this.positionBuffer.itemSize, gl.FLOAT, false, 0, 0 );

      if ( args.textureCoordinateAttribute !== null ) {
        gl.bindBuffer( gl.ARRAY_BUFFER, this.textureBuffer );
        gl.vertexAttribPointer( args.textureCoordinateAttribute, this.textureBuffer.itemSize, gl.FLOAT, false, 0, 0 );
      }

      if ( args.normalAttribute !== null ) {
        gl.bindBuffer( gl.ARRAY_BUFFER, this.normalBuffer );
        gl.vertexAttribPointer( args.normalAttribute, this.normalBuffer.itemSize, gl.FLOAT, false, 0, 0 );
      }

      if ( args.transformAttribute !== null ) {
        gl.uniformMatrix4fv( args.transformAttribute, false, args.transform.getMatrix().entries );
      }

      if ( args.inverseTransposeAttribute !== null ) {
        gl.uniformMatrix4fv( args.inverseTransposeAttribute, false, args.transform.getInverseTransposed().entries );
      }

      gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer );
      gl.drawElements( gl.TRIANGLES, this.indexBuffer.numItems, gl.UNSIGNED_SHORT, 0 );
    },

    intersect: function( ray, epsilon ) {
      epsilon = epsilon === undefined ? 0.00001 : epsilon;

      // center is the origin for now, but leaving in computations so that we can change that in the future. optimize away if needed
      var center = new Vector3();

      var rayDir = ray.direction;
      var pos = ray.position;
      var centerToRay = pos.minus( center );

      // basically, we can use the quadratic equation to solve for both possible hit points (both +- roots are the hit points)
      var tmp = rayDir.dot( centerToRay );
      var centerToRayDistSq = centerToRay.magnitudeSquared();
      var det = 4 * tmp * tmp - 4 * ( centerToRayDistSq - this.radius * this.radius );
      if ( det < epsilon ) {
        // ray misses sphere entirely
        return null;
      }

      var base = rayDir.dot( center ) - rayDir.dot( pos );
      var sqt = Math.sqrt( det ) / 2;

      // the "first" entry point distance into the sphere. if we are inside the sphere, it is behind us
      var ta = base - sqt;

      // the "second" entry point distance
      var tb = base + sqt;

      if ( tb < epsilon ) {
        // sphere is behind ray, so don't return an intersection
        return null;
      }

      var hitPositionB = ray.pointAtDistance( tb );
      var normalB = hitPositionB.minus( center ).normalized();

      if ( ta < epsilon ) {
        // we are inside the sphere
        // in => out
        return {
          distance: tb,
          hitPoint: hitPositionB,
          normal: normalB.negated(),
          fromOutside: false
        };
      }
      else {
        // two possible hits
        var hitPositionA = ray.pointAtDistance( ta );
        var normalA = hitPositionA.minus( center ).normalized();

        // close hit, we have out => in
        return {
          distance: ta,
          hitPoint: hitPositionA,
          normal: normalA,
          fromOutside: true
        };
      }
    }
  } );

} );
<commit_msg>Made Vector3 constructor arguments required, see https://github.com/phetsims/dot/issues/85<commit_after>// Copyright 2013-2015, University of Colorado Boulder

/**
 * Experimental Sphere
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

define( function( require ) {
  'use strict';

  var GLNode = require( 'MOBIUS/GLNode' );
  var inherit = require( 'PHET_CORE/inherit' );
  var mobius = require( 'MOBIUS/mobius' );
  var Vector3 = require( 'DOT/Vector3' );

  var Float32Array = window.Float32Array || window.Array;

  var Sphere = mobius.Sphere = function Sphere( gl, radius, latitudeStrips, longitudeStrips ) {
    GLNode.call( this );

    this.gl = gl;
    this.radius = radius;
    this.latitudeStrips = latitudeStrips || 10;
    this.longitudeStrips = longitudeStrips || 10;

    this.setupBuffers();
  };

  return inherit( GLNode, Sphere, {
    // TODO: should we allow buffer parameters to change?
    setupBuffers: function() {
      var gl = this.gl;
      this.positionBuffer = gl.createBuffer();
      this.normalBuffer = gl.createBuffer();
      this.indexBuffer = gl.createBuffer();
      this.textureBuffer = gl.createBuffer();

      var positionData = [];
      var normalData = [];
      var indexData = [];
      var textureData = [];

      var latitudeSamples = this.latitudeStrips + 1;
      var longitudeSamples = this.longitudeStrips;

      var thetaIndex;

      for ( var phiIndex = 0; phiIndex < latitudeSamples; phiIndex++ ) {
        var v = phiIndex / (latitudeSamples - 1.0);
        var phi = ( v - 0.5 ) * Math.PI;

        var cosPhi = Math.cos( phi );
        var sinPhi = Math.sin( phi );

        for ( thetaIndex = 0; thetaIndex <= longitudeSamples; thetaIndex++ ) {
          var u = thetaIndex / longitudeSamples;
          var theta = u * 2 * Math.PI;

          var cosTheta = Math.cos( theta );
          var sinTheta = Math.sin( theta );

          var x = sinTheta * cosPhi;
          var y = sinTheta * sinPhi;
          var z = cosTheta;

          positionData.push( x * this.radius );
          positionData.push( y * this.radius );
          positionData.push( z * this.radius );

          normalData.push( x );
          normalData.push( y );
          normalData.push( z );

          textureData.push( u );
          textureData.push( v );
        }

        if ( phiIndex > 0 ) {
          var baseA = (phiIndex - 1) * (longitudeSamples + 1);
          var baseB = phiIndex * (longitudeSamples + 1);

          for ( thetaIndex = 1; thetaIndex <= longitudeSamples; thetaIndex++ ) {
            // upper-left triangle
            indexData.push( baseA + thetaIndex - 1 );
            indexData.push( baseB + thetaIndex - 1 );
            indexData.push( baseB + thetaIndex );

            // lower-right triangle
            indexData.push( baseA + thetaIndex - 1 );
            indexData.push( baseB + thetaIndex );
            indexData.push( baseA + thetaIndex );
          }
        }
      }

      gl.bindBuffer( gl.ARRAY_BUFFER, this.normalBuffer );
      gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( normalData ), gl.STATIC_DRAW );
      this.normalBuffer.itemSize = 3;
      this.normalBuffer.numItems = normalData.length / 3;

      gl.bindBuffer( gl.ARRAY_BUFFER, this.textureBuffer );
      gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( textureData ), gl.STATIC_DRAW );
      this.textureBuffer.itemSize = 2;
      this.textureBuffer.numItems = textureData.length / 2;

      gl.bindBuffer( gl.ARRAY_BUFFER, this.positionBuffer );
      gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( positionData ), gl.STATIC_DRAW );
      this.positionBuffer.itemSize = 3;
      this.positionBuffer.numItems = positionData.length / 3;

      gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer );
      gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indexData ), gl.STREAM_DRAW );
      this.indexBuffer.itemSize = 3;
      this.indexBuffer.numItems = indexData.length;
    },

    renderSelf: function( args ) {
      var gl = this.gl;

      gl.bindBuffer( gl.ARRAY_BUFFER, this.positionBuffer );
      gl.vertexAttribPointer( args.positionAttribute, this.positionBuffer.itemSize, gl.FLOAT, false, 0, 0 );

      if ( args.textureCoordinateAttribute !== null ) {
        gl.bindBuffer( gl.ARRAY_BUFFER, this.textureBuffer );
        gl.vertexAttribPointer( args.textureCoordinateAttribute, this.textureBuffer.itemSize, gl.FLOAT, false, 0, 0 );
      }

      if ( args.normalAttribute !== null ) {
        gl.bindBuffer( gl.ARRAY_BUFFER, this.normalBuffer );
        gl.vertexAttribPointer( args.normalAttribute, this.normalBuffer.itemSize, gl.FLOAT, false, 0, 0 );
      }

      if ( args.transformAttribute !== null ) {
        gl.uniformMatrix4fv( args.transformAttribute, false, args.transform.getMatrix().entries );
      }

      if ( args.inverseTransposeAttribute !== null ) {
        gl.uniformMatrix4fv( args.inverseTransposeAttribute, false, args.transform.getInverseTransposed().entries );
      }

      gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer );
      gl.drawElements( gl.TRIANGLES, this.indexBuffer.numItems, gl.UNSIGNED_SHORT, 0 );
    },

    intersect: function( ray, epsilon ) {
      epsilon = epsilon === undefined ? 0.00001 : epsilon;

      // center is the origin for now, but leaving in computations so that we can change that in the future. optimize away if needed
      var center = new Vector3( 0, 0, 0 );

      var rayDir = ray.direction;
      var pos = ray.position;
      var centerToRay = pos.minus( center );

      // basically, we can use the quadratic equation to solve for both possible hit points (both +- roots are the hit points)
      var tmp = rayDir.dot( centerToRay );
      var centerToRayDistSq = centerToRay.magnitudeSquared();
      var det = 4 * tmp * tmp - 4 * ( centerToRayDistSq - this.radius * this.radius );
      if ( det < epsilon ) {
        // ray misses sphere entirely
        return null;
      }

      var base = rayDir.dot( center ) - rayDir.dot( pos );
      var sqt = Math.sqrt( det ) / 2;

      // the "first" entry point distance into the sphere. if we are inside the sphere, it is behind us
      var ta = base - sqt;

      // the "second" entry point distance
      var tb = base + sqt;

      if ( tb < epsilon ) {
        // sphere is behind ray, so don't return an intersection
        return null;
      }

      var hitPositionB = ray.pointAtDistance( tb );
      var normalB = hitPositionB.minus( center ).normalized();

      if ( ta < epsilon ) {
        // we are inside the sphere
        // in => out
        return {
          distance: tb,
          hitPoint: hitPositionB,
          normal: normalB.negated(),
          fromOutside: false
        };
      }
      else {
        // two possible hits
        var hitPositionA = ray.pointAtDistance( ta );
        var normalA = hitPositionA.minus( center ).normalized();

        // close hit, we have out => in
        return {
          distance: ta,
          hitPoint: hitPositionA,
          normal: normalA,
          fromOutside: true
        };
      }
    }
  } );

} );
<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Tandem is a general instance registry that can be used to track creation/disposal of instances in PhET Simulations.
 * It is used for phetio.js instrumentation for PhET-iO support.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var tandemNamespace = require( 'TANDEM/tandemNamespace' );
  var toCamelCase = require( 'PHET_CORE/toCamelCase' );

  // text
  var packageString = require( 'text!REPOSITORY/package.json' );

  // constants
  var packageJSON = JSON.parse( packageString );

  // variables
  var launched = false;

  // increment names of required tandems to avoid collisions for uninstrumented sims with phetioValidateTandems=false
  var requiredTandemIndex = 0;

  // increment names of required tandems to avoid collisions for uninstrumented sims with phetioValidateTandems=false
  var optionalTandemIndex = 0;

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @param {Object} [options]
   * @constructor
   */
  function Tandem( id, options ) {

    options = _.extend( {
      static: false,
      enabled: true,
      tandemRequiredButNotSupplied: false, // When set to true, if there isn't a tandem supplied, it will fail out.
      tandemOptionalAndNotSupplied: false
    }, options );

    // @public (read-only)
    this.id = ( id !== undefined ) ? id : '';

    // @private (read-only)
    this.static = options.static;
    this.tandemRequiredButNotSupplied = options.tandemRequiredButNotSupplied;
    this.tandemOptionalAndNotSupplied = options.tandemOptionalAndNotSupplied;
    this.enabled = options.enabled;
  }

  var staticInstances = [];

  tandemNamespace.register( 'Tandem', Tandem );

  // Listeners that will be notified when items are registered/deregistered
  var instanceListeners = [];

  inherit( Object, Tandem, {

    // @public (read-only) permit checking whether something is probably a Tandem instance for error checking
    isTandem: true,

    /**
     * Adds an instance of any type.  For example, it could be an axon Property, scenery Node or Sun button.  Each
     * item should only be added to the registry once, but that is not enforced here in Tandem.  For PhET-iO, phet-io.js
     * enforces one entry per ID in phetio.addInstance
     *
     * This is used to register instances with PhET-iO.
     * @param {Object} instance - the instance to add
     * @param {function} type - the PhET-iO type function, such as TString or TProperty(TNumber('volts'))
     * @public
     */
    addInstance: function( instance, type ) {

      if ( window.phet && window.phet.chipper && phet.chipper.brand === 'phet-io' && this.enabled ) {

        if ( phet.phetio.queryParameters.phetioValidateTandems ) {
          assert && assert( !this.tandemRequiredButNotSupplied, 'Tandem was required but not supplied' );
        }

        // validateTandems is false and printMissingTandems flag is present for a tandem that is required but not supplied.
        else if ( phet.phetio.queryParameters.printMissingTandems && this.tandemRequiredButNotSupplied ) {
          var stackTrace = new Error().stack;
          console.log( 'Required Tandem not supplied.\n' +
                       'this.id = ' + this.id + '\n' +
                       'Stack trace: ' + stackTrace );
        }

        if ( !type ) {
          console.log( 'Missing type declaration for ' + this.id );
        }

        // ifphetio returns a no-op function, so to test whether a valid T wrapper type was passed, we search for the typeName
        assert && assert( type && type.typeName, 'type must be specified and have a typeName' );

        if ( this.tandemOptionalAndNotSupplied ) {
          if ( phet.phetio.queryParameters.printMissingTandems ) {
            var stackTrace2 = new Error().stack;

            // Generally PhETFont is not desired because there are so many untandemized instances.
            if ( stackTrace2.indexOf( 'PhetFont' ) === -1 ) {
              console.log( 'Optional Tandem not supplied.\n' +
                           'this.id = ' + this.id + '\n' +
                           'Stack trace: ' + stackTrace2 );
            }
          }

          // For optionally instrumented types that are not provided tandems, the instance isn't really "added"
          // but likewise, it in not an error
          return;
        }

        if ( this.static && !launched ) {
          staticInstances.push( { tandem: this, instance: instance, type: type } );
        }
        else {
          for ( var i = 0; i < instanceListeners.length; i++ ) {
            instanceListeners[ i ].addInstance( this.id, instance, type );
          }
        }
      }
    },

    /**
     * Removes an instance from the
     * @param {Object} instance - the instance to remove
     * @public
     */
    removeInstance: function( instance ) {

      // Only active when running as phet-io
      if ( phet.chipper.brand === 'phet-io' && this.enabled ) {
        for ( var i = 0; i < instanceListeners.length; i++ ) {
          instanceListeners[ i ].removeInstance( this.id, instance );
        }
      }
    },

    /**
     * Create a new Tandem by appending the given id
     * @param {string} id
     * @param {Object} [options]
     * @returns {Tandem}
     * @public
     */
    createTandem: function( id, options ) {

      // Make sure the id was provided
      assert && assert( typeof id === 'string' && id.length > 0, 'id must be defined' );

      var string = ( this.id.length > 0 ) ? ( this.id + '.' + id ) : id;

      // Any child of something static is also static
      options = _.extend( { static: this.static, enabled: this.enabled }, options );

      return new Tandem( string, options );
    },

    /**
     * Creates a group tandem for creating multiple indexed child tandems, such as:
     * sim.screen.model.electron_0
     * sim.screen.model.electron_1
     *
     * In this case, 'sim.screen.model.electron' is the group tandem id.
     *
     * Used for arrays, observable arrays, or when many elements of the same type are created and they do not otherwise
     * have unique identifiers.
     * @param id
     * @returns {GroupTandem}
     */
    createGroupTandem: function( id ) {

      // Unfortunately we must resort to globals here since loading through the namespace would create a cycle
      return new GroupTandem( this.id + '.' + id );
    },

    /**
     * Get the last part of the tandem (after the last .), used in Joist for creating button names dynamically based
     * on screen names
     * @return {string} the tail of the tandem
     */
    get tail() {
      assert && assert( this.id.indexOf( '.' ) >= 0, 'tandem ID does not have a tail' );

      var lastIndexOfDot = this.id.lastIndexOf( '.' );
      var tail = this.id.substring( lastIndexOfDot + 1 );
      assert && assert( tail.length > 0, 'tandem ID did not have a tail' );
      return tail;
    },

    /**
     * Returns a Tandem for everything except the tail.
     * @returns {Tandem}
     */
    get parentTandem() {
      assert && assert( this.id.indexOf( '.' ) >= 0, 'tandem ID does not have a tail' );

      var lastIndexOfDot = this.id.lastIndexOf( '.' );
      var headID = this.id.substring( 0, lastIndexOfDot );

      return new Tandem( headID, {
        static: this.static,
        tandemRequiredButNotSupplied: this.tandemRequiredButNotSupplied,
        enabled: this.enabled
      } );
    },

    /**
     * When using subtyping, the instance listeners must only be notified once rather than once for every level
     * in the inheritance hierarchy.  When a subtype constructor has a tandem.addInstance call, it should
     * pass a supertype tandem to the parent constructor so that it won't try to register the item twice.
     * @returns {SupertypeTandem}
     */
    createSupertypeTandem: function() {
      return new SupertypeTandem( this.id );
    }
  }, {

    /**
     * Some common code (such as CheckBox or RadioButton) must always be instrumented and hence requires a tandem to be
     * passed in.  The options hash indicates this with {tandem: Tandem.tandemRequired()}
     * @returns {Tandem}
     */
    tandemRequired: function() {
      return rootTandem.createTandem( 'requiredTandem' + (requiredTandemIndex++), {
        tandemRequiredButNotSupplied: true // will be checked in addInstance
      } );
    },

    /**
     *
     * Used to indicate a common code component that supports tandem, but doesn't not require it.
     * If a tandem is not passed through to this instance, then it will not be instrumented.
     * @returns {Tandem}
     */
    tandemOptional: function() {
      return rootTandem.createTandem( 'optionalTandem' + (optionalTandemIndex++), {
        tandemOptionalAndNotSupplied: true
      } );
    },

    /**
     * Adds a listener that will be notified when items are registered/deregistered
     * Listeners have the form
     * {
     *   addInstance(id,instance),
     *   removeInstance(id,instance)
     * }
     * where id is of type {string} and instance is of type {Object}
     *
     * @param {Object} instanceListener - described above
     * @public
     * @static
     */
    addInstanceListener: function( instanceListener ) {
      instanceListeners.push( instanceListener );
    },

    /**
     * Create a tandem based on the name of the running simulation.
     * @returns {Tandem}
     */
    createRootTandem: function() {
      return new Tandem( toCamelCase( packageJSON.name ) );
    },

    /**
     * Create a child of the root tandem.
     * @param {string} name
     * @returns {Tandem}
     */
    createStaticTandem: function( name ) {
      return Tandem.createRootTandem().createTandem( name, { static: true } );
    },

    /**
     * When the simulation is launched, all static instances are registered.
     */
    launch: function() {
      assert && assert( !launched, 'Tandem was launched twice' );
      launched = true;
      while ( staticInstances.length > 0 ) {
        var staticInstance = staticInstances.shift();
        staticInstance.tandem.addInstance( staticInstance.instance, staticInstance.type );
      }
    },

    /**
     * Catch cases where tandem is being supplied to a class that doesn't support tandem.
     * @param options
     */
    disallowTandem: function( options ) {

      if ( window.phet && window.phet.chipper && phet.chipper.brand === 'phet-io' &&
           phet.phetio && phet.phetio.queryParameters && phet.phetio.queryParameters.phetioValidateTandems ) {
        assert && assert( !options.tandem, 'tandem is not allowed' );
      }
    },

    /**
     * When running in PhET-iO brand, some code (such as user interface components) must be instrumented for PhET-iO.
     * Uninstrumented files should call this function to indicate they still need to be instrumented, so they aren't
     * missed.  See https://github.com/phetsims/phet-io/issues/668
     */
    indicateUninstrumentedCode: function() {

      // Guard against undefined errors
      if ( window.phet && window.phet.chipper && phet.chipper.brand === 'phet-io' &&
           phet.phetio && phet.phetio.queryParameters ) {

        // Assert if validating tandems
        if ( phet.phetio.queryParameters.phetioValidateTandems ) {
          assert && assert( false, 'Uninstrumented code detected' );
        }

        // Print stack trace if query parameter supplied
        if ( phet.phetio.queryParameters.printMissingTandems ) {
          var stackTrace = new Error().stack;
          console.log( 'Uninstrumented Code! Tandem not supplied.\n' +
                       'Stack trace: ' + stackTrace );
        }

      }
    }
  } );

  var rootTandem = Tandem.createRootTandem();

  // Tandem checks for listeners added before the Tandem module was loaded.  This is necessary so that phetio.js can
  // receive notifications about items created during static initialization such as Solute.js
  // which is created before Sim.js runs.
  if ( window.tandemPreloadInstanceListeners ) {
    for ( var i = 0; i < window.tandemPreloadInstanceListeners.length; i++ ) {
      Tandem.addInstanceListener( window.tandemPreloadInstanceListeners[ i ] );
    }
  }

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createGroupTandem
   * Declared in the same file to avoid circular reference errors in module loading.
   */
  function GroupTandem( id ) {

    Tandem.call( this, id );

    // @private for generating indices from a pool
    this.groupElementIndex = 0;
  }

  tandemNamespace.register( 'Tandem.GroupTandem', GroupTandem );

  inherit( Tandem, GroupTandem, {

    createNextTandem: function() {
      return new Tandem( this.id + '_' + ( this.groupElementIndex++ ) );
    }
  } );

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createSupertypeTandem
   */
  function SupertypeTandem( id ) {
    Tandem.call( this, id );
  }

  tandemNamespace.register( 'Tandem.SupertypeTandem', SupertypeTandem );

  inherit( Tandem, SupertypeTandem, {

    // @public - Override to make no-op, see createSupertypeTandem
    addInstance: function( instance, type ) {},

    // @public - Override to make no-op, see createSupertypeTandem
    removeInstance: function( instance ) {}
  } );

  return Tandem;
} );

<commit_msg>documentation for printMissingTandems, https://github.com/phetsims/phet-io/issues/1070<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Tandem is a general instance registry that can be used to track creation/disposal of instances in PhET Simulations.
 * It is used for phetio.js instrumentation for PhET-iO support.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var tandemNamespace = require( 'TANDEM/tandemNamespace' );
  var toCamelCase = require( 'PHET_CORE/toCamelCase' );

  // text
  var packageString = require( 'text!REPOSITORY/package.json' );

  // constants
  var packageJSON = JSON.parse( packageString );

  // variables
  var launched = false;

  // increment names of required tandems to avoid collisions for uninstrumented sims with phetioValidateTandems=false
  var requiredTandemIndex = 0;

  // increment names of required tandems to avoid collisions for uninstrumented sims with phetioValidateTandems=false
  var optionalTandemIndex = 0;

  // increment names of required tandems to avoid collisions for uninstrumented sims with phetioValidateTandems=false
  var uninstrumentCodeIndex = 0;

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @param {Object} [options]
   * @constructor
   */
  function Tandem( id, options ) {

    options = _.extend( {
      static: false,
      enabled: true,
      tandemRequiredButNotSupplied: false, // When set to true, if there isn't a tandem supplied, it will fail out.
      tandemOptionalAndNotSupplied: false
    }, options );

    // @public (read-only)
    this.id = ( id !== undefined ) ? id : '';

    // @private (read-only)
    this.static = options.static;
    this.tandemRequiredButNotSupplied = options.tandemRequiredButNotSupplied;
    this.tandemOptionalAndNotSupplied = options.tandemOptionalAndNotSupplied;
    this.enabled = options.enabled;
  }

  var staticInstances = [];

  tandemNamespace.register( 'Tandem', Tandem );

  // Listeners that will be notified when items are registered/deregistered
  var instanceListeners = [];

  inherit( Object, Tandem, {

    // @public (read-only) permit checking whether something is probably a Tandem instance for error checking
    isTandem: true,

    /**
     * Adds an instance of any type.  For example, it could be an axon Property, scenery Node or Sun button.  Each
     * item should only be added to the registry once, but that is not enforced here in Tandem.  For PhET-iO, phet-io.js
     * enforces one entry per ID in phetio.addInstance
     *
     * This is used to register instances with PhET-iO.
     * @param {Object} instance - the instance to add
     * @param {function} type - the PhET-iO type function, such as TString or TProperty(TNumber('volts'))
     * @public
     */
    addInstance: function( instance, type ) {

      if ( window.phet && window.phet.chipper && phet.chipper.brand === 'phet-io' && this.enabled ) {

        if ( phet.phetio.queryParameters.phetioValidateTandems ) {
          assert && assert( !this.tandemRequiredButNotSupplied, 'Tandem was required but not supplied' );
        }

        // validateTandems is false and printMissingTandems flag is present for a tandem that is required but not supplied.
        else if ( phet.phetio.queryParameters.printMissingTandems && this.tandemRequiredButNotSupplied ) {
          var stackTrace = new Error().stack;
          console.log( 'Required Tandem not supplied.\n' +
                       'this.id = ' + this.id + '\n' +
                       'Stack trace: ' + stackTrace );
        }

        if ( !type ) {
          console.log( 'Missing type declaration for ' + this.id );
        }

        // ifphetio returns a no-op function, so to test whether a valid T wrapper type was passed, we search for the typeName
        assert && assert( type && type.typeName, 'type must be specified and have a typeName' );

        if ( this.tandemOptionalAndNotSupplied ) {
          if ( phet.phetio.queryParameters.printMissingTandems ) {
            var stackTrace2 = new Error().stack;

            // Generally PhETFont is not desired because there are so many untandemized instances.
            if ( stackTrace2.indexOf( 'PhetFont' ) === -1 ) {
              console.log( 'Optional Tandem not supplied.\n' +
                           'this.id = ' + this.id + '\n' +
                           'Stack trace: ' + stackTrace2 );
            }
          }

          // For optionally instrumented types that are not provided tandems, the instance isn't really "added"
          // but likewise, it in not an error
          return;
        }

        if ( this.static && !launched ) {
          staticInstances.push( { tandem: this, instance: instance, type: type } );
        }
        else {
          for ( var i = 0; i < instanceListeners.length; i++ ) {
            instanceListeners[ i ].addInstance( this.id, instance, type );
          }
        }
      }
    },

    /**
     * Removes an instance from the
     * @param {Object} instance - the instance to remove
     * @public
     */
    removeInstance: function( instance ) {

      // Only active when running as phet-io
      if ( phet.chipper.brand === 'phet-io' && this.enabled ) {
        for ( var i = 0; i < instanceListeners.length; i++ ) {
          instanceListeners[ i ].removeInstance( this.id, instance );
        }
      }
    },

    /**
     * Create a new Tandem by appending the given id
     * @param {string} id
     * @param {Object} [options]
     * @returns {Tandem}
     * @public
     */
    createTandem: function( id, options ) {

      // Make sure the id was provided
      assert && assert( typeof id === 'string' && id.length > 0, 'id must be defined' );

      var string = ( this.id.length > 0 ) ? ( this.id + '.' + id ) : id;

      // Any child of something static is also static
      options = _.extend( { static: this.static, enabled: this.enabled }, options );

      return new Tandem( string, options );
    },

    /**
     * Creates a group tandem for creating multiple indexed child tandems, such as:
     * sim.screen.model.electron_0
     * sim.screen.model.electron_1
     *
     * In this case, 'sim.screen.model.electron' is the group tandem id.
     *
     * Used for arrays, observable arrays, or when many elements of the same type are created and they do not otherwise
     * have unique identifiers.
     * @param id
     * @returns {GroupTandem}
     */
    createGroupTandem: function( id ) {

      // Unfortunately we must resort to globals here since loading through the namespace would create a cycle
      return new GroupTandem( this.id + '.' + id );
    },

    /**
     * Get the last part of the tandem (after the last .), used in Joist for creating button names dynamically based
     * on screen names
     * @return {string} the tail of the tandem
     */
    get tail() {
      assert && assert( this.id.indexOf( '.' ) >= 0, 'tandem ID does not have a tail' );

      var lastIndexOfDot = this.id.lastIndexOf( '.' );
      var tail = this.id.substring( lastIndexOfDot + 1 );
      assert && assert( tail.length > 0, 'tandem ID did not have a tail' );
      return tail;
    },

    /**
     * Returns a Tandem for everything except the tail.
     * @returns {Tandem}
     */
    get parentTandem() {
      assert && assert( this.id.indexOf( '.' ) >= 0, 'tandem ID does not have a tail' );

      var lastIndexOfDot = this.id.lastIndexOf( '.' );
      var headID = this.id.substring( 0, lastIndexOfDot );

      return new Tandem( headID, {
        static: this.static,
        tandemRequiredButNotSupplied: this.tandemRequiredButNotSupplied,
        enabled: this.enabled
      } );
    },

    /**
     * When using subtyping, the instance listeners must only be notified once rather than once for every level
     * in the inheritance hierarchy.  When a subtype constructor has a tandem.addInstance call, it should
     * pass a supertype tandem to the parent constructor so that it won't try to register the item twice.
     * @returns {SupertypeTandem}
     */
    createSupertypeTandem: function() {
      return new SupertypeTandem( this.id );
    }
  }, {

    /**
     * Some common code (such as CheckBox or RadioButton) must always be instrumented and hence requires a tandem to be
     * passed in.  The options hash indicates this with {tandem: Tandem.tandemRequired()}
     * @returns {Tandem}
     */
    tandemRequired: function() {
      return rootTandem.createTandem( 'requiredTandem' + (requiredTandemIndex++), {
        tandemRequiredButNotSupplied: true // will be checked in addInstance
      } );
    },

    /**
     *
     * Used to indicate a common code component that supports tandem, but doesn't not require it.
     * If a tandem is not passed through to this instance, then it will not be instrumented.
     * @returns {Tandem}
     */
    tandemOptional: function() {
      return rootTandem.createTandem( 'optionalTandem' + (optionalTandemIndex++), {
        tandemOptionalAndNotSupplied: true
      } );
    },

    /**
     * Adds a listener that will be notified when items are registered/deregistered
     * Listeners have the form
     * {
     *   addInstance(id,instance),
     *   removeInstance(id,instance)
     * }
     * where id is of type {string} and instance is of type {Object}
     *
     * @param {Object} instanceListener - described above
     * @public
     * @static
     */
    addInstanceListener: function( instanceListener ) {
      instanceListeners.push( instanceListener );
    },

    /**
     * Create a tandem based on the name of the running simulation.
     * @returns {Tandem}
     */
    createRootTandem: function() {
      return new Tandem( toCamelCase( packageJSON.name ) );
    },

    /**
     * Create a child of the root tandem.
     * @param {string} name
     * @returns {Tandem}
     */
    createStaticTandem: function( name ) {
      return Tandem.createRootTandem().createTandem( name, { static: true } );
    },

    /**
     * When the simulation is launched, all static instances are registered.
     */
    launch: function() {
      assert && assert( !launched, 'Tandem was launched twice' );
      launched = true;
      while ( staticInstances.length > 0 ) {
        var staticInstance = staticInstances.shift();
        staticInstance.tandem.addInstance( staticInstance.instance, staticInstance.type );
      }
    },

    /**
     * Catch cases where tandem is being supplied to a class that doesn't support tandem.
     * @param options
     */
    disallowTandem: function( options ) {

      if ( window.phet && window.phet.chipper && phet.chipper.brand === 'phet-io' &&
           phet.phetio && phet.phetio.queryParameters && phet.phetio.queryParameters.phetioValidateTandems ) {
        assert && assert( !options.tandem, 'tandem is not allowed' );
      }
    },

    /**
     * When running in PhET-iO brand, some code (such as user interface components) must be instrumented for PhET-iO.
     * Uninstrumented files should call this function to indicate they still need to be instrumented, so they aren't
     * missed.  See https://github.com/phetsims/phet-io/issues/668
     */
    indicateUninstrumentedCode: function() {

      // Guard against undefined errors
      if ( window.phet && window.phet.chipper && phet.chipper.brand === 'phet-io' &&
           phet.phetio && phet.phetio.queryParameters ) {

        // Assert if validating tandems
        if ( phet.phetio.queryParameters.phetioValidateTandems ) {
          assert && assert( false, 'Uninstrumented code detected' );
        }

        // Print stack trace if query parameter supplied
        if ( phet.phetio.queryParameters.printMissingTandems ) {
          var stackTrace = new Error().stack;
          console.log( 'Uninstrumented Code! Tandem not supplied: ' +  (uninstrumentCodeIndex ++) + '.\n' +
                       'Stack trace: ' + stackTrace );
        }

      }
    }
  } );

  var rootTandem = Tandem.createRootTandem();

  // Tandem checks for listeners added before the Tandem module was loaded.  This is necessary so that phetio.js can
  // receive notifications about items created during static initialization such as Solute.js
  // which is created before Sim.js runs.
  if ( window.tandemPreloadInstanceListeners ) {
    for ( var i = 0; i < window.tandemPreloadInstanceListeners.length; i++ ) {
      Tandem.addInstanceListener( window.tandemPreloadInstanceListeners[ i ] );
    }
  }

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createGroupTandem
   * Declared in the same file to avoid circular reference errors in module loading.
   */
  function GroupTandem( id ) {

    Tandem.call( this, id );

    // @private for generating indices from a pool
    this.groupElementIndex = 0;
  }

  tandemNamespace.register( 'Tandem.GroupTandem', GroupTandem );

  inherit( Tandem, GroupTandem, {

    createNextTandem: function() {
      return new Tandem( this.id + '_' + ( this.groupElementIndex++ ) );
    }
  } );

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createSupertypeTandem
   */
  function SupertypeTandem( id ) {
    Tandem.call( this, id );
  }

  tandemNamespace.register( 'Tandem.SupertypeTandem', SupertypeTandem );

  inherit( Tandem, SupertypeTandem, {

    // @public - Override to make no-op, see createSupertypeTandem
    addInstance: function( instance, type ) {},

    // @public - Override to make no-op, see createSupertypeTandem
    removeInstance: function( instance ) {}
  } );

  return Tandem;
} );

<|endoftext|>


<commit_before>var fs = require('fs');
var pa = require('path');
var spawn = require('child_process').spawn;
var ss = require('socket.io-stream');

var server;
var express;

var VERSION = '0.1.2';

var NODE = 'node';
var PATH = __dirname;
var LOCAL = PATH + '/local/';
var USER_DIR = PATH + '/user/';
var PASS_FILE = PATH + '/etc/passwd.json';

var PASS = JSON.parse(fs.readFileSync(PASS_FILE, 'utf-8'));

var clearSreen = '\033[2J\033[1;1H';

var io;

var tasks = {};

var cleanupTask = function (pid) {
  'use strict';
  delete tasks[pid].task;
  delete tasks[pid].pid;
  delete tasks[pid].child;
  delete tasks[pid].status;
  delete tasks[pid].stdout;
  delete tasks[pid].stderr;
  delete tasks[pid];
};

var tasksInformation = function () {
  'use strict';
  var total = 0;
  var exits = 0;
  var errors = 0;

  for (var task in tasks) {
    if (tasks.hasOwnProperty(task)) {
      ++total;
      var status = tasks[task].status;

      if (status === 'EXIT') {
        ++exits;
      }
      else if (status === 'ERROR') {
        ++errors;
      }
    }
  }

  return '+' + total + '/' + exits + '/' + errors + '+';
};

var queueJob = function (cmd, data, offset, socket) {
  'use strict';

  process.nextTick(function () {
    var child = spawn(NODE, cmd, {cwd: data.cwd});
    var tokens = cmd[0].split('/');
    var isRunCommand = tokens[tokens.length - offset - 2] === 'user';
    // basis.log('Worker: '.blue + child.pid +
    //           '\nCommand:\n'.blue + data.command.trim());

    tasks[child.pid] = {};
    tasks[child.pid].child = child;
    tasks[child.pid].pid = child.pid;
    tasks[child.pid].status = 'RUNNING';
    tasks[child.pid].stdout = '';
    tasks[child.pid].stderr = '';
    tasks[child.pid].task = data.command.trim();

    child.stdout.on('data', function (data) {
      if (isRunCommand) {
         tasks[child.pid].stdout += data.toString();
      } else {
        socket.emit('stdout', data.toString());
      }
    });

    child.stderr.on('data', function (data) {
      if (isRunCommand) {
        tasks[child.pid].status = 'ERROR';
        tasks[child.pid].stderr += data.toString();
      } else {
        socket.emit('stderr', data.toString());
      }
    });

    child.on('exit', function () {
      if (isRunCommand) {
        // var msg = 'Task ' + child.pid + ' is done.';
        // socket.emit('task.notify', msg.cyan);
        tasks[child.pid].status = 'EXIT';
      } else {
        cleanupTask(child.pid);
        socket.emit('ok', tasksInformation());
      }
      // socket.emit('ok');
    });

    if (isRunCommand) {
      socket.emit('ok', tasksInformation());
    }
  });
};

var exec = function (data, socket) {
  'use strict';

  var cmd = data.command.trim().split(' ');
  var runFlag = false;

  var offset = 0;
  if (cmd[1]) {
    offset = cmd[1].split('/').length - 1;
  }

  // if run package, remove first cmd and add USER_DIR prefix
  if (cmd[0] === 'run') {
    cmd = cmd.slice(1);
    cmd[0] = USER_DIR + cmd[0];
    runFlag = true;
  }

  // add prefix if no directories specified
  var needPrefix = !~cmd[0].indexOf('/') || !runFlag;
  if (needPrefix) {
    cmd[0] = LOCAL + cmd[0];
  }

  // queue a job for nextTick
  queueJob(cmd, data, offset, socket);
};

exports.__require = function (data) {
  'use strict';
  server = data.httpd;
  express = data.http;

  // apis
  // show all tasks
  express.get('/hypernode/tasks', function (req, res) {
    var json = {};
    var count = 0;
    for (var task in tasks) {
      if (tasks.hasOwnProperty(task)) {
        json[count] = {};
        json[count].pid = tasks[task].pid;
        json[count].task = tasks[task].task;
        json[count].status = tasks[task].status;
        ++count;
      }
    }
    json = JSON.stringify(json);
    res.writeHead(200, {'Content-Type': 'application/json'});
    res.write(json);
    res.end();
  });

  // get one task
  express.get('/hypernode/task/:pid', function (req, res) {
    var pid = req.params.pid;
    var msg;
    if (tasks[pid]) {
      var json = {};
      json.pid = pid;
      json.task = tasks[pid].task;
      json.status = tasks[pid].status;
      var stdout = tasks[pid].stdout;
      stdout.replace(/\"/g, '\\"');
      stdout.replace(/\n/g, '\\n');
      json.stdout = stdout;

      var stderr = tasks[pid].stderr;
      stderr.replace(/\"/g, '\\"');
      stderr.replace(/\n/g, '\\n');
      json.stderr = stderr;
      msg = JSON.stringify(json);
    } else {
      msg = '{"code": 404, "information": "No such task."}';
    }
    res.writeHead(200, {'Content-Type': 'application/json'});
    res.write(msg);
    res.end();
  });

  // socket.io
  io = require('socket.io').listen(server);

  io.sockets.on('connection', function (socket) {
    // ask client to drop connection due to wrong password
    socket.on('login', function (data) {
      if (data.password !== PASS[data.user]) {
        socket.emit('terminate');
      } else {
        socket.emit('stdout', clearSreen);
        socket.emit('stdout', 'HyperNode Cloud Environment (version: ' + VERSION
                    + ')\nWelcome, ' + data.user + '!\nServer time: ' +
                      (new Date()).toString() +
                      '\nType "help" for user manual.\n');
        socket.emit('ok.login', USER_DIR, tasksInformation());
      }
    });

    socket.on('cmd', function (data) {
      exec(data, socket);
    });

    socket.on('empty', function () {
      socket.emit('ok', tasksInformation());
    });

    socket.on('task.kill', function (pid) {
      if (tasks[pid]) {
        if (tasks[pid].status !== 'EXIT') {
          tasks[pid].child.kill();
        } else {
          cleanupTask(pid);
        }
      }
      socket.emit('ok', tasksInformation());
    });

    socket.on('task.stdout', function (pid) {
      if (tasks[pid]) {
        socket.emit('stdout', tasks[pid].stdout);
        socket.emit('ok', tasksInformation());
      }
    });

    socket.on('task.stderr', function (pid) {
      if (tasks[pid]) {
        var msg = tasks[pid].stderr;
        if (msg.length !== 0) {
          socket.emit('stdout', tasks[pid].stdout);
        }
        socket.emit('ok', tasksInformation());
      }
    });

    socket.on('task.all', function () {
      var msg = 'All tasks:\n';
      var count = 1;
      for (var task in tasks) {
        if (tasks.hasOwnProperty(task)) {
          console.log(task);
          msg += count + '\t' + tasks[task].pid + '\t' +
            tasks[task].task + '\t[' + tasks[task].status + ']\n';
          ++count;
        }
      }
      socket.emit('stdout', msg);
      socket.emit('ok', tasksInformation());
    });

    // add socket.io-steam for file uploading
    ss(socket).on('push', function (stream, data) {
      var filename = USER_DIR + pa.basename(data.name);
      stream.pipe(fs.createWriteStream(filename));
      socket.emit('ok', tasksInformation());
    });

  });
};
<commit_msg>Added HATEOAS<commit_after>var fs = require('fs');
var pa = require('path');
var spawn = require('child_process').spawn;
var ss = require('socket.io-stream');

var server;
var express;

var VERSION = '0.1.2';

var NODE = 'node';
var PATH = __dirname;
var LOCAL = PATH + '/local/';
var USER_DIR = PATH + '/user/';
var PASS_FILE = PATH + '/etc/passwd.json';

var PASS = JSON.parse(fs.readFileSync(PASS_FILE, 'utf-8'));

var clearSreen = '\033[2J\033[1;1H';

var io;

var tasks = {};

var cleanupTask = function (pid) {
  'use strict';
  delete tasks[pid].task;
  delete tasks[pid].pid;
  delete tasks[pid].child;
  delete tasks[pid].status;
  delete tasks[pid].stdout;
  delete tasks[pid].stderr;
  delete tasks[pid];
};

var tasksInformation = function () {
  'use strict';
  var total = 0;
  var exits = 0;
  var errors = 0;

  for (var task in tasks) {
    if (tasks.hasOwnProperty(task)) {
      ++total;
      var status = tasks[task].status;

      if (status === 'EXIT') {
        ++exits;
      }
      else if (status === 'ERROR') {
        ++errors;
      }
    }
  }

  return '+' + total + '/' + exits + '/' + errors + '+';
};

var queueJob = function (cmd, data, offset, socket) {
  'use strict';

  process.nextTick(function () {
    var child = spawn(NODE, cmd, {cwd: data.cwd});
    var tokens = cmd[0].split('/');
    var isRunCommand = tokens[tokens.length - offset - 2] === 'user';
    // basis.log('Worker: '.blue + child.pid +
    //           '\nCommand:\n'.blue + data.command.trim());

    tasks[child.pid] = {};
    tasks[child.pid].child = child;
    tasks[child.pid].pid = child.pid;
    tasks[child.pid].status = 'RUNNING';
    tasks[child.pid].stdout = '';
    tasks[child.pid].stderr = '';
    tasks[child.pid].task = data.command.trim();

    child.stdout.on('data', function (data) {
      if (isRunCommand) {
         tasks[child.pid].stdout += data.toString();
      } else {
        socket.emit('stdout', data.toString());
      }
    });

    child.stderr.on('data', function (data) {
      if (isRunCommand) {
        tasks[child.pid].status = 'ERROR';
        tasks[child.pid].stderr += data.toString();
      } else {
        socket.emit('stderr', data.toString());
      }
    });

    child.on('exit', function () {
      if (isRunCommand) {
        // var msg = 'Task ' + child.pid + ' is done.';
        // socket.emit('task.notify', msg.cyan);
        tasks[child.pid].status = 'EXIT';
      } else {
        cleanupTask(child.pid);
        socket.emit('ok', tasksInformation());
      }
      // socket.emit('ok');
    });

    if (isRunCommand) {
      socket.emit('ok', tasksInformation());
    }
  });
};

var exec = function (data, socket) {
  'use strict';

  var cmd = data.command.trim().split(' ');
  var runFlag = false;

  var offset = 0;
  if (cmd[1]) {
    offset = cmd[1].split('/').length - 1;
  }

  // if run package, remove first cmd and add USER_DIR prefix
  if (cmd[0] === 'run') {
    cmd = cmd.slice(1);
    cmd[0] = USER_DIR + cmd[0];
    runFlag = true;
  }

  // add prefix if no directories specified
  var needPrefix = !~cmd[0].indexOf('/') || !runFlag;
  if (needPrefix) {
    cmd[0] = LOCAL + cmd[0];
  }

  // queue a job for nextTick
  queueJob(cmd, data, offset, socket);
};

exports.__require = function (data) {
  'use strict';
  server = data.httpd;
  express = data.http;

  // apis
  // show all tasks
  express.get('/hypernode/tasks', function (req, res) {
    var json = {};
    var count = 0;
    for (var task in tasks) {
      if (tasks.hasOwnProperty(task)) {
        json[count] = {};
        json[count].pid = tasks[task].pid;
        json[count].task = tasks[task].task;
        json[count].status = tasks[task].status;
        json[count].runtime = {};
        json[count].runtime.href = '/hypernode/task/' + json[count].pid;
        ++count;
      }
    }

    if (count === 0) {
      json.code = 302;
      json.information = 'No scheduled tasks.';
    }

    json = JSON.stringify(json);
    res.writeHead(200, {'Content-Type': 'application/json'});
    res.write(json);
    res.end();
  });

  // get one task
  express.get('/hypernode/task/:pid', function (req, res) {
    var pid = req.params.pid;
    var msg;
    if (tasks[pid]) {
      var json = {};
      json.code = 200;
      json.pid = pid;
      json.task = tasks[pid].task;
      json.status = tasks[pid].status;
      var stdout = tasks[pid].stdout;
      stdout.replace(/\"/g, '\\"');
      stdout.replace(/\n/g, '\\n');
      json.stdout = stdout;

      var stderr = tasks[pid].stderr;
      stderr.replace(/\"/g, '\\"');
      stderr.replace(/\n/g, '\\n');
      json.stderr = stderr;
      msg = JSON.stringify(json);
    } else {
      msg = '{"code": 404, "information": "No such task."}';
    }
    res.writeHead(200, {'Content-Type': 'application/json'});
    res.write(msg);
    res.end();
  });

  // socket.io
  io = require('socket.io').listen(server);

  io.sockets.on('connection', function (socket) {
    // ask client to drop connection due to wrong password
    socket.on('login', function (data) {
      if (data.password !== PASS[data.user]) {
        socket.emit('terminate');
      } else {
        socket.emit('stdout', clearSreen);
        socket.emit('stdout', 'HyperNode Cloud Environment (version: ' + VERSION
                    + ')\nWelcome, ' + data.user + '!\nServer time: ' +
                      (new Date()).toString() +
                      '\nType "help" for user manual.\n');
        socket.emit('ok.login', USER_DIR, tasksInformation());
      }
    });

    socket.on('cmd', function (data) {
      exec(data, socket);
    });

    socket.on('empty', function () {
      socket.emit('ok', tasksInformation());
    });

    socket.on('task.kill', function (pid) {
      if (tasks[pid]) {
        if (tasks[pid].status !== 'EXIT') {
          tasks[pid].child.kill();
        } else {
          cleanupTask(pid);
        }
      }
      socket.emit('ok', tasksInformation());
    });

    socket.on('task.stdout', function (pid) {
      if (tasks[pid]) {
        socket.emit('stdout', tasks[pid].stdout);
        socket.emit('ok', tasksInformation());
      }
    });

    socket.on('task.stderr', function (pid) {
      if (tasks[pid]) {
        var msg = tasks[pid].stderr;
        if (msg.length !== 0) {
          socket.emit('stdout', tasks[pid].stdout);
        }
        socket.emit('ok', tasksInformation());
      }
    });

    socket.on('task.all', function () {
      var msg = 'All tasks:\n';
      var count = 1;
      for (var task in tasks) {
        if (tasks.hasOwnProperty(task)) {
          console.log(task);
          msg += count + '\t' + tasks[task].pid + '\t' +
            tasks[task].task + '\t[' + tasks[task].status + ']\n';
          ++count;
        }
      }
      socket.emit('stdout', msg);
      socket.emit('ok', tasksInformation());
    });

    // add socket.io-steam for file uploading
    ss(socket).on('push', function (stream, data) {
      var filename = USER_DIR + pa.basename(data.name);
      stream.pipe(fs.createWriteStream(filename));
      socket.emit('ok', tasksInformation());
    });

  });
};
<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Tandem is a general instance registry that can be used to track creation/disposal of instances in PhET Simulations.
 * It is used for phetio.js instrumentation for PhET-iO support.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var tandemNamespace = require( 'TANDEM/tandemNamespace' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );

  // text
  var packageString = require( 'text!REPOSITORY/package.json' );

  // constants
  var packageJSON = JSON.parse( packageString );

  // variables
  var launched = false;

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @param {Object} [options]
   * @constructor
   */
  function Tandem( id, options ) {

    options = _.extend( { static: false }, options );

    // @public {read-only}
    this.id = (id !== undefined) ? id : '';
    this.static = options.static;
  }

  var staticInstances = [];

  tandemNamespace.register( 'Tandem', Tandem );

  // Listeners that will be notified when items are registered/deregistered
  var instanceListeners = [];

  inherit( Object, Tandem, {

    // @public (read-only) permit checking whether something is probably a Tandem instance for error checking
    isTandem: true,

    /**
     * Adds an instance of any type.  For example, it could be an axon Property, scenery Node or Sun button.  Each
     * item should only be added to the registry once, but that is not enforced here in Tandem.
     *
     * This is used to register instances with PhET-iO.
     * @param {Object} instance - the instance to add
     * @param {function} type - the PhET-iO type function, such as TString or TProperty(TNumber('volts'))
     * @public
     */
    addInstance: function( instance, type ) {

      // TODO: When all sims are re-instrumented with types, remove the phet.chipper.brand === 'phet-io' && part
      phet.chipper.brand === 'phet-io' && assert && assert( !!type, 'type must be specified' );
      if ( this.static && !launched ) {
        staticInstances.push( { tandem: this, instance: instance, type: type } );
      }
      else {
        for ( var i = 0; i < instanceListeners.length; i++ ) {
          instanceListeners[ i ].addInstance( this.id, instance, type );
        }
      }
    },

    /**
     * Removes an instance from the
     * @param {Object} instance - the instance to remove
     * @public
     */
    removeInstance: function( instance ) {
      for ( var i = 0; i < instanceListeners.length; i++ ) {
        instanceListeners[ i ].removeInstance( this.id, instance );
      }
    },

    /**
     * Create a new Tandem by appending the given id
     * @param {string} id
     * @param {Object} [options]
     * @returns {Tandem}
     * @public
     */
    createTandem: function( id, options ) {
      var string = (this.id.length > 0) ? (this.id + '.' + id) : id;

      // Any child of something static is also static
      options = _.extend( { static: this.static }, options );

      return new Tandem( string, options );
    },

    /**
     * Creates a group tandem for creating multiple indexed child tandems, such as:
     * sim.screen.model.electron_0
     * sim.screen.model.electron_1
     *
     * In this case, 'sim.screen.model.electron' is the group tandem id.
     *
     * Used for arrays, observable arrays, or when many elements of the same type are created and they do not otherwise
     * have unique identifiers.
     * @param id
     * @returns {GroupTandem}
     */
    createGroupTandem: function( id ) {

      // Unfortunately we must resort to globals here since loading through the namespace would create a cycle
      return new GroupTandem( this.id + '.' + id );
    },

    /**
     * Get the last part of the tandem (after the last .), used in Joist for creating button names dynamically based
     * on screen names
     * @return {string} the tail of the tandem
     */
    get tail() {
      assert && assert( this.id.indexOf( '.' ) >= 0, 'tandem ID does not have a tail' );

      var lastIndexOfDot = this.id.lastIndexOf( '.' );
      var tail = this.id.substring( lastIndexOfDot + 1 );
      assert && assert( tail.length > 0, 'tandem ID did not have a tail' );
      return tail;
    },

    /**
     * When using subtyping, the instance listeners must only be notified once rather than once for every level
     * in the inheritance hierarchy.  When a subtype constructor has a tandem.addInstance call, it should
     * pass a supertype tandem to the parent constructor so that it won't try to register the item twice.
     * @returns {SupertypeTandem}
     */
    createSupertypeTandem: function() {
      return new SupertypeTandem( this.id );
    }
  }, {

    /**
     * Adds a listener that will be notified when items are registered/deregistered
     * Listeners have the form
     * {
     *   addInstance(id,instance),
     *   removeInstance(id,instance)
     * }
     * where id is of type {string} and instance is of type {Object}
     *
     * @param {Object} instanceListener - described above
     * @public
     * @static
     */
    addInstanceListener: function( instanceListener ) {
      instanceListeners.push( instanceListener );
    },

    /**
     * Create a tandem based on the name of the running simulation.
     * @returns {Tandem}
     */
    createRootTandem: function() {
      return new Tandem( StringUtils.toCamelCase( packageJSON.name ) );
    },

    /**
     * Create a child of the root tandem.
     * @param {string} name
     * @returns {Tandem}
     */
    createStaticTandem: function( name ) {
      return Tandem.createRootTandem().createTandem( name, { static: true } );
    },

    /**
     * When the simulation is launched, all static instances are registered.
     */
    launch: function() {
      assert && assert( !launched, 'Tandem was launched twice' );
      launched = true;
      while ( staticInstances.length > 0 ) {
        var staticInstance = staticInstances.shift();
        staticInstance.tandem.addInstance( staticInstance.instance, staticInstance.type );
      }
    },

    /**
     * When running as phet-io, User Interface components must be registered with tandem.  This function checks the
     * options to make sure tandem exists.  See https://github.com/phetsims/phet-io/issues/77
     * @param options
     */
    validateOptions: function( options ) {
      if ( phet.chipper.brand === 'phet-io' ) {
        assert && assert( options.tandem, 'When running as PhET-iO, a tandem must be specified for each user interface component' );
      }
    }
  } );

  // Tandem checks for listeners added before the Tandem module was loaded.  This is necessary so that phetio.js can
  // receive notifications about items created during static initialization such as Solute.js
  // which is created before Sim.js runs.
  if ( window.tandemPreloadInstanceListeners ) {
    for ( var i = 0; i < window.tandemPreloadInstanceListeners.length; i++ ) {
      Tandem.addInstanceListener( window.tandemPreloadInstanceListeners[ i ] );
    }
  }

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createGroupTandem
   * Declared in the same file to avoid circular reference errors in module loading.
   */
  function GroupTandem( id ) {

    Tandem.call( this, id );

    // @private for generating indices from a pool
    this.groupElementIndex = 0;
  }

  tandemNamespace.register( 'Tandem.GroupTandem', GroupTandem );

  inherit( Tandem, GroupTandem, {

      /**
       * @param [id] {string} optional override, used when loading a state and the tandems must be restored exactly as
       * they were saved
       * @returns {Tandem}
       */
      createSpecificTandem: function( id ) {
        return new Tandem( this.id + '_' + id );
      },

      createNextTandem: function() {
        return new Tandem( this.id + '_' + (this.groupElementIndex++) );
      }
    }
  );

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createSupertypeTandem
   */
  function SupertypeTandem( id ) {
    Tandem.call( this, id );
  }

  tandemNamespace.register( 'Tandem.SupertypeTandem', SupertypeTandem );

  inherit( Tandem, SupertypeTandem, {

      // @public - Override to make no-op, see createSupertypeTandem
    addInstance: function( instance, type ) {},

      // @public - Override to make no-op, see createSupertypeTandem
    removeInstance: function( instance, type ) {}
    }
  );

  return Tandem;
} );<commit_msg>Updated docs, see https://github.com/phetsims/phet-io/issues/523<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Tandem is a general instance registry that can be used to track creation/disposal of instances in PhET Simulations.
 * It is used for phetio.js instrumentation for PhET-iO support.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var tandemNamespace = require( 'TANDEM/tandemNamespace' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );

  // text
  var packageString = require( 'text!REPOSITORY/package.json' );

  // constants
  var packageJSON = JSON.parse( packageString );

  // variables
  var launched = false;

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @param {Object} [options]
   * @constructor
   */
  function Tandem( id, options ) {

    options = _.extend( { static: false }, options );

    // @public {read-only}
    this.id = (id !== undefined) ? id : '';
    this.static = options.static;
  }

  var staticInstances = [];

  tandemNamespace.register( 'Tandem', Tandem );

  // Listeners that will be notified when items are registered/deregistered
  var instanceListeners = [];

  inherit( Object, Tandem, {

    // @public (read-only) permit checking whether something is probably a Tandem instance for error checking
    isTandem: true,

    /**
     * Adds an instance of any type.  For example, it could be an axon Property, scenery Node or Sun button.  Each
     * item should only be added to the registry once, but that is not enforced here in Tandem.  For PhET-iO, phet-io.js
     * enforces one entry per ID in phetio.addInstance
     *
     * This is used to register instances with PhET-iO.
     * @param {Object} instance - the instance to add
     * @param {function} type - the PhET-iO type function, such as TString or TProperty(TNumber('volts'))
     * @public
     */
    addInstance: function( instance, type ) {

      // TODO: When all sims are re-instrumented with types, remove the phet.chipper.brand === 'phet-io' && part
      phet.chipper.brand === 'phet-io' && assert && assert( !!type, 'type must be specified' );
      if ( this.static && !launched ) {
        staticInstances.push( { tandem: this, instance: instance, type: type } );
      }
      else {
        for ( var i = 0; i < instanceListeners.length; i++ ) {
          instanceListeners[ i ].addInstance( this.id, instance, type );
        }
      }
    },

    /**
     * Removes an instance from the
     * @param {Object} instance - the instance to remove
     * @public
     */
    removeInstance: function( instance ) {
      for ( var i = 0; i < instanceListeners.length; i++ ) {
        instanceListeners[ i ].removeInstance( this.id, instance );
      }
    },

    /**
     * Create a new Tandem by appending the given id
     * @param {string} id
     * @param {Object} [options]
     * @returns {Tandem}
     * @public
     */
    createTandem: function( id, options ) {
      var string = (this.id.length > 0) ? (this.id + '.' + id) : id;

      // Any child of something static is also static
      options = _.extend( { static: this.static }, options );

      return new Tandem( string, options );
    },

    /**
     * Creates a group tandem for creating multiple indexed child tandems, such as:
     * sim.screen.model.electron_0
     * sim.screen.model.electron_1
     *
     * In this case, 'sim.screen.model.electron' is the group tandem id.
     *
     * Used for arrays, observable arrays, or when many elements of the same type are created and they do not otherwise
     * have unique identifiers.
     * @param id
     * @returns {GroupTandem}
     */
    createGroupTandem: function( id ) {

      // Unfortunately we must resort to globals here since loading through the namespace would create a cycle
      return new GroupTandem( this.id + '.' + id );
    },

    /**
     * Get the last part of the tandem (after the last .), used in Joist for creating button names dynamically based
     * on screen names
     * @return {string} the tail of the tandem
     */
    get tail() {
      assert && assert( this.id.indexOf( '.' ) >= 0, 'tandem ID does not have a tail' );

      var lastIndexOfDot = this.id.lastIndexOf( '.' );
      var tail = this.id.substring( lastIndexOfDot + 1 );
      assert && assert( tail.length > 0, 'tandem ID did not have a tail' );
      return tail;
    },

    /**
     * When using subtyping, the instance listeners must only be notified once rather than once for every level
     * in the inheritance hierarchy.  When a subtype constructor has a tandem.addInstance call, it should
     * pass a supertype tandem to the parent constructor so that it won't try to register the item twice.
     * @returns {SupertypeTandem}
     */
    createSupertypeTandem: function() {
      return new SupertypeTandem( this.id );
    }
  }, {

    /**
     * Adds a listener that will be notified when items are registered/deregistered
     * Listeners have the form
     * {
     *   addInstance(id,instance),
     *   removeInstance(id,instance)
     * }
     * where id is of type {string} and instance is of type {Object}
     *
     * @param {Object} instanceListener - described above
     * @public
     * @static
     */
    addInstanceListener: function( instanceListener ) {
      instanceListeners.push( instanceListener );
    },

    /**
     * Create a tandem based on the name of the running simulation.
     * @returns {Tandem}
     */
    createRootTandem: function() {
      return new Tandem( StringUtils.toCamelCase( packageJSON.name ) );
    },

    /**
     * Create a child of the root tandem.
     * @param {string} name
     * @returns {Tandem}
     */
    createStaticTandem: function( name ) {
      return Tandem.createRootTandem().createTandem( name, { static: true } );
    },

    /**
     * When the simulation is launched, all static instances are registered.
     */
    launch: function() {
      assert && assert( !launched, 'Tandem was launched twice' );
      launched = true;
      while ( staticInstances.length > 0 ) {
        var staticInstance = staticInstances.shift();
        staticInstance.tandem.addInstance( staticInstance.instance, staticInstance.type );
      }
    },

    /**
     * When running as phet-io, User Interface components must be registered with tandem.  This function checks the
     * options to make sure tandem exists.  See https://github.com/phetsims/phet-io/issues/77
     * @param options
     */
    validateOptions: function( options ) {
      if ( phet.chipper.brand === 'phet-io' ) {
        assert && assert( options.tandem, 'When running as PhET-iO, a tandem must be specified for each user interface component' );
      }
    }
  } );

  // Tandem checks for listeners added before the Tandem module was loaded.  This is necessary so that phetio.js can
  // receive notifications about items created during static initialization such as Solute.js
  // which is created before Sim.js runs.
  if ( window.tandemPreloadInstanceListeners ) {
    for ( var i = 0; i < window.tandemPreloadInstanceListeners.length; i++ ) {
      Tandem.addInstanceListener( window.tandemPreloadInstanceListeners[ i ] );
    }
  }

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createGroupTandem
   * Declared in the same file to avoid circular reference errors in module loading.
   */
  function GroupTandem( id ) {

    Tandem.call( this, id );

    // @private for generating indices from a pool
    this.groupElementIndex = 0;
  }

  tandemNamespace.register( 'Tandem.GroupTandem', GroupTandem );

  inherit( Tandem, GroupTandem, {

      /**
       * @param [id] {string} optional override, used when loading a state and the tandems must be restored exactly as
       * they were saved
       * @returns {Tandem}
       */
      createSpecificTandem: function( id ) {
        return new Tandem( this.id + '_' + id );
      },

      createNextTandem: function() {
        return new Tandem( this.id + '_' + (this.groupElementIndex++) );
      }
    }
  );

  /**
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createSupertypeTandem
   */
  function SupertypeTandem( id ) {
    Tandem.call( this, id );
  }

  tandemNamespace.register( 'Tandem.SupertypeTandem', SupertypeTandem );

  inherit( Tandem, SupertypeTandem, {

      // @public - Override to make no-op, see createSupertypeTandem
    addInstance: function( instance, type ) {},

      // @public - Override to make no-op, see createSupertypeTandem
    removeInstance: function( instance, type ) {}
    }
  );

  return Tandem;
} );<|endoftext|>


<commit_before>const _ = require('lodash/fp');
const XLSX = require('xlsx');
const Workbook = require('workbook');

function payrollPaidRows(payroll) {
  const users = Object.keys(payroll.entries);
  const headRow = [
    'Username',
    'HR Number',
    'Name',
    'Paid (euros)'
  ];
  const userRows = users.map(username => {
    const user = payroll.entries[username];
    return [
      username,
      _.get('user.hr_number', user) || '',
      _.get('user.first_name', user) + ' ' + _.get('user.last_name', user) || '',
      parseInt(user.payout ? user.payout.toString().slice(0, -6) : '', 10)
    ];
  });
  const sortedRows = _.sortBy(row => row[1] || Infinity, userRows);
  return [ headRow ].concat(sortedRows);
}

function payrollHistory(payroll) {
  const events = payroll.events;
  const headRow = [
    'Event type',
    'Username',
    'Description',
    'Duration (hours)',
    'Payout (euros)'
  ];
  const eventRows = events.map(event => {
    console.log(event);
    return [
      event.event,
      _.get('args.info', event) || '',
      _.get('args.description', event) || '',
      parseInt(event.args.duration ? event.args.duration.toString() : '', 10) / 3600 || '',
      parseInt(event.args.payout ? event.args.payout.toString().slice(0, -6) : '', 10) || ''
    ];
  });
  return [ headRow ].concat(eventRows);
}

function payrollToExcel(payroll) {
  const workbook = new Workbook();
  workbook.addRowsToSheet('Payroll', payrollPaidRows(payroll));
  workbook.setColWidthChars('Payroll', 0, 10);
  workbook.setColWidthChars('Payroll', 1, 10);
  workbook.setColWidthChars('Payroll', 2, 20);
  workbook.setColWidthChars('Payroll', 3, 10);
  workbook.addRowsToSheet('Events', payrollHistory(payroll));
  workbook.setColWidthChars('Events', 0, 20);
  workbook.setColWidthChars('Events', 2, 20);
  workbook.setColWidthChars('Events', 3, 15);
  workbook.setColWidthChars('Events', 4, 15);
  workbook.finalize();

  const wopts = { bookType: 'xlsx', type: 'buffer' };
  return XLSX.write(workbook, wopts);
}

exports.payrollToExcel = payrollToExcel;
<commit_msg>Never show NaN in payout column.<commit_after>const _ = require('lodash/fp');
const XLSX = require('xlsx');
const Workbook = require('workbook');

function payrollPaidRows(payroll) {
  const users = Object.keys(payroll.entries);
  const headRow = [
    'Username',
    'HR Number',
    'Name',
    'Paid (euros)'
  ];
  const userRows = users.map(username => {
    const user = payroll.entries[username];
    return [
      username,
      _.get('user.hr_number', user) || '',
      _.get('user.first_name', user) + ' ' + _.get('user.last_name', user) || '',
      parseInt(user.payout ? user.payout.toString().slice(0, -6) : '', 10) || ''
    ];
  });
  const sortedRows = _.sortBy(row => row[1] || Infinity, userRows);
  return [ headRow ].concat(sortedRows);
}

function payrollHistory(payroll) {
  const events = payroll.events;
  const headRow = [
    'Event type',
    'Username',
    'Description',
    'Duration (hours)',
    'Payout (euros)'
  ];
  const eventRows = events.map(event => {
    console.log(event);
    return [
      event.event,
      _.get('args.info', event) || '',
      _.get('args.description', event) || '',
      parseInt(event.args.duration ? event.args.duration.toString() : '', 10) / 3600 || '',
      parseInt(event.args.payout ? event.args.payout.toString().slice(0, -6) : '', 10) || ''
    ];
  });
  return [ headRow ].concat(eventRows);
}

function payrollToExcel(payroll) {
  const workbook = new Workbook();
  workbook.addRowsToSheet('Payroll', payrollPaidRows(payroll));
  workbook.setColWidthChars('Payroll', 0, 10);
  workbook.setColWidthChars('Payroll', 1, 10);
  workbook.setColWidthChars('Payroll', 2, 20);
  workbook.setColWidthChars('Payroll', 3, 10);
  workbook.addRowsToSheet('Events', payrollHistory(payroll));
  workbook.setColWidthChars('Events', 0, 20);
  workbook.setColWidthChars('Events', 2, 20);
  workbook.setColWidthChars('Events', 3, 15);
  workbook.setColWidthChars('Events', 4, 15);
  workbook.finalize();

  const wopts = { bookType: 'xlsx', type: 'buffer' };
  return XLSX.write(workbook, wopts);
}

exports.payrollToExcel = payrollToExcel;
<|endoftext|>


<commit_before>function message(status, shake=false, id="") {
  if (shake) {
    $("#"+id).effect("shake", {direction: "right", times: 2, distance: 8}, 250)
  }
  document.getElementById('feedback').innerHTML = status
  $("#feedback").show().delay(2000).fadeOut()
}

function error(type) {
  $("."+type).css("border-color", "#E14448")
}

var login = function() {
  $.post({
    type: "POST",
    url: "/",
    data: {"username": $("#login-user").val(),
           "password": $("#login-pass").val()},
    success: function(response){
      var status = JSON.parse(response)['status']
      if (status === 'Login successful') {location.reload()}
      else {error('login-input')}}
  })
}

$(document).ready(function() {

  $(document).on("click", "#login-button", login)
  $(document).keypress(function(e) {if(e.which === 13) {login()}})

  $(document).on("click", "#signup-button", function() {
    $.post({
      type: "POST",
      url: "/signup",
      data: {"username": $("#signup-user").val(),
             "password": $("#signup-pass").val(),
             "email": $("#signup-mail").val()},
      success: function(response) {
        var status = JSON.parse(response)['status']
        if (status === 'Signup successful') {location.reload()}
        else {message(status, shake=true, id="signup-box")}
      }
    })
  })

  $(document).on("click", "#save", function() {
    $.post({
      type: "POST",
      url: "/settings",
      data: {"username": $("#settings-user").val(),
             "password": $("#settings-pass").val(),
             "email": $("#settings-mail").val()},
      success: function(response){
        message(JSON.parse(response)['status'])
      }
    })
  })
})<commit_msg>Add missing semicolons<commit_after>function message(status, shake=false, id="") {
  if (shake) {
    $("#"+id).effect("shake", {direction: "right", times: 2, distance: 8}, 250);
  }
  document.getElementById('feedback').innerHTML = status;
  $("#feedback").show().delay(2000).fadeOut();
}

function error(type) {
  $("."+type).css("border-color", "#E14448");
}

var login = function() {
  $.post({
    type: "POST",
    url: "/",
    data: {"username": $("#login-user").val(),
           "password": $("#login-pass").val()},
    success: function(response){
      var status = JSON.parse(response)['status'];
      if (status === 'Login successful') {location.reload();}
      else {error('login-input');}}
  });
}

$(document).ready(function() {

  $(document).on("click", "#login-button", login);
  $(document).keypress(function(e) {if(e.which === 13) {login();}});

  $(document).on("click", "#signup-button", function() {
    $.post({
      type: "POST",
      url: "/signup",
      data: {"username": $("#signup-user").val(),
             "password": $("#signup-pass").val(),
             "email": $("#signup-mail").val()},
      success: function(response) {
        var status = JSON.parse(response)['status'];
        if (status === 'Signup successful') {location.reload();}
        else {message(status, shake=true, id="signup-box");}
      }
    });
  });

  $(document).on("click", "#save", function() {
    $.post({
      type: "POST",
      url: "/settings",
      data: {"username": $("#settings-user").val(),
             "password": $("#settings-pass").val(),
             "email": $("#settings-mail").val()},
      success: function(response){
        message(JSON.parse(response)['status']);
      }
    });
  });
});<|endoftext|>