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

/**
 * Standard math symbols used in PhET sims.
 * Sims should use these so that they are easy to change in the future.
 * Do NOT inline characters in string concatenations.
 *
 * E.g. for an expression that involves the addition of 2 values:
 * Good:  var expressionString = value1 + ' ' + MathSymbols.PLUS + ' ' +  value2;
 * Bad:   var expressionString = value1 + ' \u002b ' + value2;
 * Wrong: var expressionString = value1 + ' + ' + value2;
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );

  var MathSymbols = {

    // binary operators
    PLUS: '+',
    MINUS: '\u2212', // longer than '-'
    TIMES: '\u00d7',
    DIVIDE: '\u00f7',
    DOT: '\u22c5',

    // unary operators
    UNARY_PLUS: '+',
    UNARY_MINUS: '-', // shorter than MINUS (PhET-specific convention)

    // relational operators
    EQUAL_TO: '=',
    NOT_EQUAL_TO: '\u2260',
    GREATER_THAN: '>',
    LESS_THAN: '<'

  };

  sceneryPhet.register( 'MathSymbols', MathSymbols );

  return MathSymbols;
} );
 <commit_msg>doc #353<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * Standard math symbols used in PhET sims.
 * Sims should use these so that they are easy to change in the future.
 * Do NOT inline characters in string concatenations.
 *
 * E.g. for an expression that involves the addition of 2 values:
 * Good:  var expressionString = value1 + ' ' + MathSymbols.PLUS + ' ' +  value2;
 * Bad:   var expressionString = value1 + ' \u002b ' + value2;
 * Wrong: var expressionString = value1 + ' + ' + value2;
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );

  var MathSymbols = {

    // binary operators
    PLUS: '+',
    MINUS: '\u2212', // longer than UNARY_MINUS (PhET-specific convention)
    TIMES: '\u00d7',
    DIVIDE: '\u00f7',
    DOT: '\u22c5',

    // unary operators
    UNARY_PLUS: '+',
    UNARY_MINUS: '-', // shorter than MINUS (PhET-specific convention)

    // relational operators
    EQUAL_TO: '=',
    NOT_EQUAL_TO: '\u2260',
    GREATER_THAN: '>',
    LESS_THAN: '<'

  };

  sceneryPhet.register( 'MathSymbols', MathSymbols );

  return MathSymbols;
} );
 <|endoftext|>


<commit_before>var roleUpgrader = require('role.upgrader');
var roleCivilian = require('role.civilian');
var roleHunter = require('role.hunter');
var roleMiner = require('role.miner');
var roleRunner = require('role.runner');
var roleReserver = require('role.reserver');
var roleRepairer = require('role.repairer');
var roleColonist = require('role.colonist');
var remoteHunter = require('remote.hunter');
var remoteMiner = require('remote.miner');
var remoteRunner = require('remote.runner');
var roleClaimer = require('role.claimer');
var roleRecycler = require('role.recycler');
var roleThief = require('role.thief');
var roleRaider = require('role.raider');
var roleRaidHealer = require('role.raidHealer');
var roleDismantler = require('role.dismantler');
var roleMineralMiner = require('role.mineralMiner');
var roleTerminalManager = require('role.terminalManager');
const roleLaborant = require('role.laborant');
var respawn = require('respawn');
var tasks = require('tasks');
var planOutheal = require('plan.outheal');
var planWaitstorm = require('plan.waitstorm');
var utils = require('utils');
const marketManager = require('marketManager');
const labManager = require('labManager');
const profiler = require('screeps-profiler');

global.playerWhiteList = ['PiratenBraut','PhillipK','CokeJunkie','KaZoiden','WASP103'];

require('prototyping')();

profiler.enable();
module.exports.loop = function(){
	profiler.wrap(function() {
		if(Game.time % 500 == 0){ // garbage collect
			for(var i in Memory.creeps) {
				if(!Game.creeps[i]) {
					delete Memory.creeps[i];
				}
			}
			for(var i in Memory.flags) {
				if(!Game.flags[i]) {
					delete Memory.flags[i];
				}
			}
		}

		var myrooms = _.filter(Game.rooms, (r) => r.find(FIND_MY_STRUCTURES, {filter: (s) => s.structureType == STRUCTURE_SPAWN}).length > 0 );
		for(var room of myrooms){
			var towers = room.find(FIND_STRUCTURES, {filter: (structure) => structure.structureType == STRUCTURE_TOWER});
			if(towers.length){
				for(i=0;i<towers.length;i++){
					var tower = towers[i];
					tower.attack(tower.pos.findClosestByRange(FIND_HOSTILE_CREEPS,{filter: (c) => !_.contains(playerWhiteList,c.owner.username)}));
					if(tower.energy > 800){
						tower.repair(tower.pos.findClosestByRange(FIND_STRUCTURES,{filter: (s) => s.structureType == STRUCTURE_RAMPART && s.hits < 5000}));
						// tower.repair(tower.pos.findClosestByRange(FIND_STRUCTURES, {filter : (structure) => (
							// structure.structureType != STRUCTURE_WALL && structure.structureType != STRUCTURE_RAMPART && structure.hits < structure.hitsMax) || (
							// (structure.structureType == STRUCTURE_WALL || structure.structureType == STRUCTURE_RAMPART) && structure.hits < 5000)
						   // })
						// );
					}
				}
			}
		}

		for(var name in Game.creeps){
			// var startCpu = Game.cpu.getUsed();
			var creep = Game.creeps[name];
			if(creep.memory.role == 'runner'){
				roleRunner.run(creep);
			}
			else if(creep.memory.role == 'miner'){
				roleMiner.run(creep);
			}
			else if(creep.memory.role == 'upgrader'){
				roleUpgrader.run(creep);
			}
			else if(creep.memory.role == 'civilian'){
				roleCivilian.run(creep);
			}
			else if(creep.memory.role == 'repairer'){
				roleRepairer.run(creep);
			}
			else if(creep.memory.role == 'hunter'){
				roleHunter.run(creep);
			}
			else if(creep.memory.role == 'remoteMiner'){
				remoteMiner.run(creep);
			}
			else if(creep.memory.role == 'remoteRunner'){
				remoteRunner.run(creep);
			}
			else if(creep.memory.role == 'reserver'){
				roleReserver.run(creep);
			}
			else if(creep.memory.role == 'remoteHunter'){
				remoteHunter.run(creep);
			}
			else if(creep.memory.role == 'shield'){
	            creep.moveTo(Game.flags['Rally']);
			}
			else if(creep.memory.role == 'raider'){
				roleRaider.run(creep);
			}
			else if(creep.memory.role == 'raidHealer'){
				roleRaidHealer.run(creep);
			}
			else if(creep.memory.role == 'thief'){
				roleThief.run(creep);
			}
			else if(creep.memory.role == 'colonist'){
				roleColonist.run(creep);
			}
			else if(creep.memory.role == 'claimer'){
				roleClaimer.run(creep);
			}
			else if(creep.memory.role == 'recycler'){
				roleRecycler.run(creep);
			}
			else if(creep.memory.role == 'planOutheal'){
				planOutheal.run(creep);
			}
			else if(creep.memory.role == 'planWaitstorm'){
				planWaitstorm.run(creep);
			}
			else if(creep.memory.role == 'dismantler'){
				roleDismantler.run(creep);
			}
			else if(creep.memory.role == 'mineralMiner'){
				roleMineralMiner.run(creep);
			}
			else if(creep.memory.role == 'terminalManager'){
				roleTerminalManager.run(creep);
			}
			else if(creep.memory.role == 'laborant'){
				roleLaborant.run(creep);
			}
			// var elapsed = Game.cpu.getUsed() - startCpu;
			// console.log('Creep '+name+' with role '+creep.memory.role+' has used '+elapsed+' CPU time');
		}
		respawn.run(myrooms);
		if(Game.time % 50 == 0){
    		marketManager.run();
		}
		for(room of myrooms){
			labManager.run(room);
		}

		if(Game.flags['blockade'] && Game.time % 1200 == 0){
			var blockadeBody = [TOUGH,TOUGH,TOUGH,MOVE,MOVE,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE];
			Game.spawns.Spawn1.room.requestCreep(blockadeBody,undefined,{role:'raider',myflag:'blockade',waypoint:['waypoint']});
			Game.spawns.Spawn2.room.requestCreep(blockadeBody,undefined,{role:'raider',myflag:'blockade',waypoint:['waypoint']});
			Game.spawns.Spawn2.room.requestCreep(blockadeBody,undefined,{role:'raider',myflag:'blockade',waypoint:['waypoint']});

			var healBody = [TOUGH,TOUGH,TOUGH,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,HEAL,HEAL,HEAL,HEAL,HEAL,HEAL,HEAL,MOVE];
			Game.spawns.Spawn1.room.requestCreep(healBody,undefined,{role:'raidHealer',myflag:'blockade',waypoint:['waypoint']});
			Game.spawns.Spawn2.room.requestCreep(healBody,undefined,{role:'raidHealer',myflag:'blockade',waypoint:['waypoint']});
		}

		var startRoom = Game.rooms['W63N66'];
		if(Game.spawns.Spawn1.hits < Game.spawns.Spawn1.hitsMax){
			startRoom.controller.activateSafeMode();
		}
		// var newRoom = Game.rooms['W55S18'];
		// var hostilesInNewRoom =  newRoom.find(FIND_HOSTILE_CREEPS,{filter: (c) => !_.contains(playerWhiteList,c.owner.username)});
		// if(hostilesInNewRoom.length > 0){
		// 	newRoom.controller.activateSafeMode();
		// }
	});
}
<commit_msg>register most things with profiler<commit_after>const profiler = require('screeps-profiler');
var roleUpgrader = require('role.upgrader');
profiler.registerObject(roleUpgrader, 'roleUpgrader');
var roleCivilian = require('role.civilian');
profiler.registerObject(roleCivilian, 'roleCivilian');
var roleHunter = require('role.hunter');
profiler.registerObject(roleHunter, 'roleHunter');
var roleMiner = require('role.miner');
profiler.registerObject(roleMiner, 'roleMiner');
var roleRunner = require('role.runner');
profiler.registerObject(roleRunner, 'roleRunner');
var roleReserver = require('role.reserver');
profiler.registerObject(roleReserver, 'roleReserver');
var roleRepairer = require('role.repairer');
profiler.registerObject(roleRepairer, 'roleRepairer');
var roleColonist = require('role.colonist');
profiler.registerObject(roleColonist, 'roleColonist');
var remoteHunter = require('remote.hunter');
profiler.registerObject(remoteHunter, 'remoteHunter');
var remoteMiner = require('remote.miner');
profiler.registerObject(remoteMiner, 'remoteMiner');
var remoteRunner = require('remote.runner');
profiler.registerObject(remoteRunner, 'remoteRunner');
var roleClaimer = require('role.claimer');
profiler.registerObject(roleClaimer, 'roleClaimer');
var roleRecycler = require('role.recycler');
profiler.registerObject(roleRecycler, 'roleRecycler');
var roleThief = require('role.thief');
profiler.registerObject(roleThief, 'roleThief');
var roleRaider = require('role.raider');
profiler.registerObject(roleRaider, 'roleRaider');
var roleRaidHealer = require('role.raidHealer');
profiler.registerObject(roleRaidHealer, 'roleRaidHealer');
var roleDismantler = require('role.dismantler');
profiler.registerObject(roleDismantler, 'roleDismantler');
var roleMineralMiner = require('role.mineralMiner');
profiler.registerObject(roleMineralMiner, 'roleMineralMiner');
var roleTerminalManager = require('role.terminalManager');
profiler.registerObject(roleTerminalManager, 'roleTerminalManager');
const roleLaborant = require('role.laborant');
profiler.registerObject(roleLaborant, 'roleLaborant');
var respawn = require('respawn');
profiler.registerObject(respawn, 'respawn');
var tasks = require('tasks');
profiler.registerObject(tasks, 'tasks');
var planOutheal = require('plan.outheal');
profiler.registerObject(planOutheal, 'planOutheal');
var planWaitstorm = require('plan.waitstorm');
profiler.registerObject(planWaitstorm, 'planWaitstorm');
var utils = require('utils');
profiler.registerObject(utils, 'utils');
const marketManager = require('marketManager');
profiler.registerObject(marketManager, 'marketManager');
const labManager = require('labManager');
profiler.registerObject(labManager, 'labManager');

global.playerWhiteList = ['PiratenBraut','PhillipK','CokeJunkie','KaZoiden','WASP103'];

require('prototyping')();

profiler.enable();
module.exports.loop = function(){
	profiler.wrap(function() {
		if(Game.time % 500 == 0){ // garbage collect
			for(var i in Memory.creeps) {
				if(!Game.creeps[i]) {
					delete Memory.creeps[i];
				}
			}
			for(var i in Memory.flags) {
				if(!Game.flags[i]) {
					delete Memory.flags[i];
				}
			}
		}

		var myrooms = _.filter(Game.rooms, (r) => r.find(FIND_MY_STRUCTURES, {filter: (s) => s.structureType == STRUCTURE_SPAWN}).length > 0 );
		for(var room of myrooms){
			var towers = room.find(FIND_STRUCTURES, {filter: (structure) => structure.structureType == STRUCTURE_TOWER});
			if(towers.length){
				for(i=0;i<towers.length;i++){
					var tower = towers[i];
					tower.attack(tower.pos.findClosestByRange(FIND_HOSTILE_CREEPS,{filter: (c) => !_.contains(playerWhiteList,c.owner.username)}));
					if(tower.energy > 800){
						tower.repair(tower.pos.findClosestByRange(FIND_STRUCTURES,{filter: (s) => s.structureType == STRUCTURE_RAMPART && s.hits < 5000}));
						// tower.repair(tower.pos.findClosestByRange(FIND_STRUCTURES, {filter : (structure) => (
							// structure.structureType != STRUCTURE_WALL && structure.structureType != STRUCTURE_RAMPART && structure.hits < structure.hitsMax) || (
							// (structure.structureType == STRUCTURE_WALL || structure.structureType == STRUCTURE_RAMPART) && structure.hits < 5000)
						   // })
						// );
					}
				}
			}
		}

		for(var name in Game.creeps){
			// var startCpu = Game.cpu.getUsed();
			var creep = Game.creeps[name];
			if(creep.memory.role == 'runner'){
				roleRunner.run(creep);
			}
			else if(creep.memory.role == 'miner'){
				roleMiner.run(creep);
			}
			else if(creep.memory.role == 'upgrader'){
				roleUpgrader.run(creep);
			}
			else if(creep.memory.role == 'civilian'){
				roleCivilian.run(creep);
			}
			else if(creep.memory.role == 'repairer'){
				roleRepairer.run(creep);
			}
			else if(creep.memory.role == 'hunter'){
				roleHunter.run(creep);
			}
			else if(creep.memory.role == 'remoteMiner'){
				remoteMiner.run(creep);
			}
			else if(creep.memory.role == 'remoteRunner'){
				remoteRunner.run(creep);
			}
			else if(creep.memory.role == 'reserver'){
				roleReserver.run(creep);
			}
			else if(creep.memory.role == 'remoteHunter'){
				remoteHunter.run(creep);
			}
			else if(creep.memory.role == 'shield'){
	            creep.moveTo(Game.flags['Rally']);
			}
			else if(creep.memory.role == 'raider'){
				roleRaider.run(creep);
			}
			else if(creep.memory.role == 'raidHealer'){
				roleRaidHealer.run(creep);
			}
			else if(creep.memory.role == 'thief'){
				roleThief.run(creep);
			}
			else if(creep.memory.role == 'colonist'){
				roleColonist.run(creep);
			}
			else if(creep.memory.role == 'claimer'){
				roleClaimer.run(creep);
			}
			else if(creep.memory.role == 'recycler'){
				roleRecycler.run(creep);
			}
			else if(creep.memory.role == 'planOutheal'){
				planOutheal.run(creep);
			}
			else if(creep.memory.role == 'planWaitstorm'){
				planWaitstorm.run(creep);
			}
			else if(creep.memory.role == 'dismantler'){
				roleDismantler.run(creep);
			}
			else if(creep.memory.role == 'mineralMiner'){
				roleMineralMiner.run(creep);
			}
			else if(creep.memory.role == 'terminalManager'){
				roleTerminalManager.run(creep);
			}
			else if(creep.memory.role == 'laborant'){
				roleLaborant.run(creep);
			}
			// var elapsed = Game.cpu.getUsed() - startCpu;
			// console.log('Creep '+name+' with role '+creep.memory.role+' has used '+elapsed+' CPU time');
		}
		respawn.run(myrooms);
		if(Game.time % 50 == 0){
    		marketManager.run();
		}
		for(room of myrooms){
			labManager.run(room);
		}

		if(Game.flags['blockade'] && Game.time % 1200 == 0){
			var blockadeBody = [TOUGH,TOUGH,TOUGH,MOVE,MOVE,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE,ATTACK,MOVE];
			Game.spawns.Spawn1.room.requestCreep(blockadeBody,undefined,{role:'raider',myflag:'blockade',waypoint:['waypoint']});
			Game.spawns.Spawn2.room.requestCreep(blockadeBody,undefined,{role:'raider',myflag:'blockade',waypoint:['waypoint']});
			Game.spawns.Spawn2.room.requestCreep(blockadeBody,undefined,{role:'raider',myflag:'blockade',waypoint:['waypoint']});

			var healBody = [TOUGH,TOUGH,TOUGH,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,HEAL,HEAL,HEAL,HEAL,HEAL,HEAL,HEAL,MOVE];
			Game.spawns.Spawn1.room.requestCreep(healBody,undefined,{role:'raidHealer',myflag:'blockade',waypoint:['waypoint']});
			Game.spawns.Spawn2.room.requestCreep(healBody,undefined,{role:'raidHealer',myflag:'blockade',waypoint:['waypoint']});
		}

		var startRoom = Game.rooms['W63N66'];
		if(Game.spawns.Spawn1.hits < Game.spawns.Spawn1.hitsMax){
			startRoom.controller.activateSafeMode();
		}
		// var newRoom = Game.rooms['W55S18'];
		// var hostilesInNewRoom =  newRoom.find(FIND_HOSTILE_CREEPS,{filter: (c) => !_.contains(playerWhiteList,c.owner.username)});
		// if(hostilesInNewRoom.length > 0){
		// 	newRoom.controller.activateSafeMode();
		// }
	});
}
<|endoftext|>


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

/*
 * A bucket that can be used to store spherical objects.  Manages the addition and removal of the spheres, stacks them
 * as they are added, and manages the stack as spheres are removed.
 *
 * This expects the spheres to have certain properties, please inspect the code to understand the 'contract' between the
 * bucket and the spheres.
 *
 * @author John Blanco
 */

import Utils from '../../../dot/js/Utils.js';
import Vector2 from '../../../dot/js/Vector2.js';
import cleanArray from '../../../phet-core/js/cleanArray.js';
import inherit from '../../../phet-core/js/inherit.js';
import merge from '../../../phet-core/js/merge.js';
import Tandem from '../../../tandem/js/Tandem.js';
import phetcommon from '../phetcommon.js';
import Bucket from './Bucket.js';
import SphereBucketIO from './SphereBucketIO.js';

/**
 * @param {Object} [options]
 * @constructor
 */
function SphereBucket( options ) {
  options = merge( {
    sphereRadius: 10,  // expected radius of the spheres that will be placed in this bucket
    usableWidthProportion: 1.0,  // proportion of the bucket width that the spheres can occupy
    tandem: Tandem.OPTIONAL,
    phetioType: SphereBucketIO
  }, options );

  Bucket.call( this, options );

  // @private
  this.sphereBucketTandem = options.tandem;

  this._sphereRadius = options.sphereRadius;
  this._usableWidthProportion = options.usableWidthProportion;

  // Empirically determined, for positioning particles inside the bucket.
  this._verticalParticleOffset = options.verticalParticleOffset || -this._sphereRadius * 0.4;

  // particles managed by this bucket
  this._particles = [];
}

phetcommon.register( 'SphereBucket', SphereBucket );

// Inherit from base type.
inherit( Bucket, SphereBucket, {

  // @public
  addParticleFirstOpen: function( particle, animate ) {
    particle.destinationProperty.set( this.getFirstOpenPosition() );
    this.addParticle( particle, animate );
  },

  // @public
  addParticleNearestOpen: function( particle, animate ) {
    particle.destinationProperty.set( this.getNearestOpenPosition() );
    this.addParticle( particle, animate );
  },

  // @private
  addParticle: function( particle, animate ) {
    if ( !animate ) {
      particle.positionProperty.set( particle.destinationProperty.get() );
    }
    this._particles.push( particle );
    const self = this;

    // add a listener that will remove this particle from the bucket if the user grabs it
    const particleRemovedListener = function() {
      self.removeParticle( particle );

      // the process of removing the particle from the bucket should also disconnect removal listener
      assert && assert( !particle.bucketRemovalListener, 'listener still present after being removed from bucket' );
    };
    particle.userControlledProperty.lazyLink( particleRemovedListener );
    particle.bucketRemovalListener = particleRemovedListener; // Attach to the particle to aid unlinking in some cases.
  },

  // @public
  removeParticle: function( particle, skipLayout ) {
    assert && assert( this.containsParticle( particle ), 'attempt made to remove particle that is not in bucket' );

    // remove the particle from the array
    this._particles = _.without( this._particles, particle );

    // remove the removal listener if it is still present
    if ( particle.bucketRemovalListener ) {
      particle.userControlledProperty.unlink( particle.bucketRemovalListener );
      delete particle.bucketRemovalListener;
    }

    // redo the layout of the particles if enabled
    if ( !skipLayout ) {
      this.relayoutBucketParticles();
    }
  },

  // @public
  containsParticle: function( particle ) {
    return this._particles.indexOf( particle ) !== -1;
  },

  // @public
  extractClosestParticle: function( position ) {
    let closestParticle = null;
    this._particles.forEach( function( particle ) {
      if ( closestParticle === null ||
           closestParticle.positionProperty.get().distance( position ) > particle.positionProperty.get().distance( position ) ) {
        closestParticle = particle;
      }
    } );
    if ( closestParticle !== null ) {
      // The particle is removed by setting 'userControlled' to true.  This
      // relies on the listener that was added when the particle was placed
      // into the bucket.
      closestParticle.userControlledProperty.set( true );
    }
    return closestParticle;
  },

  // @public
  getParticleList: function() { return this._particles; },

  // @public
  reset: function() {
    this._particles.forEach( function( particle ) {
      // Remove listeners that are watching for removal from bucket.
      if ( typeof ( particle.bucketRemovalListener ) === 'function' ) {
        particle.userControlledProperty.unlink( particle.bucketRemovalListener );
        delete particle.bucketRemovalListener;
      }
    } );
    cleanArray( this._particles );
  },

  // @private
  isPositionOpen: function( position ) {
    let positionOpen = true;
    for ( let i = 0; i < this._particles.length; i++ ) {
      const particle = this._particles[ i ];
      if ( particle.destinationProperty.get().equals( position ) ) {
        positionOpen = false;
        break;
      }
    }
    return positionOpen;
  },

  // @private
  getFirstOpenPosition: function() {
    let openPosition = Vector2.ZERO;
    const usableWidth = this.size.width * this._usableWidthProportion - 2 * this._sphereRadius;
    let offsetFromBucketEdge = ( this.size.width - usableWidth ) / 2 + this._sphereRadius;
    let numParticlesInLayer = Math.floor( usableWidth / ( this._sphereRadius * 2 ) );
    let row = 0;
    let positionInLayer = 0;
    let found = false;
    while ( !found ) {
      const testPosition = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this._sphereRadius,
        this.getYPositionForLayer( row ) );
      if ( this.isPositionOpen( testPosition ) ) {
        // We found a position that is open.
        openPosition = testPosition;
        found = true;
      }
      else {
        positionInLayer++;
        if ( positionInLayer >= numParticlesInLayer ) {
          // Move to the next layer.
          row++;
          positionInLayer = 0;
          numParticlesInLayer--;
          offsetFromBucketEdge += this._sphereRadius;
          if ( numParticlesInLayer === 0 ) {
            // This algorithm doesn't handle the situation where
            // more particles are added than can be stacked into
            // a pyramid of the needed size, but so far it hasn't
            // needed to.  If this requirement changes, the
            // algorithm will need to change too.
            numParticlesInLayer = 1;
            offsetFromBucketEdge -= this._sphereRadius;
          }
        }
      }
    }
    return openPosition;
  },

  // @private
  getLayerForYPosition: function( yPosition ) {
    return Math.abs( Utils.roundSymmetric( ( yPosition - ( this.position.y + this._verticalParticleOffset ) ) / ( this._sphereRadius * 2 * 0.866 ) ) );
  },

  /*
   * Get the nearest open position to the highest occupied layer.  This is used for particle stacking.
   *
   * @returns {Vector2}
   * @private
   */
  getNearestOpenPosition: function() {
    // Determine the highest occupied layer.  The bottom layer is 0.
    let highestOccupiedLayer = 0;
    const self = this;
    _.each( this._particles, function( particle ) {
      const layer = self.getLayerForYPosition( particle.destinationProperty.get().y );
      if ( layer > highestOccupiedLayer ) {
        highestOccupiedLayer = layer;
      }
    } );

    // Make a list of all open positions in the occupied layers.
    const openPositions = [];
    const usableWidth = this.size.width * this._usableWidthProportion - 2 * this._sphereRadius;
    let offsetFromBucketEdge = ( this.size.width - usableWidth ) / 2 + this._sphereRadius;
    let numParticlesInLayer = Math.floor( usableWidth / ( this._sphereRadius * 2 ) );

    // Loop, searching for open positions in the particle stack.
    for ( let layer = 0; layer <= highestOccupiedLayer + 1; layer++ ) {

      // Add all open positions in the current layer.
      for ( let positionInLayer = 0; positionInLayer < numParticlesInLayer; positionInLayer++ ) {
        const testPosition = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this._sphereRadius,
          this.getYPositionForLayer( layer ) );
        if ( this.isPositionOpen( testPosition ) ) {

          // We found a position that is unoccupied.
          if ( layer === 0 || this.countSupportingParticles( testPosition ) === 2 ) {
            // This is a valid open position.
            openPositions.push( testPosition );
          }
        }
      }

      // Adjust variables for the next layer.
      numParticlesInLayer--;
      offsetFromBucketEdge += this._sphereRadius;
      if ( numParticlesInLayer === 0 ) {
        // If the stacking pyramid is full, meaning that there are
        // no positions that are open within it, this algorithm
        // classifies the positions directly above the top of the
        // pyramid as being open.  This would result in a stack
        // of particles with a pyramid base.  So far, this hasn't
        // been a problem, but this limitation may limit
        // reusability of this algorithm.
        numParticlesInLayer = 1;
        offsetFromBucketEdge -= this._sphereRadius;
      }
    }

    // Find the closest open position to the provided current position.
    // Only the X-component is used for this determination, because if
    // the Y-component is used the particles often appear to fall sideways
    // when released above the bucket, which just looks weird.
    let closestOpenPosition = openPositions[ 0 ] || Vector2.ZERO;

    _.each( openPositions, function( position ) {
      if ( position.distance( position ) < closestOpenPosition.distance( position ) ) {
        // This position is closer.
        closestOpenPosition = position;
      }
    } );
    return closestOpenPosition;
  },

  // @private
  getYPositionForLayer: function( layer ) {
    return this.position.y + this._verticalParticleOffset + layer * this._sphereRadius * 2 * 0.866;
  },

  /*
   * Determine whether a particle is 'dangling', i.e. hanging above an open
   * space in the stack of particles.  Dangling particles should fall.
   * @private
   */
  isDangling: function( particle ) {
    const onBottomRow = particle.destinationProperty.get().y === this.position.y + this._verticalParticleOffset;
    return !onBottomRow && this.countSupportingParticles( particle.destinationProperty.get() ) < 2;
  },

  // @private
  countSupportingParticles: function( position ) {
    let count = 0;
    for ( let i = 0; i < this._particles.length; i++ ) {
      const p = this._particles[ i ];
      if ( p.destinationProperty.get().y < position.y && // Must be in a lower layer
           p.destinationProperty.get().distance( position ) < this._sphereRadius * 3 ) {
        // Must be a supporting particle.
        count++;
      }
    }
    return count;
  },

  // @private
  relayoutBucketParticles: function() {
    let particleMoved;
    do {
      for ( let i = 0; i < this._particles.length; i++ ) {
        particleMoved = false;
        const particle = this._particles[ i ];
        if ( this.isDangling( particle ) ) {
          particle.destinationProperty.set( this.getNearestOpenPosition() );
          particleMoved = true;
          break;
        }
      }
    } while ( particleMoved );
  }

} );

export default SphereBucket;<commit_msg>Reintroduce position into the sphereBucket.getNearestOpenPosition(). https://github.com/phetsims/build-an-atom/issues/210<commit_after>// Copyright 2013-2020, University of Colorado Boulder

/*
 * A bucket that can be used to store spherical objects.  Manages the addition and removal of the spheres, stacks them
 * as they are added, and manages the stack as spheres are removed.
 *
 * This expects the spheres to have certain properties, please inspect the code to understand the 'contract' between the
 * bucket and the spheres.
 *
 * @author John Blanco
 */

import Utils from '../../../dot/js/Utils.js';
import Vector2 from '../../../dot/js/Vector2.js';
import cleanArray from '../../../phet-core/js/cleanArray.js';
import inherit from '../../../phet-core/js/inherit.js';
import merge from '../../../phet-core/js/merge.js';
import Tandem from '../../../tandem/js/Tandem.js';
import phetcommon from '../phetcommon.js';
import Bucket from './Bucket.js';
import SphereBucketIO from './SphereBucketIO.js';

/**
 * @param {Object} [options]
 * @constructor
 */
function SphereBucket( options ) {
  options = merge( {
    sphereRadius: 10,  // expected radius of the spheres that will be placed in this bucket
    usableWidthProportion: 1.0,  // proportion of the bucket width that the spheres can occupy
    tandem: Tandem.OPTIONAL,
    phetioType: SphereBucketIO
  }, options );

  Bucket.call( this, options );

  // @private
  this.sphereBucketTandem = options.tandem;

  this._sphereRadius = options.sphereRadius;
  this._usableWidthProportion = options.usableWidthProportion;

  // Empirically determined, for positioning particles inside the bucket.
  this._verticalParticleOffset = options.verticalParticleOffset || -this._sphereRadius * 0.4;

  // particles managed by this bucket
  this._particles = [];
}

phetcommon.register( 'SphereBucket', SphereBucket );

// Inherit from base type.
inherit( Bucket, SphereBucket, {

  // @public
  addParticleFirstOpen: function( particle, animate ) {
    particle.destinationProperty.set( this.getFirstOpenPosition() );
    this.addParticle( particle, animate );
  },

  // @public
  addParticleNearestOpen: function( particle, animate ) {
    particle.destinationProperty.set( this.getNearestOpenPosition( particle.destinationProperty.get() ) );
    this.addParticle( particle, animate );
  },

  // @private
  addParticle: function( particle, animate ) {
    if ( !animate ) {
      particle.positionProperty.set( particle.destinationProperty.get() );
    }
    this._particles.push( particle );
    const self = this;

    // add a listener that will remove this particle from the bucket if the user grabs it
    const particleRemovedListener = function() {
      self.removeParticle( particle );

      // the process of removing the particle from the bucket should also disconnect removal listener
      assert && assert( !particle.bucketRemovalListener, 'listener still present after being removed from bucket' );
    };
    particle.userControlledProperty.lazyLink( particleRemovedListener );
    particle.bucketRemovalListener = particleRemovedListener; // Attach to the particle to aid unlinking in some cases.
  },

  // @public
  removeParticle: function( particle, skipLayout ) {
    assert && assert( this.containsParticle( particle ), 'attempt made to remove particle that is not in bucket' );

    // remove the particle from the array
    this._particles = _.without( this._particles, particle );

    // remove the removal listener if it is still present
    if ( particle.bucketRemovalListener ) {
      particle.userControlledProperty.unlink( particle.bucketRemovalListener );
      delete particle.bucketRemovalListener;
    }

    // redo the layout of the particles if enabled
    if ( !skipLayout ) {
      this.relayoutBucketParticles();
    }
  },

  // @public
  containsParticle: function( particle ) {
    return this._particles.indexOf( particle ) !== -1;
  },

  // @public
  extractClosestParticle: function( position ) {
    let closestParticle = null;
    this._particles.forEach( function( particle ) {
      if ( closestParticle === null ||
           closestParticle.positionProperty.get().distance( position ) > particle.positionProperty.get().distance( position ) ) {
        closestParticle = particle;
      }
    } );
    if ( closestParticle !== null ) {
      // The particle is removed by setting 'userControlled' to true.  This
      // relies on the listener that was added when the particle was placed
      // into the bucket.
      closestParticle.userControlledProperty.set( true );
    }
    return closestParticle;
  },

  // @public
  getParticleList: function() { return this._particles; },

  // @public
  reset: function() {
    this._particles.forEach( function( particle ) {
      // Remove listeners that are watching for removal from bucket.
      if ( typeof ( particle.bucketRemovalListener ) === 'function' ) {
        particle.userControlledProperty.unlink( particle.bucketRemovalListener );
        delete particle.bucketRemovalListener;
      }
    } );
    cleanArray( this._particles );
  },

  // @private
  isPositionOpen: function( position ) {
    let positionOpen = true;
    for ( let i = 0; i < this._particles.length; i++ ) {
      const particle = this._particles[ i ];
      if ( particle.destinationProperty.get().equals( position ) ) {
        positionOpen = false;
        break;
      }
    }
    return positionOpen;
  },

  // @private
  getFirstOpenPosition: function() {
    let openPosition = Vector2.ZERO;
    const usableWidth = this.size.width * this._usableWidthProportion - 2 * this._sphereRadius;
    let offsetFromBucketEdge = ( this.size.width - usableWidth ) / 2 + this._sphereRadius;
    let numParticlesInLayer = Math.floor( usableWidth / ( this._sphereRadius * 2 ) );
    let row = 0;
    let positionInLayer = 0;
    let found = false;
    while ( !found ) {
      const testPosition = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this._sphereRadius,
        this.getYPositionForLayer( row ) );
      if ( this.isPositionOpen( testPosition ) ) {
        // We found a position that is open.
        openPosition = testPosition;
        found = true;
      }
      else {
        positionInLayer++;
        if ( positionInLayer >= numParticlesInLayer ) {
          // Move to the next layer.
          row++;
          positionInLayer = 0;
          numParticlesInLayer--;
          offsetFromBucketEdge += this._sphereRadius;
          if ( numParticlesInLayer === 0 ) {
            // This algorithm doesn't handle the situation where
            // more particles are added than can be stacked into
            // a pyramid of the needed size, but so far it hasn't
            // needed to.  If this requirement changes, the
            // algorithm will need to change too.
            numParticlesInLayer = 1;
            offsetFromBucketEdge -= this._sphereRadius;
          }
        }
      }
    }
    return openPosition;
  },

  // @private
  getLayerForYPosition: function( yPosition ) {
    return Math.abs( Utils.roundSymmetric( ( yPosition - ( this.position.y + this._verticalParticleOffset ) ) / ( this._sphereRadius * 2 * 0.866 ) ) );
  },

  /*
   * Get the nearest open position to the highest occupied layer.  This is used for particle stacking.
   *
   * @param {Vector2} position
   * @returns {Vector2}
   * @private
   */
  getNearestOpenPosition: function( position ) {
    // Determine the highest occupied layer.  The bottom layer is 0.
    let highestOccupiedLayer = 0;
    const self = this;
    _.each( this._particles, function( particle ) {
      const layer = self.getLayerForYPosition( particle.destinationProperty.get().y );
      if ( layer > highestOccupiedLayer ) {
        highestOccupiedLayer = layer;
      }
    } );

    // Make a list of all open positions in the occupied layers.
    const openPositions = [];
    const usableWidth = this.size.width * this._usableWidthProportion - 2 * this._sphereRadius;
    let offsetFromBucketEdge = ( this.size.width - usableWidth ) / 2 + this._sphereRadius;
    let numParticlesInLayer = Math.floor( usableWidth / ( this._sphereRadius * 2 ) );

    // Loop, searching for open positions in the particle stack.
    for ( let layer = 0; layer <= highestOccupiedLayer + 1; layer++ ) {

      // Add all open positions in the current layer.
      for ( let positionInLayer = 0; positionInLayer < numParticlesInLayer; positionInLayer++ ) {
        const testPosition = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this._sphereRadius,
          this.getYPositionForLayer( layer ) );
        if ( this.isPositionOpen( testPosition ) ) {

          // We found a position that is unoccupied.
          if ( layer === 0 || this.countSupportingParticles( testPosition ) === 2 ) {
            // This is a valid open position.
            openPositions.push( testPosition );
          }
        }
      }

      // Adjust variables for the next layer.
      numParticlesInLayer--;
      offsetFromBucketEdge += this._sphereRadius;
      if ( numParticlesInLayer === 0 ) {
        // If the stacking pyramid is full, meaning that there are
        // no positions that are open within it, this algorithm
        // classifies the positions directly above the top of the
        // pyramid as being open.  This would result in a stack
        // of particles with a pyramid base.  So far, this hasn't
        // been a problem, but this limitation may limit
        // reusability of this algorithm.
        numParticlesInLayer = 1;
        offsetFromBucketEdge -= this._sphereRadius;
      }
    }

    // Find the closest open position to the provided current position.
    // Only the X-component is used for this determination, because if
    // the Y-component is used the particles often appear to fall sideways
    // when released above the bucket, which just looks weird.
    let closestOpenPosition = openPositions[ 0 ] || Vector2.ZERO;

    _.each( openPositions, function( openPosition ) {
      if ( openPosition.distance( position ) < closestOpenPosition.distance( position ) ) {
        // This openPosition is closer.
        closestOpenPosition = openPosition;
      }
    } );
    return closestOpenPosition;
  },

  // @private
  getYPositionForLayer: function( layer ) {
    return this.position.y + this._verticalParticleOffset + layer * this._sphereRadius * 2 * 0.866;
  },

  /*
   * Determine whether a particle is 'dangling', i.e. hanging above an open
   * space in the stack of particles.  Dangling particles should fall.
   * @private
   */
  isDangling: function( particle ) {
    const onBottomRow = particle.destinationProperty.get().y === this.position.y + this._verticalParticleOffset;
    return !onBottomRow && this.countSupportingParticles( particle.destinationProperty.get() ) < 2;
  },

  // @private
  countSupportingParticles: function( position ) {
    let count = 0;
    for ( let i = 0; i < this._particles.length; i++ ) {
      const p = this._particles[ i ];
      if ( p.destinationProperty.get().y < position.y && // Must be in a lower layer
           p.destinationProperty.get().distance( position ) < this._sphereRadius * 3 ) {
        // Must be a supporting particle.
        count++;
      }
    }
    return count;
  },

  // @private
  relayoutBucketParticles: function() {
    let particleMoved;
    do {
      for ( let i = 0; i < this._particles.length; i++ ) {
        particleMoved = false;
        const particle = this._particles[ i ];
        if ( this.isDangling( particle ) ) {
          particle.destinationProperty.set( this.getNearestOpenPosition( particle.destinationProperty.get() ) );
          particleMoved = true;
          break;
        }
      }
    } while ( particleMoved );
  }
} );

export default SphereBucket;<|endoftext|>


<commit_before>'use strict';

const loaderUtils = require('loader-utils');
const babel = require('babel-core');
const {default: generate} = require('babel-generator');

const DYNAMIC_REQUIRES_OK = Symbol.for("ok");
const DYNAMIC_REQUIRES_WARN = Symbol.for("warn");
const DYNAMIC_REQUIRES_ERROR = Symbol.for("error");
const DYNAMIC_REQUIRES_VALUES = [
  DYNAMIC_REQUIRES_OK,
  DYNAMIC_REQUIRES_WARN,
  DYNAMIC_REQUIRES_ERROR
].map(s => Symbol.keyFor(s));

const loader = function(source, inputSourceMap) {
  this.cacheable && this.cacheable();

  const config = loaderUtils.getLoaderConfig(this, "elmAssetsLoader")
  config.dynamicRequires = config.dynamicRequires || 'warn';

  if(!(config.hasOwnProperty('module') &&
       config.hasOwnProperty('tagger'))) {
    this.emitError("Please configure module and tagger to use this loader.");
  }

  if (!DYNAMIC_REQUIRES_VALUES.includes(config.dynamicRequires)) {
    this.emitError(`Expecting dynamicRequires to be one of
                    ${DYNAMIC_REQUIRES_VALUES.join(' | ')}.
                    You gave me: ${config.dynamicRequires}`);
  }

  const packageName = config['package'] || 'user/project';
  const taggerName = '_' + [
    packageName.replace(/-/g, '_').replace(/\//g, '$'),
    config.module.replace(/\./g, '_'),
    config.tagger
  ].join('$');

  const dynamicRequires = Symbol.for(config.dynamicRequires);

  const localPath = config.localPath;

  const webpackRemainingChain = loaderUtils.getRemainingRequest(this).split("!");
  const filename  = webpackRemainingChain[webpackRemainingChain.length - 1];
  const babelOptions = {
    inputSourceMap: inputSourceMap,
    sourceRoot: process.cwd(),
    filename: filename,
    compact: false
  };

  const result = transform(source, this, taggerName, dynamicRequires, localPath, babelOptions);

  this.callback(null, result.code, result.map);
};

const transform = function(source, loaderContext, taggerName, dynamicRequires, localPath, babelOptions) {
  babelOptions.plugins = [
    assetTransformer(loaderContext, taggerName, dynamicRequires, localPath)
  ];

  const result = babel.transform(source, babelOptions);
  const code = result.code;
  const map = result.map;

  if (map && (!map.sourcesContent || !map.sourcesContent.length)) {
    map.sourcesContent = [source];
  }

  return {
    code: code,
    map: map
  };
}

const assetTransformer = function(loaderContext, taggerName, dynamicRequires, localPath) {
  const plugin = function({ types: t }) {
    return {
      visitor: {
        CallExpression: callExpressionVisitor(t, loaderContext, taggerName, dynamicRequires, localPath)
      }
    }
  };

  return plugin;
}

const REPLACED_NODE = Symbol('elmAssetsLoaderReplaced');

const callExpressionVisitor = function(t, loaderContext, taggerName, dynamicRequires, localPath) {
  const visitor = function(path) {
    // avoid infinite recursion
    if (path.node[REPLACED_NODE]) {
      return;
    }

    // calling the tagger we want?

    if (!t.isIdentifier(path.node.callee)) {
      return;
    }

    if (path.node.callee.name !== taggerName) {
      return;
    }

    // check for multiple args.
    // though this isn't likely to happen in practice, since multiple
    // argument calls get compiled to something like:
    //
    //     A2(_user$project$MultiArg$Asset, 'elm_logo.svg', 'elm_logo.svg');
    if (path.node.arguments.length > 1) {
      loaderContext.emitError("Tagger that tags multiple strings is currently not supported.");
    }

    // check for zero args.
    // though this isn't likely to happen in practice, since zero
    // argument calls mean:
    //
    //     var _user$project$NoArg$assetPath = 'star.png';
    if (path.node.arguments.length === 0) {
      loaderContext.emitError("Tagger must tag only one string.");
    }

    const argument = path.node.arguments[0];

    // check for non-string-literals
    if (!(t.isLiteral(argument) && typeof argument.value === 'string')) {
      const actualCode = generate(path.node).code;

      if (dynamicRequires === DYNAMIC_REQUIRES_ERROR) {
        loaderContext.emitError("Failing hard to make sure all assets are run through webpack. Dynamically constructed asset path like this is not supported:" + actualCode);
      } else if (dynamicRequires === DYNAMIC_REQUIRES_WARN) {
        loaderContext.emitWarning("This asset path, which is dynamically constructed, will not be run through webpack: " + actualCode);
      }

      /*
        Trying to webpackify non-string-literals is like opening a can of bad things.
        Even a simple string concatenation like:

            ComplexCallAsset ("elm_logo" ++ ".svg")

        results in a function call in compiled JavaScript code:

            _user$project$ComplexCall$ComplexCallAsset(
                A2(_elm_lang$core$Basics_ops[\'++\'], \'elm_logo\', \'.svg\'))

        If we wrap this in a call to `require`, webpack will create a
        [context module][context-module].

            _user$project$ComplexCall$ComplexCallAsset(
                require(A2(_elm_lang$core$Basics_ops[\'++\'], \'elm_logo\', \'.svg\')))

       Which "contains references to all modules in that directory that can be
       required with a request matching the regular expression." Since webpack
       can't predict what will come out of the `A2` function call, webpack
       will try to include all possible modules in the context module. This
       leads to bloated bundles and lots of warnings from trying to load every
       file in the directory where the Elm file is in.

          [context-module]: https://webpack.github.io/docs/context.html#context-module
      */
      return;
    }

    // transform argument value to local path if desired

    if (typeof localPath === 'function') {
      argument.value = localPath(argument.value);
      if (typeof argument.value !== 'string') {
        loaderContext.emitError('localPath returned something not a string: ' + argument.value);
      }
    }

    const requireExpression = t.callExpression(t.Identifier('require'), [argument]);
    const replacement = t.callExpression(path.node.callee, [requireExpression]);
    replacement[REPLACED_NODE] = true;

    path.replaceWith(replacement);
  }

  return visitor;
}

module.exports = loader;
<commit_msg>Bundle up transformer options into a single object<commit_after>'use strict';

const loaderUtils = require('loader-utils');
const babel = require('babel-core');
const {default: generate} = require('babel-generator');

const DYNAMIC_REQUIRES_OK = Symbol.for("ok");
const DYNAMIC_REQUIRES_WARN = Symbol.for("warn");
const DYNAMIC_REQUIRES_ERROR = Symbol.for("error");
const DYNAMIC_REQUIRES_VALUES = [
  DYNAMIC_REQUIRES_OK,
  DYNAMIC_REQUIRES_WARN,
  DYNAMIC_REQUIRES_ERROR
].map(s => Symbol.keyFor(s));

const loader = function(source, inputSourceMap) {
  this.cacheable && this.cacheable();

  const config = loaderUtils.getLoaderConfig(this, "elmAssetsLoader")
  config.dynamicRequires = config.dynamicRequires || 'warn';

  if(!(config.hasOwnProperty('module') &&
       config.hasOwnProperty('tagger'))) {
    this.emitError("Please configure module and tagger to use this loader.");
  }

  if (!DYNAMIC_REQUIRES_VALUES.includes(config.dynamicRequires)) {
    this.emitError(`Expecting dynamicRequires to be one of
                    ${DYNAMIC_REQUIRES_VALUES.join(' | ')}.
                    You gave me: ${config.dynamicRequires}`);
  }

  const packageName = config['package'] || 'user/project';
  const taggerName = '_' + [
    packageName.replace(/-/g, '_').replace(/\//g, '$'),
    config.module.replace(/\./g, '_'),
    config.tagger
  ].join('$');

  const transformerOptions = {
    taggerName: taggerName,
    dynamicRequires: Symbol.for(config.dynamicRequires),
    localPath: config.localPath
  };

  const webpackRemainingChain = loaderUtils.getRemainingRequest(this).split("!");
  const filename  = webpackRemainingChain[webpackRemainingChain.length - 1];
  const babelOptions = {
    inputSourceMap: inputSourceMap,
    sourceRoot: process.cwd(),
    filename: filename,
    compact: false
  };

  const result = transform(source, this, transformerOptions, babelOptions);

  this.callback(null, result.code, result.map);
};

const transform = function(source, loaderContext, transformerOptions, babelOptions) {
  babelOptions.plugins = [
    assetTransformer(loaderContext, transformerOptions)
  ];

  const result = babel.transform(source, babelOptions);
  const code = result.code;
  const map = result.map;

  if (map && (!map.sourcesContent || !map.sourcesContent.length)) {
    map.sourcesContent = [source];
  }

  return {
    code: code,
    map: map
  };
}

const assetTransformer = function(loaderContext, options) {
  const plugin = function({ types: t }) {
    return {
      visitor: {
        CallExpression: callExpressionVisitor(t, loaderContext, options)
      }
    }
  };

  return plugin;
}

const REPLACED_NODE = Symbol('elmAssetsLoaderReplaced');

const callExpressionVisitor = function(t, loaderContext, options) {
  const visitor = function(path) {
    // avoid infinite recursion
    if (path.node[REPLACED_NODE]) {
      return;
    }

    // calling the tagger we want?

    if (!t.isIdentifier(path.node.callee)) {
      return;
    }

    if (path.node.callee.name !== options.taggerName) {
      return;
    }

    // check for multiple args.
    // though this isn't likely to happen in practice, since multiple
    // argument calls get compiled to something like:
    //
    //     A2(_user$project$MultiArg$Asset, 'elm_logo.svg', 'elm_logo.svg');
    if (path.node.arguments.length > 1) {
      loaderContext.emitError("Tagger that tags multiple strings is currently not supported.");
    }

    // check for zero args.
    // though this isn't likely to happen in practice, since zero
    // argument calls mean:
    //
    //     var _user$project$NoArg$assetPath = 'star.png';
    if (path.node.arguments.length === 0) {
      loaderContext.emitError("Tagger must tag only one string.");
    }

    const argument = path.node.arguments[0];

    // check for non-string-literals
    if (!(t.isLiteral(argument) && typeof argument.value === 'string')) {
      const actualCode = generate(path.node).code;

      if (options.dynamicRequires === DYNAMIC_REQUIRES_ERROR) {
        loaderContext.emitError("Failing hard to make sure all assets are run through webpack. Dynamically constructed asset path like this is not supported:" + actualCode);
      } else if (options.dynamicRequires === DYNAMIC_REQUIRES_WARN) {
        loaderContext.emitWarning("This asset path, which is dynamically constructed, will not be run through webpack: " + actualCode);
      }

      /*
        Trying to webpackify non-string-literals is like opening a can of bad things.
        Even a simple string concatenation like:

            ComplexCallAsset ("elm_logo" ++ ".svg")

        results in a function call in compiled JavaScript code:

            _user$project$ComplexCall$ComplexCallAsset(
                A2(_elm_lang$core$Basics_ops[\'++\'], \'elm_logo\', \'.svg\'))

        If we wrap this in a call to `require`, webpack will create a
        [context module][context-module].

            _user$project$ComplexCall$ComplexCallAsset(
                require(A2(_elm_lang$core$Basics_ops[\'++\'], \'elm_logo\', \'.svg\')))

       Which "contains references to all modules in that directory that can be
       required with a request matching the regular expression." Since webpack
       can't predict what will come out of the `A2` function call, webpack
       will try to include all possible modules in the context module. This
       leads to bloated bundles and lots of warnings from trying to load every
       file in the directory where the Elm file is in.

          [context-module]: https://webpack.github.io/docs/context.html#context-module
      */
      return;
    }

    // transform argument value to local path if desired

    if (typeof options.localPath === 'function') {
      argument.value = options.localPath(argument.value);
      if (typeof argument.value !== 'string') {
        loaderContext.emitError('localPath returned something not a string: ' + argument.value);
      }
    }

    const requireExpression = t.callExpression(t.Identifier('require'), [argument]);
    const replacement = t.callExpression(path.node.callee, [requireExpression]);
    replacement[REPLACED_NODE] = true;

    path.replaceWith(replacement);
  }

  return visitor;
}

module.exports = loader;
<|endoftext|>


<commit_before>"use strict";

const express = require("express");
const app = express();

// install request
const request = require('request');

const PORT = process.env.PORT || 3000;
const rovi = process.env.ROVI_APIKEY || '';
const echo = process.env.ECHONEST_APIKEY || '';

// CORS (Cross-Origin Resource Sharing) headers to support Cross-site HTTP requests
app.all('*', function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    res.header("X-Frame-Options", "SAMEORIGIN");
    next();
});

// Request artist bio, photo, and discography from Rovi API
app.get('/api/:artist/:sig', (req, res) => {
  const artist = req.params.artist;
  const sig = req.params.sig;
  const url = "http://api.rovicorp.com/search/v2.1/music/search?apikey=" + rovi + "&sig=" + sig + "&query=" + artist + "&entitytype=artist&size=1&include=images,musicbio,discography,videos&formatid=62&type=main";

  request.get(url, (err, response, body) => {
    if (err) throw err;

    res.send(JSON.parse(body));
  });
});

// Request artist's video information from Echonest API
app.get('/videos/:artist', (req, res) => {
  const artist = req.params.artist;
  const url1 = "http://developer.echonest.com/api/v4/artist/search?api_key=" + echo + "&format=json&name=" + artist + "&results=1";

  request.get(url1, (err, response, body) => {
    if (err) throw err;

    var bodyparse = JSON.parse(body);
    var artistId = bodyparse.response.artists[0].id;
    const url2 = "http://developer.echonest.com/api/v4/artist/video?api_key=" + echo + "&id=" + artistId + "&format=json&results=3&start=0";

    request.get(url2, (err, response, body) => {
      if (err) throw err;

      res.send(JSON.parse(body));
    });
  });
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
<commit_msg>Add API route to get album art<commit_after>"use strict";

const express = require("express");
const app = express();

// install request
const request = require('request');

const PORT = process.env.PORT || 3000;
const rovi = process.env.ROVI_APIKEY || '';
const echo = process.env.ECHONEST_APIKEY || '';

// CORS (Cross-Origin Resource Sharing) headers to support Cross-site HTTP requests
app.all('*', function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    res.header("X-Frame-Options", "SAMEORIGIN");
    next();
});

// Request artist bio, photo, and discography from Rovi API
app.get('/api/:artist/:sig', (req, res) => {
  const artist = req.params.artist;
  const sig = req.params.sig;
  const url = "http://api.rovicorp.com/search/v2.1/music/search?apikey=" + rovi + "&sig=" + sig + "&query=" + artist + "&entitytype=artist&size=1&include=images,musicbio,discography,videos&formatid=62&type=main";

  request.get(url, (err, response, body) => {
    if (err) throw err;

    res.send(JSON.parse(body));
  });
});

// Request album information from Rovi API
app.get('/api/:albumId/:sig', (req, res) => {
  const albumId = req.params.albumId;
  const sig = req.params.sig;
  const url = "http://api.rovicorp.com/data/v1.1/album/images?apikey=" + rovi + "&sig=" + sig + "&albumid=" + albumId + "&imagesize=200-300x200-300";

  request.get(url, (err, response, body) => {
    if (err) throw err;

    res.send(JSON.parse(body));
  });
});

// Request artist's video information from Echonest API
app.get('/videos/:artist', (req, res) => {
  const artist = req.params.artist;
  const url1 = "http://developer.echonest.com/api/v4/artist/search?api_key=" + echo + "&format=json&name=" + artist + "&results=1";

  request.get(url1, (err, response, body) => {
    if (err) throw err;

    var bodyparse = JSON.parse(body);
    var artistId = bodyparse.response.artists[0].id;
    const url2 = "http://developer.echonest.com/api/v4/artist/video?api_key=" + echo + "&id=" + artistId + "&format=json&results=3&start=0";

    request.get(url2, (err, response, body) => {
      if (err) throw err;

      res.send(JSON.parse(body));
    });
  });
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
<|endoftext|>


<commit_before>const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const dotenv = require('dotenv');
const passport = require('passport');
const BearerStrategy = require('passport-http-bearer').Strategy;
const GoogleStrategy = require('passport-google-oauth20').Strategy;

const Messages = require('./models/messages');
const User = require('./models/user');

const HOST = process.env.HOST;

dotenv.config();
const frontendUrl = process.env.FRONTEND_URL;

mongoose.Promise = global.Promise;

// Prepend timestamp to all `logs`
const log = (...args) => console.log(new Date().toISOString(), ...args);

log(`Server running in ${process.env.NODE_ENV} mode`);

const app = express();
app.use(bodyParser.json());

app.all('/*', (req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers',
    'Content-Type, '
    + 'Access-Control-Allow-Headers, '
    + 'Authorization, '
    + 'X-Requested-With'
  );
  next();
});

// ===== AUTH =====

passport.use(new BearerStrategy(
    (accessToken, done) => {
      // log(`token: ${accessToken}`);
      User.findOne({ accessToken })
      .then((user) => {
        log(`userId: ${user._id}`);
        done(null, user, { scope: 'read' });
      });
    }
));

passport.use(new GoogleStrategy({
  clientID: process.env.CLIENTID,
  clientSecret: process.env.CLIENTSECRET,
  callbackURL: `${process.env.BACKEND_URL}/auth/google/callback`
}, (accessToken, refreshToken, profile, done) => {
  log('=======', accessToken);

  User.findOneAndUpdate({ googleId: profile.id },
    {
      $set: {
        googleId: profile.id,
        nickname: profile.name.givenName || '',
        userName: profile.displayName || '',
        email: profile.emails[0].value || '',
        avatar: profile.photos[0].value || '',
        accessToken
      }
    },
    { upsert: true, new: true, setDefaultsOnInsert: true })
    .then((user) => {
      log('user', user);
      done(null, user);
    })
    .catch((err) => {
      log(err);
    });
}));

app.get('/auth/google',
  passport.authenticate('google', { scope: ['profile', 'email'] })
);

app.get('/auth/google/callback',
  passport.authenticate('google',
    { failureRedirect: frontendUrl, session: false }
  ),

  ({ user }, res) => {
    log(`Authenticated user: ${user}`);
    res.cookie('accessToken', user.accessToken, { expires: 0 });
    res.redirect(`${frontendUrl}/#/app`);
  }
);

app.get('/auth/logout', (req, res) => {
  req.logout();
  res.redirect(frontendUrl);
});

// ===== MESSAGES =====

app.get('/messages',
  passport.authenticate('bearer', { session: false }),

  ({ user }, res) => {
    log('GET /messages/');

    Messages.find().sort({ date: -1 })
    .then(messages =>
      res.json({
        currentUser: user._id,
        currentAvatar: user.avatar,
        currentNickname: user.nickname,
        messages: messages.map(message =>
          Object.assign(message, {
            comments: message.comments
              .filter(comment =>
                user._id.equals(comment.from) || user._id.equals(comment.to)
              )
              .sort((x, y) => new Date(x.date) - new Date(y.date))
          })
        ),
      })
    );
  }
);

app.post('/messages',
  passport.authenticate('bearer', { session: false }),

  ({ user, body }, res) => {
    log(`POST /messages, body: ${body}`);

    Messages.create(Object.assign(body, { userId: user._id }))
    .then(({ _id }) => {
      res.status(201).json({ _id });
    })
    .catch((err) => {
      log(`Err: ${err}`);
      res.sendStatus(404);
    });
  }
);

app.delete('/messages/:messageId',
  passport.authenticate('bearer', { session: false }),

  ({ user, params: { messageId } }, res) => {
    log(`DELETE /messages/${messageId}`);

    Messages.findOne({ _id: messageId, userId: user._id })
      .then((result) => {
        if (result) {
          result.remove();
          res.sendStatus(200);
        } else {
          res.sendStatus(404);
        }
      })
      .catch((err) => {
        console.error(err);
        res.sendStatus(404);
      });
  }
);

// ===== MEMBERS =====

app.get('/members', passport.authenticate('bearer', { session: false }),
  (req, res) => {
    log('GET /members');

    // When multiple families implemented, limit find to current family
    User.find({}, { accessToken: 0, __v: 0, googleId: 0 })

    .then(users => res.json(users))

    .catch((err) => {
      console.error(err);
      res.sendStatus(400);
    });
  }
);

// ===== CURRENT USER =====
// limited functionality now; to be used for multiple families functionality

app.get('/user', passport.authenticate('bearer', { session: false }),
  ({ user }, res) =>
    res.json({
      currentUser: {
        id: user._id,
        avatar: user.avatar,
        nickname: user.nickname,
        fullname: user.userName,
      }
    })
);

// ===== COMMENTS =====

app.post('/comments', passport.authenticate('bearer', { session: false }),
  ({ body, user }, res) => {
    log(`POST /comments/:${body.messageId}`);

    Messages.update(
      { _id: body.messageId },
      { $push: { comments: {
        from: user._id,
        to: body.to,
        text: body.text,
      } } }
    )

    .then((data) => {
      if (data.nModified > 0) {
        res.sendStatus(200);
      } else {
        res.sendStatus(404);
      }
    })

    .catch((err) => {
      console.error(err);
      res.sendStatus(404);
    });
  }
);

app.delete('/comments/:messageId/:commentId',
  passport.authenticate('bearer', { session: false }),
  ({ user, params: { messageId, commentId } }, res) => {
    log(`DELETE /comments/:${messageId}/:${commentId}`);

    Messages.update(
      { _id: messageId },
      { $pull: { comments: { _id: commentId, from: user._id } } }
    )
    .then((status) => {
      if (status.nModified > 0) {
        res.sendStatus(202);
      } else {
        res.sendStatus(404);
      }
    })
    .catch((err) => {
      console.error(err.message);
      return res.sendStatus(404);
    });
  }
);

// ===== SERVER =====

let server;

function runServer(callback) {
  return new Promise((resolve, reject) => {
    mongoose.connect(process.env.DATABASE_URL, (err) => {
      if (err && callback) {
        log(err);
        return callback(err);
      }
      return null;
    });
    server = app.listen(process.env.PORT, HOST, () => {
      log(`Your app is listening on port ${process.env.PORT}`);
      if (callback) {
        callback();
      }
      resolve(server);
    }).on('error', (err) => {
      reject(err);
    });
  });
}

function closeServer() {
  return new Promise((resolve, reject) => {
    log('Closing server');
    server.close((err) => {
      if (err) {
        reject(err);
        return;
      }
      resolve();
    });
  });
}

if (require.main === module) {
  runServer();
}

module.exports = { app, runServer, closeServer };
<commit_msg>httponly header in cookie set to false (#33)<commit_after>const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const dotenv = require('dotenv');
const passport = require('passport');
const BearerStrategy = require('passport-http-bearer').Strategy;
const GoogleStrategy = require('passport-google-oauth20').Strategy;

const Messages = require('./models/messages');
const User = require('./models/user');

const HOST = process.env.HOST;

dotenv.config();
const frontendUrl = process.env.FRONTEND_URL;

mongoose.Promise = global.Promise;

// Prepend timestamp to all `logs`
const log = (...args) => console.log(new Date().toISOString(), ...args);

log(`Server running in ${process.env.NODE_ENV} mode`);

const app = express();
app.use(bodyParser.json());

app.all('/*', (req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers',
    'Content-Type, '
    + 'Access-Control-Allow-Headers, '
    + 'Authorization, '
    + 'X-Requested-With'
  );
  next();
});

// ===== AUTH =====

passport.use(new BearerStrategy(
    (accessToken, done) => {
      // log(`token: ${accessToken}`);
      User.findOne({ accessToken })
      .then((user) => {
        log(`userId: ${user._id}`);
        done(null, user, { scope: 'read' });
      });
    }
));

passport.use(new GoogleStrategy({
  clientID: process.env.CLIENTID,
  clientSecret: process.env.CLIENTSECRET,
  callbackURL: `${process.env.BACKEND_URL}/auth/google/callback`
}, (accessToken, refreshToken, profile, done) => {
  log('=======', accessToken);

  User.findOneAndUpdate({ googleId: profile.id },
    {
      $set: {
        googleId: profile.id,
        nickname: profile.name.givenName || '',
        userName: profile.displayName || '',
        email: profile.emails[0].value || '',
        avatar: profile.photos[0].value || '',
        accessToken
      }
    },
    { upsert: true, new: true, setDefaultsOnInsert: true })
    .then((user) => {
      log('user', user);
      done(null, user);
    })
    .catch((err) => {
      log(err);
    });
}));

app.get('/auth/google',
  passport.authenticate('google', { scope: ['profile', 'email'] })
);

app.get('/auth/google/callback',
  passport.authenticate('google',
    { failureRedirect: frontendUrl, session: false }
  ),

  ({ user }, res) => {
    log(`Authenticated user: ${user}`);
    res.cookie('accessToken', user.accessToken, { expires: 0, httpOnly: false });
    res.redirect(`${frontendUrl}/#/app`);
  }
);

app.get('/auth/logout', (req, res) => {
  req.logout();
  res.redirect(frontendUrl);
});

// ===== MESSAGES =====

app.get('/messages',
  passport.authenticate('bearer', { session: false }),

  ({ user }, res) => {
    log('GET /messages/');

    Messages.find().sort({ date: -1 })
    .then(messages =>
      res.json({
        currentUser: user._id,
        currentAvatar: user.avatar,
        currentNickname: user.nickname,
        messages: messages.map(message =>
          Object.assign(message, {
            comments: message.comments
              .filter(comment =>
                user._id.equals(comment.from) || user._id.equals(comment.to)
              )
              .sort((x, y) => new Date(x.date) - new Date(y.date))
          })
        ),
      })
    );
  }
);

app.post('/messages',
  passport.authenticate('bearer', { session: false }),

  ({ user, body }, res) => {
    log(`POST /messages, body: ${body}`);

    Messages.create(Object.assign(body, { userId: user._id }))
    .then(({ _id }) => {
      res.status(201).json({ _id });
    })
    .catch((err) => {
      log(`Err: ${err}`);
      res.sendStatus(404);
    });
  }
);

app.delete('/messages/:messageId',
  passport.authenticate('bearer', { session: false }),

  ({ user, params: { messageId } }, res) => {
    log(`DELETE /messages/${messageId}`);

    Messages.findOne({ _id: messageId, userId: user._id })
      .then((result) => {
        if (result) {
          result.remove();
          res.sendStatus(200);
        } else {
          res.sendStatus(404);
        }
      })
      .catch((err) => {
        console.error(err);
        res.sendStatus(404);
      });
  }
);

// ===== MEMBERS =====

app.get('/members', passport.authenticate('bearer', { session: false }),
  (req, res) => {
    log('GET /members');

    // When multiple families implemented, limit find to current family
    User.find({}, { accessToken: 0, __v: 0, googleId: 0 })

    .then(users => res.json(users))

    .catch((err) => {
      console.error(err);
      res.sendStatus(400);
    });
  }
);

// ===== CURRENT USER =====
// limited functionality now; to be used for multiple families functionality

app.get('/user', passport.authenticate('bearer', { session: false }),
  ({ user }, res) =>
    res.json({
      currentUser: {
        id: user._id,
        avatar: user.avatar,
        nickname: user.nickname,
        fullname: user.userName,
      }
    })
);

// ===== COMMENTS =====

app.post('/comments', passport.authenticate('bearer', { session: false }),
  ({ body, user }, res) => {
    log(`POST /comments/:${body.messageId}`);

    Messages.update(
      { _id: body.messageId },
      { $push: { comments: {
        from: user._id,
        to: body.to,
        text: body.text,
      } } }
    )

    .then((data) => {
      if (data.nModified > 0) {
        res.sendStatus(200);
      } else {
        res.sendStatus(404);
      }
    })

    .catch((err) => {
      console.error(err);
      res.sendStatus(404);
    });
  }
);

app.delete('/comments/:messageId/:commentId',
  passport.authenticate('bearer', { session: false }),
  ({ user, params: { messageId, commentId } }, res) => {
    log(`DELETE /comments/:${messageId}/:${commentId}`);

    Messages.update(
      { _id: messageId },
      { $pull: { comments: { _id: commentId, from: user._id } } }
    )
    .then((status) => {
      if (status.nModified > 0) {
        res.sendStatus(202);
      } else {
        res.sendStatus(404);
      }
    })
    .catch((err) => {
      console.error(err.message);
      return res.sendStatus(404);
    });
  }
);

// ===== SERVER =====

let server;

function runServer(callback) {
  return new Promise((resolve, reject) => {
    mongoose.connect(process.env.DATABASE_URL, (err) => {
      if (err && callback) {
        log(err);
        return callback(err);
      }
      return null;
    });
    server = app.listen(process.env.PORT, HOST, () => {
      log(`Your app is listening on port ${process.env.PORT}`);
      if (callback) {
        callback();
      }
      resolve(server);
    }).on('error', (err) => {
      reject(err);
    });
  });
}

function closeServer() {
  return new Promise((resolve, reject) => {
    log('Closing server');
    server.close((err) => {
      if (err) {
        reject(err);
        return;
      }
      resolve();
    });
  });
}

if (require.main === module) {
  runServer();
}

module.exports = { app, runServer, closeServer };
<|endoftext|>


<commit_before>// @flow
import React from 'react'
import {
  Alert,
  ListView,
  Modal,
  StyleSheet,
  Text,
  TouchableOpacity,
  View
} from 'react-native'
import { NavigationActions, StackNavigator } from 'react-navigation'
import { withMappedNavigationProps } from 'react-navigation-props-mapper'
import expect from 'expect'
import moment from 'moment'
import {connect} from 'react-redux'

import SRFloatingButton from './SRFloatingButton'
import SRRatingView from './SRRatingView'
import SRStudyListCell from './SRStudyListCell'
import SRStudyTaskEditor from './SRStudyTaskEditor'
import SRTypographicCell from './SRTypographicCell'
import { SRSGrade } from './SRSpacedRepetition'
import { SRSpacedRepetition } from './SRSpacedRepetition'
import { actionCreators, SRStudyTask, SRStudyTaskIntensity } from './dataModel/SRSimpleDataModel'
import { processDataForList } from './dataModel/SRDataPresenter'
import { SRDarkColor, SRYellowColor, SRBrightColor, SRRedColor } from './utilities/SRColors'
import { uuid } from './utilities/UUID'

const studyListTitle = 'Reviews'

const AddStudyTaskNavigator = StackNavigator({
  SRStudyTaskEditor: { screen: withMappedNavigationProps(SRStudyTaskEditor) }
})

export class SRStudyList extends React.Component {

  static navigationOptions = (props) => {
    return {
      // headerLeft: <Button title='⚙' onPress={() => params.openSettings()} />,
      tabBarLabel: studyListTitle,
      tabBarIcon: ({ tintColor }) => (
        <Text>🔜</Text>
      ),
      headerTintColor: SRDarkColor,
      headerStyle: {
        backgroundColor: SRBrightColor
      },
      title: studyListTitle,
    }
  }

  state: {
    addTaskModalisVisible: bool,
    dataSource: ListView.DataSource,
    ratingModalisVisible: bool,
    renderEmptyStateHeader: bool,
    selectedID: string,
    studyTasks: Array<any>,
    keepSpinning: bool,
  }

  constructor() {
    super();
    const dataSource = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});

    this.state = {
      addTaskModalisVisible: false,
      dataSource: dataSource.cloneWithRows([]),
      ratingModalisVisible: false,
      selectedID: '',
      studyTasks: [],
      renderEmptyStateHeader: false,
      keepSpinning: false,
    };
  }

  componentWillMount() {
    const {studyTasks} = this.props
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(processDataForList(studyTasks))
    })
  }

  componentDidMount() {
    // this.props.navigation.setParams({openSettings: this.openSettings})

  }

  componentWillReceiveProps(newProps: Object) {
    const {studyTasks} = newProps
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(processDataForList(studyTasks))
    })
    this.updateStuff()
  }

  updateStuff = () => {
      const {studyTasks} = this.props
    const { addTaskModalisVisible, dataSource, keepSpinning } = this.state
    console.log(`FOO LENGTH: ${studyTasks}`)
    const foo = processDataForList(studyTasks)
    var showEmptyStateHeader = false
    const noDataYet = foo.length == 0 //dataSource.getRowCount() == 0
    console.log(`updateStuff(): noDataYet: ${noDataYet}`)
    if(noDataYet) {
      showEmptyStateHeader = true
      // see if this is called after table view changes
    } else {
      const firstItem = foo[0]//dataSource.getRowData(0, 0)
      const onlyFutureTasks = (new Date(firstItem.date) > new Date())
      if(onlyFutureTasks) {
        showEmptyStateHeader = true
      }
    }

    this.setState({
      renderEmptyStateHeader: showEmptyStateHeader,
      keepSpinning: noDataYet,
    })
  }

  render() {
    const { addTaskModalisVisible,
      dataSource,
      studyTasks,
      renderEmptyStateHeader,
      keepSpinning,
      ratingModalisVisible
    } = this.state
    const { addItem } = this.props

    const addTaskScreenProps = {
      saveAction: (item) => {
        this.addTask(item)
        this.setAddTaskModalVisible(!addTaskModalisVisible)
      },
      cancelAction: () => this.setAddTaskModalVisible(!addTaskModalisVisible),
      readonly: false,
    }

    return (
      <View style={styles.container}>
        {this._renderEmptyStateHeader(renderEmptyStateHeader)}
        <ListView
          contentContainerStyle={styles.tableViewContainer}
          style={styles.tableView}
          dataSource={dataSource}
          enableEmptySections={true}
          renderRow={(item, sectionID, rowID, highlightRow) => {

            const d = new Date(item.date)
            const itemIsOverDue = new Date() > d
            const itemIsToday = d.toDateString() == new Date().toDateString()
            const allowRating = itemIsToday || itemIsOverDue
            const formattedDate = this.formatCellDate(d)

            if(allowRating) {
              return (

                <SRTypographicCell
                  onPressDetailsButton={() => this.navigateToDetails(item)}
                  onPressRateButton={() => this.rateItem(item.id)}
                >
                  {{title: item.taskName, notes: item.notes, date: formattedDate}}
                </SRTypographicCell>

              )
            } else {
              return (

                <SRStudyListCell
                  onPressDetailsButton={() => this.navigateToDetails(item)}
                >
                {{title: item.taskName, notes: item.notes, date: formattedDate}}
              </SRStudyListCell>

            )}
          }}
        />

        <View style={styles.floatingButton}>
          <SRFloatingButton
            keepSpinning={keepSpinning}
            style={styles.addTouchable}
            onPress={()=>{
            this.setAddTaskModalVisible(!this.state.addTaskModalisVisible)
          }}>

            <Text style={styles.floatingButtonText}>⬥</Text>

          </SRFloatingButton>
        </View>

        <Modal
          animationType={"fade"}
          transparent={true}
          visible={ratingModalisVisible}
          >
         <SRRatingView
           dismissAction={() => this.setRatingModalVisible(!ratingModalisVisible)}
           ratedCallback={(index) => this.rateTask(index)}
         />
        </Modal>

        <Modal
          animationType={"slide"}
          transparent={false}
          visible={addTaskModalisVisible}
          >
         <AddStudyTaskNavigator screenProps={addTaskScreenProps}/>
        </Modal>

      </View>
    )
  }

  // UI state

  _renderEmptyStateHeader = (flag) => {
    if(flag) {
      return (
        <View style={styles.emptyStateHeaderBackground}>
          <View style={styles.circle} />
          <View style={styles.rectangle} />
          <View style={styles.triangleContainer}>
            <View style={styles.triangle} />
          </View>
        </View>
      )
    } else {
      return null
    }
  }

  rateItem = (id: string) => {
    this.setState({selectedID: id})
    this.setRatingModalVisible(true)
  }

  setRatingModalVisible(visible: bool) {
    this.setState({ratingModalisVisible: visible});
  }

  setAddTaskModalVisible(visible: bool) {
    this.setState({addTaskModalisVisible: visible})
  }

  navigateToDetails = (item: any) => {
    const { navigation } = this.props
    const displayProps = {
      readonly: true,
      item: item,
      saveAction: (newItem, oldItem) => this.updateTask(newItem, oldItem),
      deleteAction: () => this.deleteTask(item),
    }
    navigation.navigate('SRStudyTaskEditor', displayProps)
  }

  openSettings = () => {

  }

  //
  // TODO: refactor out of here
  addTask = (task: {taskName: string, notes: string, date: string}) => {
    const {addItem} = this.props
    const {taskName, notes, date} = task

    const studyTask = new SRStudyTask(
      uuid(),
      taskName,
      notes,
      [date],
      [],
      SRStudyTaskIntensity.NORMAL,
      new SRSpacedRepetition(),
    )

    addItem(studyTask)
  }

  updateTask = (newItem, oldItem) => {
    const { replaceItem } = this.props
    const mergedItem = this.dataWithID(oldItem.id)
    mergedItem.taskName = newItem.taskName
    mergedItem.notes = newItem.notes
    replaceItem(mergedItem)
  }

  deleteTask = (task) => {
    const { navigation, removeItem } = this.props
    Alert.alert(
      'Delete Study Task',
      'Are you sure you want to delete the study task? This cannot be undone.',
      [
        {text: 'Cancel', onPress: () => {}, style: 'cancel'},
        {text: 'Delete', onPress: () => {
          removeItem(task)
          navigation.dispatch(NavigationActions.back())
        }},
      ],
      { cancelable: true }
    )

  }

  //

  dataWithID = (id: string) => {
    expect(id).toExist('dataWithID(): Undefined id')
    const { studyTasks } = this.props
    const studyTasksCopy = [...studyTasks]
    const filteredArray = studyTasksCopy.filter((item) => item.id == id)
    expect(filteredArray.length).toBe(1, `Looking for data with id: ${id}. Item: ${JSON.stringify(studyTasksCopy)}`)
    const item = filteredArray[0]
    return item
  }

  formatCellDate = (date: string) => {
    const itemIsOverDue = moment(date).isBefore(new Date(), 'day')

    var formattedDate = ''
    const momentDate = moment(date)

    formattedDate = momentDate.calendar(null, {
        sameDay: '[Today]',
        nextDay: '[Tomorrow]',
        nextWeek: 'dddd',
        lastDay: '[Yesterday]',
        lastWeek: '[Last] dddd',
        sameElse: 'D MMM'
    });

    if (itemIsOverDue) {
      const momentFromNow = moment(date)
      momentFromNow.format('dd')
      formattedDate = momentFromNow.fromNow()
    }
    return formattedDate
  }

  rateTask = (index: Number) => {
    var grade = ''
    switch(index) {
      case 0:
        grade = SRSGrade.BAD
        break;
      case 1:
        grade = SRSGrade.OK
        break
      case 2:
        grade = SRSGrade.GOOD
        break
      default:
      throw "No valid rating selected"
    }
    const { selectedID, dataSource } = this.state
    expect(selectedID).toExist('rateTask(): undefined id')
    const item = this.dataWithID(selectedID)

    // update SRS, rating history, date rated,
    item.ratingHistory.push(grade)

    const dateRated = new Date().toString()
    item.dates.push(dateRated)

    const { easinessFactor, interval, repetition } = item.srs
    const updatedSRS = new SRSpacedRepetition(easinessFactor, interval, repetition).grade(grade)
    expect(updatedSRS.easinessFactor).toNotEqual(easinessFactor)
    item.srs = updatedSRS

    const { store } = this.props.screenProps
    store.dispatch(actionCreators.replace(item))

    this.setRatingModalVisible(false)
  }
}

const mapDispatchToProps = dispatch => {
  return {
    addItem: item => {
      dispatch(actionCreators.add(item))
    },
    removeItem: item => {
      dispatch(actionCreators.remove(item))
    },
    replaceItem: item => {
      dispatch(actionCreators.replace(item))
    }
  }
}

const mapStateToProps = (state) => {
    return {
        studyTasks: state.studyTasks
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(SRStudyList)

const styles = StyleSheet.create({
  container: {
   flex: 1,
  },
  tableViewContainer: {
    paddingBottom: 10,
  },
  tableView: {
    backgroundColor: SRBrightColor,
  },
  floatingButton: {
    justifyContent: 'center',
    alignItems: 'stretch',
    position: 'absolute',
    bottom: 15,
    right: 15,
  },
  addTouchable: {
    flex:1,
    justifyContent: 'center',
    width: 60,
    height: 60,
    borderRadius: 30,
    backgroundColor: 'rgba(57, 62, 65, 0.9)',
  },
  floatingButtonText: {
    marginBottom: 3,
    fontSize: 20,
    textAlign: 'center',
    color: 'rgba(255, 252, 49, 0.9)',
  },
  emptyStateHeaderBackground: {
    backgroundColor: SRDarkColor,
    // marginBottom: 10,
    padding: 18,
  },
  circle: {
    backgroundColor: SRRedColor,
    borderRadius: 38/2,
    height: 38,
    width: 38
  },
  rectangle: {
    backgroundColor: SRBrightColor,
    height: 85,
    width: 135,
    marginTop:28,
    marginBottom: 16,
  },
  triangleContainer: {
    justifyContent: 'center',
    alignItems: 'center'
  },
  triangle: {
    width: 0,
    height: 0,
    backgroundColor: 'transparent',
    borderStyle: 'solid',
    borderLeftWidth: 19,
    borderRightWidth: 19,
    borderBottomWidth: 38,
    borderLeftColor: 'transparent',
    borderRightColor: 'transparent',
    borderBottomColor: SRYellowColor
 }
})
<commit_msg>Fixed inappropriate usage of studyTasks.<commit_after>// @flow
import React from 'react'
import {
  Alert,
  ListView,
  Modal,
  StyleSheet,
  Text,
  TouchableOpacity,
  View
} from 'react-native'
import { NavigationActions, StackNavigator } from 'react-navigation'
import { withMappedNavigationProps } from 'react-navigation-props-mapper'
import expect from 'expect'
import moment from 'moment'
import {connect} from 'react-redux'

import SRFloatingButton from './SRFloatingButton'
import SRRatingView from './SRRatingView'
import SRStudyListCell from './SRStudyListCell'
import SRStudyTaskEditor from './SRStudyTaskEditor'
import SRTypographicCell from './SRTypographicCell'
import { SRSGrade } from './SRSpacedRepetition'
import { SRSpacedRepetition } from './SRSpacedRepetition'
import { actionCreators, SRStudyTask, SRStudyTaskIntensity } from './dataModel/SRSimpleDataModel'
import { processDataForList } from './dataModel/SRDataPresenter'
import { SRDarkColor, SRYellowColor, SRBrightColor, SRRedColor } from './utilities/SRColors'
import { uuid } from './utilities/UUID'

const studyListTitle = 'Reviews'

const AddStudyTaskNavigator = StackNavigator({
  SRStudyTaskEditor: { screen: withMappedNavigationProps(SRStudyTaskEditor) }
})

export class SRStudyList extends React.Component {

  static navigationOptions = (props) => {
    return {
      // headerLeft: <Button title='⚙' onPress={() => params.openSettings()} />,
      tabBarLabel: studyListTitle,
      tabBarIcon: ({ tintColor }) => (
        <Text>🔜</Text>
      ),
      headerTintColor: SRDarkColor,
      headerStyle: {
        backgroundColor: SRBrightColor
      },
      title: studyListTitle,
    }
  }

  state: {
    addTaskModalisVisible: bool,
    dataSource: ListView.DataSource,
    ratingModalisVisible: bool,
    renderEmptyStateHeader: bool,
    selectedID: string,
    keepSpinning: bool,
  }

  constructor() {
    super();
    const dataSource = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});

    this.state = {
      addTaskModalisVisible: false,
      dataSource: dataSource.cloneWithRows([]),
      ratingModalisVisible: false,
      selectedID: '',
      renderEmptyStateHeader: false,
      keepSpinning: false,
    };
  }

  componentWillMount() {
    const {studyTasks} = this.props
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(processDataForList(studyTasks))
    })
  }

  componentDidMount() {
    // this.props.navigation.setParams({openSettings: this.openSettings})

  }

  componentWillReceiveProps(newProps: Object) {
    const {studyTasks} = newProps
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(processDataForList(studyTasks))
    })
    this.updateStuff()
  }

  updateStuff = () => {
    const {studyTasks} = this.props
    const { addTaskModalisVisible, dataSource, keepSpinning } = this.state
    console.log(`FOO LENGTH: ${studyTasks}`)
    const foo = processDataForList(studyTasks)
    var showEmptyStateHeader = false
    const noDataYet = foo.length == 0 //dataSource.getRowCount() == 0
    console.log(`updateStuff(): noDataYet: ${noDataYet}`)
    if(noDataYet) {
      showEmptyStateHeader = true
      // see if this is called after table view changes
    } else {
      const firstItem = foo[0]//dataSource.getRowData(0, 0)
      const onlyFutureTasks = (new Date(firstItem.date) > new Date())
      if(onlyFutureTasks) {
        showEmptyStateHeader = true
      }
    }

    this.setState({
      renderEmptyStateHeader: showEmptyStateHeader,
      keepSpinning: noDataYet,
    })
  }

  render() {
    const { addTaskModalisVisible,
      dataSource,
      renderEmptyStateHeader,
      keepSpinning,
      ratingModalisVisible
    } = this.state
    const { addItem } = this.props

    const addTaskScreenProps = {
      saveAction: (item) => {
        this.addTask(item)
        this.setAddTaskModalVisible(!addTaskModalisVisible)
      },
      cancelAction: () => this.setAddTaskModalVisible(!addTaskModalisVisible),
      readonly: false,
    }

    return (
      <View style={styles.container}>
        {this._renderEmptyStateHeader(renderEmptyStateHeader)}
        <ListView
          contentContainerStyle={styles.tableViewContainer}
          style={styles.tableView}
          dataSource={dataSource}
          enableEmptySections={true}
          renderRow={(item, sectionID, rowID, highlightRow) => {

            const d = new Date(item.date)
            const itemIsOverDue = new Date() > d
            const itemIsToday = d.toDateString() == new Date().toDateString()
            const allowRating = itemIsToday || itemIsOverDue
            const formattedDate = this.formatCellDate(d)

            if(allowRating) {
              return (

                <SRTypographicCell
                  onPressDetailsButton={() => this.navigateToDetails(item)}
                  onPressRateButton={() => this.rateItem(item.id)}
                >
                  {{title: item.taskName, notes: item.notes, date: formattedDate}}
                </SRTypographicCell>

              )
            } else {
              return (

                <SRStudyListCell
                  onPressDetailsButton={() => this.navigateToDetails(item)}
                >
                {{title: item.taskName, notes: item.notes, date: formattedDate}}
              </SRStudyListCell>

            )}
          }}
        />

        <View style={styles.floatingButton}>
          <SRFloatingButton
            keepSpinning={keepSpinning}
            style={styles.addTouchable}
            onPress={()=>{
            this.setAddTaskModalVisible(!this.state.addTaskModalisVisible)
          }}>

            <Text style={styles.floatingButtonText}>⬥</Text>

          </SRFloatingButton>
        </View>

        <Modal
          animationType={"fade"}
          transparent={true}
          visible={ratingModalisVisible}
          >
         <SRRatingView
           dismissAction={() => this.setRatingModalVisible(!ratingModalisVisible)}
           ratedCallback={(index) => this.rateTask(index)}
         />
        </Modal>

        <Modal
          animationType={"slide"}
          transparent={false}
          visible={addTaskModalisVisible}
          >
         <AddStudyTaskNavigator screenProps={addTaskScreenProps}/>
        </Modal>

      </View>
    )
  }

  // UI state

  _renderEmptyStateHeader = (flag) => {
    if(flag) {
      return (
        <View style={styles.emptyStateHeaderBackground}>
          <View style={styles.circle} />
          <View style={styles.rectangle} />
          <View style={styles.triangleContainer}>
            <View style={styles.triangle} />
          </View>
        </View>
      )
    } else {
      return null
    }
  }

  rateItem = (id: string) => {
    this.setState({selectedID: id})
    this.setRatingModalVisible(true)
  }

  setRatingModalVisible(visible: bool) {
    this.setState({ratingModalisVisible: visible});
  }

  setAddTaskModalVisible(visible: bool) {
    this.setState({addTaskModalisVisible: visible})
  }

  navigateToDetails = (item: any) => {
    const { navigation } = this.props
    const displayProps = {
      readonly: true,
      item: item,
      saveAction: (newItem, oldItem) => this.updateTask(newItem, oldItem),
      deleteAction: () => this.deleteTask(item),
    }
    navigation.navigate('SRStudyTaskEditor', displayProps)
  }

  openSettings = () => {

  }

  //
  // TODO: refactor out of here
  addTask = (task: {taskName: string, notes: string, date: string}) => {
    const {addItem} = this.props
    const {taskName, notes, date} = task

    const studyTask = new SRStudyTask(
      uuid(),
      taskName,
      notes,
      [date],
      [],
      SRStudyTaskIntensity.NORMAL,
      new SRSpacedRepetition(),
    )

    addItem(studyTask)
  }

  updateTask = (newItem, oldItem) => {
    const { replaceItem } = this.props
    const mergedItem = this.dataWithID(oldItem.id)
    mergedItem.taskName = newItem.taskName
    mergedItem.notes = newItem.notes
    replaceItem(mergedItem)
  }

  deleteTask = (task) => {
    const { navigation, removeItem } = this.props
    Alert.alert(
      'Delete Study Task',
      'Are you sure you want to delete the study task? This cannot be undone.',
      [
        {text: 'Cancel', onPress: () => {}, style: 'cancel'},
        {text: 'Delete', onPress: () => {
          removeItem(task)
          navigation.dispatch(NavigationActions.back())
        }},
      ],
      { cancelable: true }
    )

  }

  //

  dataWithID = (id: string) => {
    expect(id).toExist('dataWithID(): Undefined id')
    const { studyTasks } = this.props
    const filteredArray = studyTasks.filter((item) => item.id == id)
    expect(filteredArray.length).toBe(1, `Looking for data with id: ${id}. Item: ${JSON.stringify(studyTasks)}`)
    const item = filteredArray[0]
    return item
  }

  formatCellDate = (date: string) => {
    const itemIsOverDue = moment(date).isBefore(new Date(), 'day')

    var formattedDate = ''
    const momentDate = moment(date)

    formattedDate = momentDate.calendar(null, {
        sameDay: '[Today]',
        nextDay: '[Tomorrow]',
        nextWeek: 'dddd',
        lastDay: '[Yesterday]',
        lastWeek: '[Last] dddd',
        sameElse: 'D MMM'
    });

    if (itemIsOverDue) {
      const momentFromNow = moment(date)
      momentFromNow.format('dd')
      formattedDate = momentFromNow.fromNow()
    }
    return formattedDate
  }

  rateTask = (index: Number) => {
    var grade = ''
    switch(index) {
      case 0:
        grade = SRSGrade.BAD
        break;
      case 1:
        grade = SRSGrade.OK
        break
      case 2:
        grade = SRSGrade.GOOD
        break
      default:
      throw "No valid rating selected"
    }
    const { selectedID, dataSource } = this.state
    expect(selectedID).toExist('rateTask(): undefined id')
    const item = this.dataWithID(selectedID)

    // update SRS, rating history, date rated,
    item.ratingHistory.push(grade)

    const dateRated = new Date().toString()
    item.dates.push(dateRated)

    const { easinessFactor, interval, repetition } = item.srs
    const updatedSRS = new SRSpacedRepetition(easinessFactor, interval, repetition).grade(grade)
    expect(updatedSRS.easinessFactor).toNotEqual(easinessFactor)
    item.srs = updatedSRS

    const { store } = this.props.screenProps
    store.dispatch(actionCreators.replace(item))

    this.setRatingModalVisible(false)
  }
}

const mapDispatchToProps = dispatch => {
  return {
    addItem: item => {
      dispatch(actionCreators.add(item))
    },
    removeItem: item => {
      dispatch(actionCreators.remove(item))
    },
    replaceItem: item => {
      dispatch(actionCreators.replace(item))
    }
  }
}

const mapStateToProps = (state) => {
    return {
        studyTasks: state.studyTasks
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(SRStudyList)

const styles = StyleSheet.create({
  container: {
   flex: 1,
  },
  tableViewContainer: {
    paddingBottom: 10,
  },
  tableView: {
    backgroundColor: SRBrightColor,
  },
  floatingButton: {
    justifyContent: 'center',
    alignItems: 'stretch',
    position: 'absolute',
    bottom: 15,
    right: 15,
  },
  addTouchable: {
    flex:1,
    justifyContent: 'center',
    width: 60,
    height: 60,
    borderRadius: 30,
    backgroundColor: 'rgba(57, 62, 65, 0.9)',
  },
  floatingButtonText: {
    marginBottom: 3,
    fontSize: 20,
    textAlign: 'center',
    color: 'rgba(255, 252, 49, 0.9)',
  },
  emptyStateHeaderBackground: {
    backgroundColor: SRDarkColor,
    // marginBottom: 10,
    padding: 18,
  },
  circle: {
    backgroundColor: SRRedColor,
    borderRadius: 38/2,
    height: 38,
    width: 38
  },
  rectangle: {
    backgroundColor: SRBrightColor,
    height: 85,
    width: 135,
    marginTop:28,
    marginBottom: 16,
  },
  triangleContainer: {
    justifyContent: 'center',
    alignItems: 'center'
  },
  triangle: {
    width: 0,
    height: 0,
    backgroundColor: 'transparent',
    borderStyle: 'solid',
    borderLeftWidth: 19,
    borderRightWidth: 19,
    borderBottomWidth: 38,
    borderLeftColor: 'transparent',
    borderRightColor: 'transparent',
    borderBottomColor: SRYellowColor
 }
})
<|endoftext|>