<commit_before>/**
 * Implements a folder to open different document in tabs.
 *
 * @constructor
 * @this {Folder}
 */
Folder = function(index){
	this.nameId = index;
	this.index = index;
	this.documents = [];
	this.documentDialogs = [];
	this.tabData = [];
	this.selection = 0;
}

/**
 * Initializes the content window by defining HTML containers.
 *
 * @this {Folder}
 */
Folder.prototype.initialize = function(){
	this.tabs = $("
").appendTo(this.content);
	this.documentDiv = $("
").appendTo(this.content);
	$(this.label).html(this.getName());
};

/**
 * Update this folder name (id) after another folder was removed.
 *
 * @this {Folder}
 * @param {number} index The new index of the folder.
 */
Folder.prototype.updateName = function(index){
	if( this.nameId != index ){
		this.nameId = index;
		$(this.label).html(this.getName());
	}
};

/**
 * Getter for the folder's name which is substituted of a 'Folder'-String + index.
 *
 * @this {Folder}
 * @return {string} The folders name.
 */
Folder.prototype.getName = function(){
	return Util.getString('folder')+" #"+this.nameId;
};

/**
 * Resizes the folder and all containing document dialogs.
 *
 * @this {Folder}
 */
Folder.prototype.resizeContent = function(){
	var cw = this;
	var padding = parseInt($(cw.documentDiv).css("padding-bottom"))+parseInt($(cw.documentDiv).css("padding-top"));
	$(this.documentDiv).css('height',(this.content.height()-this.tabs.height()-padding)+'px');
	var padding = parseInt($(cw.documentDiv).css("padding-bottom"))+parseInt($(cw.documentDiv).css("padding-top"));
	$.each(this.tabData,function(i,div){
		div.tabDiv.css('height',(cw.content.height()-cw.tabs.height()-padding)+'px');
	});
	var dialog = this.dialog();
	if( dialog != null ){
		dialog.resize();
	}
};

/**
 * Getter for the document dialog of the activated tab.
 *
 * @this {Folder}
 * @return {string} The actually displayed document dialog or null if there are no tabs opened.
 */
Folder.prototype.dialog = function(){
	if( this.documentDialogs.length > 0 ){
		return this.documentDialogs[ this.selection ];
	}
	return null;
};

/**
 * Displays a specific tab.
 *
 * @this {Folder}
 * @param {JSON} tab The tab data consists of divs for tab and content.
 */
Folder.prototype.selectTab = function(tab){
	if( typeof tab.tab != 'undefined' ){
		for( var i in this.tabData ){
			if( this.tabData[i] != tab ){
				$(this.tabData[i].tabDiv).css('display','none');
				$(this.tabData[i].tab).removeClass('selected');
			}
			else {
				this.selection = i;
				$(this.tabData[i].tabDiv).css('display','block');
				$(this.tabData[i].tab).addClass('selected');
			}
		}
	}
	else if( tab < this.tabData.length ){
		this.selection = tab;
		for( var i=0; i<this.tabData.length; i++ ){
			$(this.tabData[i].tabDiv).css('display','none');
			$(this.tabData[i].tab).removeClass('selected');
		}
		$(this.tabData[tab].tabDiv).css('display','block');
		$(this.tabData[tab].tab).addClass('selected');
	}
	this.resizeContent();
};

/**
 * Adds a new tab for a given document, optionally, a start page, a document type and/or a (fulltext) position are given
 *
 * @this {Folder}
 * @param {JSON} data It consists of the document and optional information (page, document type, fulltext position).
 */
Folder.prototype.addTab = function(data){
	var container = this;
	this.documents.push(data.document);
	var tab = $("
").appendTo(this.tabs);
	var tabLink = $(""+data.document.nameShort+"").appendTo(tab);
	var tabClose = $("").appendTo(tab);
	var tabDiv = $("
").appendTo(this.documentDiv);
	var tabData = {
		tab: tab,
		tabDiv: tabDiv
	};
	this.tabData.push(tabData);
	$(tabLink).click(function(){
		container.selectTab(tabData);
	});
	$(tabClose).click(function(){
		container.removeTab(tabData);
	});
	var padding = parseInt($(this.documentDiv).css("padding-bottom"))+parseInt($(this.documentDiv).css("padding-top"));
	tabDiv.css('height',(this.content.height()-this.tabs.height()-padding)+'px');
	var documentDialog = new DocumentDialog(this,data.document,tabDiv,data.page,data.position);
	documentDialog.activateFacet(data.entity);
	documentDialog.setDocType(data.type,data.position);
	this.documentDialogs.push(documentDialog);
	this.selectTab(tabData);
	this.resizeContent();
};

/**
 * Stops processing of the actual shown tab (if ESC was pressed).
 *
 * @this {Folder}
 */
Folder.prototype.stopProcessing = function(){
	var dialog = this.dialog();
	if( dialog != null ){
		dialog.stopProcessing();
	}
};

/**
 * Removes a tab from the folder.
 *
 * @this {Folder}
 * @param {JSON} tab The tab to remove.
 */
Folder.prototype.removeTab = function(tab){
	for( var i in this.tabData ){
		if( this.tabData[i] == tab ){
			$(this.tabData[i].tabDiv).remove();
			$(this.tabData[i].tab).remove();
			this.documents.splice(i,1);
			this.documentDialogs.splice(i,1);
			this.tabData.splice(i,1);
			break;
		}
	}
	if( this.tabData.length > 0 ){
		this.selectTab(this.tabData[0]);
	}
	else {
		this.resize();
	}
};

/**
 * Getter for the current selected tab index (required for constructing magnetic links).
 *
 * @this {Folder}
 * @return {number} The current tab index.
 */
Folder.prototype.getSelectedTab = function(){
	return this.selection;
};

/**
 * Setter to select a specific tab (required for utilizing magnetic links).
 *
 * @this {Folder}
 * @param {number} index The index of the tab to be shown.
 */
Folder.prototype.setSelectedTab = function(index){
	this.selectTab(index);
};
<commit_msg>bla<commit_after>/**
 * Implements a folder to open different document in tabs.
 *
 * @constructor
 * @this {Folder}
 */
Folder = function(index){
	this.nameId = index;
	this.index = index;
	this.documents = [];
	this.documentDialogs = [];
	this.tabData = [];
	this.selection = 0;
}

/**
 * Initializes the content window by defining HTML containers.
 *
 * @this {Folder}
 */
Folder.prototype.initialize = function(){
	this.tabs = $("
").appendTo(this.content);
	this.documentDiv = $("
").appendTo(this.content);
	$(this.label).html(this.getName());
};

/**
 * Update this folder name (id) after another folder was removed.
 *
 * @this {Folder}
 * @param {number} index The new index of the folder.
 */
Folder.prototype.updateName = function(index){
	if( this.nameId != index ){
		this.nameId = index;
		$(this.label).html(this.getName());
	}
};

/**
 * Getter for the folder's name which is substituted of a 'Folder'-String + index.
 *
 * @this {Folder}
 * @return {string} The folders name.
 */
Folder.prototype.getName = function(){
	return Util.getString('folder')+" #"+this.nameId;
};

/**
 * Resizes the folder and all containing document dialogs.
 *
 * @this {Folder}
 */
Folder.prototype.resizeContent = function(){
	var cw = this;
	var padding = parseInt($(cw.documentDiv).css("padding-bottom"))+parseInt($(cw.documentDiv).css("padding-top"));
	$(this.documentDiv).css('height',(this.content.height()-this.tabs.height()-padding)+'px');
	var padding = parseInt($(cw.documentDiv).css("padding-bottom"))+parseInt($(cw.documentDiv).css("padding-top"));
	$.each(this.tabData,function(i,div){
		div.tabDiv.css('height',(cw.content.height()-cw.tabs.height()-padding)+'px');
	});
	var dialog = this.dialog();
	if( dialog != null ){
		dialog.resize();
	}
};

/**
 * Getter for the document dialog of the activated tab.
 *
 * @this {Folder}
 * @return {string} The actually displayed document dialog or null if there are no tabs opened.
 */
Folder.prototype.dialog = function(){
	if( this.documentDialogs.length > 0 ){
		return this.documentDialogs[ this.selection ];
	}
	return null;
};

/**
 * Displays a specific tab.
 *
 * @this {Folder}
 * @param {JSON} tab The tab data consists of divs for tab and content.
 */
Folder.prototype.selectTab = function(tab){
	if( typeof tab.tab != 'undefined' ){
		for( var i in this.tabData ){
			if( this.tabData[i] != tab ){
				$(this.tabData[i].tabDiv).css('display','none');
				$(this.tabData[i].tab).removeClass('selected');
			}
			else {
				this.selection = i;
				$(this.tabData[i].tabDiv).css('display','block');
				$(this.tabData[i].tab).addClass('selected');
			}
		}
	}
	else if( tab < this.tabData.length ){
		this.selection = tab;
		for( var i=0; i<this.tabData.length; i++ ){
			$(this.tabData[i].tabDiv).css('display','none');
			$(this.tabData[i].tab).removeClass('selected');
		}
		$(this.tabData[tab].tabDiv).css('display','block');
		$(this.tabData[tab].tab).addClass('selected');
	}
	this.resizeContent();
};

/**
 * Adds a new tab for a given document, optionally, a start page, a document type and/or a (fulltext) position are given
 *
 * @this {Folder}
 * @param {JSON} data It consists of the document and optional information (page, document type, fulltext position).
 */
Folder.prototype.addTab = function(data){
	var container = this;
	this.documents.push(data.document);
	var tab = $("
").appendTo(this.tabs);
	var tabLink = $(""+data.document.nameShort+"").appendTo(tab);
	var tabClose = $("").appendTo(tab);
	var tabDiv = $("
").appendTo(this.documentDiv);
	var tabData = {
		tab: tab,
		tabDiv: tabDiv
	};
	this.tabData.push(tabData);
	$(tabLink).click(function(){
		container.selectTab(tabData);
	});
	$(tabClose).click(function(){
		container.removeTab(tabData);
	});
	var padding = parseInt($(this.documentDiv).css("padding-bottom"))+parseInt($(this.documentDiv).css("padding-top"));
	tabDiv.css('height',(this.content.height()-this.tabs.height()-padding)+'px');
	var documentDialog = new DocumentDialog(this,data.document,tabDiv,data.page,data.position);
	documentDialog.activateFacet(data.entity);
	documentDialog.setDocType(data.type,data.position);
	this.documentDialogs.push(documentDialog);
	this.selectTab(tabData);
	this.resizeContent();
};

/**
 * Stops processing of the actual shown tab (if ESC was pressed).
 *
 * @this {Folder}
 */
Folder.prototype.stopProcessing = function(){
	var dialog = this.dialog();
	if( dialog != null ){
		dialog.stopProcessing();
	}
};

/**
 * Removes a tab from the folder.
 *
 * @this {Folder}
 * @param {JSON} tab The tab to remove.
 */
Folder.prototype.removeTab = function(tab){
	for( var i in this.tabData ){
		if( this.tabData[i] == tab ){
			$(this.tabData[i].tabDiv).remove();
			$(this.tabData[i].tab).remove();
			this.documents.splice(i,1);
			this.documentDialogs.splice(i,1);
			this.tabData.splice(i,1);
			break;
		}
	}
	if( this.tabData.length > 0 ){
		this.selectTab(this.tabData[0]);
	}
	else {
		this.resize();
	}
};

/**
 * Getter for the current selected tab index (required for constructing magnetic links).
 *
 * @this {Folder}
 * @return {number} The current tab index.
 */
Folder.prototype.getSelectedTab = function(){
	return this.selection;
};

/**
 * Setter to select a specific tab (required for utilizing magnetic links).
 *
 * @this {Folder}
 * @param {number} index The index of the tab to be shown.
 */
Folder.prototype.setSelectedTab = function(index){
	this.selectTab(index);
};
<|endoftext|>


<commit_before>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Reward node that shows many nodes animating continuously, for fun!  Shown when a perfect score is achieved in a game.
 * You can also test this by running vegas/vegas_en.html and clicking on the "Reward" screen.
 * Note that the number of images falling is constant, so if the screen is stretched out vertically (tall thin window)
 * they will be less dense.
 *
 * There are two ways to run the animation step function.  The client code can manually call step(dt), or the client
 * code can pass in an Events instance that triggers events on 'step'. In the latter case, the listener will
 * automatically be removed when the animation is complete.
 *
 * For details about the development of the RewardNode, please see https://github.com/phetsims/vegas/issues/4
 *
 * @author Sam Reid
 */

import Bounds2 from '../../dot/js/Bounds2.js';
import ScreenView from '../../joist/js/ScreenView.js';
import merge from '../../phet-core/js/merge.js';
import FaceNode from '../../scenery-phet/js/FaceNode.js';
import StarNode from '../../scenery-phet/js/StarNode.js';
import CanvasNode from '../../scenery/js/nodes/CanvasNode.js';
import Node from '../../scenery/js/nodes/Node.js';
import Tandem from '../../tandem/js/Tandem.js';
import vegas from './vegas.js';

// constants
const DEBUG = false; // shows a gray rectangle for the CanvasNode to help ensure that its bounds are accurate
const MAX_SPEED = 200; // The maximum speed an image can fall in screen pixels per second.

class RewardNode extends CanvasNode {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {

    // @private
    options = merge( {

      // Scale things up for rasterization and back down for rendering so they have nice resolution on retina
      scaleForResolution: 2,

      // {Node[]} Nodes to appear in the reward node.  They will be cached as images to improve performance.
      // The simulation should override this array to provide images specific to the simulation.
      // If null, the default is initialized below.
      nodes: null,

      // If you pass in a stepEmitter {Emitter}, it will drive the animation
      stepEmitter: null
    }, options );

    if ( options.nodes === null ) {
      options.nodes = RewardNode.createRandomNodes( [
        new FaceNode( 40, { headStroke: 'black', headLineWidth: 1.5 } ),
        new StarNode()
      ], 150 );
    }

    super( options );

    // @private
    this.options = options;

    // Bounds in which to render the canvas, which represents the full window. See below for how this is computed based
    // on ScreenView bounds and relative transforms
    this.canvasDisplayBounds = new Bounds2( 0, 0, 0, 0 );

    // @private If you pass in a stepEmitter, it will drive the animation
    this.stepEmitterListener = null;
    if ( options.stepEmitter ) {
      this.stepEmitterListener = dt => this.step( dt );
      options.stepEmitter.addListener( this.stepEmitterListener );
    }

    // @private {Node[]}
    // Cache each unique node as an image for faster rendering in canvas.  Use an intermediate imageWrapper since the
    // images will be returned later asynchronously. And we need a place to store them, and know when they have arrived.
    this.imageWrappers = [];

    // find the unique nodes in the array
    const uniqueNodes = _.uniq( options.nodes );
    uniqueNodes.forEach( ( node, i ) => {
      this.imageWrappers.push( {
        // The image to be rendered in the canvas, will be filled in by toImage callback
        image: null,

        // Record the width and height so the nodes can be positioned before the toImage call has completed
        width: node.width,
        height: node.height,

        // The node itself is recorded in the imageWrapper so the imageWrapper can be looked up based on the original node
        node: node
      } );

      const parent = new Node( { children: [ node ], scale: options.scaleForResolution } );
      parent.toImage( image => {
        this.imageWrappers[ i ].image = image;
        parent.dispose(); // not needed anymore, see https://github.com/phetsims/area-model-common/issues/128
      } );
    } );

    // @private {boolean} Some initialization must occur after this node is attached to the scene graph,
    // see documentation for initialize() method below.
    this.isInitialized = false;

    // @private these will be set by init
    this.scene = null; // {Node}
    this.screenView = null; // {ScreenView}
    this.updateBounds = null; // {function}

    // @private {function} For PhET-iO brand only: make sure this Node is initialized when state is being set for PhET-iO
    this.initializationVerifier = null;
    if ( Tandem.PHET_IO_ENABLED && phet.phetio.phetioEngine.phetioStateEngine ) {
      this.initializationVerifier = () => {
        if ( !this.isInitialized ) {
          this.initialize();
        }
      };
      Tandem.PHET_IO_ENABLED && phet.phetio.phetioEngine.phetioStateEngine.stateSetEmitter.addListener( this.initializationVerifier );
    }

    // @private
    this.disposeRewardNode = () => {
      options.stepEmitter && options.stepEmitter.removeListener( this.stepEmitterListener );
      this.screenView && this.screenView.transformEmitter.removeListener( this.updateBounds );
      this.initializationVerifier && phet.phetio.phetioEngine.phetioStateEngine.stateSetEmitter.removeListener( this.initializationVerifier );
    };
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.disposeRewardNode();
    super.dispose();
  }

  /**
   * Convenience factory method to create an array of the specified nodes in an even distribution.
   * @param {Node[]} nodes array of nodes to use
   * @param {number} count
   * @returns {Node[]}
   * @public
   */
  static createRandomNodes( nodes, count ) {
    const array = [];
    for ( let i = 0; i < count; i++ ) {
      array.push( nodes[ i % nodes.length ] );
    }
    return array;
  }

  /**
   * Paint the rewards on the canvas
   * @param {CanvasRenderingContext2D} context
   * @protected
   */
  paintCanvas( context ) {

    // If the debugging flag is on, show the bounds of the canvas
    if ( DEBUG ) {
      const bounds = this.canvasDisplayBounds;

      // Fill the canvas with gray
      context.fillStyle = 'rgba(50,50,50,0.5)';
      context.fillRect( bounds.minX, bounds.minY, bounds.width, bounds.height );

      // Stroke the canvas border with blue
      context.strokeStyle = '#0000ff';
      context.lineWidth = 5;
      context.strokeRect( bounds.minX, bounds.minY, bounds.width, bounds.height );
    }
    context.scale( 1 / this.options.scaleForResolution, 1 / this.options.scaleForResolution );

    // Display the rewards, but check that they exist first.  They do not exist when attached to the timer with stepEmitter
    if ( this.rewards ) {
      for ( let i = 0; i < this.rewards.length; i++ ) {
        const reward = this.rewards[ i ];
        if ( reward.imageWrapper.image ) {
          context.drawImage( reward.imageWrapper.image, reward.x, reward.y );
        }
      }
    }
  }

  /**
   * Find the root of the scenegraph.
   * @returns {Node}
   * @private
   */
  getScene() {
    return this.getUniqueTrail().nodes[ 0 ];
  }

  /**
   * Finds the first parent that is a ScreenView so we can listen for its transform, see https://github.com/phetsims/vegas/issues/4
   * @returns {Node}
   * @private
   */
  getScreenView() {
    return this.getUniqueTrail( function( node ) { return node instanceof ScreenView; } ).rootNode();
  }

  /**
   * Only init after being attached to the scene graph, since we must ascertain the local bounds are such that they take
   * up the global screen.
   * 1. listen to the size of the scene/display
   * 2. record the trail between the scene and your CanvasNode, and
   * 3. apply the inverse of that transform to the CanvasNode (whenever an ancestor's transform changes, or when the scene/display size changes).
   *
   * @jonathanolson said: for implementing now, I'd watch the iso transform, compute the inverse, and set bounds on
   * changes to be precise (since you need them anyways to draw).
   * @public
   */
  initialize() {
    assert && assert( !this.isInitialized, 'already initialized' );

    this.scene = this.getScene();
    this.screenView = this.getScreenView();

    // Listen to the bounds of the scene, so the canvas can be resized if the window is reshaped
    this.updateBounds = () => {

      const local = this.globalToLocalBounds( phet.joist.sim.display.bounds );
      this.setCanvasBounds( local );

      // Also, store the bounds in the options so the debug flag can render the bounds
      this.canvasDisplayBounds = local;
    };

    // When the ScreenView transform changes, update the bounds.  This prevents a "behind by one" problem, see https://github.com/phetsims/vegas/issues/4
    this.screenView.transformEmitter.addListener( this.updateBounds );

    // Set the initial bounds
    this.updateBounds();

    /*
     * Store each reward, which has an imageWrapper (see above), x, y, speed. It is not an image, it is not a node,
     * but it is one of the things that animates as falling in the RewardNode and its associated data.
     * @private
     */
    // TODO should we create a separate class for this?  See https://github.com/phetsims/vegas/issues/79.
    this.rewards = [];
    const self = this;
    for ( let i = 0; i < this.options.nodes.length; i++ ) {

      const node = this.options.nodes[ i ];

      // IIFE
      ( node => {

        //find the image wrapper corresponding to the node
        const imageWrapper = _.find( self.imageWrappers, function( imageWrapper ) {return imageWrapper.node === node;} );
        self.rewards.push( {
          imageWrapper: imageWrapper,
          x: self.sampleImageXValue( imageWrapper ),
          y: self.sampleImageYValue( imageWrapper ),
          speed: ( phet.joist.random.nextDouble() + 1 ) * MAX_SPEED
        } );
      } )( node );
    }

    this.isInitialized = true;
  }

  /**
   * Selects a random X value for the image when it is created.
   * @param {Node} imageWrapper
   * @returns {number}
   * @private
   */
  sampleImageXValue( imageWrapper ) {
    return ( phet.joist.random.nextDouble() * this.canvasDisplayBounds.width + this.canvasDisplayBounds.left ) *
           this.options.scaleForResolution - imageWrapper.width / 2;
  }

  /**
   * Selects a random Y value for the image when it is created, or when it goes back to the top of the screen
   * @param {Node} imageWrapper
   * @returns {number}
   * @public
   */
  sampleImageYValue( imageWrapper ) {
    // Start things about 1 second off the top of the screen
    return this.canvasDisplayBounds.top - phet.joist.random.nextDouble() * this.canvasDisplayBounds.height * 2 -
           MAX_SPEED - imageWrapper.height;
  }

  /**
   * Moves the rewards down according to their speed.
   * @param {number} dt
   * @public
   */
  step( dt ) {
    if ( !this.isInitialized && this.getScene() !== null ) {
      this.initialize();
    }

    // Update all of the rewards
    const maxY = this.canvasDisplayBounds.height * this.options.scaleForResolution;
    for ( let i = 0; i < this.rewards.length; i++ ) {
      const reward = this.rewards[ i ];

      // Move each node straight down at constant speed
      reward.y += reward.speed * dt;

      // Move back to the top after the node falls off the bottom
      if ( reward.y > maxY ) {
        reward.x = this.sampleImageXValue( reward.imageWrapper );
        reward.y = this.sampleImageYValue( reward.imageWrapper );
      }
    }
    this.invalidatePaint();
  }
}

vegas.register( 'RewardNode', RewardNode );
export default RewardNode;<commit_msg>initialize should be @private, https://github.com/phetsims/vegas/issues/82<commit_after>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Reward node that shows many nodes animating continuously, for fun!  Shown when a perfect score is achieved in a game.
 * You can also test this by running vegas/vegas_en.html and clicking on the "Reward" screen.
 * Note that the number of images falling is constant, so if the screen is stretched out vertically (tall thin window)
 * they will be less dense.
 *
 * There are two ways to run the animation step function.  The client code can manually call step(dt), or the client
 * code can pass in an Events instance that triggers events on 'step'. In the latter case, the listener will
 * automatically be removed when the animation is complete.
 *
 * For details about the development of the RewardNode, please see https://github.com/phetsims/vegas/issues/4
 *
 * @author Sam Reid
 */

import Bounds2 from '../../dot/js/Bounds2.js';
import ScreenView from '../../joist/js/ScreenView.js';
import merge from '../../phet-core/js/merge.js';
import FaceNode from '../../scenery-phet/js/FaceNode.js';
import StarNode from '../../scenery-phet/js/StarNode.js';
import CanvasNode from '../../scenery/js/nodes/CanvasNode.js';
import Node from '../../scenery/js/nodes/Node.js';
import Tandem from '../../tandem/js/Tandem.js';
import vegas from './vegas.js';

// constants
const DEBUG = false; // shows a gray rectangle for the CanvasNode to help ensure that its bounds are accurate
const MAX_SPEED = 200; // The maximum speed an image can fall in screen pixels per second.

class RewardNode extends CanvasNode {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {

    // @private
    options = merge( {

      // Scale things up for rasterization and back down for rendering so they have nice resolution on retina
      scaleForResolution: 2,

      // {Node[]} Nodes to appear in the reward node.  They will be cached as images to improve performance.
      // The simulation should override this array to provide images specific to the simulation.
      // If null, the default is initialized below.
      nodes: null,

      // If you pass in a stepEmitter {Emitter}, it will drive the animation
      stepEmitter: null
    }, options );

    if ( options.nodes === null ) {
      options.nodes = RewardNode.createRandomNodes( [
        new FaceNode( 40, { headStroke: 'black', headLineWidth: 1.5 } ),
        new StarNode()
      ], 150 );
    }

    super( options );

    // @private
    this.options = options;

    // Bounds in which to render the canvas, which represents the full window. See below for how this is computed based
    // on ScreenView bounds and relative transforms
    this.canvasDisplayBounds = new Bounds2( 0, 0, 0, 0 );

    // @private If you pass in a stepEmitter, it will drive the animation
    this.stepEmitterListener = null;
    if ( options.stepEmitter ) {
      this.stepEmitterListener = dt => this.step( dt );
      options.stepEmitter.addListener( this.stepEmitterListener );
    }

    // @private {Node[]}
    // Cache each unique node as an image for faster rendering in canvas.  Use an intermediate imageWrapper since the
    // images will be returned later asynchronously. And we need a place to store them, and know when they have arrived.
    this.imageWrappers = [];

    // find the unique nodes in the array
    const uniqueNodes = _.uniq( options.nodes );
    uniqueNodes.forEach( ( node, i ) => {
      this.imageWrappers.push( {
        // The image to be rendered in the canvas, will be filled in by toImage callback
        image: null,

        // Record the width and height so the nodes can be positioned before the toImage call has completed
        width: node.width,
        height: node.height,

        // The node itself is recorded in the imageWrapper so the imageWrapper can be looked up based on the original node
        node: node
      } );

      const parent = new Node( { children: [ node ], scale: options.scaleForResolution } );
      parent.toImage( image => {
        this.imageWrappers[ i ].image = image;
        parent.dispose(); // not needed anymore, see https://github.com/phetsims/area-model-common/issues/128
      } );
    } );

    // @private {boolean} Some initialization must occur after this node is attached to the scene graph,
    // see documentation for initialize() method below.
    this.isInitialized = false;

    // @private these will be set by init
    this.scene = null; // {Node}
    this.screenView = null; // {ScreenView}
    this.updateBounds = null; // {function}

    // @private {function} For PhET-iO brand only: make sure this Node is initialized when state is being set for PhET-iO
    this.initializationVerifier = null;
    if ( Tandem.PHET_IO_ENABLED && phet.phetio.phetioEngine.phetioStateEngine ) {
      this.initializationVerifier = () => {
        if ( !this.isInitialized ) {
          this.initialize();
        }
      };
      Tandem.PHET_IO_ENABLED && phet.phetio.phetioEngine.phetioStateEngine.stateSetEmitter.addListener( this.initializationVerifier );
    }

    // @private
    this.disposeRewardNode = () => {
      options.stepEmitter && options.stepEmitter.removeListener( this.stepEmitterListener );
      this.screenView && this.screenView.transformEmitter.removeListener( this.updateBounds );
      this.initializationVerifier && phet.phetio.phetioEngine.phetioStateEngine.stateSetEmitter.removeListener( this.initializationVerifier );
    };
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.disposeRewardNode();
    super.dispose();
  }

  /**
   * Convenience factory method to create an array of the specified nodes in an even distribution.
   * @param {Node[]} nodes array of nodes to use
   * @param {number} count
   * @returns {Node[]}
   * @public
   */
  static createRandomNodes( nodes, count ) {
    const array = [];
    for ( let i = 0; i < count; i++ ) {
      array.push( nodes[ i % nodes.length ] );
    }
    return array;
  }

  /**
   * Paint the rewards on the canvas
   * @param {CanvasRenderingContext2D} context
   * @protected
   */
  paintCanvas( context ) {

    // If the debugging flag is on, show the bounds of the canvas
    if ( DEBUG ) {
      const bounds = this.canvasDisplayBounds;

      // Fill the canvas with gray
      context.fillStyle = 'rgba(50,50,50,0.5)';
      context.fillRect( bounds.minX, bounds.minY, bounds.width, bounds.height );

      // Stroke the canvas border with blue
      context.strokeStyle = '#0000ff';
      context.lineWidth = 5;
      context.strokeRect( bounds.minX, bounds.minY, bounds.width, bounds.height );
    }
    context.scale( 1 / this.options.scaleForResolution, 1 / this.options.scaleForResolution );

    // Display the rewards, but check that they exist first.  They do not exist when attached to the timer with stepEmitter
    if ( this.rewards ) {
      for ( let i = 0; i < this.rewards.length; i++ ) {
        const reward = this.rewards[ i ];
        if ( reward.imageWrapper.image ) {
          context.drawImage( reward.imageWrapper.image, reward.x, reward.y );
        }
      }
    }
  }

  /**
   * Find the root of the scenegraph.
   * @returns {Node}
   * @private
   */
  getScene() {
    return this.getUniqueTrail().nodes[ 0 ];
  }

  /**
   * Finds the first parent that is a ScreenView so we can listen for its transform, see https://github.com/phetsims/vegas/issues/4
   * @returns {Node}
   * @private
   */
  getScreenView() {
    return this.getUniqueTrail( function( node ) { return node instanceof ScreenView; } ).rootNode();
  }

  /**
   * Only init after being attached to the scene graph, since we must ascertain the local bounds are such that they take
   * up the global screen.
   * 1. listen to the size of the scene/display
   * 2. record the trail between the scene and your CanvasNode, and
   * 3. apply the inverse of that transform to the CanvasNode (whenever an ancestor's transform changes, or when the scene/display size changes).
   *
   * @jonathanolson said: for implementing now, I'd watch the iso transform, compute the inverse, and set bounds on
   * changes to be precise (since you need them anyways to draw).
   * @private
   */
  initialize() {
    assert && assert( !this.isInitialized, 'already initialized' );

    this.scene = this.getScene();
    this.screenView = this.getScreenView();

    // Listen to the bounds of the scene, so the canvas can be resized if the window is reshaped
    this.updateBounds = () => {

      const local = this.globalToLocalBounds( phet.joist.sim.display.bounds );
      this.setCanvasBounds( local );

      // Also, store the bounds in the options so the debug flag can render the bounds
      this.canvasDisplayBounds = local;
    };

    // When the ScreenView transform changes, update the bounds.  This prevents a "behind by one" problem, see https://github.com/phetsims/vegas/issues/4
    this.screenView.transformEmitter.addListener( this.updateBounds );

    // Set the initial bounds
    this.updateBounds();

    /*
     * Store each reward, which has an imageWrapper (see above), x, y, speed. It is not an image, it is not a node,
     * but it is one of the things that animates as falling in the RewardNode and its associated data.
     * @private
     */
    // TODO should we create a separate class for this?  See https://github.com/phetsims/vegas/issues/79.
    this.rewards = [];
    const self = this;
    for ( let i = 0; i < this.options.nodes.length; i++ ) {

      const node = this.options.nodes[ i ];

      // IIFE
      ( node => {

        //find the image wrapper corresponding to the node
        const imageWrapper = _.find( self.imageWrappers, function( imageWrapper ) {return imageWrapper.node === node;} );
        self.rewards.push( {
          imageWrapper: imageWrapper,
          x: self.sampleImageXValue( imageWrapper ),
          y: self.sampleImageYValue( imageWrapper ),
          speed: ( phet.joist.random.nextDouble() + 1 ) * MAX_SPEED
        } );
      } )( node );
    }

    this.isInitialized = true;
  }

  /**
   * Selects a random X value for the image when it is created.
   * @param {Node} imageWrapper
   * @returns {number}
   * @private
   */
  sampleImageXValue( imageWrapper ) {
    return ( phet.joist.random.nextDouble() * this.canvasDisplayBounds.width + this.canvasDisplayBounds.left ) *
           this.options.scaleForResolution - imageWrapper.width / 2;
  }

  /**
   * Selects a random Y value for the image when it is created, or when it goes back to the top of the screen
   * @param {Node} imageWrapper
   * @returns {number}
   * @public
   */
  sampleImageYValue( imageWrapper ) {
    // Start things about 1 second off the top of the screen
    return this.canvasDisplayBounds.top - phet.joist.random.nextDouble() * this.canvasDisplayBounds.height * 2 -
           MAX_SPEED - imageWrapper.height;
  }

  /**
   * Moves the rewards down according to their speed.
   * @param {number} dt
   * @public
   */
  step( dt ) {
    if ( !this.isInitialized && this.getScene() !== null ) {
      this.initialize();
    }

    // Update all of the rewards
    const maxY = this.canvasDisplayBounds.height * this.options.scaleForResolution;
    for ( let i = 0; i < this.rewards.length; i++ ) {
      const reward = this.rewards[ i ];

      // Move each node straight down at constant speed
      reward.y += reward.speed * dt;

      // Move back to the top after the node falls off the bottom
      if ( reward.y > maxY ) {
        reward.x = this.sampleImageXValue( reward.imageWrapper );
        reward.y = this.sampleImageYValue( reward.imageWrapper );
      }
    }
    this.invalidatePaint();
  }
}

vegas.register( 'RewardNode', RewardNode );
export default RewardNode;<|endoftext|>


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

/**
 * Handles serializing and deserializing versions for simulations.
 *
 **************************************************************
 * IMPORTANT NOTE: This file is copied from PERENNIAL to CHIPPER frequently. If this is in CHIPPER, then it is AUTO
 * GENERATED; do not edit, as it will be overwritten. Make sure that any change here is supported by usages of
 * CHIPPER's SimVersion (i.e. in JOIST/updateCheck.js), see https://github.com/phetsims/perennial/issues/111 for more
 **************************************************************
 *
 * See https://github.com/phetsims/chipper/issues/560 for discussion on version ID definition.
 *
 * The canonical description of our general versions:
 *
 * Each version string has the form: {{MAJOR}}.{{MINOR}}.{{MAINTENANCE}}[-{{TEST_TYPE}}.{{TEST_NUMBER}}] where:
 *
 * MAJOR: Sequential integer, starts at 1, and is generally incremented when there are significant changes to a simulation.
 * MINOR: Sequential integer, starts at 0, and is generally incremented when there are smaller changes to a simulation.
 *   Resets to 0 whenever the major number is incremented.
 * MAINTENANCE: Sequential integer, starts at 0, and is incremented whenever we build with the same major/minor (but with different SHAs).
 *   Resets to 0 whenever the minor number is incremented.
 * TEST_TYPE (when present): Indicates that this is a non-production build when present. Typically will take the values:
 *   'dev' - A normal dev deployment, which goes to phet-dev.colorado.edu/html/
 *   'rc' -  A release-candidate deployment (off of a release branch). Also goes to phet-dev.colorado.edu/html/ only.
 *   anything else - A one-off deployment name, which is the same name as the branch it was deployed from.
 * TEST_NUMBER (when present): Indicates the version of the test/one-off type (gets incremented for every deployment).
 *   starts at 0 in package.json, but since it is incremented on every deploy, the first version published will be 1.
 *
 * It used to be (pre-chipper-2.0) that sometimes a shortened form of the (non-'phet') brand would be added to the end
 * (e.g. '1.3.0-dev.1-phetio' or '1.3.0-dev.1-adaptedfromphet'), or as a direct prefix for the TEST_TYPE (e.g.
 * 1.1.0-phetiodev.1 or 1.1.0-phetio). We have since moved to a deployment model where there are
 * subdirectories for each brand, so this is no longer part of the version. Since this was not used for any production sim
 * builds that we need statistics from, it is excluded in SimVersion.js or its description.
 *
 * Examples:
 *
 * 1.5.0                - Production simulation version (no test type). Major = 1, minor = 5, maintenance = 0
 * 1.5.0-rc.1           - Example of a release-candidate build version that would be published before '1.5.0' for testing.
 * 1.5.0-dev.1          - Example of a dev build that would be from master.
 * 1.5.0-sonification.1 - Example of a one-off build (which would be from the branch 'sonification')
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

/* eslint-env browser, node */
'use strict';

// To support loading in Node.js and the browser
const assert = typeof module !== 'undefined' ? require( 'assert' ) : window && window.assert;

( function( global, assert ) {

  class SimVersion {
    /**
     * @constructor
     *
     * @param {number} major - The major part of the version (the 3 in 3.1.2)
     * @param {number} minor - The minor part of the version (the 1 in 3.1.2)
     * @param {number} maintenance - The maintenance part of the version (the 2 in 3.1.2)
     * @param {Object} [options]
     */
    constructor( major, minor, maintenance, options ) {
      const {
        // {string|null} - If provided, indicates the time at which the sim file was built
        buildTimestamp = null,

        // {string|null} - The test name, e.g. the 'rc' in rc.1. Also can be the one-off version name, if provided.
        testType = null,

        // {number|null} - The test number, e.g. the 1 in rc.1
        testNumber = null
      } = options || {};

      assert && assert( typeof major === 'number' && major >= 0 && major % 1 === 0, 'major version should be a non-negative integer' );
      assert && assert( typeof minor === 'number' && minor >= 0 && minor % 1 === 0, 'minor version should be a non-negative integer' );
      assert && assert( typeof maintenance === 'number' && maintenance >= 0 && maintenance % 1 === 0, 'maintenance version should be a non-negative integer' );
      assert && assert( typeof testType !== 'string' || typeof testNumber === 'number', 'if testType is provided, testNumber should be a number' );

      // @public {number}
      this.major = major;

      // @public {number}
      this.minor = minor;

      // @public {number}
      this.maintenance = maintenance;

      // @public {string|null}
      this.testType = testType;

      // @public {number|null}
      this.testNumber = testNumber;

      // @public {string|null} - If provided, like '2015-06-12 16:05:03 UTC' (phet.chipper.buildTimestamp)
      this.buildTimestamp = buildTimestamp;
    }

    /**
     * Convert into a plain JS object meant for JSON serialization.
     * @public
     *
     * @returns {Object}
     */
    serialize() {
      return {
        major: this.major,
        minor: this.minor,
        maintenance: this.maintenance,
        testType: this.testType,
        testNumber: this.testNumber,
        buildTimestamp: this.buildTimestamp
      };
    }

    /**
     * @returns {boolean}
     * @public
     */
    get isSimNotPublished() {
      return this.major < 1 || // e.g. 0.0.0-dev.1
             ( this.major === 1 && // e.g. 1.0.0-dev.1
               this.minor === 0 &&
               this.maintenance === 0 &&
               this.testType );
    }

    /**
     * @returns {boolean}
     * @public
     */
    get isSimPublished() {
      return !this.isSimNotPublished;
    }

    /**
     * Takes a serialized form of the SimVersion and returns an actual instance.
     * @public
     *
     * @param {Object}
     * @returns {SimVersion}
     */
    static deserialize( { major, minor, maintenance, testType, testNumber, buildTimestamp } ) {
      return new SimVersion( major, minor, maintenance, {
        testType: testType,
        testNumber: testNumber,
        buildTimestamp: buildTimestamp
      } );
    }

    /**
     * Compares versions, returning -1 if this version is before the passed in version, 0 if equal, or 1 if this version
     * is after.
     * @public
     *
     * This function only compares major/minor/maintenance, leaving other details to the client.
     *
     * @param {SimVersion} version
     */
    compareNumber( version ) {
      if ( this.major < version.major ) { return -1; }
      if ( this.major > version.major ) { return 1; }
      if ( this.minor < version.minor ) { return -1; }
      if ( this.minor > version.minor ) { return 1; }
      if ( this.maintenance < version.maintenance ) { return -1; }
      if ( this.maintenance > version.maintenance ) { return 1; }
      return 0; // equal
    }

    /**
     * Returns the string form of the version.
     * @public
     *
     * @returns {string}
     */
    toString() {
      let str = `${this.major}.${this.minor}.${this.maintenance}`;
      if ( typeof this.testType === 'string' ) {
        str += `-${this.testType}.${this.testNumber}`;
      }
      return str;
    }

    /**
     * Parses a sim version from a string form.
     * @public
     *
     * @param {string} versionString - e.g. '1.0.0', '1.0.1-dev.3', etc.
     * @param {string} [buildTimestamp] - Optional build timestamp, like '2015-06-12 16:05:03 UTC' (phet.chipper.buildTimestamp)
     * @returns {SimVersion}
     */
    static parse( versionString, buildTimestamp ) {
      const matches = versionString.match( /^(\d+)\.(\d+)\.(\d+)(-(([^.-]+)\.(\d+)))?(-([^.-]+))?$/ );

      if ( !matches ) {
        throw new Error( 'could not parse version: ' + versionString );
      }

      const major = parseInt( matches[ 1 ], 10 );
      const minor = parseInt( matches[ 2 ], 10 );
      const maintenance = parseInt( matches[ 3 ], 10 );
      const testType = matches[ 6 ];
      const testNumber = matches[ 7 ] === undefined ? matches[ 7 ] : parseInt( matches[ 7 ], 10 );

      return new SimVersion( major, minor, maintenance, {
        testType: testType,
        testNumber: testNumber,
        buildTimestamp: buildTimestamp
      } );
    }

    /**
     * Parses a branch in the form {{MAJOR}}.{{MINOR}} and returns a corresponding version. Uses 0 for the maintenance version (unknown).
     * @public
     *
     * @param {string} branch - e.g. '1.0'
     * @returns {SimVersion}
     */
    static fromBranch( branch ) {
      const bits = branch.split( '.' );
      assert && assert( bits.length === 2, `Bad branch, should be {{MAJOR}}.{{MINOR}}, had: ${branch}` );

      const major = parseInt( branch.split( '.' )[ 0 ], 10 );
      const minor = parseInt( branch.split( '.' )[ 1 ], 10 );

      return new SimVersion( major, minor, 0 );
    }

    /**
     * Ensures that a branch name is ok to be a release branch.
     * @public
     *
     * @param {string} branch - e.g. '1.0'
     */
    static ensureReleaseBranch( branch ) {
      const version = SimVersion.fromBranch( branch );
      assert && assert( version.major > 0, 'Major version for a branch should be greater than zero' );
      assert && assert( version.minor >= 0, 'Minor version for a branch should be greater than (or equal) to zero' );
    }
  }

  // Node.js-compatible definition
  if ( typeof module !== 'undefined' ) {
    module.exports = SimVersion;
  }
  else {

    // Browser support, assign with
    window.phet = window.phet || {};
    window.phet.preloads = window.phet.preloads || {};
    window.phet.preloads.chipper = window.phet.preloads.chipper || {};
    window.phet.preloads.chipper.SimVersion = SimVersion;
  }
} )( ( 1, eval )( 'this' ), assert );
// Indirect eval usage done since babel likes to wrap things in strict mode.
// See http://perfectionkills.com/unnecessarily-comprehensive-look-into-a-rather-insignificant-issue-of-global-objects-creation/#ecmascript_5_strict_mode
<commit_msg>update copyright dates from daily grunt work<commit_after>// Copyright 2019-2020, University of Colorado Boulder

/**
 * Handles serializing and deserializing versions for simulations.
 *
 **************************************************************
 * IMPORTANT NOTE: This file is copied from PERENNIAL to CHIPPER frequently. If this is in CHIPPER, then it is AUTO
 * GENERATED; do not edit, as it will be overwritten. Make sure that any change here is supported by usages of
 * CHIPPER's SimVersion (i.e. in JOIST/updateCheck.js), see https://github.com/phetsims/perennial/issues/111 for more
 **************************************************************
 *
 * See https://github.com/phetsims/chipper/issues/560 for discussion on version ID definition.
 *
 * The canonical description of our general versions:
 *
 * Each version string has the form: {{MAJOR}}.{{MINOR}}.{{MAINTENANCE}}[-{{TEST_TYPE}}.{{TEST_NUMBER}}] where:
 *
 * MAJOR: Sequential integer, starts at 1, and is generally incremented when there are significant changes to a simulation.
 * MINOR: Sequential integer, starts at 0, and is generally incremented when there are smaller changes to a simulation.
 *   Resets to 0 whenever the major number is incremented.
 * MAINTENANCE: Sequential integer, starts at 0, and is incremented whenever we build with the same major/minor (but with different SHAs).
 *   Resets to 0 whenever the minor number is incremented.
 * TEST_TYPE (when present): Indicates that this is a non-production build when present. Typically will take the values:
 *   'dev' - A normal dev deployment, which goes to phet-dev.colorado.edu/html/
 *   'rc' -  A release-candidate deployment (off of a release branch). Also goes to phet-dev.colorado.edu/html/ only.
 *   anything else - A one-off deployment name, which is the same name as the branch it was deployed from.
 * TEST_NUMBER (when present): Indicates the version of the test/one-off type (gets incremented for every deployment).
 *   starts at 0 in package.json, but since it is incremented on every deploy, the first version published will be 1.
 *
 * It used to be (pre-chipper-2.0) that sometimes a shortened form of the (non-'phet') brand would be added to the end
 * (e.g. '1.3.0-dev.1-phetio' or '1.3.0-dev.1-adaptedfromphet'), or as a direct prefix for the TEST_TYPE (e.g.
 * 1.1.0-phetiodev.1 or 1.1.0-phetio). We have since moved to a deployment model where there are
 * subdirectories for each brand, so this is no longer part of the version. Since this was not used for any production sim
 * builds that we need statistics from, it is excluded in SimVersion.js or its description.
 *
 * Examples:
 *
 * 1.5.0                - Production simulation version (no test type). Major = 1, minor = 5, maintenance = 0
 * 1.5.0-rc.1           - Example of a release-candidate build version that would be published before '1.5.0' for testing.
 * 1.5.0-dev.1          - Example of a dev build that would be from master.
 * 1.5.0-sonification.1 - Example of a one-off build (which would be from the branch 'sonification')
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

/* eslint-env browser, node */
'use strict';

// To support loading in Node.js and the browser
const assert = typeof module !== 'undefined' ? require( 'assert' ) : window && window.assert;

( function( global, assert ) {

  class SimVersion {
    /**
     * @constructor
     *
     * @param {number} major - The major part of the version (the 3 in 3.1.2)
     * @param {number} minor - The minor part of the version (the 1 in 3.1.2)
     * @param {number} maintenance - The maintenance part of the version (the 2 in 3.1.2)
     * @param {Object} [options]
     */
    constructor( major, minor, maintenance, options ) {
      const {
        // {string|null} - If provided, indicates the time at which the sim file was built
        buildTimestamp = null,

        // {string|null} - The test name, e.g. the 'rc' in rc.1. Also can be the one-off version name, if provided.
        testType = null,

        // {number|null} - The test number, e.g. the 1 in rc.1
        testNumber = null
      } = options || {};

      assert && assert( typeof major === 'number' && major >= 0 && major % 1 === 0, 'major version should be a non-negative integer' );
      assert && assert( typeof minor === 'number' && minor >= 0 && minor % 1 === 0, 'minor version should be a non-negative integer' );
      assert && assert( typeof maintenance === 'number' && maintenance >= 0 && maintenance % 1 === 0, 'maintenance version should be a non-negative integer' );
      assert && assert( typeof testType !== 'string' || typeof testNumber === 'number', 'if testType is provided, testNumber should be a number' );

      // @public {number}
      this.major = major;

      // @public {number}
      this.minor = minor;

      // @public {number}
      this.maintenance = maintenance;

      // @public {string|null}
      this.testType = testType;

      // @public {number|null}
      this.testNumber = testNumber;

      // @public {string|null} - If provided, like '2015-06-12 16:05:03 UTC' (phet.chipper.buildTimestamp)
      this.buildTimestamp = buildTimestamp;
    }

    /**
     * Convert into a plain JS object meant for JSON serialization.
     * @public
     *
     * @returns {Object}
     */
    serialize() {
      return {
        major: this.major,
        minor: this.minor,
        maintenance: this.maintenance,
        testType: this.testType,
        testNumber: this.testNumber,
        buildTimestamp: this.buildTimestamp
      };
    }

    /**
     * @returns {boolean}
     * @public
     */
    get isSimNotPublished() {
      return this.major < 1 || // e.g. 0.0.0-dev.1
             ( this.major === 1 && // e.g. 1.0.0-dev.1
               this.minor === 0 &&
               this.maintenance === 0 &&
               this.testType );
    }

    /**
     * @returns {boolean}
     * @public
     */
    get isSimPublished() {
      return !this.isSimNotPublished;
    }

    /**
     * Takes a serialized form of the SimVersion and returns an actual instance.
     * @public
     *
     * @param {Object}
     * @returns {SimVersion}
     */
    static deserialize( { major, minor, maintenance, testType, testNumber, buildTimestamp } ) {
      return new SimVersion( major, minor, maintenance, {
        testType: testType,
        testNumber: testNumber,
        buildTimestamp: buildTimestamp
      } );
    }

    /**
     * Compares versions, returning -1 if this version is before the passed in version, 0 if equal, or 1 if this version
     * is after.
     * @public
     *
     * This function only compares major/minor/maintenance, leaving other details to the client.
     *
     * @param {SimVersion} version
     */
    compareNumber( version ) {
      if ( this.major < version.major ) { return -1; }
      if ( this.major > version.major ) { return 1; }
      if ( this.minor < version.minor ) { return -1; }
      if ( this.minor > version.minor ) { return 1; }
      if ( this.maintenance < version.maintenance ) { return -1; }
      if ( this.maintenance > version.maintenance ) { return 1; }
      return 0; // equal
    }

    /**
     * Returns the string form of the version.
     * @public
     *
     * @returns {string}
     */
    toString() {
      let str = `${this.major}.${this.minor}.${this.maintenance}`;
      if ( typeof this.testType === 'string' ) {
        str += `-${this.testType}.${this.testNumber}`;
      }
      return str;
    }

    /**
     * Parses a sim version from a string form.
     * @public
     *
     * @param {string} versionString - e.g. '1.0.0', '1.0.1-dev.3', etc.
     * @param {string} [buildTimestamp] - Optional build timestamp, like '2015-06-12 16:05:03 UTC' (phet.chipper.buildTimestamp)
     * @returns {SimVersion}
     */
    static parse( versionString, buildTimestamp ) {
      const matches = versionString.match( /^(\d+)\.(\d+)\.(\d+)(-(([^.-]+)\.(\d+)))?(-([^.-]+))?$/ );

      if ( !matches ) {
        throw new Error( 'could not parse version: ' + versionString );
      }

      const major = parseInt( matches[ 1 ], 10 );
      const minor = parseInt( matches[ 2 ], 10 );
      const maintenance = parseInt( matches[ 3 ], 10 );
      const testType = matches[ 6 ];
      const testNumber = matches[ 7 ] === undefined ? matches[ 7 ] : parseInt( matches[ 7 ], 10 );

      return new SimVersion( major, minor, maintenance, {
        testType: testType,
        testNumber: testNumber,
        buildTimestamp: buildTimestamp
      } );
    }

    /**
     * Parses a branch in the form {{MAJOR}}.{{MINOR}} and returns a corresponding version. Uses 0 for the maintenance version (unknown).
     * @public
     *
     * @param {string} branch - e.g. '1.0'
     * @returns {SimVersion}
     */
    static fromBranch( branch ) {
      const bits = branch.split( '.' );
      assert && assert( bits.length === 2, `Bad branch, should be {{MAJOR}}.{{MINOR}}, had: ${branch}` );

      const major = parseInt( branch.split( '.' )[ 0 ], 10 );
      const minor = parseInt( branch.split( '.' )[ 1 ], 10 );

      return new SimVersion( major, minor, 0 );
    }

    /**
     * Ensures that a branch name is ok to be a release branch.
     * @public
     *
     * @param {string} branch - e.g. '1.0'
     */
    static ensureReleaseBranch( branch ) {
      const version = SimVersion.fromBranch( branch );
      assert && assert( version.major > 0, 'Major version for a branch should be greater than zero' );
      assert && assert( version.minor >= 0, 'Minor version for a branch should be greater than (or equal) to zero' );
    }
  }

  // Node.js-compatible definition
  if ( typeof module !== 'undefined' ) {
    module.exports = SimVersion;
  }
  else {

    // Browser support, assign with
    window.phet = window.phet || {};
    window.phet.preloads = window.phet.preloads || {};
    window.phet.preloads.chipper = window.phet.preloads.chipper || {};
    window.phet.preloads.chipper.SimVersion = SimVersion;
  }
} )( ( 1, eval )( 'this' ), assert );
// Indirect eval usage done since babel likes to wrap things in strict mode.
// See http://perfectionkills.com/unnecessarily-comprehensive-look-into-a-rather-insignificant-issue-of-global-objects-creation/#ecmascript_5_strict_mode
<|endoftext|>


<commit_before>// Copyright 2019-2021, University of Colorado Boulder

/**
 * Base view for all "show a single molecule in the center" screens
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

import Vector3 from '../../dot/js/Vector3.js';
import asyncLoader from '../../phet-core/js/asyncLoader.js';
import openPopup from '../../phet-core/js/openPopup.js';
import PhetFont from '../../scenery-phet/js/PhetFont.js';
import sceneryPhetStrings from '../../scenery-phet/js/sceneryPhetStrings.js';
import HBox from '../../scenery/js/nodes/HBox.js';
import Path from '../../scenery/js/nodes/Path.js';
import Text from '../../scenery/js/nodes/Text.js';
import Utils from '../../scenery/js/util/Utils.js';
import exclamationTriangleSolidShape from '../../sherpa/js/fontawesome-5/exclamationTriangleSolidShape.js';
import mobius from './mobius.js';

const webglWarningBodyString = sceneryPhetStrings.webglWarning.body;

// {THREE.TextureLoader|null} - "singleton" for the texture loader
let textureLoader = null;

const scratchFloatArray = new Float32Array( 128 );

const ThreeUtils = {
  /**
   * Converts a Vector3 to a THREE.Vector3
   * @public
   *
   * @param {Vector3} vector
   * @returns {THREE.Vector3}
   */
  vectorToThree( vector ) {
    return new THREE.Vector3( vector.x, vector.y, vector.z );
  },

  /**
   * Converts a THREE.Vector3 to a Vector3
   * @public
   *
   * @param {THREE.Vector3} vector
   * @returns {Vector3}
   */
  threeToVector( vector ) {
    return new Vector3( vector.x, vector.y, vector.z );
  },

  /**
   * Converts a Color to a THREE.Color
   * @public
   *
   * @param {Color} color
   * @returns {THREE.Color}
   */
  colorToThree( color ) {
    return new THREE.Color( color.toNumber() );
  },

  /**
   * Returns an array of [ x, y, z, ... ] vertices for a quad pointed towards the camera.
   * @public
   *
   * @param {Bounds2} bounds2 - x,y
   * @param {number} z
   * @returns {Float32Array}
   */
  frontVertices( bounds2, z ) {
    return scratchFloatArray.slice( 0, ThreeUtils.writeFrontVertices( scratchFloatArray, 0, bounds2, z ) );
  },

  /**
   * Returns an array of [ x, y, z, ... ] vertices for a quad pointed up.
   * @public
   *
   * @param {Bounds2} bounds2 - x,z
   * @param {number} y
   * @returns {Float32Array}
   */
  topVertices( bounds2, y ) {
    return scratchFloatArray.slice( 0, ThreeUtils.writeTopVertices( scratchFloatArray, 0, bounds2, y ) );
  },

  /**
   * Returns an array of [ x, y, z, ... ] vertices for a quad pointed towards the right.
   * @public
   *
   * @param {Bounds2} bounds2 - z,y
   * @param {number} x
   * @returns {Float32Array}
   */
  rightVertices( bounds2, x ) {
    return scratchFloatArray.slice( 0, ThreeUtils.writeRightVertices( scratchFloatArray, 0, bounds2, x ) );
  },

  /**
   * Returns an array of [ x, y, z, ... ] vertices for a quad pointed towards the left.
   * @public
   *
   * @param {Bounds2} bounds2 - z,y
   * @param {number} x
   * @returns {Float32Array}
   */
  leftVertices( bounds2, x ) {
    return scratchFloatArray.slice( 0, ThreeUtils.writeLeftVertices( scratchFloatArray, 0, bounds2, x ) );
  },

  /**
   * Writes a single triangle into a buffer, returning the new index location. Assumes vertices in counterclockwise
   * order.
   * @public
   *
   * Writes 9 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {number} x0
   * @param {number} y0
   * @param {number} z0
   * @param {number} x1
   * @param {number} y1
   * @param {number} z1
   * @param {number} x2
   * @param {number} y2
   * @param {number} z2
   * @returns {number} - The index for the next write
   */
  writeTriangle( array, index, x0, y0, z0, x1, y1, z1, x2, y2, z2 ) {
    array[ index + 0 ] = x0;
    array[ index + 1 ] = y0;
    array[ index + 2 ] = z0;
    array[ index + 3 ] = x1;
    array[ index + 4 ] = y1;
    array[ index + 5 ] = z1;
    array[ index + 6 ] = x2;
    array[ index + 7 ] = y2;
    array[ index + 8 ] = z2;

    return index + 9;
  },

  /**
   * Writes a single quad into a buffer, returning the new index location. Assumes verties in counterclockwise order.
   * @public
   *
   * Writes 18 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {number} x0
   * @param {number} y0
   * @param {number} z0
   * @param {number} x1
   * @param {number} y1
   * @param {number} z1
   * @param {number} x2
   * @param {number} y2
   * @param {number} z2
   * @param {number} z3
   * @param {number} y3
   * @param {number} z3
   * @returns {number}
   */
  writeQuad( array, index, x0, y0, z0, x1, y1, z1, x2, y2, z2, x3, y3, z3 ) {
    index = ThreeUtils.writeTriangle(
      array, index,
      x0, y0, z0,
      x1, y1, z1,
      x2, y2, z2
    );
    index = ThreeUtils.writeTriangle(
      array, index,
      x0, y0, z0,
      x2, y2, z2,
      x3, y3, z3
    );
    return index;
  },

  /**
   * Writes a single front-facing quad into a buffer, returning the new index location. Assumes verties in
   * counterclockwise order.
   * @public
   *
   * Writes 18 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {Bounds2} bounds2 - x,y
   * @param {number} z
   * @returns {number}
   */
  writeFrontVertices( array, index, bounds2, z ) {
    return ThreeUtils.writeQuad(
      array, index,
      bounds2.minX, bounds2.maxY, z,
      bounds2.minX, bounds2.minY, z,
      bounds2.maxX, bounds2.minY, z,
      bounds2.maxX, bounds2.maxY, z
    );
  },

  /**
   * Writes a single up-facing quad into a buffer, returning the new index location. Assumes verties in
   * counterclockwise order.
   * @public
   *
   * Writes 18 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {Bounds2} bounds2 - x,z
   * @param {number} y
   * @returns {number}
   */
  writeTopVertices( array, index, bounds2, y ) {
    return ThreeUtils.writeQuad(
      array, index,
      bounds2.minX, y, bounds2.maxY,
      bounds2.maxX, y, bounds2.maxY,
      bounds2.maxX, y, bounds2.minY,
      bounds2.minX, y, bounds2.minY
    );
  },

  /**
   * Writes a single right-facing quad into a buffer, returning the new index location. Assumes verties in
   * counterclockwise order.
   * @public
   *
   * Writes 18 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {Bounds2} bounds2 - z,y
   * @param {number} x
   * @returns {number}
   */
  writeRightVertices( array, index, bounds2, x ) {
    return ThreeUtils.writeQuad(
      array, index,
      x, bounds2.minY, bounds2.maxX,
      x, bounds2.minY, bounds2.minX,
      x, bounds2.maxY, bounds2.minX,
      x, bounds2.maxY, bounds2.maxX
    );
  },

  /**
   * Writes a single left-facing quad into a buffer, returning the new index location. Assumes verties in
   * counterclockwise order.
   * @public
   *
   * Writes 18 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {Bounds2} bounds2 - z,y
   * @param {number} x
   * @returns {number}
   */
  writeLeftVertices( array, index, bounds2, x ) {
    return ThreeUtils.writeQuad(
      array, index,
      x, bounds2.minY, bounds2.maxX,
      x, bounds2.maxY, bounds2.maxX,
      x, bounds2.maxY, bounds2.minX,
      x, bounds2.minY, bounds2.minX
    );
  },

  /**
   * Returns a THREE.TextureLoader instance (using a singleton so we don't create more than we need).
   * @public
   *
   * @returns {THREE.TextureLoader}
   */
  get textureLoader() {
    if ( !textureLoader ) {
      textureLoader = new THREE.TextureLoader();
    }
    return textureLoader;
  },

  /**
   * Returns a THREE.Texture for a given HTMLImageElement.
   * @public
   *
   * @param {HTMLImageElement} image
   * @param {boolean} [waitForLoad]
   * @returns {THREE.Texture}
   */
  imageToTexture( image, waitForLoad ) {
    if ( waitForLoad ) {
      return ThreeUtils.textureLoader.load( image.src, asyncLoader.createLock() );
    }
    else {
      return ThreeUtils.textureLoader.load( image.src );
    }
  },

  /**
   * Checks if webgl is enabled by the browser.
   * @public
   *
   * @returns {boolean}
   */
  isWebGLEnabled() {
    return phet.chipper.queryParameters.webgl && Utils.isWebGLSupported;
  },

  /**
   * Shows a warning with a link to more information about PhET simulation webgl compatibility.
   * @public
   *
   * @param {ScreenView} screenView
   */
  showWebGLWarning( screenView ) {
    const warningNode = new HBox( {
      children: [
        new Path( exclamationTriangleSolidShape, {
          fill: '#E87600', // "safety orange", according to Wikipedia
          scale: 0.06
        } ),
        new Text( webglWarningBodyString, {
          font: new PhetFont( 16 ),
          fill: '#000',
          maxWidth: 600
        } )
      ],
      spacing: 12,
      align: 'center',
      cursor: 'pointer',
      center: screenView.layoutBounds.center
    } );
    screenView.addChild( warningNode );

    warningNode.mouseArea = warningNode.touchArea = warningNode.localBounds;

    warningNode.addInputListener( {
      up: function() {
        openPopup( `http://phet.colorado.edu/webgl-disabled-page?simLocale=${phet.joist.sim.locale}` );
      }
    } );
  }
};

mobius.register( 'ThreeUtils', ThreeUtils );
export default ThreeUtils;<commit_msg>Update http=>https for webgl diabled pages, see https://github.com/phetsims/tasks/issues/1089<commit_after>// Copyright 2019-2021, University of Colorado Boulder

/**
 * Base view for all "show a single molecule in the center" screens
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

import Vector3 from '../../dot/js/Vector3.js';
import asyncLoader from '../../phet-core/js/asyncLoader.js';
import openPopup from '../../phet-core/js/openPopup.js';
import PhetFont from '../../scenery-phet/js/PhetFont.js';
import sceneryPhetStrings from '../../scenery-phet/js/sceneryPhetStrings.js';
import HBox from '../../scenery/js/nodes/HBox.js';
import Path from '../../scenery/js/nodes/Path.js';
import Text from '../../scenery/js/nodes/Text.js';
import Utils from '../../scenery/js/util/Utils.js';
import exclamationTriangleSolidShape from '../../sherpa/js/fontawesome-5/exclamationTriangleSolidShape.js';
import mobius from './mobius.js';

const webglWarningBodyString = sceneryPhetStrings.webglWarning.body;

// {THREE.TextureLoader|null} - "singleton" for the texture loader
let textureLoader = null;

const scratchFloatArray = new Float32Array( 128 );

const ThreeUtils = {
  /**
   * Converts a Vector3 to a THREE.Vector3
   * @public
   *
   * @param {Vector3} vector
   * @returns {THREE.Vector3}
   */
  vectorToThree( vector ) {
    return new THREE.Vector3( vector.x, vector.y, vector.z );
  },

  /**
   * Converts a THREE.Vector3 to a Vector3
   * @public
   *
   * @param {THREE.Vector3} vector
   * @returns {Vector3}
   */
  threeToVector( vector ) {
    return new Vector3( vector.x, vector.y, vector.z );
  },

  /**
   * Converts a Color to a THREE.Color
   * @public
   *
   * @param {Color} color
   * @returns {THREE.Color}
   */
  colorToThree( color ) {
    return new THREE.Color( color.toNumber() );
  },

  /**
   * Returns an array of [ x, y, z, ... ] vertices for a quad pointed towards the camera.
   * @public
   *
   * @param {Bounds2} bounds2 - x,y
   * @param {number} z
   * @returns {Float32Array}
   */
  frontVertices( bounds2, z ) {
    return scratchFloatArray.slice( 0, ThreeUtils.writeFrontVertices( scratchFloatArray, 0, bounds2, z ) );
  },

  /**
   * Returns an array of [ x, y, z, ... ] vertices for a quad pointed up.
   * @public
   *
   * @param {Bounds2} bounds2 - x,z
   * @param {number} y
   * @returns {Float32Array}
   */
  topVertices( bounds2, y ) {
    return scratchFloatArray.slice( 0, ThreeUtils.writeTopVertices( scratchFloatArray, 0, bounds2, y ) );
  },

  /**
   * Returns an array of [ x, y, z, ... ] vertices for a quad pointed towards the right.
   * @public
   *
   * @param {Bounds2} bounds2 - z,y
   * @param {number} x
   * @returns {Float32Array}
   */
  rightVertices( bounds2, x ) {
    return scratchFloatArray.slice( 0, ThreeUtils.writeRightVertices( scratchFloatArray, 0, bounds2, x ) );
  },

  /**
   * Returns an array of [ x, y, z, ... ] vertices for a quad pointed towards the left.
   * @public
   *
   * @param {Bounds2} bounds2 - z,y
   * @param {number} x
   * @returns {Float32Array}
   */
  leftVertices( bounds2, x ) {
    return scratchFloatArray.slice( 0, ThreeUtils.writeLeftVertices( scratchFloatArray, 0, bounds2, x ) );
  },

  /**
   * Writes a single triangle into a buffer, returning the new index location. Assumes vertices in counterclockwise
   * order.
   * @public
   *
   * Writes 9 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {number} x0
   * @param {number} y0
   * @param {number} z0
   * @param {number} x1
   * @param {number} y1
   * @param {number} z1
   * @param {number} x2
   * @param {number} y2
   * @param {number} z2
   * @returns {number} - The index for the next write
   */
  writeTriangle( array, index, x0, y0, z0, x1, y1, z1, x2, y2, z2 ) {
    array[ index + 0 ] = x0;
    array[ index + 1 ] = y0;
    array[ index + 2 ] = z0;
    array[ index + 3 ] = x1;
    array[ index + 4 ] = y1;
    array[ index + 5 ] = z1;
    array[ index + 6 ] = x2;
    array[ index + 7 ] = y2;
    array[ index + 8 ] = z2;

    return index + 9;
  },

  /**
   * Writes a single quad into a buffer, returning the new index location. Assumes verties in counterclockwise order.
   * @public
   *
   * Writes 18 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {number} x0
   * @param {number} y0
   * @param {number} z0
   * @param {number} x1
   * @param {number} y1
   * @param {number} z1
   * @param {number} x2
   * @param {number} y2
   * @param {number} z2
   * @param {number} z3
   * @param {number} y3
   * @param {number} z3
   * @returns {number}
   */
  writeQuad( array, index, x0, y0, z0, x1, y1, z1, x2, y2, z2, x3, y3, z3 ) {
    index = ThreeUtils.writeTriangle(
      array, index,
      x0, y0, z0,
      x1, y1, z1,
      x2, y2, z2
    );
    index = ThreeUtils.writeTriangle(
      array, index,
      x0, y0, z0,
      x2, y2, z2,
      x3, y3, z3
    );
    return index;
  },

  /**
   * Writes a single front-facing quad into a buffer, returning the new index location. Assumes verties in
   * counterclockwise order.
   * @public
   *
   * Writes 18 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {Bounds2} bounds2 - x,y
   * @param {number} z
   * @returns {number}
   */
  writeFrontVertices( array, index, bounds2, z ) {
    return ThreeUtils.writeQuad(
      array, index,
      bounds2.minX, bounds2.maxY, z,
      bounds2.minX, bounds2.minY, z,
      bounds2.maxX, bounds2.minY, z,
      bounds2.maxX, bounds2.maxY, z
    );
  },

  /**
   * Writes a single up-facing quad into a buffer, returning the new index location. Assumes verties in
   * counterclockwise order.
   * @public
   *
   * Writes 18 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {Bounds2} bounds2 - x,z
   * @param {number} y
   * @returns {number}
   */
  writeTopVertices( array, index, bounds2, y ) {
    return ThreeUtils.writeQuad(
      array, index,
      bounds2.minX, y, bounds2.maxY,
      bounds2.maxX, y, bounds2.maxY,
      bounds2.maxX, y, bounds2.minY,
      bounds2.minX, y, bounds2.minY
    );
  },

  /**
   * Writes a single right-facing quad into a buffer, returning the new index location. Assumes verties in
   * counterclockwise order.
   * @public
   *
   * Writes 18 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {Bounds2} bounds2 - z,y
   * @param {number} x
   * @returns {number}
   */
  writeRightVertices( array, index, bounds2, x ) {
    return ThreeUtils.writeQuad(
      array, index,
      x, bounds2.minY, bounds2.maxX,
      x, bounds2.minY, bounds2.minX,
      x, bounds2.maxY, bounds2.minX,
      x, bounds2.maxY, bounds2.maxX
    );
  },

  /**
   * Writes a single left-facing quad into a buffer, returning the new index location. Assumes verties in
   * counterclockwise order.
   * @public
   *
   * Writes 18 entries into the array.
   *
   * @param {Float32Array|Float64Array} array
   * @param {number} index
   * @param {Bounds2} bounds2 - z,y
   * @param {number} x
   * @returns {number}
   */
  writeLeftVertices( array, index, bounds2, x ) {
    return ThreeUtils.writeQuad(
      array, index,
      x, bounds2.minY, bounds2.maxX,
      x, bounds2.maxY, bounds2.maxX,
      x, bounds2.maxY, bounds2.minX,
      x, bounds2.minY, bounds2.minX
    );
  },

  /**
   * Returns a THREE.TextureLoader instance (using a singleton so we don't create more than we need).
   * @public
   *
   * @returns {THREE.TextureLoader}
   */
  get textureLoader() {
    if ( !textureLoader ) {
      textureLoader = new THREE.TextureLoader();
    }
    return textureLoader;
  },

  /**
   * Returns a THREE.Texture for a given HTMLImageElement.
   * @public
   *
   * @param {HTMLImageElement} image
   * @param {boolean} [waitForLoad]
   * @returns {THREE.Texture}
   */
  imageToTexture( image, waitForLoad ) {
    if ( waitForLoad ) {
      return ThreeUtils.textureLoader.load( image.src, asyncLoader.createLock() );
    }
    else {
      return ThreeUtils.textureLoader.load( image.src );
    }
  },

  /**
   * Checks if webgl is enabled by the browser.
   * @public
   *
   * @returns {boolean}
   */
  isWebGLEnabled() {
    return phet.chipper.queryParameters.webgl && Utils.isWebGLSupported;
  },

  /**
   * Shows a warning with a link to more information about PhET simulation webgl compatibility.
   * @public
   *
   * @param {ScreenView} screenView
   */
  showWebGLWarning( screenView ) {
    const warningNode = new HBox( {
      children: [
        new Path( exclamationTriangleSolidShape, {
          fill: '#E87600', // "safety orange", according to Wikipedia
          scale: 0.06
        } ),
        new Text( webglWarningBodyString, {
          font: new PhetFont( 16 ),
          fill: '#000',
          maxWidth: 600
        } )
      ],
      spacing: 12,
      align: 'center',
      cursor: 'pointer',
      center: screenView.layoutBounds.center
    } );
    screenView.addChild( warningNode );

    warningNode.mouseArea = warningNode.touchArea = warningNode.localBounds;

    warningNode.addInputListener( {
      up: function() {
        openPopup( `https://phet.colorado.edu/webgl-disabled-page?simLocale=${phet.joist.sim.locale}` );
      }
    } );
  }
};

mobius.register( 'ThreeUtils', ThreeUtils );
export default ThreeUtils;<|endoftext|>


<commit_before>/** @jsx React.DOM */

var SeasonRow = React.createClass({
	render: function() {
		hrefTarget = "/admin/seasons/" + encodeURIComponent(this.props.season.Name + ";" + this.props.season.Year);
		return (

				{this.props.season.Name}
				{this.props.season.Year}
				{this.props.season.Active ? 'True' : 'False'}

		);
	}
});

var SeasonTable = React.createClass({
	loadSeasonsFromServer: function() {
		$.ajax({url:"/api/seasons/",
				type: 'GET',
				dataType: 'json',
				success: function(data) {
					this.setState({seasons: data});
				}.bind(this),
				error: function(xhr, status, err) {
        			console.error(this.props.url, status, err.toString());
      			}.bind(this)
		});
	},
	getInitialState: function() {
		return {seasons:[]};
	},
	componentDidMount: function() {
	    this.loadSeasonsFromServer();
	    setInterval(this.loadSeasonsFromServer, this.props.pollInterval);
  	},
	render: function() {
		var rows = [];
		this.state.seasons.forEach(function(season) {
			rows.push(<SeasonRow season={season} />);
		});
		return (

						Name
						Year
						Active

{rows}

		);
	}
});

React.renderComponent(<SeasonTable pollInterval="1000" />, document.getElementById('seasons'));
<commit_msg>Removed auto-refresh from the admin panels.<commit_after>/** @jsx React.DOM */

var SeasonRow = React.createClass({
	render: function() {
		hrefTarget = "/admin/seasons/" + encodeURIComponent(this.props.season.Name + ";" + this.props.season.Year);
		return (

				{this.props.season.Name}
				{this.props.season.Year}
				{this.props.season.Active ? 'True' : 'False'}

		);
	}
});

var SeasonTable = React.createClass({
	loadSeasonsFromServer: function() {
		$.ajax({url:"/api/seasons/",
				type: 'GET',
				dataType: 'json',
				success: function(data) {
					this.setState({seasons: data});
				}.bind(this),
				error: function(xhr, status, err) {
        			console.error(this.props.url, status, err.toString());
      			}.bind(this)
		});
	},
	getInitialState: function() {
		return {seasons:[]};
	},
	componentDidMount: function() {
	    this.loadSeasonsFromServer();
  	},
	render: function() {
		var rows = [];
		this.state.seasons.forEach(function(season) {
			rows.push(<SeasonRow season={season} />);
		});
		return (

						Name
						Year
						Active

{rows}

		);
	}
});

React.renderComponent(<SeasonTable pollInterval="1000" />, document.getElementById('seasons'));
<|endoftext|>